Files
Mihawk a0ea678ee2 Game Forwarder tiles: the tile app and its launch payload, built into the title
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.
2026-10-18 00:23:06 -04:00

39 lines
1.4 KiB
C

/* PS5 RetroArch - what a Game Forwarder tile (forwarder/tile.c) and its launch payload
* (forwarder/launch.c) share.
*
* Copyright (C) 2026 Mihawk
* SPDX-License-Identifier: GPL-3.0-or-later
*
* A tile tells the payload which tile it is by writing its title id over
* FORWARDER_SLOT_MARKER in the payload's bytes before it sends them; the payload then reads
* that tile's forward.txt. No file is written for the hand-over, so two tiles cannot race.
*/
#ifndef PS5_RETROARCH_FORWARDER_H
#define PS5_RETROARCH_FORWARDER_H
#include <stddef.h>
/* The payload's slot holds this until a tile writes its title id over it. */
#define FORWARDER_SLOT_MARKER "PS5RA-FORWARDER-TILE-ID-SLOT"
#define FORWARDER_SLOT_SIZE 32
/* A title id and its NUL: four capitals and five digits (PPSA99170). */
#define FORWARDER_ID_SIZE 10
/* The arguments a tile's forward.txt may hold, one a line, and the longest line. */
#define FORWARDER_MAX_ARGUMENTS 16
#define FORWARDER_MAX_ARGUMENT 512
static inline int forwarder_valid_id(const char *id, size_t length)
{
if (!id || length != 9)
return 0;
for (size_t i = 0; i < 9; i++)
if (i < 4 ? !(id[i] >= 'A' && id[i] <= 'Z') : !(id[i] >= '0' && id[i] <= '9'))
return 0;
return 1;
}
int forwarder_title_id(const char *param, char id[FORWARDER_ID_SIZE]);
int forwarder_stamp(char *payload, long size, const char *id);
#endif