mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
host: publish the disc roster to the setup wizard, and flush every disc
The runtime launcher has built a multi-disc roster from game.toml [game] discs for a while. The SETUP host never did -- it did not read that key at all -- so on a first run the wizard could not know a title was a multi-disc set and asked for a single image. The other discs were discovered missing much later, mid-game, at the first swap. Read [game] discs in psxrecomp_codegen_host_apply and publish it as gi.discs / gi.num_discs, matching the runtime's rule that a roster only appears when there is more than one image. A single-disc title publishes nothing and its wizard is byte-for-byte the page it was before. The reader is ~60 lines rather than a TOML dependency: this host shells out to Python for every real parse, and one array of strings does not justify linking a parser. Only [game] discs is recognised; anything else is skipped rather than guessed at. Relative entries are absolutised against the project root, because the launcher existence-checks what we publish and its cwd is not necessarily that root -- a relative path would otherwise render as "missing" on a perfectly good install. Implements persist_setup_discs (recomp-ui, appended additively to RecompLauncherCGameInfo). disc.cfg becomes one path per line in disc order. That is additive in the direction that matters: a reader taking only the first line still gets disc 1, exactly what it got before. A slot the player has not located is written as an EMPTY line rather than skipped, so line N is always disc N and filling one in later cannot renumber the others. Nothing located at all leaves any existing disc.cfg alone instead of replacing it with blank lines. The BIOS half delegates to host_persist_setup with rom_path NULL, which runs its bios.cfg logic and skips its single-path disc write, so the two entry points cannot drift on BIOS handling. Verified against Final Fantasy VII's real 3-disc game.toml by driving psxrecomp_codegen_host_apply directly: num_discs = 3, persist_setup_discs set, all three absolute paths in disc order. Requires the matching recomp-ui change to be visible; without it the roster is published to a launcher that ignores the field, which is inert rather than broken. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWiDNcWuNtDWHE7Pnhq4jp
This commit is contained in:
@@ -1323,6 +1323,108 @@ static int resolve_bios_arg(char* out, size_t cap) {
|
||||
}
|
||||
|
||||
/* bios_path NULL = leave bios.cfg untouched; "" = clear (OpenBIOS); else write. */
|
||||
/* ---- multi-disc roster (game.toml [game] discs) --------------------------
|
||||
*
|
||||
* The RUNTIME launcher has published a disc roster for a while, built from
|
||||
* game.toml [game] discs. The SETUP host never did -- so on a first run the
|
||||
* wizard could not know a title was a 3-disc set and asked for a single image.
|
||||
* The other discs were then discovered missing much later, mid-game, at the
|
||||
* first swap.
|
||||
*
|
||||
* Mirrors the runtime's rule: a roster is published only when there is more
|
||||
* than one image, so single-disc titles see no behavioural change at all.
|
||||
* Matches LNG_MAX_DISCS in recomp-ui; a longer list is clamped, exactly as the
|
||||
* launcher clamps it. */
|
||||
#define PSX_HOST_MAX_DISCS 8
|
||||
|
||||
static char g_disc_paths[PSX_HOST_MAX_DISCS][1024];
|
||||
static RecompLauncherCDisc g_disc_roster[PSX_HOST_MAX_DISCS];
|
||||
static int g_num_discs;
|
||||
|
||||
static int host_path_is_absolute(const char* p) {
|
||||
if (!p || !p[0]) return 0;
|
||||
if (p[0] == '/' || p[0] == '\\') return 1;
|
||||
return (p[1] == ':' && ((p[0] >= 'A' && p[0] <= 'Z') ||
|
||||
(p[0] >= 'a' && p[0] <= 'z')));
|
||||
}
|
||||
|
||||
/* Minimal reader for one known key rather than a TOML library: this host shells
|
||||
* out to Python for every real parse, and pulling in a parser to read a single
|
||||
* array of strings would be a dependency for one line of config.
|
||||
*
|
||||
* Only [game] discs is recognised, and only string entries -- anything else in
|
||||
* the file is skipped rather than guessed at. */
|
||||
static void load_game_toml_disc_roster(void) {
|
||||
FILE* f;
|
||||
char line[1600];
|
||||
int in_game = 0, in_array = 0, i;
|
||||
|
||||
g_num_discs = 0;
|
||||
if (!g_game_toml[0]) return;
|
||||
f = fopen(g_game_toml, "rb");
|
||||
if (!f) return;
|
||||
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
const char* p = line;
|
||||
while (*p == ' ' || *p == '\t') ++p;
|
||||
if (!in_array) {
|
||||
if (*p == '[') { /* section header */
|
||||
in_game = (strncmp(p, "[game]", 6) == 0);
|
||||
continue;
|
||||
}
|
||||
if (*p == '#' || !*p) continue;
|
||||
if (!in_game) continue;
|
||||
if (strncmp(p, "discs", 5) != 0) continue;
|
||||
p += 5;
|
||||
while (*p == ' ' || *p == '\t') ++p;
|
||||
/* Guard against a longer key that merely starts with "discs". */
|
||||
if (*p != '=') continue;
|
||||
++p;
|
||||
in_array = 1;
|
||||
}
|
||||
for (; *p; ++p) {
|
||||
if (*p == ']') { in_array = 0; break; }
|
||||
if (*p == '#') break; /* trailing comment */
|
||||
if (*p != '"') continue;
|
||||
{
|
||||
const char* q = ++p;
|
||||
size_t n;
|
||||
while (*p && *p != '"') ++p;
|
||||
if (!*p) break; /* unterminated -- give up */
|
||||
n = (size_t)(p - q);
|
||||
if (g_num_discs < PSX_HOST_MAX_DISCS &&
|
||||
n < sizeof(g_disc_paths[0])) {
|
||||
memcpy(g_disc_paths[g_num_discs], q, n);
|
||||
g_disc_paths[g_num_discs][n] = '\0';
|
||||
++g_num_discs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!in_array) break;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
/* game.toml may hold repo-relative paths. The launcher existence-checks
|
||||
* what we publish, and its cwd is not necessarily the project root, so a
|
||||
* relative entry would render as "missing" on a perfectly good install. */
|
||||
for (i = 0; i < g_num_discs; ++i) {
|
||||
char abs[1024];
|
||||
if (host_path_is_absolute(g_disc_paths[i])) continue;
|
||||
if (!g_project_root[0]) continue;
|
||||
if (join_path(abs, sizeof(abs), g_project_root, g_disc_paths[i]))
|
||||
snprintf(g_disc_paths[i], sizeof(g_disc_paths[i]), "%s", abs);
|
||||
}
|
||||
|
||||
/* One image is not a set: leave GameInfo dark so nothing changes for
|
||||
* single-disc titles. */
|
||||
if (g_num_discs < 2) { g_num_discs = 0; return; }
|
||||
for (i = 0; i < g_num_discs; ++i) {
|
||||
g_disc_roster[i].number = i + 1;
|
||||
g_disc_roster[i].label = NULL; /* launcher formats "Disc N" */
|
||||
g_disc_roster[i].path = g_disc_paths[i];
|
||||
}
|
||||
}
|
||||
|
||||
static int host_persist_setup(void* ctx, const char* rom_path,
|
||||
const char* bios_path) {
|
||||
char path[1200];
|
||||
@@ -1368,6 +1470,61 @@ static int host_persist_setup(void* ctx, const char* rom_path,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Multi-disc flush (RecompLauncherCGameInfo.persist_setup_discs).
|
||||
*
|
||||
* disc.cfg becomes one path per line, in disc order. Readers that take only
|
||||
* the first line keep working and get disc 1 -- which is what they got before
|
||||
* this existed -- so the format change is additive in the direction that
|
||||
* matters. A slot the player has not located is written as an EMPTY line
|
||||
* rather than skipped, so line N is always disc N and filling one in later
|
||||
* cannot renumber the others.
|
||||
*
|
||||
* The BIOS half is delegated to host_persist_setup: passing rom_path NULL runs
|
||||
* its bios.cfg logic and skips its single-path disc write, so the two entry
|
||||
* points cannot drift on BIOS handling. */
|
||||
static int host_persist_setup_discs(void* ctx, const char* const* disc_paths,
|
||||
int disc_count, const char* bios_path) {
|
||||
char body[PSX_HOST_MAX_DISCS * 1030];
|
||||
char path[1200];
|
||||
size_t o = 0;
|
||||
int i, any = 0;
|
||||
|
||||
host_persist_setup(ctx, NULL, bios_path);
|
||||
|
||||
if (!disc_paths || disc_count <= 0)
|
||||
return 0;
|
||||
if (disc_count > PSX_HOST_MAX_DISCS)
|
||||
disc_count = PSX_HOST_MAX_DISCS;
|
||||
|
||||
body[0] = '\0';
|
||||
for (i = 0; i < disc_count; ++i) {
|
||||
const char* d = disc_paths[i] ? disc_paths[i] : "";
|
||||
size_t n = strlen(d);
|
||||
if (o + n + 2 >= sizeof(body))
|
||||
return 0;
|
||||
if (i) body[o++] = '\n';
|
||||
if (n) { memcpy(body + o, d, n); o += n; any = 1; }
|
||||
body[o] = '\0';
|
||||
}
|
||||
/* Nothing located: leave any existing disc.cfg alone rather than replacing
|
||||
* it with a file of blank lines. */
|
||||
if (!any)
|
||||
return 0;
|
||||
|
||||
if (g_project_root[0] &&
|
||||
join_path(path, sizeof(path), g_project_root, "disc.cfg"))
|
||||
write_line_file(path, body);
|
||||
write_line_file("disc.cfg", body);
|
||||
if (g_exe_path[0])
|
||||
write_sidecar_near_exe(g_exe_path, "disc.cfg", body);
|
||||
if (g_build_dir[0]) {
|
||||
char build_exe[1200];
|
||||
if (join_path(build_exe, sizeof(build_exe), g_build_dir, g_exe_basename))
|
||||
write_sidecar_near_exe(build_exe, "disc.cfg", body);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void persist_relaunch_sidecars(const char* near_exe,
|
||||
const char* disc_path) {
|
||||
char bios_line[1100];
|
||||
@@ -3956,6 +4113,15 @@ void psxrecomp_codegen_host_apply(RecompLauncherCGameInfo* gi,
|
||||
(void)find_python(g_python, sizeof(g_python));
|
||||
gi->persist_setup = host_persist_setup;
|
||||
gi->persist_setup_ctx = NULL;
|
||||
/* Multi-disc titles only: load_game_toml_disc_roster leaves g_num_discs 0
|
||||
* for a single image, so the wizard keeps its one-image layout and the
|
||||
* launcher keeps taking the single-path flush. */
|
||||
load_game_toml_disc_roster();
|
||||
if (g_num_discs > 1) {
|
||||
gi->discs = g_disc_roster;
|
||||
gi->num_discs = g_num_discs;
|
||||
gi->persist_setup_discs = host_persist_setup_discs;
|
||||
}
|
||||
gi->prepare_with_progress = host_prepare_generate;
|
||||
gi->prepare_use_selected_rom = 1;
|
||||
/* Number prefix is applied in the setup UI (BIOS adds a step). */
|
||||
|
||||
Reference in New Issue
Block a user