From 91d683d87d2b15703fce7cbf94e13804ce5c011e Mon Sep 17 00:00:00 2001 From: Alex Vanderveen Date: Mon, 10 Aug 2026 22:53:17 -0400 Subject: [PATCH] bios hotfix --- docs/BIOS_SELECTION.md | 21 +- docs/NETPLAY.md | 13 +- runtime/src/main.cpp | 423 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 419 insertions(+), 38 deletions(-) diff --git a/docs/BIOS_SELECTION.md b/docs/BIOS_SELECTION.md index 6c5b9f5c..3568d472 100644 --- a/docs/BIOS_SELECTION.md +++ b/docs/BIOS_SELECTION.md @@ -124,8 +124,15 @@ which is why it is enforced rather than warned about. ## Netplay lobby settle -Online lobbies advertise a per-peer `bios_offer` on `set_ready` and freeze a -single `match_caps.session_bios` at host Start: +Online and LAN lobbies advertise a per-peer BIOS offer and freeze a single +session BIOS at host Start (`openbios` or `scph1001`): + +- **Online:** `bios_offer` on `set_ready` → host publishes + `match_caps.session_bios`. +- **LAN:** peers append offer fields on `MOTK3 JOIN`; host broadcasts them on + `MOTK4 UPDATE` and includes the settled token on `MOTK1 START`. + +Settle rule (same for both): - **OpenBIOS** if any seated peer prefers OpenBIOS, or any peer cannot run SCPH-1001 (no linked retail backend and/or no validated dump), or a peer @@ -134,12 +141,22 @@ single `match_caps.session_bios` at host Start: OpenBIOS. Every peer applies that session BIOS before boot. Mixed BIOSes are invalid for +<<<<<<< HEAD rollback (kernel RAM layout differs). +======= +rollback (kernel RAM layout differs). If the session settles to SCPH-1001 but a +peer has no validated dump, that peer **aborts the launch** rather than silently +falling back to OpenBIOS (which would desync immediately). +>>>>>>> fca7047 (bios hotfix) Session BIOS is **ephemeral**: it affects only that match’s runtime boot. It does **not** rewrite `bios.cfg`, `settings.toml`, or the launcher Settings BIOS row. Soft-return shows the player’s durable preference again; the next Start +<<<<<<< HEAD re-settles from `bios_offer` as usual. +======= +re-settles from offers as usual. +>>>>>>> fca7047 (bios hotfix) ## Why both are compiled in diff --git a/docs/NETPLAY.md b/docs/NETPLAY.md index e2cd06a3..736bfa09 100644 --- a/docs/NETPLAY.md +++ b/docs/NETPLAY.md @@ -127,12 +127,19 @@ device. Rollback and delay-sync both carry multitap pad bytes. -**BIOS settle:** each peer advertises `bios_offer` on ready (can run -OpenBIOS / SCPH-1001, and whether OpenBIOS is selected). At Start the host -publishes `match_caps.session_bios` (`openbios` or `scph1001`). The session +**BIOS settle:** each peer advertises a BIOS offer (can run OpenBIOS / +SCPH-1001, and whether OpenBIOS is selected) — online on ready, LAN on JOIN. +At Start the host freezes one session BIOS (`openbios` or `scph1001`) via +`match_caps.session_bios` (online) or the `MOTK1 START` line (LAN). The session uses OpenBIOS unless every seated peer can run SCPH-1001 and nobody selected +<<<<<<< HEAD OpenBIOS. That choice boots the match only — it does not change each peer’s saved BIOS preference. See `docs/BIOS_SELECTION.md` (Netplay lobby settle). +======= +OpenBIOS. Peers that cannot apply a settled SCPH-1001 abort instead of falling +back. That choice boots the match only — it does not change each peer’s saved +BIOS preference. See `docs/BIOS_SELECTION.md` (Netplay lobby settle). +>>>>>>> fca7047 (bios hotfix) --- diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index c0857f20..934abc75 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -6345,9 +6345,30 @@ namespace { int g_lnch_lan_my_slot = -1; std::string g_lnch_lan_player_id; /* local process identity for LAN seats */ + /* Per-seat BIOS offers for LAN settle (mirrors online bios_offer). */ + struct AeLanSlotBios { + int valid = 0; + int prefer_openbios = 1; + int can_openbios = 1; + int can_scph1001 = 0; + }; + AeLanSlotBios g_lnch_lan_slot_bios[kAeLanMaxSlots]{}; + /* Match-only BIOS token from lobby settle or LAN START ("openbios"|"scph1001"). */ + char g_lnch_session_bios[16]{}; + static int ae_np_lan_occupied(const AeLanLobbyState& state); static int ae_np_lan_endpoint_port(const std::string& endpoint); static bool ae_np_read_lan_file_state(AeLanLobbyState* state); + static void ae_np_set_session_bios_token(const char* token); + static void ae_np_clear_session_bios_token(void); + static void ae_np_lan_clear_slot_bios(int slot); + static void ae_np_lan_store_slot_bios(int slot, int prefer_open, int can_open, + int can_scph); + static void ae_np_lan_sync_local_slot_bios(void); + static int ae_np_lan_settle_session_bios(char* out, size_t out_cap); + static void ae_np_append_lan_bios_join(char* msg, size_t msg_cap, int* io_off); + static int ae_np_parse_lan_bios_tail(char* p, int* prefer_open, int* can_open, + int* can_scph); #ifdef _WIN32 using AeLanSock = SOCKET; @@ -7030,22 +7051,40 @@ namespace { static void ae_np_lan_send_update_to_peers(const AeLanLobbyState& state_in) { AeLanLobbyState state = state_in; ae_np_lan_sync_legacy_names(state); - char msg[1536]; - int off = std::snprintf(msg, sizeof(msg), - "MOTK3 UPDATE\n%d\n%d\n%d\n%u\n", - state.max_slots, state.host_slot, - state.started ? 1 : 0, - (unsigned)(state.session_id ? state.session_id : 1u)); - for (int i = 0; i < state.max_slots && i < kAeLanMaxSlots && off > 0 && - off < (int)sizeof(msg) - 128; ++i) { - off += std::snprintf(msg + off, sizeof(msg) - (size_t)off, "%s\n%s\n", - state.slot_id[i].c_str(), - state.slot_name[i].c_str()); + if (g_lnch_hosting_lan) ae_np_lan_sync_local_slot_bios(); + char msg3[1536]; + int off3 = std::snprintf(msg3, sizeof(msg3), + "MOTK3 UPDATE\n%d\n%d\n%d\n%u\n", + state.max_slots, state.host_slot, + state.started ? 1 : 0, + (unsigned)(state.session_id ? state.session_id : 1u)); + for (int i = 0; i < state.max_slots && i < kAeLanMaxSlots && off3 > 0 && + off3 < (int)sizeof(msg3) - 128; ++i) { + off3 += std::snprintf(msg3 + off3, sizeof(msg3) - (size_t)off3, + "%s\n%s\n", state.slot_id[i].c_str(), + state.slot_name[i].c_str()); } - if (off <= 0) return; + /* MOTK4 carries per-seat bios_offer so guests can preview settle. */ + char msg4[2048]; + int off4 = std::snprintf(msg4, sizeof(msg4), + "MOTK4 UPDATE\n%d\n%d\n%d\n%u\n", + state.max_slots, state.host_slot, + state.started ? 1 : 0, + (unsigned)(state.session_id ? state.session_id : 1u)); + for (int i = 0; i < state.max_slots && i < kAeLanMaxSlots && off4 > 0 && + off4 < (int)sizeof(msg4) - 160; ++i) { + const AeLanSlotBios& b = g_lnch_lan_slot_bios[i]; + off4 += std::snprintf( + msg4 + off4, sizeof(msg4) - (size_t)off4, "%s\n%s\n%d\n%d\n%d\n%d\n", + state.slot_id[i].c_str(), state.slot_name[i].c_str(), + b.valid ? 1 : 0, b.prefer_openbios ? 1 : 0, b.can_openbios ? 1 : 0, + b.can_scph1001 ? 1 : 0); + } + if (off3 <= 0 && off4 <= 0) return; for (int i = 0; i < kAeLanMaxSlots; ++i) { if (!g_lnch_lan_peer_ok[i]) continue; - ae_np_lan_udp_sendto(g_lnch_lan_peers[i], msg); + if (off3 > 0) ae_np_lan_udp_sendto(g_lnch_lan_peers[i], msg3); + if (off4 > 0) ae_np_lan_udp_sendto(g_lnch_lan_peers[i], msg4); } } @@ -7089,6 +7128,9 @@ namespace { g_lnch_hosting_lan = true; g_lnch_joined_lan = false; g_lnch_lan_endpoint = state.endpoint; + for (int i = 0; i < kAeLanMaxSlots; ++i) g_lnch_lan_slot_bios[i] = {}; + ae_np_clear_session_bios_token(); + ae_np_lan_sync_local_slot_bios(); static bool atexit_hooked = false; if (!atexit_hooked) { std::atexit(ae_np_lan_atexit_cleanup); @@ -7245,6 +7287,62 @@ namespace { return 0; } + /* MOTK4 UPDATE: MOTK3 fields + per-slot bios_offer (valid/prefer/can_*). */ + static int ae_np_lan_parse_motk4_update(char* body, AeLanLobbyState* out) { + if (!body || !out) return -1; + char* lines[8] = {}; + char* p = body; + for (int i = 0; i < 4; ++i) { + lines[i] = p; + char* nl = std::strchr(p, '\n'); + if (!nl) return -1; + *nl = '\0'; + p = nl + 1; + } + int max_slots = std::atoi(lines[0]); + int host_slot = std::atoi(lines[1]); + int started = std::atoi(lines[2]); + unsigned sid = (unsigned)std::strtoul(lines[3], nullptr, 10); + if (max_slots < 2) max_slots = 2; + if (max_slots > kAeLanMaxSlots) max_slots = kAeLanMaxSlots; + if (host_slot < 0 || host_slot >= max_slots) host_slot = 0; + out->max_slots = max_slots; + out->host_slot = host_slot; + out->started = started != 0; + out->session_id = sid ? (uint32_t)sid : 1u; + for (int i = 0; i < kAeLanMaxSlots; ++i) { + out->slot_name[i].clear(); + out->slot_id[i].clear(); + g_lnch_lan_slot_bios[i] = {}; + } + for (int i = 0; i < max_slots; ++i) { + char* nl = std::strchr(p, '\n'); + if (!nl) return -1; + *nl = '\0'; + out->slot_id[i] = p; + p = nl + 1; + nl = std::strchr(p, '\n'); + if (!nl) return -1; + *nl = '\0'; + out->slot_name[i] = p; + p = nl + 1; + int bios_lines[4] = {}; + for (int b = 0; b < 4; ++b) { + nl = std::strchr(p, '\n'); + if (!nl) return -1; + *nl = '\0'; + bios_lines[b] = std::atoi(p); + p = nl + 1; + } + if (bios_lines[0]) { + ae_np_lan_store_slot_bios(i, bios_lines[1], bios_lines[2], + bios_lines[3]); + } + } + ae_np_lan_sync_legacy_names(*out); + return 0; + } + /* MOTK3 UPDATE: header + (player_id, display_name) per slot. */ static int ae_np_lan_parse_motk3_update(char* body, AeLanLobbyState* out) { if (!body || !out) return -1; @@ -7307,9 +7405,10 @@ namespace { std::string me = psx_lobby_display_name(); if (me.empty()) me = "Player"; const char* my_id = ae_np_lan_local_player_id(); - char msg[384]; - std::snprintf(msg, sizeof(msg), "MOTK3 JOIN\n%s\n%s\n%s\n", my_id, me.c_str(), - password ? password : ""); + char msg[448]; + int off = std::snprintf(msg, sizeof(msg), "MOTK3 JOIN\n%s\n%s\n%s\n", my_id, + me.c_str(), password ? password : ""); + ae_np_append_lan_bios_join(msg, sizeof(msg), &off); ae_np_lan_udp_sendto(to, msg); const uint32_t deadline = SDL_GetTicks() + 1000u; @@ -7494,6 +7593,129 @@ namespace { ae_np_refresh_bios_offer(nullptr); } + static void ae_np_set_session_bios_token(const char* token) { + g_lnch_session_bios[0] = '\0'; + if (!token || !token[0]) return; + if (std::strcmp(token, "openbios") != 0 && + std::strcmp(token, "scph1001") != 0) + return; + std::snprintf(g_lnch_session_bios, sizeof(g_lnch_session_bios), "%s", + token); + } + + static void ae_np_clear_session_bios_token(void) { + g_lnch_session_bios[0] = '\0'; + } + + static void ae_np_lan_clear_slot_bios(int slot) { + if (slot < 0 || slot >= kAeLanMaxSlots) return; + g_lnch_lan_slot_bios[slot] = {}; + } + + static void ae_np_lan_store_slot_bios(int slot, int prefer_open, int can_open, + int can_scph) { + if (slot < 0 || slot >= kAeLanMaxSlots) return; + AeLanSlotBios& b = g_lnch_lan_slot_bios[slot]; + b.valid = 1; + b.prefer_openbios = prefer_open ? 1 : 0; + b.can_openbios = can_open ? 1 : 0; + b.can_scph1001 = can_scph ? 1 : 0; + if (!b.can_openbios && !b.can_scph1001) b.can_openbios = 1; + } + + static void ae_np_lan_sync_local_slot_bios(void) { + ae_np_refresh_bios_offer_from_disk(); + const PsxLobbyBiosOffer* offer = psx_lobby_bios_offer(); + int slot = -1; + if (g_lnch_hosting_lan) { + AeLanLobbyState st; + if (ae_np_read_lan_state(&st)) slot = st.host_slot; + } else { + slot = g_lnch_lan_my_slot; + } + if (slot < 0 || !offer || !offer->valid) return; + ae_np_lan_store_slot_bios(slot, offer->prefer_openbios, offer->can_openbios, + offer->can_scph1001); + } + + /* Same settle rule as psx_lobby_settle_session_bios, over LAN seat offers. */ + static int ae_np_lan_settle_session_bios(char* out, size_t out_cap) { + if (!out || out_cap < 9) return -1; + out[0] = '\0'; + AeLanLobbyState st; + if (!ae_np_read_lan_state(&st)) { + std::strncpy(out, "openbios", out_cap - 1); + out[out_cap - 1] = '\0'; + return 0; + } + ae_np_lan_sync_local_slot_bios(); + int any_prefer_open = 0; + int any_cannot_scph = 0; + int saw_peer = 0; + for (int i = 0; i < st.max_slots && i < kAeLanMaxSlots; ++i) { + if (st.slot_name[i].empty()) continue; + saw_peer = 1; + const AeLanSlotBios& b = g_lnch_lan_slot_bios[i]; + if (!b.valid) { + any_cannot_scph = 1; + continue; + } + if (b.prefer_openbios) any_prefer_open = 1; + if (!b.can_scph1001) any_cannot_scph = 1; + } + if (!saw_peer) any_cannot_scph = 1; + if (any_prefer_open || any_cannot_scph) + std::strncpy(out, "openbios", out_cap - 1); + else + std::strncpy(out, "scph1001", out_cap - 1); + out[out_cap - 1] = '\0'; + return 0; + } + + static void ae_np_append_lan_bios_join(char* msg, size_t msg_cap, int* io_off) { + if (!msg || !io_off || *io_off < 0) return; + ae_np_refresh_bios_offer_from_disk(); + const PsxLobbyBiosOffer* offer = psx_lobby_bios_offer(); + const char* prefer = "openbios"; + int can_open = 1; + int can_scph = 0; + if (offer && offer->valid) { + prefer = offer->prefer_openbios ? "openbios" : "scph1001"; + can_open = offer->can_openbios ? 1 : 0; + can_scph = offer->can_scph1001 ? 1 : 0; + } + const int n = std::snprintf(msg + *io_off, msg_cap - (size_t)*io_off, + "%s\n%d\n%d\n", prefer, can_open, can_scph); + if (n > 0) *io_off += n; + } + + /* Parse optional JOIN bios tail: prefer\ncan_open\ncan_scph\n */ + static int ae_np_parse_lan_bios_tail(char* p, int* prefer_open, int* can_open, + int* can_scph) { + if (!p || !prefer_open || !can_open || !can_scph) return -1; + char* lines[3] = {}; + for (int i = 0; i < 3; ++i) { + if (!p || !*p) return -1; + lines[i] = p; + char* nl = std::strchr(p, '\n'); + if (nl) { + *nl = '\0'; + p = nl + 1; + } else { + p = p + std::strlen(p); + } + } + if (std::strcmp(lines[0], "openbios") == 0) + *prefer_open = 1; + else if (std::strcmp(lines[0], "scph1001") == 0) + *prefer_open = 0; + else + return -1; + *can_open = (std::atoi(lines[1]) != 0) ? 1 : 0; + *can_scph = (std::atoi(lines[2]) != 0) ? 1 : 0; + return 0; + } + PsxLobbyMatchCaps ae_netplay_caps_from_settings(const RecompLauncherCSettings* s) { PsxLobbyMatchCaps caps{}; caps.valid = 1; @@ -7728,10 +7950,12 @@ namespace { if (inet_pton(AF_INET, host, &to.sin_addr) == 1) { std::string me = psx_lobby_display_name(); if (me.empty()) me = "Player"; - char msg[384]; - std::snprintf(msg, sizeof(msg), "MOTK3 JOIN\n%s\n%s\n%s\n", - ae_np_lan_local_player_id(), me.c_str(), - g_lnch_remote_lan_state.password.c_str()); + char msg[448]; + int off = std::snprintf( + msg, sizeof(msg), "MOTK3 JOIN\n%s\n%s\n%s\n", + ae_np_lan_local_player_id(), me.c_str(), + g_lnch_remote_lan_state.password.c_str()); + ae_np_append_lan_bios_join(msg, sizeof(msg), &off); ae_np_lan_udp_sendto(to, msg); } } @@ -7784,8 +8008,15 @@ namespace { const char* name = p; p = nl + 1; nl = std::strchr(p, '\n'); - if (nl) *nl = '\0'; - const char* pass = p; + const char* pass; + char* bios_tail = nullptr; + if (nl) { + *nl = '\0'; + pass = p; + bios_tail = nl + 1; + } else { + pass = p; + } AeLanLobbyState st; if (!ae_np_read_lan_state(&st)) continue; if (st.password != pass) { @@ -7797,6 +8028,15 @@ namespace { ae_np_lan_udp_sendto(from, "MOTK1 ERR\nfull\n"); continue; } + int prefer_open = 1, can_open = 1, can_scph = 0; + if (bios_tail && + ae_np_parse_lan_bios_tail(bios_tail, &prefer_open, &can_open, + &can_scph) == 0) { + ae_np_lan_store_slot_bios(slot, prefer_open, can_open, can_scph); + } else { + /* Legacy JOIN without bios_offer — cannot assume SCPH. */ + ae_np_lan_clear_slot_bios(slot); + } st.started = false; ae_np_lan_sync_legacy_names(st); if (!ae_np_write_lan_state(st)) continue; @@ -7832,6 +8072,8 @@ namespace { ae_np_lan_udp_sendto(from, "MOTK1 ERR\nfull\n"); continue; } + /* Legacy MOTK1 JOIN has no bios_offer. */ + ae_np_lan_clear_slot_bios(slot); st.started = false; ae_np_lan_sync_legacy_names(st); if (!ae_np_write_lan_state(st)) continue; @@ -7867,6 +8109,7 @@ namespace { if (cleared >= 0) { st.slot_name[cleared].clear(); st.slot_id[cleared].clear(); + ae_np_lan_clear_slot_bios(cleared); } } else { /* Legacy leave without name: drop first guest. */ @@ -7875,6 +8118,7 @@ namespace { if (!st.slot_name[i].empty()) { st.slot_name[i].clear(); st.slot_id[i].clear(); + ae_np_lan_clear_slot_bios(i); cleared = i; break; } @@ -7890,6 +8134,26 @@ namespace { if (!g_lnch_remote_lan) continue; + if (std::strncmp(buf, "MOTK4 UPDATE\n", 13) == 0) { + AeLanLobbyState st = g_lnch_remote_lan_state; + if (ae_np_lan_parse_motk4_update(buf + 13, &st) != 0) continue; + st.endpoint = g_lnch_lan_endpoint; + g_lnch_remote_lan_state = st; + const int my_slot = + ae_np_lan_find_slot_by_id(st, ae_np_lan_local_player_id()); + if (my_slot < 0) { + g_lnch_joined_lan = false; + g_lnch_remote_lan = false; + g_lnch_lan_endpoint.clear(); + g_lnch_lan_my_slot = -1; + ae_np_lan_udp_close(); + } else { + g_lnch_lan_my_slot = my_slot; + ae_np_lan_sync_local_slot_bios(); + } + continue; + } + if (std::strncmp(buf, "MOTK3 UPDATE\n", 13) == 0) { AeLanLobbyState st = g_lnch_remote_lan_state; if (ae_np_lan_parse_motk3_update(buf + 13, &st) != 0) continue; @@ -7972,9 +8236,9 @@ namespace { } if (std::strncmp(buf, "MOTK1 START\n", 12) == 0) { g_lnch_remote_lan_state.started = true; - /* MOTK1 START\n\n[\n\n\n] - * Trailing caps lines are host-authoritative (LAN had no - * match_caps channel; guests used local UI defaults). */ + /* MOTK1 START\n\n[\n\n\n + * [\n]] + * Trailing caps are host-authoritative (incl. settled BIOS). */ char* p = buf + 12; char* nl = std::strchr(p, '\n'); if (nl) *nl = '\0'; @@ -7987,8 +8251,8 @@ namespace { } if (nl) { p = nl + 1; - char* lines[3] = {}; - for (int i = 0; i < 3; ++i) { + char* lines[4] = {}; + for (int i = 0; i < 4; ++i) { if (!p || !*p) break; lines[i] = p; char* n2 = std::strchr(p, '\n'); @@ -8013,6 +8277,12 @@ namespace { } if (lines[2] && lines[2][0]) g_lnch_rollback = (std::atoi(lines[2]) != 0) ? 1 : 0; + if (lines[3] && lines[3][0]) + ae_np_set_session_bios_token(lines[3]); + else + /* Legacy host: force OpenBIOS so mixed local prefs + * cannot silently desync. */ + ae_np_set_session_bios_token("openbios"); } continue; } @@ -8620,9 +8890,13 @@ namespace { out->is_host = (slot == state.host_slot) ? 1 : 0; out->is_local = (slot == g_lnch_lan_my_slot) ? 1 : 0; out->latency_ms = -1; - out->bios_offer_valid = 0; - out->bios_can_scph1001 = 0; - out->bios_prefer_openbios = 0; + if (out->is_local) ae_np_lan_sync_local_slot_bios(); + { + const AeLanSlotBios& b = g_lnch_lan_slot_bios[slot]; + out->bios_offer_valid = b.valid; + out->bios_can_scph1001 = b.can_scph1001; + out->bios_prefer_openbios = b.prefer_openbios; + } return 1; } ++seen; @@ -8692,6 +8966,7 @@ namespace { if (state.slot_name[slot].empty()) return -1; state.slot_name[slot].clear(); state.slot_id[slot].clear(); + ae_np_lan_clear_slot_bios(slot); state.started = false; ae_np_lan_sync_legacy_names(state); if (!ae_np_write_lan_state(state)) return -1; @@ -8720,13 +8995,24 @@ namespace { AeLanLobbyState state; if (!ae_np_read_lan_state(&state) || ae_np_lan_occupied(state) < 2) return -1; + if (settings && settings->bios_path[0]) + ae_np_refresh_bios_offer(settings->bios_path); + else + ae_np_refresh_bios_offer_from_disk(); + char session_bios[16] = {}; + (void)ae_np_lan_settle_session_bios(session_bios, sizeof(session_bios)); + ae_np_set_session_bios_token(session_bios[0] ? session_bios + : "openbios"); + std::fprintf(stdout, "psxrecomp: LAN settled session BIOS = %s\n", + g_lnch_session_bios[0] ? g_lnch_session_bios + : "openbios"); state.started = true; state.session_id += 1u; if (state.session_id == 0) state.session_id = 1; g_lnch_lan_session_id = state.session_id; if (!ae_np_write_lan_state(state)) return -1; ae_np_lan_send_update_to_peers(state); - char start_msg[96]; + char start_msg[128]; int delay = g_lnch_lobby_input_delay; int pred = g_lnch_lobby_input_prediction; if (delay < 2) delay = 2; @@ -8734,9 +9020,11 @@ namespace { if (pred < 2) pred = 2; if (pred > 16) pred = 16; std::snprintf(start_msg, sizeof(start_msg), - "MOTK1 START\n%u\n%d\n%d\n%d\n", + "MOTK1 START\n%u\n%d\n%d\n%d\n%s\n", (unsigned)state.session_id, delay, pred, - g_lnch_rollback ? 1 : 0); + g_lnch_rollback ? 1 : 0, + g_lnch_session_bios[0] ? g_lnch_session_bios + : "openbios"); for (int i = 0; i < kAeLanMaxSlots; ++i) { if (g_lnch_lan_peer_ok[i]) ae_np_lan_udp_sendto(g_lnch_lan_peers[i], start_msg); @@ -8753,6 +9041,8 @@ namespace { PsxLobbyMatchCaps caps = ae_netplay_caps_from_settings(settings); (void)psx_lobby_settle_session_bios(caps.session_bios, sizeof(caps.session_bios)); + ae_np_set_session_bios_token(caps.session_bios[0] ? caps.session_bios + : "openbios"); std::fprintf(stdout, "psxrecomp: lobby settled session BIOS = %s\n", caps.session_bios[0] ? caps.session_bios : "openbios"); return psx_lobby_request_start(&caps); @@ -8763,6 +9053,11 @@ namespace { !g_lnch_pending_direct_launch.enabled) { AeLanLobbyState state; if (ae_np_read_lan_state(&state) && state.started) { + /* Guests may see started=1 on UPDATE before MOTK1 START + * delivers session_bios — wait so both peers boot the same BIOS. */ + if (g_lnch_joined_lan && !g_lnch_session_bios[0]) + return g_lnch_pending_direct_launch.enabled || + psx_lobby_launch_pending(); /* Free the lobby UDP port before delay-sync binds it. */ ae_np_lan_udp_close(); g_lnch_lan_session_id = state.session_id ? state.session_id : 1u; @@ -8820,6 +9115,7 @@ namespace { /* After a match soft-exit: keep seats, clear started/ready, rebind LAN UDP. */ void ae_np_prepare_lobby_rematch(void) { g_lnch_pending_direct_launch = {}; + ae_np_clear_session_bios_token(); psx_lobby_set_ready(0); psx_lobby_clear_launch_pending(); psx_lobby_clear_signals(); @@ -8907,6 +9203,8 @@ namespace { out->force_turn = caps->force_turn ? 1 : 0; out->rollback = (caps && caps->valid && caps->rollback) ? 1 : 0; + if (caps->session_bios[0]) + ae_np_set_session_bios_token(caps->session_bios); return 1; } @@ -10597,6 +10895,7 @@ int main(int argc, char** argv) { /* Match BIOS settle (lobby bios_offer → session_bios). * Ephemeral for this boot only — seed / bios.cfg keep * the player's offline preference. */ +<<<<<<< HEAD if (resolve_match_session_bios_path( caps->session_bios, seed.has_bios_path ? seed.bios_path @@ -10620,6 +10919,44 @@ int main(int argc, char** argv) { match_session_bios_path.string().c_str()); } } +======= + if (caps->session_bios[0]) + ae_np_set_session_bios_token(caps->session_bios); + } + } + /* LAN START / lobby token — apply even when match_caps absent. */ + if (ls.netplay_launch.enabled && g_lnch_session_bios[0] && + resolve_match_session_bios_path( + g_lnch_session_bios, + seed.has_bios_path ? seed.bios_path + : std::filesystem::path{}, + ls.bios_path, argv[0], + &match_session_bios_path)) { + match_session_bios_set = true; + if (std::strcmp(g_lnch_session_bios, "scph1001") == 0 && + match_session_bios_path.empty()) { + std::fprintf(stderr, + "psxrecomp: session BIOS scph1001 required but no " + "validated dump found — aborting launch (mixed " + "OpenBIOS/SCPH would desync)\n"); + match_session_bios_set = false; + ls.netplay_launch.enabled = 0; + g_lnch_pending_direct_launch = {}; + ae_np_clear_session_bios_token(); + if (overlay_init_thread.joinable()) + overlay_init_thread.join(); + SDL_Quit(); + return 1; + } else if (match_session_bios_path.empty()) { + std::fprintf(stdout, + "psxrecomp: netplay session BIOS = OpenBIOS " + "(match only; preference unchanged)\n"); + } else { + std::fprintf(stdout, + "psxrecomp: netplay session BIOS = SCPH-1001 " + "(%s; match only; preference unchanged)\n", + match_session_bios_path.string().c_str()); +>>>>>>> fca7047 (bios hotfix) } } #endif @@ -12260,6 +12597,7 @@ soft_return_lobby: } if (net_cfg.enabled) { const PsxLobbyMatchCaps* caps = psx_lobby_match_caps(); +<<<<<<< HEAD std::filesystem::path session_path; if (caps && caps->valid && resolve_match_session_bios_path( @@ -12267,6 +12605,25 @@ soft_return_lobby: ls.bios_path[0] ? std::filesystem::path(ls.bios_path) : std::filesystem::path{}, ls.bios_path, argv[0], &session_path)) { +======= + if (caps && caps->valid && caps->session_bios[0]) + ae_np_set_session_bios_token(caps->session_bios); + std::filesystem::path session_path; + if (g_lnch_session_bios[0] && + resolve_match_session_bios_path( + g_lnch_session_bios, + ls.bios_path[0] ? std::filesystem::path(ls.bios_path) + : std::filesystem::path{}, + ls.bios_path, argv[0], &session_path)) { + if (std::strcmp(g_lnch_session_bios, "scph1001") == 0 && + session_path.empty()) { + std::fprintf(stderr, + "psxrecomp: rematch session BIOS scph1001 required " + "but no validated dump — aborting\n"); + SDL_Quit(); + return 1; + } +>>>>>>> fca7047 (bios hotfix) if (session_path.empty()) { std::filesystem::path ob = resolve_bios_path(PSX_BUNDLED_BIOS_PATH, argv[0]);