mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
A home screen tile is a title of its own (PPSA99170 to PPSA99269) that starts one
game in RetroArch. Titles cannot launch titles (sceSystemServiceLaunchApp returns
0x8094000F from one, sceLncUtilLaunchApp is a null import), so the tile hands a
payload to the console's ELF loader on 127.0.0.1:9021 and exits:
- forwarder/tile.c: the tile's eboot.bin. Reads its title id from
/app0/sce_sys/param.json, stamps it into /app0/launch.elf's slot, sends it to
the loader, logs /app0/tile.log, and leaves through LoadExec("exit") (a return
from main ends in a SIGSYS error notice).
- forwarder/launch.c: the payload. Reads /data/homebrew/<tile>/forward.txt (one
argument a line: --rom, --core, --exit-after-game, src/frontend_mode_ps5.cpp),
waits for the tile to leave, and starts PPSA99169 with those arguments through
sceLncUtilLaunchApp (sceSystemServiceLaunchApp as the fallback).
- tools/build-forwarder.sh builds both; tools/build-title.sh puts them in
dist/PPSA99169/forwarder/ and counts them in the title's identity; the notices
cover both files.
Evidence:
- host: python3 -m unittest tests.test_forwarder (the tile stamps and sends the
payload byte for byte, refuses a lowercase id, a missing or a doubled slot; the
payload passes the five arguments, falls back to SystemService, and an
unstamped payload starts nothing) - OK.
- console (base PS5, 13.40, ShadowMountPlus 1.7-beta5fix1), the test tile
PPSA99170 registered through ShadowMountPlus's manual list:
tile.log "tile PPSA99170: launch payload 110832 bytes, 110832 sent to
127.0.0.1:9021 (errno 0)"; launch.log five arguments, sceLncUtilLaunchApp
00002018; RetroArch's trace "forwarder: /app0/content/nes/<game> with
/app0/cores/fceumm_libretro.so, closing after it"; the tile exited cleanly
(no error notice), back to the home screen after the game.
- gates: format, unit (206 tests), build (tile 11,123 B, payload 110,832 B),
integration (206 tests; a first run timed out starting two scraper test
servers under load, both passed alone and on the rerun), evidence - PASS.
124 lines
4.4 KiB
C
124 lines
4.4 KiB
C
/* PS5 RetroArch - the Game Forwarder's launch payload.
|
|
*
|
|
* Copyright (C) 2026 Mihawk
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* Run by the console's ELF loader, sent there by a tile (forwarder/tile.c) with that tile's
|
|
* title id written into tile_slot. A payload may start a title where the tile may not, so
|
|
* this waits for the tile to close (up to 15 s), then starts RetroArch (FORWARDER_TARGET)
|
|
* with the arguments in /data/homebrew/<tile>/forward.txt, one a line, through the launch
|
|
* calls the console tooling's control payload uses: sceLncUtilLaunchApp as the foreground
|
|
* user, then sceSystemServiceLaunchApp. Each step is written to the tile's launch.log.
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "forwarder.h"
|
|
|
|
#ifndef FORWARDER_HOMEBREW
|
|
#define FORWARDER_HOMEBREW "/data/homebrew"
|
|
#endif
|
|
#ifndef FORWARDER_TARGET
|
|
#define FORWARDER_TARGET "PPSA99169"
|
|
#endif
|
|
#ifndef FORWARDER_WAIT_STEP_US
|
|
#define FORWARDER_WAIT_STEP_US 250000
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
uint32_t size;
|
|
int user_id;
|
|
uint32_t app_opt;
|
|
uint64_t crash_report;
|
|
uint64_t check_flag;
|
|
} lnc_app_param_t;
|
|
|
|
int sceSystemServiceLaunchApp(const char *title_id, const char *argv[], lnc_app_param_t *param);
|
|
int sceLncUtilLaunchApp(const char *title_id, const char *argv[], lnc_app_param_t *param);
|
|
uint32_t sceLncUtilGetAppIdOfRunningBigApp(void);
|
|
int sceLncUtilGetAppTitleId(uint32_t app_id, char *title_id);
|
|
int sceUserServiceInitialize(void *param);
|
|
int sceUserServiceGetForegroundUser(int *user_id);
|
|
|
|
/* The tile writes its title id over the marker in these bytes (forwarder.h). */
|
|
__attribute__((used)) char tile_slot[FORWARDER_SLOT_SIZE] = FORWARDER_SLOT_MARKER;
|
|
|
|
/* The running big app's title id, or "" when none runs. */
|
|
static void running_title(char title[16])
|
|
{
|
|
const uint32_t app = sceLncUtilGetAppIdOfRunningBigApp();
|
|
memset(title, 0, 16);
|
|
if (app != 0 && app != 0xffffffffu && sceLncUtilGetAppTitleId(app, title) != 0)
|
|
memset(title, 0, 16);
|
|
title[15] = '\0';
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
char tile[FORWARDER_ID_SIZE];
|
|
memcpy(tile, (const char *)tile_slot, sizeof(tile) - 1);
|
|
tile[sizeof(tile) - 1] = '\0';
|
|
if (!forwarder_valid_id(tile, strlen(tile)))
|
|
{
|
|
printf("forwarder payload: no tile's title id in the slot; nothing launched\n");
|
|
return 1;
|
|
}
|
|
char path[256];
|
|
snprintf(path, sizeof(path), FORWARDER_HOMEBREW "/%s/launch.log", tile);
|
|
FILE *log = fopen(path, "w");
|
|
char lines[FORWARDER_MAX_ARGUMENTS][FORWARDER_MAX_ARGUMENT];
|
|
const char *arguments[FORWARDER_MAX_ARGUMENTS + 1];
|
|
int count = 0;
|
|
snprintf(path, sizeof(path), FORWARDER_HOMEBREW "/%s/forward.txt", tile);
|
|
FILE *input = fopen(path, "r");
|
|
while (input && count < FORWARDER_MAX_ARGUMENTS && fgets(lines[count], sizeof lines[count], input))
|
|
{
|
|
lines[count][strcspn(lines[count], "\r\n")] = '\0';
|
|
if (lines[count][0])
|
|
{
|
|
arguments[count] = lines[count];
|
|
count++;
|
|
}
|
|
}
|
|
if (input)
|
|
fclose(input);
|
|
arguments[count] = NULL;
|
|
if (log)
|
|
fprintf(log, "launch %s: %d argument(s) from forward.txt%s\n", tile, count,
|
|
input ? "" : " (none: the file could not be read)");
|
|
const int init = sceUserServiceInitialize(NULL);
|
|
int user = -1;
|
|
const int foreground = sceUserServiceGetForegroundUser(&user);
|
|
char title[16];
|
|
int waited = 0;
|
|
for (running_title(title); strcmp(title, tile) == 0 && waited < 60; waited++)
|
|
{
|
|
usleep(FORWARDER_WAIT_STEP_US);
|
|
running_title(title);
|
|
}
|
|
if (log)
|
|
{
|
|
fprintf(log, "launch %s: user service %08x, foreground user %d (%08x); waited %d step(s) for the tile, "
|
|
"running now: '%s'\n",
|
|
tile, (unsigned)init, user, (unsigned)foreground, waited, title);
|
|
fflush(log);
|
|
}
|
|
lnc_app_param_t param = {sizeof(param), user, 0, 0, 0};
|
|
int result = sceLncUtilLaunchApp(FORWARDER_TARGET, arguments, ¶m);
|
|
if (log)
|
|
fprintf(log, "launch %s: sceLncUtilLaunchApp(%s) = %08x\n", tile, FORWARDER_TARGET, (unsigned)result);
|
|
if (result < 0)
|
|
{
|
|
result = sceSystemServiceLaunchApp(FORWARDER_TARGET, arguments, ¶m);
|
|
if (log)
|
|
fprintf(log, "launch %s: sceSystemServiceLaunchApp(%s) = %08x\n", tile, FORWARDER_TARGET,
|
|
(unsigned)result);
|
|
}
|
|
if (log)
|
|
fclose(log);
|
|
return result < 0;
|
|
}
|