This commit is contained in:
Alex Vanderveen
2026-08-29 01:50:12 -04:00
parent 004388512f
commit d14ca6917c
4 changed files with 304 additions and 11 deletions
+27
View File
@@ -74,6 +74,14 @@ multi-track cue. The runtime mounts the resolved path, fingerprints the TOC
| `required_tracks` | `0` | Exact `iso_track_count` when > 0 (e.g. MotK Redump = `17`) |
| `required_leadout_lba` | unset | Exact lead-out LBA when set |
| `required_disc_fp` | `""` | Exact lowercase hex SHA-256 TOC fingerprint when non-empty |
| `required_disc_fps` | unset | Multi-disc: array parallel to `[game] discs`, the TOC fingerprint of each disc. Each disc of a set has its own TOC, so a set gated only on `required_disc_fp` (the boot disc's) refuses online play on every other disc the launcher lets the player select. A disc with no entry falls back to `required_disc_fp`. |
The whole `[netplay]` policy is resolved **per mounted disc**, not once per
build — `required_tracks` and `required_leadout_lba` are per-disc facts too (a
set may mix a CD-DA disc with a data-only one, and the lead-out LBA is the
disc's size). Only `required_disc_fps` carries per-disc data today; the
resolution point is `netplay_expect_for_disc()` in `runtime/src/main.cpp`, and
that is where the others go when a title needs them.
Offline Play may still launch with a TOC warning; first-run setup Finish and
online Create/Join require `netplay_ok` (and online also a clean verify +
@@ -107,6 +115,25 @@ respective files.
| `stack_base` | game | hex string, initial `$sp` value for the game |
| `disc` | game (single-disc) | path to .cue, relative to project root |
| `discs` | game (multi-disc) | array of .cue paths; `disc` is sugar for `discs = [disc]` |
| `disc_serials` | game (multi-disc, optional) | array parallel to `discs`: the serial each disc carries (`["SCUS-94163", "SCUS-94164", "SCUS-94165"]`). Without it every disc is checked against `[game] id` — the BOOT disc's serial — so selecting disc 2 reports "wrong disc". A disc with no entry here is not serial-gated; the ISO-header check still applies. |
### Multi-disc selection
A build whose `discs` array has more than one entry grows a **Disc Selection**
dropdown in the launcher, above the Serial/Region/ISO-header checklist. The
choice is persisted in `settings.toml`:
```toml
[disc]
path = "/abs/path/Game (Disc 2).bin" # the image actually mounted
selected = 2 # 1-based index into [game] discs
```
`selected` names the disc and `path` only survives when it *is* that disc
(same file-name stem), so writing `selected` alone — from an external launcher
or by hand — switches discs even though `path` still points at the previous
one. That is what makes disc choice manageable like any other setting. Both
keys are written only for multi-disc titles.
## Recompiler block
+33 -2
View File
@@ -1223,6 +1223,10 @@ GameConfig load_game_config(const fs::path& config_path_in) {
const auto& d = toml::find<std::string>(game, "disc");
discs.push_back(fs::absolute(root / d));
}
/* Per-disc serials, parallel to `discs`. Absent => no per-disc gate. */
std::vector<std::string> disc_serials;
if (game.contains("disc_serials"))
disc_serials = toml::find<std::vector<std::string>>(game, "disc_serials");
// Optional expected disc identity (launcher verification badge).
bool has_disc_crc = false;
@@ -1244,6 +1248,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
bool has_netplay_required_leadout = false;
uint32_t netplay_required_leadout_lba = 0;
std::string netplay_required_disc_fp;
std::vector<std::string> netplay_required_disc_fps;
std::string netplay_local_viewport;
std::string netplay_local_viewport_aspect;
if (cfg.contains("netplay")) {
@@ -1262,6 +1267,14 @@ GameConfig load_game_config(const fs::path& config_path_in) {
for (char& c : netplay_required_disc_fp)
c = (char)std::tolower((unsigned char)c);
}
/* Per-disc fingerprints for a multi-disc set, parallel to [game]
* discs. Lowercased here so the runtime compares like for like. */
if (np.contains("required_disc_fps")) {
netplay_required_disc_fps =
toml::find<std::vector<std::string>>(np, "required_disc_fps");
for (std::string& fp : netplay_required_disc_fps)
for (char& c : fp) c = (char)std::tolower((unsigned char)c);
}
if (np.contains("local_viewport")) {
netplay_local_viewport = toml::find<std::string>(np, "local_viewport");
for (char& c : netplay_local_viewport)
@@ -2073,6 +2086,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
/*text_size*/ text_size,
/*stack_base*/ stack_base,
/*discs*/ discs,
/*disc_serials*/ disc_serials,
/*has_disc_crc*/ has_disc_crc,
/*disc_crc*/ disc_crc,
/*disc_sha1*/ disc_sha1,
@@ -2081,6 +2095,7 @@ GameConfig load_game_config(const fs::path& config_path_in) {
/*has_netplay_required_leadout*/ has_netplay_required_leadout,
/*netplay_required_leadout_lba*/ netplay_required_leadout_lba,
/*netplay_required_disc_fp*/ netplay_required_disc_fp,
/*netplay_required_disc_fps*/ netplay_required_disc_fps,
/*netplay_local_viewport*/ netplay_local_viewport,
/*netplay_local_viewport_aspect*/ netplay_local_viewport_aspect,
/*seeds_path*/ seeds_path,
@@ -2423,6 +2438,15 @@ UserSettings load_user_settings(const fs::path& path) {
const auto p = toml::find<std::string>(d, "path");
if (!p.empty()) { s.disc_path = fs::path(p); s.has_disc_path = true; }
});
/* Multi-disc selection, 1-based. Anything below 1 is a malformed
* hand-edit, not a request to boot disc 0 -- clamp up and keep going
* rather than silently mounting nothing. The upper bound is the disc
* roster's size, which only the runtime knows, so it clamps there. */
if (d.contains("selected")) try_get([&]{
const auto n = toml::find<int>(d, "selected");
s.disc_index = n < 1 ? 1 : n;
s.has_disc_index = true;
});
}
if (doc.contains("memcard")) {
const toml::value& m = toml::find(doc, "memcard");
@@ -2640,8 +2664,15 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
}
if (s.has_bios_path)
f << "\n[bios]\npath = \"" << fwd(s.bios_path) << "\"\n";
if (s.has_disc_path)
f << "\n[disc]\npath = \"" << fwd(s.disc_path) << "\"\n";
if (s.has_disc_path || s.has_disc_index) {
f << "\n[disc]\n";
if (s.has_disc_path)
f << "path = \"" << fwd(s.disc_path) << "\"\n";
/* Only meaningful for a multi-disc title; harmless (and informative)
* for a single-disc one, where it is always 1. */
if (s.has_disc_index)
f << "selected = " << s.disc_index << "\n";
}
if (s.has_memcard_dir || s.has_memcard1_path || s.has_memcard2_path ||
s.has_memcard1_enabled || s.has_memcard2_enabled) {
f << "\n[memcard]\n";
+29
View File
@@ -741,6 +741,17 @@ struct GameConfig {
// the union here).
std::vector<std::filesystem::path> discs;
// Optional per-disc serials, PARALLEL to `discs` ([game] disc_serials).
// Each disc of a set carries its own serial (FF7 US: SCUS-94163 / -64 /
// -65), so a set verified against the BOOT disc's serial alone would
// report every other disc as "wrong disc" the moment the player selects
// it. May be shorter than `discs` (or absent entirely, which every
// single-disc title and every port written before this is): a disc with
// no serial here is simply not serial-gated -- the ISO header check
// still applies. Written by tools/new_project_layout from the same probe
// that produced disc_set.json.
std::vector<std::string> disc_serials;
// Optional expected disc identity, for the launcher's "Disc verified" badge.
// disc_crc: full-file CRC32 (IEEE) of the data track. disc_sha1: lowercase
// hex SHA-1. Either may be absent (has_disc_crc / disc_sha1.empty()).
@@ -757,6 +768,16 @@ struct GameConfig {
bool has_netplay_required_leadout = false;
uint32_t netplay_required_leadout_lba = 0;
std::string netplay_required_disc_fp; // lowercase hex SHA-256
// Per-disc TOC fingerprints for a MULTI-DISC set, PARALLEL to [game]
// discs ([netplay] required_disc_fps). Every disc of a set has its own
// TOC, so a set gated on one fingerprint refuses online play on every
// disc but that one -- and the flat required_disc_fp above can only ever
// name the boot disc. May be shorter than `discs`, or absent (every
// single-disc title): a disc with no fingerprint here falls back to the
// flat value, and a set that declares neither is simply not fingerprint-
// gated. Values come from the same verify_disc_set.py probe that writes
// disc_set.json.
std::vector<std::string> netplay_required_disc_fps;
// local_viewport = "vertical_split": while real netplay is active, crop
// presentation to this peer's left/right split-screen half. This is a
// presentation-only helper for titles that still render native split-screen
@@ -1231,6 +1252,14 @@ struct UserSettings {
// [bios] / [disc] / [memcard]
bool has_bios_path = false; std::filesystem::path bios_path;
bool has_disc_path = false; std::filesystem::path disc_path;
// [disc] selected: which image of a MULTI-DISC set ([game] discs in
// game.toml) the player last chose, 1-based. Stored as an ordinary
// setting so the launcher's Disc Selection dropdown persists across
// sessions and an external launcher can drive it like any other row.
// Ignored by single-disc titles. When it disagrees with disc_path, the
// rule is in resolve_selected_disc() (runtime/src/main.cpp): the index
// names the disc, disc_path only survives when it is that same disc.
bool has_disc_index = false; int disc_index = 1;
bool has_memcard_dir = false; std::filesystem::path memcard_dir;
// Per-slot memory-card overrides. An explicit card path overrides the
// <dir>/card<N>.mcd default; the enable flag inserts/removes the card.
+215 -9
View File
@@ -2204,6 +2204,40 @@ static bool pick_runtime_file(const char* title, const char* filter,
#endif
}
/* Per-disc expected serials for a multi-disc set, keyed by the disc image's
* UPPERCASED file-name stem (game.toml [game] disc_serials, in the order of
* [game] discs). Every disc of a set carries its own serial, so checking disc
* 2 against the BOOT disc's serial reports the player's correct disc as the
* wrong game -- which is what the launcher's disc dropdown would trip on
* every time, and what the launch-time check warned about even when the
* player picked exactly the right image. A disc absent from this map is not
* serial-gated at all; the ISO-header check still applies. Keyed by stem
* rather than full path because a .cue and the .bin it owns are one disc and
* either may be what the player picked. Populated from game.toml; empty for
* every single-disc title, which therefore behaves exactly as before. */
static std::unordered_map<std::string, std::string> g_disc_serials;
/* Per-disc netplay TOC fingerprints, same keying and same reason as
* g_disc_serials: every disc of a set has its own TOC, so a set gated on the
* BOOT disc's fingerprint refuses online play on every other disc the player
* can now select. Populated from game.toml [netplay] required_disc_fps; empty
* for every single-disc title and every port that predates the key, which
* therefore keep the flat [netplay] required_disc_fp behaviour exactly. */
static std::unordered_map<std::string, std::string> g_disc_netplay_fps;
static std::string uppercase_ascii(std::string s);
/* The serial THIS image is expected to carry: the set's per-disc value when
* the game declared one, the game's own id otherwise. An image that belongs
* to a declared set but has no serial listed is returned ungated (""), never
* gated against another disc's number. */
static std::string expected_serial_for_disc(const std::filesystem::path& disc,
const std::string& fallback) {
if (g_disc_serials.empty()) return fallback;
const auto it = g_disc_serials.find(uppercase_ascii(disc.stem().string()));
return it != g_disc_serials.end() ? it->second : std::string();
}
static std::string uppercase_ascii(std::string s) {
for (char& c : s) {
c = (char)std::toupper((unsigned char)c);
@@ -2247,17 +2281,20 @@ static DiscValidation validate_disc_image(const std::filesystem::path& selected_
// the launcher badge can never drift apart. No CRC here — the launch check
// only cares about openability / header / serial; the launcher does the
// (slower) CRC pass when an expected CRC is configured.
/* A multi-disc set is checked against the SELECTED disc's serial, not the
* boot disc's -- see expected_serial_for_disc(). */
const std::string expect = expected_serial_for_disc(selected_path, game_id);
const PSXRecompV4::DiscIdentity id =
PSXRecompV4::identify_disc(selected_path, game_id, /*expected_crc*/0,
PSXRecompV4::identify_disc(selected_path, expect, /*expected_crc*/0,
/*has_expected_crc*/false, /*compute_crc*/false);
DiscValidation v;
v.opened = id.opened;
v.has_header = id.has_header;
v.id_matches = game_id.empty() ? true : id.serial_matches;
v.id_matches = expect.empty() ? true : id.serial_matches;
v.detail = id.detail;
if (id.opened && id.has_header && !v.id_matches && v.detail.empty()) {
v.detail = "The disc header is readable, but it does not contain the expected game ID " +
uppercase_ascii(game_id) + " in the early disc metadata.";
uppercase_ascii(expect) + " in the early disc metadata.";
}
return v;
}
@@ -2300,6 +2337,35 @@ static std::filesystem::path normalize_disc_path_for_launch(const std::filesyste
return p;
}
/* Which image of a MULTI-DISC set to mount, given the roster this build was
* made from (game.toml [game] discs), the player's persisted [disc] selected
* index, and the [disc] path the launcher last wrote.
*
* The index names the disc; the path only survives when it IS that disc.
* That ordering is the whole point of storing the index: an external launcher
* (or a hand edit) changes discs by writing one integer, and it takes effect
* even though settings.toml still carries the previous disc's path. A player
* who browsed for their own copy of the selected disc is still honoured,
* because a relocated or container-swapped image keeps its stem -- a Redump
* dump names the disc in the file name and only the extension moves
* (".. (Disc 2).cue" -> ".. (Disc 2).bin", which is exactly the substitution
* normalize_disc_path_for_launch performs).
*
* Single-disc titles (roster of 0 or 1) are returned unchanged: the persisted
* path wins, exactly as it did before any of this existed. */
static std::filesystem::path resolve_selected_disc(
const std::vector<std::filesystem::path>& roster, int selected_1based,
const std::filesystem::path& persisted) {
if (roster.size() < 2) return persisted;
const int idx = selected_1based - 1;
if (idx < 0 || idx >= (int)roster.size()) return persisted;
if (!persisted.empty() &&
uppercase_ascii(persisted.stem().string()) ==
uppercase_ascii(roster[(size_t)idx].stem().string()))
return persisted;
return roster[(size_t)idx];
}
/* BIOS selection state (docs/BIOS_SELECTION.md). s_openbios_allowed is the
* game's [runtime] openbios; s_bundled_bios_rel is where the shipped
* redistributable image lives relative to the executable. */
@@ -7309,6 +7375,27 @@ static void sdl_vblank_present(void) {
/* game.toml [netplay] + last TOC fingerprint — shared by launcher verify and
* the pre-psx_netplay_start gate (works with or without RECOMP_LAUNCHER). */
static PSXRecompV4::NetplayDiscExpect g_netplay_disc_expect{};
/* The netplay mount policy for ONE image. The policy is per-disc, not
* per-build: the TOC fingerprint is the disc's own, and the same is true in
* principle of required_tracks / required_leadout_lba (a set may mix a
* CD-DA disc with a data-only one, and the lead-out LBA is literally the
* disc's size). Resolving the whole struct per image -- rather than reaching
* for the one global -- is what keeps those honest as soon as a title needs
* them; today only the fingerprint has per-disc data to apply.
*
* A disc with no per-disc fingerprint keeps the flat [netplay]
* required_disc_fp, so single-disc titles are unaffected. */
static PSXRecompV4::NetplayDiscExpect netplay_expect_for_disc(
const std::filesystem::path& disc) {
PSXRecompV4::NetplayDiscExpect e = g_netplay_disc_expect;
if (!g_disc_netplay_fps.empty()) {
const auto it =
g_disc_netplay_fps.find(uppercase_ascii(disc.stem().string()));
if (it != g_disc_netplay_fps.end()) e.required_disc_fp = it->second;
}
return e;
}
static std::string g_session_disc_fp;
static bool g_session_netplay_disc_ok = false;
@@ -7494,12 +7581,24 @@ namespace {
int ae_disc_verify(const char* disc_path, RecompLauncherCDiscVerify* out) {
if (!disc_path || !disc_path[0] || !out) return 0;
std::memset(out, 0, sizeof(*out));
/* Which serial THIS disc should carry -- the set's per-disc value, or
* the game's own for a single-disc title. The expected CRC is disc
* 1's and is only consulted when the game supplied one, so a set that
* ships a CRC and lets the player pick disc 2 lands on the "warn"
* verdict rather than "bad". */
const std::string expect_serial =
expected_serial_for_disc(std::filesystem::path(disc_path),
g_lnch_expected_serial);
/* The netplay gate is the SELECTED disc's, for the same reason the
* serial is -- see netplay_expect_for_disc(). */
const PSXRecompV4::NetplayDiscExpect np_expect =
netplay_expect_for_disc(std::filesystem::path(disc_path));
PSXRecompV4::DiscIdentity id = PSXRecompV4::identify_disc(
disc_path, g_lnch_expected_serial, g_lnch_expected_crc,
disc_path, expect_serial, g_lnch_expected_crc,
g_lnch_has_crc, /*compute_crc*/ g_lnch_has_crc,
g_lnch_netplay_available ? &g_netplay_disc_expect : nullptr);
g_lnch_netplay_available ? &np_expect : nullptr);
const std::string& serial = !id.detected_serial.empty()
? id.detected_serial : g_lnch_expected_serial;
? id.detected_serial : expect_serial;
std::snprintf(out->serial, sizeof(out->serial), "%s", serial.c_str());
std::snprintf(out->region, sizeof(out->region), "%s", id.region.c_str());
out->iso_ok = id.has_header ? 1 : 0;
@@ -10901,6 +11000,15 @@ int main(int argc, char** argv) {
std::string resolved_language = "en";
std::vector<PSXRecompV4::RuntimeConfig::LanguageOption> lang_menu_options;
std::filesystem::path resolved_disc;
/* game.toml [game] discs, in disc order -- the images this build was made
* from. Single-disc titles leave one entry (or none). This is the roster
* the launcher's Disc Selection dropdown offers and the list [disc]
* selected indexes into; it is NOT a scan of the player's folder, so a
* player who moved an image still browses for it. */
std::vector<std::filesystem::path> game_discs;
/* 1-based selected disc, from settings.toml [disc] selected. Kept at main
* scope because it round-trips through both launcher entry points. */
int selected_disc_index = 1;
std::string window_title = PSX_WINDOW_TITLE;
uint16_t debug_port = (uint16_t)DEFAULT_DEBUG_PORT;
std::string game_name;
@@ -10964,6 +11072,26 @@ int main(int argc, char** argv) {
(gc.netplay_local_viewport_aspect == "16:9") ? 1 :
(gc.netplay_local_viewport_aspect == "21:9") ? 2 :
(gc.netplay_local_viewport_aspect == "adaptive") ? 3 : 0;
game_discs = gc.discs;
/* Per-disc serial gate, shared by the launch-time disc check and
* the launcher's disc verdict. Keyed by the image's uppercased
* stem so a .cue and its .bin agree. */
g_disc_serials.clear();
for (size_t i = 0;
i < gc.discs.size() && i < gc.disc_serials.size(); ++i) {
if (gc.disc_serials[i].empty()) continue;
g_disc_serials[uppercase_ascii(gc.discs[i].stem().string())] =
gc.disc_serials[i];
}
/* Same keying for the per-disc netplay TOC fingerprints. */
g_disc_netplay_fps.clear();
for (size_t i = 0;
i < gc.discs.size() && i < gc.netplay_required_disc_fps.size();
++i) {
if (gc.netplay_required_disc_fps[i].empty()) continue;
g_disc_netplay_fps[uppercase_ascii(gc.discs[i].stem().string())] =
gc.netplay_required_disc_fps[i];
}
if (!gc.discs.empty()) resolved_disc = gc.discs.front();
if (gc.runtime.has_memcard_dir) memcard_dir = gc.runtime.memcard_dir;
if (gc.runtime.has_window_title) window_title = gc.runtime.window_title;
@@ -11464,6 +11592,22 @@ int main(int argc, char** argv) {
}
if (us.has_disc_path && !disc_override_path)
resolved_disc = normalize_disc_path_for_launch(us.disc_path);
/* Multi-disc: the remembered disc NUMBER decides which image boots,
* so a skip-launcher boot resumes on the disc the player left off on
* and an external launcher can move them by writing one field. */
if (us.has_disc_index) selected_disc_index = us.disc_index;
if (!game_discs.empty())
selected_disc_index =
std::min(std::max(selected_disc_index, 1), (int)game_discs.size());
if (!disc_override_path && game_discs.size() > 1) {
const auto sel = resolve_selected_disc(game_discs,
selected_disc_index,
resolved_disc);
/* Never normalize an empty path -- fs::absolute("") is the cwd,
* which would turn "no disc yet" into a bogus mount. */
if (!sel.empty())
resolved_disc = normalize_disc_path_for_launch(sel);
}
if (us.has_memcard_dir) memcard_dir = us.memcard_dir;
if (us.has_memcard1_path) memcard1_path = us.memcard1_path;
if (us.has_memcard2_path) memcard2_path = us.memcard2_path;
@@ -12114,6 +12258,9 @@ int main(int argc, char** argv) {
}
ls.memcard_enabled[0] = seed.memcard1_enabled ? 1 : 0;
ls.memcard_enabled[1] = seed.memcard2_enabled ? 1 : 0;
/* Which disc the dropdown opens on (1-based; ignored when the
* game is single-disc and gi.discs is empty). */
ls.disc_index = selected_disc_index;
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ENABLED)
ls.multitap_enabled = seed.multitap_enabled ? 1 : 0;
#endif
@@ -12144,6 +12291,27 @@ int main(int argc, char** argv) {
g_lnch_has_crc = game_has_disc_crc;
g_lnch_argv0 = argv[0];
/* Multi-disc roster for the launcher's Disc Selection dropdown.
* The ABI BORROWS every pointer, so this storage has to outlive
* recomp_launcher_run_window below -- hence plain locals in this
* scope rather than a temporary. Paths go through the same
* normalization as every other mount so a roster entry and the
* initial disc are spelled identically. Labels are left null:
* recomp-ui formats "Disc N" from the number itself, and one
* source of that string beats two. */
std::vector<std::string> rui_disc_paths;
std::vector<RecompLauncherCDisc> rui_discs;
if (game_discs.size() > 1) {
rui_disc_paths.reserve(game_discs.size());
for (const auto& d : game_discs)
rui_disc_paths.push_back(
normalize_disc_path_for_launch(d).string());
rui_discs.reserve(rui_disc_paths.size());
for (size_t i = 0; i < rui_disc_paths.size(); ++i)
rui_discs.push_back(RecompLauncherCDisc{
(int)i + 1, nullptr, rui_disc_paths[i].c_str()});
}
RecompLauncherCGameInfo gi{};
ae_fill_psx_launcher_game_info(
&gi,
@@ -12161,6 +12329,8 @@ int main(int argc, char** argv) {
rui_lang_labels.empty() ? nullptr : rui_lang_labels.data(),
(int)rui_lang_labels.size(),
/*resume_netplay_room=*/0);
gi.discs = rui_discs.empty() ? nullptr : rui_discs.data();
gi.num_discs = (int)rui_discs.size();
#if defined(PSX_HAS_SETUP_WIZARD)
/* MotK ships tools/prepare_disc.py (2448→2352). Offer it in the
* first-run wizard so players need not run the script by hand. */
@@ -12246,6 +12416,16 @@ int main(int argc, char** argv) {
seed.disc_path = rui_out_disc;
seed.has_disc_path = true;
}
/* Persist the Disc Selection choice next to the disc path, so
* the next session opens on the same disc and an external
* launcher sees it as an ordinary settings row. Written for
* multi-disc titles only -- a single-disc game has nothing to
* select and should not grow a meaningless key. */
if (game_discs.size() > 1 && ls.disc_index > 0) {
selected_disc_index = ls.disc_index;
seed.disc_index = ls.disc_index;
seed.has_disc_index = true;
}
seed.fullscreen = ls.fullscreen; seed.has_fullscreen = true;
seed.skip_launcher = ls.skip_launcher != 0; seed.has_skip_launcher = true;
/* aspect_index round-trips 0/1/2 -> 4:3 / 16:9 / 21:9, superseding the
@@ -13431,17 +13611,21 @@ session_reboot:
const std::filesystem::path disc_check = resolved_disc;
if (!disc_check.empty()) {
#if defined(RECOMP_LAUNCHER)
const std::string& expect_serial = g_lnch_expected_serial;
const std::string expect_serial =
expected_serial_for_disc(disc_check, g_lnch_expected_serial);
const uint32_t expect_crc = g_lnch_expected_crc;
const bool has_crc = g_lnch_has_crc;
#else
const std::string expect_serial = game_id;
const std::string expect_serial =
expected_serial_for_disc(disc_check, game_id);
const uint32_t expect_crc = game_disc_crc;
const bool has_crc = game_has_disc_crc;
#endif
const PSXRecompV4::NetplayDiscExpect np_expect =
netplay_expect_for_disc(disc_check);
PSXRecompV4::DiscIdentity nid = PSXRecompV4::identify_disc(
disc_check, expect_serial, expect_crc, has_crc,
/*compute_crc*/ false, &g_netplay_disc_expect);
/*compute_crc*/ false, &np_expect);
g_session_disc_fp = nid.disc_fp;
g_session_netplay_disc_ok = nid.netplay_ok && !nid.disc_fp.empty();
psx_lobby_set_disc_fp(nid.disc_fp.c_str());
@@ -13954,6 +14138,7 @@ soft_return_lobby:
RecompLauncherCSettings ls{};
ls.output_method = 2;
ls.window_scale = std::max(1, std::min(4, g_video_win_w / 320));
ls.disc_index = selected_disc_index;
ls.fullscreen = g_fullscreen ? 1 : 0;
ls.ignore_aspect = 0;
ls.linear_filter = (g_video_texfilter != 0) ? 1 : 0;
@@ -14058,6 +14243,20 @@ soft_return_lobby:
for (const auto& lo : lang_menu_options)
rui_lang_labels.push_back(lo.label.c_str());
/* Same borrowed-roster contract as the first-open launcher above. */
std::vector<std::string> rui_disc_paths;
std::vector<RecompLauncherCDisc> rui_discs;
if (game_discs.size() > 1) {
rui_disc_paths.reserve(game_discs.size());
for (const auto& d : game_discs)
rui_disc_paths.push_back(
normalize_disc_path_for_launch(d).string());
rui_discs.reserve(rui_disc_paths.size());
for (size_t i = 0; i < rui_disc_paths.size(); ++i)
rui_discs.push_back(RecompLauncherCDisc{
(int)i + 1, nullptr, rui_disc_paths[i].c_str()});
}
RecompLauncherCGameInfo gi{};
ae_fill_psx_launcher_game_info(
&gi,
@@ -14075,6 +14274,8 @@ soft_return_lobby:
rui_lang_labels.empty() ? nullptr : rui_lang_labels.data(),
(int)rui_lang_labels.size(),
/*resume_netplay_room=*/1);
gi.discs = rui_discs.empty() ? nullptr : rui_discs.data();
gi.num_discs = (int)rui_discs.size();
#if defined(PSX_HAS_SETUP_WIZARD) && defined(PSX_HAS_GAME_CODEGEN)
psx_game_codegen_setup_apply(&gi);
#endif
@@ -14198,6 +14399,11 @@ soft_return_lobby:
}
us.deadzone = player_deadzone[0];
us.has_deadzone = true;
if (game_discs.size() > 1 && ls.disc_index > 0) {
selected_disc_index = ls.disc_index;
us.disc_index = ls.disc_index;
us.has_disc_index = true;
}
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ENABLED)
multitap_enabled = ls.multitap_enabled != 0;
us.multitap_enabled = multitap_enabled;