mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
bios select and rematch pathing fix
This commit is contained in:
@@ -122,6 +122,21 @@ refuse to load under a different one. Without that check, switching BIOS would
|
||||
silently corrupt a restored state — the only quiet failure mode in this design,
|
||||
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:
|
||||
|
||||
- **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
|
||||
sends no offer (legacy client).
|
||||
- **SCPH-1001** only when every seated peer can run it and nobody selected
|
||||
OpenBIOS.
|
||||
|
||||
Every peer applies that session BIOS before boot. Mixed BIOSes are invalid for
|
||||
rollback (kernel RAM layout differs). Offline / single-player selection is
|
||||
unchanged by this rule.
|
||||
|
||||
## Why both are compiled in
|
||||
|
||||
The alternative — a build flavour per BIOS — was the original design, and it
|
||||
|
||||
@@ -127,6 +127,12 @@ 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
|
||||
uses OpenBIOS unless every seated peer can run SCPH-1001 and nobody selected
|
||||
OpenBIOS. See `docs/BIOS_SELECTION.md` (Netplay lobby settle).
|
||||
|
||||
---
|
||||
|
||||
## Disc identity for multi-track titles
|
||||
|
||||
@@ -43,8 +43,24 @@ typedef struct PsxLobbyMember {
|
||||
char player_id[PSX_LOBBY_ID_LEN];
|
||||
char display_name[PSX_LOBBY_NAME_LEN];
|
||||
int ready;
|
||||
/* Peer BIOS capability from set_ready bios_offer (0 if legacy/missing). */
|
||||
int bios_offer_valid;
|
||||
int bios_can_openbios; /* linked OpenBIOS backend */
|
||||
int bios_can_scph1001; /* linked retail + validated dump available */
|
||||
int bios_prefer_openbios; /* explicit OpenBIOS pick (not retail) */
|
||||
} PsxLobbyMember;
|
||||
|
||||
/*
|
||||
* Local BIOS capability advertised on set_ready (see docs/BIOS_SELECTION.md
|
||||
* netplay settle rule). Updated by the host runtime via psx_lobby_set_bios_offer.
|
||||
*/
|
||||
typedef struct PsxLobbyBiosOffer {
|
||||
int valid;
|
||||
int can_openbios;
|
||||
int can_scph1001;
|
||||
int prefer_openbios; /* 1 = OpenBIOS selected; 0 = retail / willing SCPH */
|
||||
} PsxLobbyBiosOffer;
|
||||
|
||||
/*
|
||||
* Host-authoritative sim settings negotiated over the lobby.
|
||||
* Guests apply these on launch so both peers boot with matching caps.
|
||||
@@ -65,6 +81,8 @@ typedef struct PsxLobbyMatchCaps {
|
||||
/* DualShock-on-multitap-tap hack (0/1). Host-authoritative for the match. */
|
||||
int multitap_analog;
|
||||
char language[PSX_LOBBY_LANG_LEN];
|
||||
/* Settled match BIOS: "openbios" | "scph1001" | "" (unset / legacy). */
|
||||
char session_bios[16];
|
||||
} PsxLobbyMatchCaps;
|
||||
|
||||
typedef struct PsxLobbyJoinInfo {
|
||||
@@ -87,9 +105,11 @@ typedef struct PsxLobbyJoinInfo {
|
||||
* 3) ws://netplay.retcomm.net:8765 */
|
||||
const char *psx_lobby_default_url(void);
|
||||
|
||||
int psx_lobby_connect(const char *ws_url); /* 0 ok */
|
||||
int psx_lobby_connect(const char *ws_url); /* 0 = connected or connect started */
|
||||
void psx_lobby_disconnect(void);
|
||||
int psx_lobby_connected(void);
|
||||
/* 1 while DNS/TCP/WebSocket upgrade runs off the UI thread. */
|
||||
int psx_lobby_connecting(void);
|
||||
|
||||
void psx_lobby_set_display_name(const char *name);
|
||||
const char *psx_lobby_display_name(void);
|
||||
@@ -216,9 +236,21 @@ int psx_lobby_local_ready(void);
|
||||
/* True when every seated player is ready and player_count >= 2. */
|
||||
int psx_lobby_all_ready(void);
|
||||
|
||||
/* Toggle ready in the current lobby. */
|
||||
/* Toggle ready in the current lobby (attaches current bios_offer). */
|
||||
int psx_lobby_set_ready(int ready);
|
||||
|
||||
/* Local BIOS offer used on the next set_ready (and included in settle). */
|
||||
void psx_lobby_set_bios_offer(const PsxLobbyBiosOffer *offer);
|
||||
const PsxLobbyBiosOffer *psx_lobby_bios_offer(void);
|
||||
|
||||
/*
|
||||
* Settle session BIOS from seated peers' bios_offer (+ local offer):
|
||||
* OpenBIOS if anyone prefers OpenBIOS or anyone cannot run SCPH-1001
|
||||
* (missing offer ⇒ cannot); else SCPH-1001 when everyone can.
|
||||
* Writes "openbios" or "scph1001" into out. Returns 0 on success.
|
||||
*/
|
||||
int psx_lobby_settle_session_bios(char *out, size_t out_cap);
|
||||
|
||||
/*
|
||||
* Host: ask server to broadcast launch. When match_caps is non-NULL and valid,
|
||||
* it is attached to start so launch freezes the latest host settings.
|
||||
|
||||
@@ -1170,6 +1170,11 @@ function(psxrecomp_add_runtime_target target)
|
||||
if(OpenGL_FOUND)
|
||||
target_link_libraries(${target} PRIVATE OpenGL::GL)
|
||||
endif()
|
||||
# Async lobby connect (psx_lobby_client.c) uses pthread on Unix.
|
||||
if(PSXRECOMP_HAS_LOBBY_CLIENT)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(${target} PRIVATE Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ---- Vulkan backend (gpu_vk_renderer.c) --------------------------------
|
||||
|
||||
+415
-156
@@ -6309,6 +6309,7 @@ namespace {
|
||||
|
||||
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);
|
||||
|
||||
#ifdef _WIN32
|
||||
using AeLanSock = SOCKET;
|
||||
@@ -6501,9 +6502,23 @@ namespace {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Sync browse: broadcast BROWSE across the common lobby port range and
|
||||
* collect BEACON replies for wait_ms. */
|
||||
static void ae_np_lan_browse(uint32_t wait_ms) {
|
||||
/* Async LAN browse: broadcast once, collect BEACONs across frames in
|
||||
* ae_np_lan_browse_pump (no SDL_Delay busy-wait on the UI thread). */
|
||||
static AeLanSock g_lan_browse_sock = kAeLanSockInvalid;
|
||||
static uint32_t g_lan_browse_t0 = 0;
|
||||
static uint32_t g_lan_browse_deadline = 0;
|
||||
static int g_lan_browse_file_pending = 0;
|
||||
|
||||
static void ae_np_lan_browse_close(void) {
|
||||
if (g_lan_browse_sock != kAeLanSockInvalid)
|
||||
ae_np_lan_sock_close(&g_lan_browse_sock);
|
||||
g_lan_browse_deadline = 0;
|
||||
g_lan_browse_t0 = 0;
|
||||
}
|
||||
|
||||
static void ae_np_lan_browse_start(uint32_t wait_ms) {
|
||||
if (g_lan_browse_sock != kAeLanSockInvalid)
|
||||
return; /* already collecting */
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
@@ -6525,33 +6540,81 @@ namespace {
|
||||
}
|
||||
|
||||
const char browse[] = "MOTK1 BROWSE\n";
|
||||
const uint32_t t0 = SDL_GetTicks();
|
||||
for (int port = kAeLanBrowsePortLo; port <= kAeLanBrowsePortHi; ++port)
|
||||
ae_np_lan_broadcast_msg(s, port, browse);
|
||||
|
||||
const uint32_t deadline = t0 + (wait_ms ? wait_ms : 250u);
|
||||
while ((int32_t)(deadline - SDL_GetTicks()) > 0) {
|
||||
g_lan_browse_sock = s;
|
||||
g_lan_browse_t0 = SDL_GetTicks();
|
||||
g_lan_browse_deadline =
|
||||
g_lan_browse_t0 + (wait_ms ? wait_ms : 250u);
|
||||
}
|
||||
|
||||
static void ae_np_lan_file_promote_without_rtt(void) {
|
||||
AeLanLobbyState st;
|
||||
if (!ae_np_read_lan_file_state(&st) || st.started) {
|
||||
g_lnch_lan_latency_ms = -1;
|
||||
return;
|
||||
}
|
||||
g_lnch_lan_latency_ms = -1;
|
||||
if (ae_np_lan_discovered_find(st.endpoint.c_str()) >= 0)
|
||||
return;
|
||||
AeLanDiscovered d{};
|
||||
std::snprintf(d.endpoint, sizeof(d.endpoint), "%s", st.endpoint.c_str());
|
||||
std::snprintf(d.name, sizeof(d.name), "%s",
|
||||
st.name.empty() ? "LAN Lobby" : st.name.c_str());
|
||||
std::snprintf(d.game, sizeof(d.game), "%s",
|
||||
st.game.empty() ? "PSX" : st.game.c_str());
|
||||
d.player_count = ae_np_lan_occupied(st);
|
||||
d.max_slots = st.max_slots >= 2 ? st.max_slots : 2;
|
||||
d.has_password = st.password.empty() ? 0 : 1;
|
||||
d.latency_ms = -1;
|
||||
d.input_delay = g_lnch_lobby_input_delay;
|
||||
d.input_prediction = g_lnch_lobby_input_prediction;
|
||||
d.rollback = g_lnch_rollback ? 1 : 0;
|
||||
d.session_id = st.session_id ? st.session_id : 1u;
|
||||
d.last_seen_ms = SDL_GetTicks();
|
||||
ae_np_lan_discovered_upsert(d);
|
||||
}
|
||||
|
||||
static void ae_np_lan_browse_pump(void) {
|
||||
if (g_lan_browse_sock == kAeLanSockInvalid) {
|
||||
if (g_lan_browse_file_pending) {
|
||||
g_lan_browse_file_pending = 0;
|
||||
if (!g_lnch_hosting_lan && !g_lnch_joined_lan)
|
||||
ae_np_lan_file_promote_without_rtt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
char buf[1024];
|
||||
sockaddr_in from{};
|
||||
#ifdef _WIN32
|
||||
int fromlen = (int)sizeof(from);
|
||||
const int n = recvfrom(s, buf, (int)sizeof(buf) - 1, 0,
|
||||
const int n = recvfrom(g_lan_browse_sock, buf, (int)sizeof(buf) - 1, 0,
|
||||
(sockaddr*)&from, &fromlen);
|
||||
#else
|
||||
socklen_t fromlen = sizeof(from);
|
||||
const int n = (int)recvfrom(s, buf, sizeof(buf) - 1, 0,
|
||||
const int n = (int)recvfrom(g_lan_browse_sock, buf, sizeof(buf) - 1, 0,
|
||||
(sockaddr*)&from, &fromlen);
|
||||
#endif
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
const int rtt = (int)(SDL_GetTicks() - t0);
|
||||
(void)ae_np_lan_parse_beacon(buf, &from, rtt);
|
||||
} else {
|
||||
SDL_Delay(5);
|
||||
}
|
||||
if (n <= 0)
|
||||
break;
|
||||
buf[n] = '\0';
|
||||
const int rtt = (int)(SDL_GetTicks() - g_lan_browse_t0);
|
||||
(void)ae_np_lan_parse_beacon(buf, &from, rtt);
|
||||
}
|
||||
ae_np_lan_sock_close(&s);
|
||||
|
||||
if ((int32_t)(g_lan_browse_deadline - SDL_GetTicks()) > 0)
|
||||
return;
|
||||
|
||||
ae_np_lan_browse_close();
|
||||
ae_np_lan_prune_discovered();
|
||||
if (g_lan_browse_file_pending) {
|
||||
g_lan_browse_file_pending = 0;
|
||||
if (!g_lnch_hosting_lan && !g_lnch_joined_lan)
|
||||
ae_np_lan_file_promote_without_rtt();
|
||||
}
|
||||
}
|
||||
|
||||
static void ae_np_lan_apply_discovered_caps(const char* endpoint) {
|
||||
@@ -7300,7 +7363,7 @@ namespace {
|
||||
if (g_lnch_hosting_lan) {
|
||||
g_lnch_lan_latency_ms = 0;
|
||||
/* Still browse so the host list can show other LAN rooms. */
|
||||
ae_np_lan_browse(200u);
|
||||
ae_np_lan_browse_start(200u);
|
||||
AeLanLobbyState self{};
|
||||
if (ae_np_read_lan_file_state(&self) && !self.started) {
|
||||
AeLanDiscovered d{};
|
||||
@@ -7325,39 +7388,11 @@ namespace {
|
||||
}
|
||||
if (g_lnch_joined_lan) return;
|
||||
|
||||
/* Cross-machine discovery: broadcast BROWSE, collect BEACONs. */
|
||||
ae_np_lan_browse(300u);
|
||||
|
||||
/* Legacy same-machine file registry (shared cwd). */
|
||||
AeLanLobbyState st;
|
||||
if (!ae_np_read_lan_file_state(&st)) {
|
||||
g_lnch_lan_latency_ms = -1;
|
||||
return;
|
||||
}
|
||||
if (st.started) return;
|
||||
g_lnch_lan_latency_ms = ae_np_lan_probe_rtt_ms(st.endpoint, 200u);
|
||||
if (g_lnch_lan_latency_ms < 0) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(ae_np_lan_file(), ec);
|
||||
} else if (ae_np_lan_discovered_find(st.endpoint.c_str()) < 0) {
|
||||
/* Promote file row into the discovered table so list_get is uniform. */
|
||||
AeLanDiscovered d{};
|
||||
std::snprintf(d.endpoint, sizeof(d.endpoint), "%s", st.endpoint.c_str());
|
||||
std::snprintf(d.name, sizeof(d.name), "%s",
|
||||
st.name.empty() ? "LAN Lobby" : st.name.c_str());
|
||||
std::snprintf(d.game, sizeof(d.game), "%s",
|
||||
st.game.empty() ? "PSX" : st.game.c_str());
|
||||
d.player_count = ae_np_lan_occupied(st);
|
||||
d.max_slots = st.max_slots >= 2 ? st.max_slots : 2;
|
||||
d.has_password = st.password.empty() ? 0 : 1;
|
||||
d.latency_ms = g_lnch_lan_latency_ms;
|
||||
d.input_delay = g_lnch_lobby_input_delay;
|
||||
d.input_prediction = g_lnch_lobby_input_prediction;
|
||||
d.rollback = g_lnch_rollback ? 1 : 0;
|
||||
d.session_id = st.session_id ? st.session_id : 1u;
|
||||
d.last_seen_ms = SDL_GetTicks();
|
||||
ae_np_lan_discovered_upsert(d);
|
||||
}
|
||||
/* Cross-machine discovery + legacy file row — non-blocking. */
|
||||
g_lan_browse_file_pending = 1;
|
||||
ae_np_lan_browse_start(300u);
|
||||
/* If the browse socket failed to open, promote the file row now. */
|
||||
ae_np_lan_browse_pump();
|
||||
}
|
||||
|
||||
static bool ae_np_lan_list_visible(void) {
|
||||
@@ -7379,6 +7414,48 @@ namespace {
|
||||
return ae_np_lan_list_visible() ? 1 : 0;
|
||||
}
|
||||
|
||||
/* Advertise local BIOS capability for lobby settle (OpenBIOS vs SCPH-1001). */
|
||||
static void ae_np_refresh_bios_offer(const char* launcher_bios_path) {
|
||||
PsxLobbyBiosOffer offer{};
|
||||
offer.valid = 1;
|
||||
offer.can_openbios =
|
||||
(s_openbios_allowed && psx_bios_bundled() != nullptr) ? 1 : 0;
|
||||
const char* argv0 = g_lnch_argv0 ? g_lnch_argv0 : "";
|
||||
auto path_is_retail = [&](const std::filesystem::path& p) -> bool {
|
||||
std::error_code ec;
|
||||
if (p.empty() || !std::filesystem::exists(p, ec)) return false;
|
||||
const PsxBiosBackend* b = bios_backend_for_file(p, nullptr, nullptr);
|
||||
return b && b->image && !b->image->image_bundled;
|
||||
};
|
||||
bool has_dump = false;
|
||||
if (launcher_bios_path && launcher_bios_path[0]) {
|
||||
const auto p = resolve_bios_path(launcher_bios_path, argv0);
|
||||
has_dump = path_is_retail(p);
|
||||
}
|
||||
if (!has_dump) has_dump = path_is_retail(read_cached_path(argv0, "bios.cfg"));
|
||||
if (!has_dump) has_dump = path_is_retail(discover_retail_bios_near(argv0));
|
||||
offer.can_scph1001 =
|
||||
(psx_bios_has_selectable() && has_dump) ? 1 : 0;
|
||||
|
||||
/* Empty / bundled path = explicit OpenBIOS preference. */
|
||||
offer.prefer_openbios = 1;
|
||||
if (launcher_bios_path && launcher_bios_path[0]) {
|
||||
const auto p = resolve_bios_path(launcher_bios_path, argv0);
|
||||
if (path_is_retail(p))
|
||||
offer.prefer_openbios = 0;
|
||||
} else {
|
||||
/* No launcher path — bios.cfg empty ⇒ OpenBIOS; retail path ⇒ SCPH. */
|
||||
const auto cached = read_cached_path(argv0, "bios.cfg");
|
||||
if (path_is_retail(cached)) offer.prefer_openbios = 0;
|
||||
}
|
||||
if (!offer.can_openbios && offer.can_scph1001) offer.prefer_openbios = 0;
|
||||
psx_lobby_set_bios_offer(&offer);
|
||||
}
|
||||
|
||||
static void ae_np_refresh_bios_offer_from_disk(void) {
|
||||
ae_np_refresh_bios_offer(nullptr);
|
||||
}
|
||||
|
||||
PsxLobbyMatchCaps ae_netplay_caps_from_settings(const RecompLauncherCSettings* s) {
|
||||
PsxLobbyMatchCaps caps{};
|
||||
caps.valid = 1;
|
||||
@@ -7912,15 +7989,31 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
int ae_np_connecting(void*) {
|
||||
return psx_lobby_connecting();
|
||||
}
|
||||
|
||||
void ae_np_pump(void*) {
|
||||
psx_lobby_pump();
|
||||
ae_np_lan_browse_pump();
|
||||
ae_np_lan_udp_pump();
|
||||
/* Lobby UI has no Ready toggle; production WS still requires every
|
||||
* seated player ready before start. Keep seats ready while in-room
|
||||
* (including after soft-return rematch clears ready). */
|
||||
if (!g_lnch_hosting_lan && !g_lnch_joined_lan &&
|
||||
psx_lobby_in_lobby() && !psx_lobby_local_ready()) {
|
||||
(void)psx_lobby_set_ready(1);
|
||||
* (including after soft-return rematch clears ready). Re-advertise
|
||||
* when the local BIOS offer changes so settle stays current. */
|
||||
if (!g_lnch_hosting_lan && !g_lnch_joined_lan && psx_lobby_in_lobby()) {
|
||||
static PsxLobbyBiosOffer s_last_offer{};
|
||||
ae_np_refresh_bios_offer_from_disk();
|
||||
const PsxLobbyBiosOffer* cur = psx_lobby_bios_offer();
|
||||
const int offer_changed =
|
||||
!cur || !s_last_offer.valid ||
|
||||
cur->can_openbios != s_last_offer.can_openbios ||
|
||||
cur->can_scph1001 != s_last_offer.can_scph1001 ||
|
||||
cur->prefer_openbios != s_last_offer.prefer_openbios;
|
||||
if (!psx_lobby_local_ready() || offer_changed) {
|
||||
(void)psx_lobby_set_ready(1);
|
||||
if (cur) s_last_offer = *cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8470,6 +8563,9 @@ namespace {
|
||||
: 0;
|
||||
}
|
||||
out->latency_ms = psx_lobby_member_latency_ms(mem.slot);
|
||||
out->bios_offer_valid = mem.bios_offer_valid;
|
||||
out->bios_can_scph1001 = mem.bios_can_scph1001;
|
||||
out->bios_prefer_openbios = mem.bios_prefer_openbios;
|
||||
return 1;
|
||||
}
|
||||
if (ae_np_use_lan_members()) {
|
||||
@@ -8486,6 +8582,9 @@ 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;
|
||||
return 1;
|
||||
}
|
||||
++seen;
|
||||
@@ -8510,6 +8609,9 @@ namespace {
|
||||
: 0;
|
||||
}
|
||||
out->latency_ms = psx_lobby_member_latency_ms(mem.slot);
|
||||
out->bios_offer_valid = mem.bios_offer_valid;
|
||||
out->bios_can_scph1001 = mem.bios_can_scph1001;
|
||||
out->bios_prefer_openbios = mem.bios_prefer_openbios;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -8569,7 +8671,11 @@ namespace {
|
||||
|
||||
int ae_np_local_ready(void*) { return psx_lobby_local_ready(); }
|
||||
int ae_np_all_ready(void*) { return psx_lobby_all_ready(); }
|
||||
int ae_np_set_ready(void*, int ready) { return psx_lobby_set_ready(ready); }
|
||||
int ae_np_set_ready(void*, int ready) {
|
||||
if (ready)
|
||||
ae_np_refresh_bios_offer_from_disk();
|
||||
return psx_lobby_set_ready(ready);
|
||||
}
|
||||
|
||||
int ae_np_request_start(void*, const RecompLauncherCSettings* settings) {
|
||||
if (g_lnch_hosting_lan) {
|
||||
@@ -8600,9 +8706,17 @@ namespace {
|
||||
return 0;
|
||||
}
|
||||
if (!psx_lobby_is_host()) return -1;
|
||||
/* Ensure host seat is ready even if pump hasn't run since rematch. */
|
||||
/* Publish current BIOS offer, then ensure host seat is ready. */
|
||||
if (settings && settings->bios_path[0])
|
||||
ae_np_refresh_bios_offer(settings->bios_path);
|
||||
else
|
||||
ae_np_refresh_bios_offer_from_disk();
|
||||
(void)psx_lobby_set_ready(1);
|
||||
PsxLobbyMatchCaps caps = ae_netplay_caps_from_settings(settings);
|
||||
(void)psx_lobby_settle_session_bios(caps.session_bios,
|
||||
sizeof(caps.session_bios));
|
||||
std::fprintf(stdout, "psxrecomp: lobby settled session BIOS = %s\n",
|
||||
caps.session_bios[0] ? caps.session_bios : "openbios");
|
||||
return psx_lobby_request_start(&caps);
|
||||
}
|
||||
|
||||
@@ -8804,7 +8918,96 @@ namespace {
|
||||
ae_np_input_prediction_set,
|
||||
ae_np_multitap_analog_get,
|
||||
ae_np_multitap_analog_set,
|
||||
ae_np_connecting,
|
||||
};
|
||||
|
||||
/* Shared by first-boot launcher and soft-return rematch UI so capability
|
||||
* flags (View mode / perspective / Skip FMVs / renderer labels) cannot
|
||||
* drift apart — a bare launcher_profile_apply("psx") hides those rows. */
|
||||
std::string g_rui_keybinds_path;
|
||||
std::string g_rui_config_ini_path;
|
||||
static const char* const kPsxRendererLabels[] = {
|
||||
"Software",
|
||||
"OpenGL (Recommended)",
|
||||
"Vulkan",
|
||||
};
|
||||
|
||||
void ae_rui_set_sidecar_paths(const char* argv0) {
|
||||
const auto exe = exe_dir_from_argv(argv0 ? argv0 : "");
|
||||
g_rui_keybinds_path = (exe / "keybinds.ini").string();
|
||||
g_rui_config_ini_path = (exe / "config.ini").string();
|
||||
}
|
||||
|
||||
void ae_fill_psx_launcher_game_info(
|
||||
RecompLauncherCGameInfo* gi,
|
||||
const char* game_name_c,
|
||||
const char* region_c,
|
||||
int game_players_n,
|
||||
bool ws_offered_b,
|
||||
bool ws_ultrawide_offered_b,
|
||||
bool skip_fmv_offered_b,
|
||||
bool turbo_loads_offered_b,
|
||||
bool vulkan_offered_b,
|
||||
bool ctrl_lock_mode_b,
|
||||
bool ctrl_lock_device_b,
|
||||
int locked_pad_mode_i,
|
||||
const char* const* language_labels,
|
||||
int num_languages,
|
||||
int resume_netplay_room)
|
||||
{
|
||||
if (!gi) return;
|
||||
launcher_profile_apply("psx", gi);
|
||||
gi->name = game_name_c;
|
||||
gi->region = region_c;
|
||||
gi->keybinds_path =
|
||||
g_rui_keybinds_path.empty() ? nullptr : g_rui_keybinds_path.c_str();
|
||||
gi->config_path =
|
||||
g_rui_config_ini_path.empty() ? nullptr : g_rui_config_ini_path.c_str();
|
||||
gi->has_expected_crc = 0;
|
||||
gi->num_known_sha256 = 0;
|
||||
gi->widescreen_supported = ws_offered_b ? 1 : 0;
|
||||
gi->num_players = game_players_n;
|
||||
gi->msu1_supported = 0;
|
||||
gi->sram_path = nullptr;
|
||||
gi->has_bios = psx_bios_has_selectable();
|
||||
gi->pad_mode_selectable = ctrl_lock_mode_b ? 0 : 1;
|
||||
gi->locked_pad_mode = locked_pad_mode_i;
|
||||
gi->lock_device = ctrl_lock_device_b ? 1 : 0;
|
||||
gi->aspect_mask =
|
||||
0x1 | (ws_offered_b ? 0x2 : 0) | (ws_ultrawide_offered_b ? 0x4 : 0);
|
||||
gi->renderer_labels = kPsxRendererLabels;
|
||||
gi->num_renderers = vulkan_offered_b ? 3 : 2;
|
||||
gi->has_skip_fmv = skip_fmv_offered_b ? 1 : 0;
|
||||
gi->has_turbo_loads = turbo_loads_offered_b ? 1 : 0;
|
||||
gi->has_geometry_precision = 1;
|
||||
if (language_labels && num_languages > 0) {
|
||||
gi->language_labels = language_labels;
|
||||
gi->num_languages = num_languages;
|
||||
} else {
|
||||
gi->language_labels = nullptr;
|
||||
gi->num_languages = 0;
|
||||
}
|
||||
gi->disc_verify = ae_disc_verify;
|
||||
gi->memcard_inspect = ae_memcard_inspect;
|
||||
gi->mods = PSXRecompV4::mod_runtime_launcher_provider();
|
||||
gi->bios_verify = ae_bios_verify;
|
||||
#if defined(PSX_HAS_RECOMP_NET) && defined(PSX_HAS_LOBBY_CLIENT)
|
||||
g_lnch_game_players = game_players_n;
|
||||
gi->netplay_supported =
|
||||
(game_players_n >= 2 && game_players_n <= PSX_MAX_PLAYERS) ? 1 : 0;
|
||||
gi->netplay = &g_lnch_netplay_callbacks;
|
||||
#else
|
||||
gi->netplay_supported = 0;
|
||||
gi->netplay = nullptr;
|
||||
#endif
|
||||
gi->resume_netplay_room = resume_netplay_room ? 1 : 0;
|
||||
#if defined(PSX_HAS_LOBBY_CLIENT)
|
||||
gi->resume_netplay_endpoint =
|
||||
resume_netplay_room ? ae_np_lan_endpoint_cstr() : nullptr;
|
||||
#else
|
||||
gi->resume_netplay_endpoint = nullptr;
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
@@ -9973,15 +10176,8 @@ int main(int argc, char** argv) {
|
||||
/* recomp-ui creates + owns its SDL2/GL window internally, so there
|
||||
* is no launcher window/context to manage here. */
|
||||
std::string assets_dir_str = exe_dir_from_argv(argv[0]).string();
|
||||
/* Same path the runtime's psx_keybinds_init(argv0) reads — keep the
|
||||
* launcher Controls page and in-game keyboard map on one file. */
|
||||
static std::string s_rui_keybinds_path;
|
||||
static std::string s_rui_config_ini_path;
|
||||
s_rui_keybinds_path = (exe_dir_from_argv(argv[0]) / "keybinds.ini").string();
|
||||
/* Same config.ini the runtime reads for [KeyMap] VolumeUp/Down —
|
||||
* never cwd-relative "config.ini" (that drifted edits off the exe). */
|
||||
s_rui_config_ini_path =
|
||||
(exe_dir_from_argv(argv[0]) / "config.ini").string();
|
||||
/* Same keybinds.ini / config.ini the runtime reads — never cwd. */
|
||||
ae_rui_set_sidecar_paths(argv[0]);
|
||||
std::string rui_initial_disc = resolved_disc.string();
|
||||
std::string rui_title = (game_name.empty() ? std::string("PSX") : game_name)
|
||||
+ " - Launcher";
|
||||
@@ -10103,67 +10299,30 @@ int main(int argc, char** argv) {
|
||||
rui_lang_labels.reserve(lang_menu_options.size());
|
||||
for (const auto& lo : lang_menu_options) rui_lang_labels.push_back(lo.label.c_str());
|
||||
|
||||
RecompLauncherCGameInfo gi{};
|
||||
static const char* const kPsxRendererLabels[] = {
|
||||
"Software",
|
||||
"OpenGL (Recommended)",
|
||||
"Vulkan"
|
||||
};
|
||||
/* System identity + full PS1 settings-surface capability set (theme,
|
||||
* platform label, rom_noun, pad-mode support, aspect mask base, and
|
||||
* all has_* deep-settings flags) — one profile call keeps them from
|
||||
* drifting apart across PSX titles. Per-game specifics below override
|
||||
* only what the profile can't know. */
|
||||
launcher_profile_apply("psx", &gi);
|
||||
gi.name = game_name.empty() ? nullptr : game_name.c_str();
|
||||
gi.region = rui_region.empty() ? nullptr : rui_region.c_str();
|
||||
gi.keybinds_path = s_rui_keybinds_path.c_str();
|
||||
gi.config_path = s_rui_config_ini_path.c_str();
|
||||
gi.has_expected_crc = 0; /* the launcher's simple file-CRC doesn't fit
|
||||
PSX multi-track discs — skip verification */
|
||||
gi.num_known_sha256 = 0;
|
||||
gi.widescreen_supported = ws_offered ? 1 : 0;
|
||||
gi.num_players = game_players;
|
||||
gi.msu1_supported = 0;
|
||||
gi.sram_path = nullptr; /* PSX uses memory cards, not SRAM -> hide SAVES */
|
||||
/* BIOS row: ask the registry, not psx_bios_image, which is not
|
||||
* populated until a backend is activated. With OpenBIOS AND a
|
||||
* retail image linked, choosing retail (and clearing back to
|
||||
* bundled) is exactly what the row is for. */
|
||||
gi.has_bios = psx_bios_has_selectable();
|
||||
/* Pad-mode + aspect capabilities sourced from game.toml
|
||||
* [controller]/[widescreen] via GameConfig. PSX always has pad modes. */
|
||||
gi.pad_mode_selectable = ctrl_lock_mode ? 0 : 1;
|
||||
gi.locked_pad_mode = ctrl_locked_mode[0]; /* game-declared default_mode */
|
||||
gi.lock_device = ctrl_lock_device ? 1 : 0;
|
||||
gi.aspect_mask = 0x1 | (ws_offered ? 0x2 : 0) | (ws_ultrawide_offered ? 0x4 : 0);
|
||||
gi.renderer_labels = kPsxRendererLabels;
|
||||
gi.num_renderers = vulkan_offered ? 3 : 2;
|
||||
gi.has_skip_fmv = skip_fmv_offered ? 1 : 0;
|
||||
gi.has_turbo_loads = turbo_loads_offered ? 1 : 0;
|
||||
/* Geometry precision is a property of the PS1 pipeline, not of any
|
||||
* particular disc, so every PSX title exposes it. */
|
||||
gi.has_geometry_precision = 1;
|
||||
/* Localization menu: shown only when the game declares languages. */
|
||||
if (!rui_lang_labels.empty()) {
|
||||
gi.language_labels = rui_lang_labels.data();
|
||||
gi.num_languages = (int)rui_lang_labels.size();
|
||||
}
|
||||
|
||||
/* Real disc-verify + memcard-inspect (identify_disc / memcard_summary_path
|
||||
* via the ae_* callbacks). The launcher re-runs these whenever the
|
||||
* user changes the disc or a memory card, so the verdict + block
|
||||
* grids reflect the actual images.
|
||||
* The expected serial/CRC ride on file-scope statics (the C-ABI
|
||||
* callback can't capture these locals). */
|
||||
/* Real disc-verify + memcard-inspect expect serial/CRC on file-scope
|
||||
* statics (C-ABI callback can't capture these locals). */
|
||||
g_lnch_expected_serial = game_id;
|
||||
g_lnch_expected_crc = game_disc_crc;
|
||||
g_lnch_has_crc = game_has_disc_crc;
|
||||
g_lnch_argv0 = argv[0];
|
||||
gi.disc_verify = ae_disc_verify;
|
||||
gi.memcard_inspect = ae_memcard_inspect;
|
||||
gi.mods = PSXRecompV4::mod_runtime_launcher_provider();
|
||||
gi.bios_verify = ae_bios_verify;
|
||||
|
||||
RecompLauncherCGameInfo gi{};
|
||||
ae_fill_psx_launcher_game_info(
|
||||
&gi,
|
||||
game_name.empty() ? nullptr : game_name.c_str(),
|
||||
rui_region.empty() ? nullptr : rui_region.c_str(),
|
||||
game_players,
|
||||
ws_offered,
|
||||
ws_ultrawide_offered,
|
||||
skip_fmv_offered,
|
||||
turbo_loads_offered,
|
||||
vulkan_offered,
|
||||
ctrl_lock_mode,
|
||||
ctrl_lock_device,
|
||||
ctrl_locked_mode[0],
|
||||
rui_lang_labels.empty() ? nullptr : rui_lang_labels.data(),
|
||||
(int)rui_lang_labels.size(),
|
||||
/*resume_netplay_room=*/0);
|
||||
#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. */
|
||||
@@ -10227,16 +10386,10 @@ int main(int argc, char** argv) {
|
||||
#endif
|
||||
#endif /* PSX_HAS_SETUP_WIZARD */
|
||||
launcher_boot_timing_mark("host:setup_checks_done");
|
||||
/* Full netplay UI: only when this build linked recomp-net + lobby
|
||||
* (-DPSX_NETPLAY=ON) and the title is multiplayer-capable. */
|
||||
#if defined(PSX_HAS_RECOMP_NET) && defined(PSX_HAS_LOBBY_CLIENT)
|
||||
g_lnch_netplay_game_name = game_name.empty() ? "PSX" : game_name;
|
||||
g_lnch_game_players = game_players;
|
||||
apply_offline_pad_count(game_players, multitap_enabled);
|
||||
psx_lobby_set_max_slots(game_players);
|
||||
gi.netplay_supported =
|
||||
(game_players >= 2 && game_players <= PSX_MAX_PLAYERS) ? 1 : 0;
|
||||
gi.netplay = &g_lnch_netplay_callbacks;
|
||||
#endif
|
||||
|
||||
char rui_out_disc[1024] = {0};
|
||||
@@ -10400,6 +10553,46 @@ int main(int argc, char** argv) {
|
||||
seed.language = caps->language;
|
||||
seed.has_language = true;
|
||||
}
|
||||
/* Match BIOS settle (lobby bios_offer → session_bios). */
|
||||
if (caps->session_bios[0] &&
|
||||
std::strcmp(caps->session_bios, "openbios") == 0) {
|
||||
seed.bios_path.clear();
|
||||
seed.has_bios_path = false;
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: netplay session BIOS = OpenBIOS\n");
|
||||
} else if (caps->session_bios[0] &&
|
||||
std::strcmp(caps->session_bios, "scph1001") == 0) {
|
||||
std::filesystem::path retail;
|
||||
std::error_code ec;
|
||||
auto try_retail = [&](const std::filesystem::path& p) {
|
||||
if (p.empty() || !std::filesystem::exists(p, ec))
|
||||
return;
|
||||
const PsxBiosBackend* b =
|
||||
bios_backend_for_file(p, nullptr, nullptr);
|
||||
if (b && b->image && !b->image->image_bundled)
|
||||
retail = p;
|
||||
};
|
||||
if (seed.has_bios_path) try_retail(seed.bios_path);
|
||||
if (retail.empty() && ls.bios_path[0])
|
||||
try_retail(resolve_bios_path(ls.bios_path, argv[0]));
|
||||
if (retail.empty())
|
||||
try_retail(read_cached_path(argv[0], "bios.cfg"));
|
||||
if (retail.empty())
|
||||
try_retail(discover_retail_bios_near(argv[0]));
|
||||
if (!retail.empty()) {
|
||||
seed.bios_path = retail;
|
||||
seed.has_bios_path = true;
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: netplay session BIOS = SCPH-1001 (%s)\n",
|
||||
retail.string().c_str());
|
||||
} else {
|
||||
seed.bios_path.clear();
|
||||
seed.has_bios_path = false;
|
||||
std::fprintf(stderr,
|
||||
"psxrecomp: session BIOS scph1001 settled but no "
|
||||
"dump found — falling back to OpenBIOS\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -10482,12 +10675,17 @@ int main(int argc, char** argv) {
|
||||
settings_bios_storage = seed.bios_path.string();
|
||||
bios_path = settings_bios_storage.c_str();
|
||||
bios_explicit = true;
|
||||
bios_from_cli = false; /* launcher pick supersedes argv --bios */
|
||||
write_cached_path(argv[0], "bios.cfg", seed.bios_path);
|
||||
} else if (!bios_from_cli) {
|
||||
/* Cleared to bundled OpenBIOS — drop any cached retail pick. */
|
||||
} else {
|
||||
/* OpenBIOS: always clear, even when launched with --bios.
|
||||
* Write an empty bios.cfg (do not delete — missing triggers
|
||||
* first-run retail rediscovery). */
|
||||
bios_from_cli = false;
|
||||
bios_explicit = false;
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(sidecar_cfg_path(argv[0], "bios.cfg"), ec);
|
||||
settings_bios_storage.clear();
|
||||
bios_path = PSX_BUNDLED_BIOS_PATH;
|
||||
write_cached_path(argv[0], "bios.cfg", {});
|
||||
}
|
||||
if (seed.has_disc_path) {
|
||||
seed.disc_path = normalize_disc_path_for_launch(seed.disc_path);
|
||||
@@ -11686,29 +11884,67 @@ soft_return_lobby:
|
||||
+ " - Launcher";
|
||||
std::string rui_initial_disc = disc_path_str;
|
||||
|
||||
ae_rui_set_sidecar_paths(argv[0]);
|
||||
g_lnch_expected_serial = game_id;
|
||||
g_lnch_expected_crc = game_disc_crc;
|
||||
g_lnch_has_crc = game_has_disc_crc;
|
||||
g_lnch_argv0 = argv[0];
|
||||
g_lnch_netplay_game_name = game_name.empty() ? "PSX" : game_name;
|
||||
psx_lobby_set_max_slots(game_players);
|
||||
|
||||
RecompLauncherCSettings ls{};
|
||||
ls.output_method = 2;
|
||||
ls.window_scale = std::max(1, std::min(4, g_video_win_w / 320));
|
||||
ls.fullscreen = g_fullscreen ? 1 : 0;
|
||||
ls.ignore_aspect = 0;
|
||||
ls.linear_filter = (g_video_texfilter != 0) ? 1 : 0;
|
||||
ls.widescreen =
|
||||
(g_video_aspect_num == 16 && g_video_aspect_den == 9) ? 1 : 0;
|
||||
ls.widescreen_hud = ls.widescreen;
|
||||
ls.enable_audio = 1;
|
||||
ls.audio_freq = 44100;
|
||||
ls.volume = host_volume_get();
|
||||
ls.window_width = g_video_win_w;
|
||||
ls.renderer = g_video_renderer;
|
||||
if (ls.renderer < 0 || ls.renderer > (vulkan_offered ? 2 : 1))
|
||||
ls.renderer = PSXRecompV4::DEFAULT_VIDEO_RENDERER;
|
||||
ls.supersampling = g_video_scale;
|
||||
ls.antialiasing = g_video_aa ? 1 : 0;
|
||||
ls.texture_filter = g_video_texfilter;
|
||||
ls.geometry_correction = g_video_geometry_correction ? 1 : 0;
|
||||
ls.perspective_texturing = g_video_perspective_texturing ? 1 : 0;
|
||||
ls.screen_kind = g_video_screen;
|
||||
ls.frame_interp = g_frame_interpolation ? 1 : 0;
|
||||
ls.frame_interp_fps = g_frame_interpolation_fps;
|
||||
ls.spu_hq = g_audio_spu_hq ? 1 : 0;
|
||||
ls.auto_skip_fmv = g_auto_skip_fmv ? 1 : 0;
|
||||
ls.turbo_loads = g_turbo_loads_enabled ? 1 : 0;
|
||||
ls.auto_skip_fmv = (skip_fmv_offered && g_auto_skip_fmv) ? 1 : 0;
|
||||
ls.turbo_loads = (turbo_loads_offered && g_turbo_loads_enabled) ? 1 : 0;
|
||||
ls.aspect_index = (g_video_aspect_num * 9 == g_video_aspect_den * 21) ? 2
|
||||
: (g_video_aspect_num == 16 && g_video_aspect_den == 9) ? 1 : 0;
|
||||
ls.language_index = 0;
|
||||
for (size_t li = 0; li < lang_menu_options.size(); li++) {
|
||||
if (lang_menu_options[li].code == resolved_language) {
|
||||
ls.language_index = (int)li;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::snprintf(ls.netplay_player_name, sizeof(ls.netplay_player_name), "%s",
|
||||
psx_lobby_display_name());
|
||||
std::snprintf(ls.bios_path, sizeof(ls.bios_path), "%s", bios_path_str.c_str());
|
||||
/* Prefer absolute BIOS path so rematch does not depend on cwd. */
|
||||
{
|
||||
std::filesystem::path resolved =
|
||||
resolve_bios_path(bios_path_str.c_str(), argv[0]);
|
||||
std::error_code ec;
|
||||
if (!resolved.empty() && std::filesystem::exists(resolved, ec)) {
|
||||
auto abs = std::filesystem::weakly_canonical(resolved, ec);
|
||||
if (ec) abs = std::filesystem::absolute(resolved, ec);
|
||||
std::snprintf(ls.bios_path, sizeof(ls.bios_path), "%s",
|
||||
abs.string().c_str());
|
||||
} else {
|
||||
std::snprintf(ls.bios_path, sizeof(ls.bios_path), "%s",
|
||||
bios_path_str.c_str());
|
||||
}
|
||||
}
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MULTITAP_ENABLED)
|
||||
ls.multitap_enabled = multitap_enabled ? 1 : 0;
|
||||
#endif
|
||||
@@ -11725,7 +11961,8 @@ soft_return_lobby:
|
||||
const std::string& d = player_device[i];
|
||||
ls.player_src[i] =
|
||||
PSXRecompV4::launcher_source_from_device(d);
|
||||
ls.deadzone[i] = (player_deadzone[i] * 100 + 16383) / 32767;
|
||||
ls.deadzone[i] =
|
||||
(player_deadzone[i] * 100 + 32767 / 2) / 32767;
|
||||
ls.pad_mode[i] = (ls.player_src[i] == 1)
|
||||
? PSXRecompV4::PAD_MODE_DIGITAL
|
||||
: player_mode[i];
|
||||
@@ -11739,23 +11976,30 @@ soft_return_lobby:
|
||||
}
|
||||
}
|
||||
|
||||
const std::string rui_region =
|
||||
!game_region.empty() ? game_region : region_label_from_serial(game_id);
|
||||
std::vector<const char*> rui_lang_labels;
|
||||
rui_lang_labels.reserve(lang_menu_options.size());
|
||||
for (const auto& lo : lang_menu_options)
|
||||
rui_lang_labels.push_back(lo.label.c_str());
|
||||
|
||||
RecompLauncherCGameInfo gi{};
|
||||
launcher_profile_apply("psx", &gi);
|
||||
gi.name = game_name.empty() ? nullptr : game_name.c_str();
|
||||
gi.num_players = game_players;
|
||||
g_lnch_game_players = game_players;
|
||||
psx_lobby_set_max_slots(game_players);
|
||||
gi.netplay_supported =
|
||||
(game_players >= 2 && game_players <= PSX_MAX_PLAYERS) ? 1 : 0;
|
||||
gi.netplay = &g_lnch_netplay_callbacks;
|
||||
gi.resume_netplay_room = 1;
|
||||
gi.resume_netplay_endpoint = ae_np_lan_endpoint_cstr();
|
||||
gi.disc_verify = ae_disc_verify;
|
||||
gi.memcard_inspect = ae_memcard_inspect;
|
||||
gi.mods = PSXRecompV4::mod_runtime_launcher_provider();
|
||||
gi.pad_mode_selectable = ctrl_lock_mode ? 0 : 1;
|
||||
gi.locked_pad_mode = ctrl_locked_mode[0];
|
||||
gi.lock_device = ctrl_lock_device ? 1 : 0;
|
||||
ae_fill_psx_launcher_game_info(
|
||||
&gi,
|
||||
game_name.empty() ? nullptr : game_name.c_str(),
|
||||
rui_region.empty() ? nullptr : rui_region.c_str(),
|
||||
game_players,
|
||||
ws_offered,
|
||||
ws_ultrawide_offered,
|
||||
skip_fmv_offered,
|
||||
turbo_loads_offered,
|
||||
vulkan_offered,
|
||||
ctrl_lock_mode,
|
||||
ctrl_lock_device,
|
||||
ctrl_locked_mode[0],
|
||||
rui_lang_labels.empty() ? nullptr : rui_lang_labels.data(),
|
||||
(int)rui_lang_labels.size(),
|
||||
/*resume_netplay_room=*/1);
|
||||
#if defined(PSX_HAS_SETUP_WIZARD) && defined(PSX_HAS_GAME_CODEGEN)
|
||||
psx_game_codegen_setup_apply(&gi);
|
||||
#endif
|
||||
@@ -11903,6 +12147,10 @@ soft_return_lobby:
|
||||
us.has_antialiasing = true;
|
||||
us.texture_filter = ls.texture_filter;
|
||||
us.has_texture_filter = true;
|
||||
us.geometry_correction = ls.geometry_correction != 0;
|
||||
us.has_geometry_correction = true;
|
||||
us.perspective_texturing = ls.perspective_texturing != 0;
|
||||
us.has_perspective_texturing = true;
|
||||
us.screen_kind = ls.screen_kind;
|
||||
us.has_screen_kind = true;
|
||||
us.frame_interpolation = ls.frame_interp != 0;
|
||||
@@ -11912,9 +12160,9 @@ soft_return_lobby:
|
||||
us.spu_hq = ls.spu_hq != 0;
|
||||
us.has_spu_hq = true;
|
||||
us.auto_skip_fmv = ls.auto_skip_fmv != 0;
|
||||
us.has_auto_skip_fmv = true;
|
||||
us.has_auto_skip_fmv = skip_fmv_offered;
|
||||
us.turbo_loads = ls.turbo_loads != 0;
|
||||
us.has_turbo_loads = true;
|
||||
us.has_turbo_loads = turbo_loads_offered;
|
||||
us.fullscreen = ls.fullscreen != 0;
|
||||
us.has_fullscreen = true;
|
||||
us.window_width = ls.window_width > 0 ? ls.window_width : g_video_win_w;
|
||||
@@ -11925,15 +12173,22 @@ soft_return_lobby:
|
||||
default: us.aspect_num = 4; us.aspect_den = 3; break;
|
||||
}
|
||||
us.has_aspect_ratio = true;
|
||||
if (ls.bios_path[0]) {
|
||||
us.bios_path = ls.bios_path;
|
||||
us.has_bios_path = true;
|
||||
}
|
||||
(void)PSXRecompV4::save_user_settings(settings_path, us);
|
||||
}
|
||||
g_video_renderer = ls.renderer;
|
||||
g_video_scale = ls.supersampling;
|
||||
g_video_aa = ls.antialiasing;
|
||||
g_video_texfilter = ls.texture_filter;
|
||||
g_video_geometry_correction = ls.geometry_correction ? 1 : 0;
|
||||
g_video_perspective_texturing = ls.perspective_texturing ? 1 : 0;
|
||||
g_video_screen = ls.screen_kind;
|
||||
g_auto_skip_fmv = ls.auto_skip_fmv ? 1 : 0;
|
||||
g_turbo_loads_enabled = ls.turbo_loads ? 1 : 0;
|
||||
g_auto_skip_fmv = skip_fmv_offered && ls.auto_skip_fmv ? 1 : 0;
|
||||
g_turbo_loads_enabled =
|
||||
turbo_loads_offered && ls.turbo_loads ? 1 : 0;
|
||||
g_fullscreen = ls.fullscreen != 0;
|
||||
g_frame_interpolation = ls.frame_interp ? 1 : 0;
|
||||
g_frame_interpolation_fps = ls.frame_interp_fps;
|
||||
@@ -11944,6 +12199,10 @@ soft_return_lobby:
|
||||
default: g_video_aspect_num = 4; g_video_aspect_den = 3; break;
|
||||
}
|
||||
g_video_win_w = ls.window_width > 0 ? ls.window_width : g_video_win_w;
|
||||
if (ls.bios_path[0])
|
||||
bios_path_str = ls.bios_path;
|
||||
else
|
||||
bios_path_str.clear();
|
||||
{
|
||||
std::string mod_error;
|
||||
if (net_cfg.enabled) {
|
||||
|
||||
+484
-95
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -23,6 +24,7 @@ const char *psx_lobby_default_url(void)
|
||||
int psx_lobby_connect(const char *ws_url) { (void)ws_url; return -1; }
|
||||
void psx_lobby_disconnect(void) {}
|
||||
int psx_lobby_connected(void) { return 0; }
|
||||
int psx_lobby_connecting(void) { return 0; }
|
||||
void psx_lobby_set_display_name(const char *name) { (void)name; }
|
||||
const char *psx_lobby_display_name(void) { return ""; }
|
||||
const char *psx_lobby_player_id(void) { return ""; }
|
||||
@@ -93,6 +95,19 @@ const PsxLobbyTurnCredentials *psx_lobby_turn_credentials(void)
|
||||
int psx_lobby_local_ready(void) { return 0; }
|
||||
int psx_lobby_all_ready(void) { return 0; }
|
||||
int psx_lobby_set_ready(int ready) { (void)ready; return -1; }
|
||||
void psx_lobby_set_bios_offer(const PsxLobbyBiosOffer *offer) { (void)offer; }
|
||||
const PsxLobbyBiosOffer *psx_lobby_bios_offer(void)
|
||||
{
|
||||
static PsxLobbyBiosOffer z;
|
||||
return &z;
|
||||
}
|
||||
int psx_lobby_settle_session_bios(char *out, size_t out_cap)
|
||||
{
|
||||
if (!out || out_cap < 9) return -1;
|
||||
strncpy(out, "openbios", out_cap - 1);
|
||||
out[out_cap - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
int psx_lobby_request_start(const PsxLobbyMatchCaps *c) { (void)c; return -1; }
|
||||
int psx_lobby_launch_pending(void) { return 0; }
|
||||
void psx_lobby_clear_launch_pending(void) {}
|
||||
@@ -112,12 +127,15 @@ void psx_lobby_clear_launch_pending(void) {}
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <process.h>
|
||||
#define close closesocket
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -165,6 +183,7 @@ typedef struct {
|
||||
int all_ready;
|
||||
int launch_pending;
|
||||
PsxLobbyMatchCaps match_caps;
|
||||
PsxLobbyBiosOffer bios_offer;
|
||||
char pending_tx[8][2048];
|
||||
int pending_n;
|
||||
/* Inbound ICE signals (WS op:signal). */
|
||||
@@ -1271,6 +1290,12 @@ static void parse_match_caps_object(const char *obj, PsxLobbyMatchCaps *out)
|
||||
out->rollback = json_get_bool(obj, "rollback", 0);
|
||||
out->multitap_analog = json_get_bool(obj, "multitap_analog", 0);
|
||||
json_get_str(obj, "language", out->language, sizeof(out->language));
|
||||
json_get_str(obj, "session_bios", out->session_bios, sizeof(out->session_bios));
|
||||
/* Normalize settled BIOS id. */
|
||||
if (out->session_bios[0] &&
|
||||
strcmp(out->session_bios, "openbios") != 0 &&
|
||||
strcmp(out->session_bios, "scph1001") != 0)
|
||||
out->session_bios[0] = '\0';
|
||||
out->valid = 1;
|
||||
}
|
||||
|
||||
@@ -1295,24 +1320,29 @@ static int append_match_caps_json(char *dst, size_t dst_cap, const PsxLobbyMatch
|
||||
}
|
||||
lang[o] = '\0';
|
||||
if (!lang[0]) strncpy(lang, "en", sizeof(lang) - 1);
|
||||
return snprintf(dst, dst_cap,
|
||||
",\"match_caps\":{\"v\":1,\"aspect_num\":%d,\"aspect_den\":%d,"
|
||||
"\"turbo_loads\":%s,\"bios_hle\":%s,\"fast_boot\":%s,"
|
||||
"\"auto_skip_fmv\":%s,\"input_delay\":%d,\"input_prediction\":%d,"
|
||||
"\"force_input_relay\":%s,\"force_turn\":%s,\"rollback\":%s,"
|
||||
"\"multitap_analog\":%s,\"language\":\"%s\"}",
|
||||
caps->aspect_num, caps->aspect_den,
|
||||
caps->turbo_loads ? "true" : "false",
|
||||
caps->bios_hle ? "true" : "false",
|
||||
caps->fast_boot ? "true" : "false",
|
||||
caps->auto_skip_fmv ? "true" : "false",
|
||||
caps->input_delay,
|
||||
caps->input_prediction,
|
||||
caps->force_input_relay ? "true" : "false",
|
||||
caps->force_turn ? "true" : "false",
|
||||
caps->rollback ? "true" : "false",
|
||||
caps->multitap_analog ? "true" : "false",
|
||||
lang);
|
||||
{
|
||||
const char *sb = caps->session_bios;
|
||||
if (!sb[0] || (strcmp(sb, "openbios") != 0 && strcmp(sb, "scph1001") != 0))
|
||||
sb = "";
|
||||
return snprintf(dst, dst_cap,
|
||||
",\"match_caps\":{\"v\":1,\"aspect_num\":%d,\"aspect_den\":%d,"
|
||||
"\"turbo_loads\":%s,\"bios_hle\":%s,\"fast_boot\":%s,"
|
||||
"\"auto_skip_fmv\":%s,\"input_delay\":%d,\"input_prediction\":%d,"
|
||||
"\"force_input_relay\":%s,\"force_turn\":%s,\"rollback\":%s,"
|
||||
"\"multitap_analog\":%s,\"language\":\"%s\",\"session_bios\":\"%s\"}",
|
||||
caps->aspect_num, caps->aspect_den,
|
||||
caps->turbo_loads ? "true" : "false",
|
||||
caps->bios_hle ? "true" : "false",
|
||||
caps->fast_boot ? "true" : "false",
|
||||
caps->auto_skip_fmv ? "true" : "false",
|
||||
caps->input_delay,
|
||||
caps->input_prediction,
|
||||
caps->force_input_relay ? "true" : "false",
|
||||
caps->force_turn ? "true" : "false",
|
||||
caps->rollback ? "true" : "false",
|
||||
caps->multitap_analog ? "true" : "false",
|
||||
lang, sb);
|
||||
}
|
||||
}
|
||||
|
||||
static void queue_send(const char *json)
|
||||
@@ -1457,19 +1487,33 @@ static void parse_slots_array(const char *json)
|
||||
++end;
|
||||
} while (*end && depth > 0);
|
||||
{
|
||||
char chunk[512];
|
||||
char chunk[768];
|
||||
char offer[512];
|
||||
size_t len = (size_t)(end - obj);
|
||||
if (len >= sizeof(chunk)) {
|
||||
len = sizeof(chunk) - 1;
|
||||
}
|
||||
memcpy(chunk, obj, len);
|
||||
chunk[len] = '\0';
|
||||
memset(&g_lc.members[n], 0, sizeof(g_lc.members[n]));
|
||||
g_lc.members[n].slot = json_get_int(chunk, "slot", n);
|
||||
json_get_str(chunk, "player_id", g_lc.members[n].player_id,
|
||||
sizeof(g_lc.members[n].player_id));
|
||||
json_get_str(chunk, "display_name", g_lc.members[n].display_name,
|
||||
sizeof(g_lc.members[n].display_name));
|
||||
g_lc.members[n].ready = json_get_bool(chunk, "ready", 0);
|
||||
if (json_extract_object(chunk, "bios_offer", offer, sizeof(offer))) {
|
||||
char prefer[24];
|
||||
prefer[0] = '\0';
|
||||
g_lc.members[n].bios_offer_valid = 1;
|
||||
g_lc.members[n].bios_can_openbios =
|
||||
json_get_bool(offer, "can_openbios", 1);
|
||||
g_lc.members[n].bios_can_scph1001 =
|
||||
json_get_bool(offer, "can_scph1001", 0);
|
||||
json_get_str(offer, "prefer", prefer, sizeof(prefer));
|
||||
g_lc.members[n].bios_prefer_openbios =
|
||||
(strcmp(prefer, "openbios") == 0) ? 1 : 0;
|
||||
}
|
||||
if (g_lc.player_id[0] &&
|
||||
strcmp(g_lc.members[n].player_id, g_lc.player_id) == 0) {
|
||||
g_lc.local_ready = g_lc.members[n].ready;
|
||||
@@ -1998,17 +2042,315 @@ static int set_nonblock(int fd)
|
||||
#endif
|
||||
}
|
||||
|
||||
int psx_lobby_connect(const char *ws_url)
|
||||
static int set_block(int fd)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
u_long mode = 0;
|
||||
return ioctlsocket(fd, FIONBIO, &mode);
|
||||
#else
|
||||
int fl = fcntl(fd, F_GETFL, 0);
|
||||
return fcntl(fd, F_SETFL, fl & ~O_NONBLOCK);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Off-thread DNS + TCP + WS upgrade so the launcher UI never blocks on
|
||||
* getaddrinfo / connect (common multi-second freeze on Windows). */
|
||||
enum { PSX_LOBBY_CONNECT_TIMEOUT_MS = 3000 };
|
||||
|
||||
static int g_lc_tcp_connecting;
|
||||
static unsigned g_connect_gen;
|
||||
static volatile int g_connect_cancel;
|
||||
static volatile int g_connect_worker_done;
|
||||
static volatile int g_connect_worker_rc;
|
||||
static volatile unsigned g_connect_worker_gen;
|
||||
static int g_connect_worker_fd;
|
||||
#if defined(_WIN32)
|
||||
static HANDLE g_connect_thread;
|
||||
#else
|
||||
static pthread_t g_connect_thread;
|
||||
static int g_connect_thread_valid;
|
||||
#endif
|
||||
|
||||
static int socket_connect_in_progress(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
const int e = WSAGetLastError();
|
||||
return e == WSAEWOULDBLOCK || e == WSAEINPROGRESS;
|
||||
#else
|
||||
return errno == EINPROGRESS || errno == EWOULDBLOCK;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int wait_socket_connected(int fd, int timeout_ms)
|
||||
{
|
||||
fd_set wfds;
|
||||
fd_set efds;
|
||||
struct timeval tv;
|
||||
int soerr = 0;
|
||||
#if defined(_WIN32)
|
||||
int len = (int)sizeof(soerr);
|
||||
#else
|
||||
socklen_t len = sizeof(soerr);
|
||||
#endif
|
||||
int r;
|
||||
|
||||
FD_ZERO(&wfds);
|
||||
FD_ZERO(&efds);
|
||||
#if defined(_WIN32)
|
||||
FD_SET((SOCKET)fd, &wfds);
|
||||
FD_SET((SOCKET)fd, &efds);
|
||||
#else
|
||||
FD_SET(fd, &wfds);
|
||||
FD_SET(fd, &efds);
|
||||
#endif
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (long)(timeout_ms % 1000) * 1000;
|
||||
#if defined(_WIN32)
|
||||
r = select(0, NULL, &wfds, &efds, &tv);
|
||||
#else
|
||||
r = select(fd + 1, NULL, &wfds, &efds, &tv);
|
||||
#endif
|
||||
if (r <= 0)
|
||||
return -1;
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&soerr, &len) != 0 || soerr != 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lobby_tcp_connect(const char *host, int port, int *out_fd)
|
||||
{
|
||||
struct addrinfo hints, *res = NULL, *rp;
|
||||
char portstr[16];
|
||||
int fd = -1;
|
||||
|
||||
if (!host || !out_fd)
|
||||
return -2;
|
||||
*out_fd = -1;
|
||||
snprintf(portstr, sizeof(portstr), "%d", port);
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if (getaddrinfo(host, portstr, &hints, &res) != 0)
|
||||
return -2;
|
||||
for (rp = res; rp; rp = rp->ai_next) {
|
||||
int rc;
|
||||
if (g_connect_cancel)
|
||||
break;
|
||||
fd = (int)socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (fd < 0)
|
||||
continue;
|
||||
set_nonblock(fd);
|
||||
rc = connect(fd, rp->ai_addr, (int)rp->ai_addrlen);
|
||||
if (rc == 0)
|
||||
break;
|
||||
if (socket_connect_in_progress()) {
|
||||
if (wait_socket_connected(fd, PSX_LOBBY_CONNECT_TIMEOUT_MS) == 0)
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
if (fd < 0)
|
||||
return -3;
|
||||
set_block(fd);
|
||||
*out_fd = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lobby_send_ws_upgrade(int fd, const char *host, int port, const char *path)
|
||||
{
|
||||
char key_raw[16];
|
||||
char key_b64[32];
|
||||
char req[512];
|
||||
int i;
|
||||
static const char *B64 =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int o = 0;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
key_raw[i] = (char)(rand() & 0xff);
|
||||
for (i = 0; i < 16; i += 3) {
|
||||
unsigned v = ((unsigned char)key_raw[i] << 16);
|
||||
if (i + 1 < 16)
|
||||
v |= ((unsigned char)key_raw[i + 1] << 8);
|
||||
if (i + 2 < 16)
|
||||
v |= (unsigned char)key_raw[i + 2];
|
||||
key_b64[o++] = B64[(v >> 18) & 63];
|
||||
key_b64[o++] = B64[(v >> 12) & 63];
|
||||
key_b64[o++] = (i + 1 < 16) ? B64[(v >> 6) & 63] : '=';
|
||||
key_b64[o++] = (i + 2 < 16) ? B64[v & 63] : '=';
|
||||
}
|
||||
key_b64[o] = '\0';
|
||||
snprintf(req, sizeof(req),
|
||||
"GET %s HTTP/1.1\r\n"
|
||||
"Host: %s:%d\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Key: %s\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n\r\n",
|
||||
path && path[0] ? path : "/", host, port, key_b64);
|
||||
if (send(fd, req, (int)strlen(req), 0) < 0)
|
||||
return -4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
static unsigned __stdcall lobby_connect_worker(void *arg)
|
||||
#else
|
||||
static void *lobby_connect_worker(void *arg)
|
||||
#endif
|
||||
{
|
||||
const unsigned gen = (unsigned)(uintptr_t)arg;
|
||||
int fd = -1;
|
||||
int rc;
|
||||
|
||||
rc = lobby_tcp_connect(g_lc.host, g_lc.port, &fd);
|
||||
if (rc == 0 && !g_connect_cancel && gen == g_connect_gen)
|
||||
rc = lobby_send_ws_upgrade(fd, g_lc.host, g_lc.port, g_lc.path);
|
||||
if (g_connect_cancel || gen != g_connect_gen) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
g_connect_worker_fd = -1;
|
||||
g_connect_worker_rc = -1;
|
||||
} else if (rc != 0) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
g_connect_worker_fd = -1;
|
||||
g_connect_worker_rc = rc;
|
||||
} else {
|
||||
g_connect_worker_fd = fd;
|
||||
g_connect_worker_rc = 0;
|
||||
}
|
||||
g_connect_worker_gen = gen;
|
||||
g_connect_worker_done = 1;
|
||||
#if defined(_WIN32)
|
||||
return 0;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void lobby_join_connect_worker(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (g_connect_thread) {
|
||||
WaitForSingleObject(g_connect_thread, INFINITE);
|
||||
CloseHandle(g_connect_thread);
|
||||
g_connect_thread = NULL;
|
||||
}
|
||||
#else
|
||||
if (g_connect_thread_valid) {
|
||||
pthread_join(g_connect_thread, NULL);
|
||||
g_connect_thread_valid = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static int lobby_connect_thread_active(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return g_connect_thread != NULL;
|
||||
#else
|
||||
return g_connect_thread_valid != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Join a finished worker and adopt the socket when the generation still
|
||||
* matches. Never call this while the worker is mid-DNS/connect from the UI
|
||||
* thread — that would reintroduce the hang. */
|
||||
static void lobby_finish_connect_worker(void)
|
||||
{
|
||||
int fd;
|
||||
int rc;
|
||||
unsigned gen;
|
||||
|
||||
if (!g_connect_worker_done)
|
||||
return;
|
||||
lobby_join_connect_worker();
|
||||
fd = g_connect_worker_fd;
|
||||
rc = g_connect_worker_rc;
|
||||
gen = g_connect_worker_gen;
|
||||
g_connect_worker_fd = -1;
|
||||
g_connect_worker_done = 0;
|
||||
g_lc_tcp_connecting = 0;
|
||||
|
||||
if (gen != g_connect_gen || rc != 0 || fd < 0) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
g_lc.fd = fd;
|
||||
set_nonblock(fd);
|
||||
lobby_store_connected_peer_ip(fd);
|
||||
if (g_lc.connected_peer_ip[0] &&
|
||||
strcmp(g_lc.connected_peer_ip, g_lc.host) != 0) {
|
||||
fprintf(stderr, "psx_lobby: WS connected peer %s (url host %s)\n",
|
||||
g_lc.connected_peer_ip, g_lc.host);
|
||||
}
|
||||
g_lc.connected = 1;
|
||||
g_lc.handshake_done = 0;
|
||||
g_lc.rx_http_len = 0;
|
||||
}
|
||||
|
||||
static void lobby_cancel_connect_async(void)
|
||||
{
|
||||
g_connect_cancel = 1;
|
||||
g_connect_gen++;
|
||||
if (g_connect_gen == 0)
|
||||
g_connect_gen = 1;
|
||||
g_lc_tcp_connecting = 0;
|
||||
/* Reap only if the worker already finished; otherwise pump() joins later. */
|
||||
if (g_connect_worker_done)
|
||||
lobby_finish_connect_worker();
|
||||
}
|
||||
|
||||
int psx_lobby_connect(const char *ws_url)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
uintptr_t th;
|
||||
#else
|
||||
int prc;
|
||||
#endif
|
||||
|
||||
if (psx_lobby_connected())
|
||||
return 0;
|
||||
/* Already starting a wanted connect — UI may call connect+list every frame. */
|
||||
if (g_lc_tcp_connecting)
|
||||
return 0;
|
||||
|
||||
/* One worker slot: if a cancelled connect is still in DNS/TCP, wait for it
|
||||
* before starting another (bounded by PSX_LOBBY_CONNECT_TIMEOUT_MS). */
|
||||
if (lobby_connect_thread_active()) {
|
||||
g_connect_cancel = 1;
|
||||
if (!g_connect_worker_done)
|
||||
lobby_join_connect_worker();
|
||||
if (g_connect_worker_done)
|
||||
lobby_finish_connect_worker();
|
||||
}
|
||||
|
||||
g_lc.ice_rtt_suspended = 0;
|
||||
lobby_ice_rtt_close();
|
||||
lobby_rtt_close();
|
||||
lobby_list_rtt_close();
|
||||
lobby_lan_beacon_close_all();
|
||||
lobby_host_advertise_reset();
|
||||
g_list_rtt_on_next_list = 0;
|
||||
if (g_lc.fd >= 0) {
|
||||
close(g_lc.fd);
|
||||
g_lc.fd = -1;
|
||||
}
|
||||
{
|
||||
char dname[PSX_LOBBY_NAME_LEN];
|
||||
strncpy(dname, g_lc.display_name, sizeof(dname) - 1);
|
||||
dname[sizeof(dname) - 1] = '\0';
|
||||
memset(&g_lc, 0, sizeof(g_lc));
|
||||
g_lc.fd = -1;
|
||||
strncpy(g_lc.display_name, dname, sizeof(g_lc.display_name) - 1);
|
||||
member_rtt_clear();
|
||||
}
|
||||
|
||||
psx_lobby_disconnect();
|
||||
#if defined(_WIN32)
|
||||
{
|
||||
static int wsa;
|
||||
@@ -2023,80 +2365,41 @@ int psx_lobby_connect(const char *ws_url)
|
||||
&g_lc.port, g_lc.path, sizeof(g_lc.path)) != 0) {
|
||||
return -1;
|
||||
}
|
||||
snprintf(portstr, sizeof(portstr), "%d", g_lc.port);
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if (getaddrinfo(g_lc.host, portstr, &hints, &res) != 0) {
|
||||
return -2;
|
||||
}
|
||||
for (rp = res; rp; rp = rp->ai_next) {
|
||||
fd = (int)socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (fd < 0) {
|
||||
continue;
|
||||
}
|
||||
if (connect(fd, rp->ai_addr, (int)rp->ai_addrlen) == 0) {
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
if (fd < 0) {
|
||||
|
||||
g_connect_cancel = 0;
|
||||
g_connect_worker_done = 0;
|
||||
g_connect_worker_rc = -1;
|
||||
g_connect_worker_fd = -1;
|
||||
g_connect_gen++;
|
||||
if (g_connect_gen == 0)
|
||||
g_connect_gen = 1;
|
||||
g_lc_tcp_connecting = 1;
|
||||
|
||||
#if defined(_WIN32)
|
||||
th = _beginthreadex(NULL, 0, lobby_connect_worker,
|
||||
(void *)(uintptr_t)g_connect_gen, 0, NULL);
|
||||
if (!th) {
|
||||
g_lc_tcp_connecting = 0;
|
||||
return -3;
|
||||
}
|
||||
g_lc.fd = fd;
|
||||
lobby_store_connected_peer_ip(fd);
|
||||
if (g_lc.connected_peer_ip[0] &&
|
||||
strcmp(g_lc.connected_peer_ip, g_lc.host) != 0) {
|
||||
fprintf(stderr, "psx_lobby: WS connected peer %s (url host %s)\n",
|
||||
g_lc.connected_peer_ip, g_lc.host);
|
||||
g_connect_thread = (HANDLE)th;
|
||||
#else
|
||||
prc = pthread_create(&g_connect_thread, NULL, lobby_connect_worker,
|
||||
(void *)(uintptr_t)g_connect_gen);
|
||||
if (prc != 0) {
|
||||
g_lc_tcp_connecting = 0;
|
||||
return -3;
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
key_raw[i] = (char)(rand() & 0xff);
|
||||
}
|
||||
/* base64 16 bytes -> 24 chars; reuse server-side style via sha1 helper file's b64? */
|
||||
{
|
||||
static const char *B64 =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int o = 0;
|
||||
for (i = 0; i < 16; i += 3) {
|
||||
unsigned v = ((unsigned char)key_raw[i] << 16);
|
||||
if (i + 1 < 16) {
|
||||
v |= ((unsigned char)key_raw[i + 1] << 8);
|
||||
}
|
||||
if (i + 2 < 16) {
|
||||
v |= (unsigned char)key_raw[i + 2];
|
||||
}
|
||||
key_b64[o++] = B64[(v >> 18) & 63];
|
||||
key_b64[o++] = B64[(v >> 12) & 63];
|
||||
key_b64[o++] = (i + 1 < 16) ? B64[(v >> 6) & 63] : '=';
|
||||
key_b64[o++] = (i + 2 < 16) ? B64[v & 63] : '=';
|
||||
}
|
||||
key_b64[o] = '\0';
|
||||
}
|
||||
snprintf(req, sizeof(req),
|
||||
"GET %s HTTP/1.1\r\n"
|
||||
"Host: %s:%d\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Key: %s\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n\r\n",
|
||||
g_lc.path, g_lc.host, g_lc.port, key_b64);
|
||||
if (send(fd, req, (int)strlen(req), 0) < 0) {
|
||||
close(fd);
|
||||
g_lc.fd = -1;
|
||||
return -4;
|
||||
}
|
||||
set_nonblock(fd);
|
||||
g_lc.connected = 1;
|
||||
g_lc.handshake_done = 0;
|
||||
g_lc.rx_http_len = 0;
|
||||
g_connect_thread_valid = 1;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void psx_lobby_disconnect(void)
|
||||
{
|
||||
/* Never block the UI on DNS/connect — cancel and let pump reap. */
|
||||
lobby_cancel_connect_async();
|
||||
|
||||
g_lc.ice_rtt_suspended = 0;
|
||||
lobby_ice_rtt_close();
|
||||
lobby_rtt_close();
|
||||
@@ -2122,6 +2425,11 @@ int psx_lobby_connected(void)
|
||||
return g_lc.connected && g_lc.fd >= 0;
|
||||
}
|
||||
|
||||
int psx_lobby_connecting(void)
|
||||
{
|
||||
return g_lc_tcp_connecting || lobby_connect_thread_active() || g_connect_worker_done;
|
||||
}
|
||||
|
||||
void psx_lobby_set_display_name(const char *name)
|
||||
{
|
||||
if (!name) {
|
||||
@@ -2443,6 +2751,8 @@ void psx_lobby_pump(void)
|
||||
#else
|
||||
ssize_t n;
|
||||
#endif
|
||||
if (g_connect_worker_done)
|
||||
lobby_finish_connect_worker();
|
||||
if (!psx_lobby_connected()) {
|
||||
return;
|
||||
}
|
||||
@@ -2748,8 +3058,8 @@ const PsxLobbyMatchCaps *psx_lobby_match_caps(void)
|
||||
|
||||
int psx_lobby_set_match_caps(const PsxLobbyMatchCaps *caps)
|
||||
{
|
||||
char msg[768];
|
||||
char caps_json[512];
|
||||
char msg[896];
|
||||
char caps_json[640];
|
||||
int n;
|
||||
if (!psx_lobby_connected() || !g_lc.in_lobby || !g_lc.is_host || !caps || !caps->valid)
|
||||
return -1;
|
||||
@@ -2808,13 +3118,87 @@ int psx_lobby_all_ready(void)
|
||||
return g_lc.all_ready != 0 && g_lc.in_lobby && g_lc.join.player_count >= 2;
|
||||
}
|
||||
|
||||
void psx_lobby_set_bios_offer(const PsxLobbyBiosOffer *offer)
|
||||
{
|
||||
if (!offer) {
|
||||
memset(&g_lc.bios_offer, 0, sizeof(g_lc.bios_offer));
|
||||
return;
|
||||
}
|
||||
g_lc.bios_offer = *offer;
|
||||
if (g_lc.bios_offer.valid) {
|
||||
/* OpenBIOS is always expected when the title allows it; keep the flag. */
|
||||
if (!g_lc.bios_offer.can_openbios && !g_lc.bios_offer.can_scph1001)
|
||||
g_lc.bios_offer.can_openbios = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const PsxLobbyBiosOffer *psx_lobby_bios_offer(void)
|
||||
{
|
||||
return &g_lc.bios_offer;
|
||||
}
|
||||
|
||||
int psx_lobby_settle_session_bios(char *out, size_t out_cap)
|
||||
{
|
||||
int i;
|
||||
int any_prefer_open = 0;
|
||||
int any_cannot_scph = 0;
|
||||
int saw_peer = 0;
|
||||
if (!out || out_cap < 9) return -1;
|
||||
out[0] = '\0';
|
||||
|
||||
for (i = 0; i < g_lc.member_count; ++i) {
|
||||
const PsxLobbyMember *m = &g_lc.members[i];
|
||||
if (!m->player_id[0] && !m->display_name[0]) continue;
|
||||
saw_peer = 1;
|
||||
if (!m->bios_offer_valid) {
|
||||
/* Legacy client / not ready yet — cannot assume SCPH. */
|
||||
any_cannot_scph = 1;
|
||||
continue;
|
||||
}
|
||||
if (m->bios_prefer_openbios) any_prefer_open = 1;
|
||||
if (!m->bios_can_scph1001) any_cannot_scph = 1;
|
||||
if (!m->bios_can_openbios && !m->bios_can_scph1001)
|
||||
any_cannot_scph = 1;
|
||||
}
|
||||
|
||||
/* Include local offer even before lobby_update echoes it. */
|
||||
if (g_lc.bios_offer.valid) {
|
||||
saw_peer = 1;
|
||||
if (g_lc.bios_offer.prefer_openbios) any_prefer_open = 1;
|
||||
if (!g_lc.bios_offer.can_scph1001) any_cannot_scph = 1;
|
||||
} else if (!saw_peer) {
|
||||
any_cannot_scph = 1;
|
||||
}
|
||||
|
||||
if (any_prefer_open || any_cannot_scph || !saw_peer)
|
||||
strncpy(out, "openbios", out_cap - 1);
|
||||
else
|
||||
strncpy(out, "scph1001", out_cap - 1);
|
||||
out[out_cap - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
int psx_lobby_set_ready(int ready)
|
||||
{
|
||||
char msg[64];
|
||||
char msg[384];
|
||||
int n;
|
||||
if (!psx_lobby_connected() || !g_lc.in_lobby) {
|
||||
return -1;
|
||||
}
|
||||
snprintf(msg, sizeof(msg), "{\"op\":\"set_ready\",\"ready\":%s}", ready ? "true" : "false");
|
||||
if (g_lc.bios_offer.valid) {
|
||||
n = snprintf(msg, sizeof(msg),
|
||||
"{\"op\":\"set_ready\",\"ready\":%s,"
|
||||
"\"bios_offer\":{\"v\":1,\"prefer\":\"%s\","
|
||||
"\"can_openbios\":%s,\"can_scph1001\":%s}}",
|
||||
ready ? "true" : "false",
|
||||
g_lc.bios_offer.prefer_openbios ? "openbios" : "scph1001",
|
||||
g_lc.bios_offer.can_openbios ? "true" : "false",
|
||||
g_lc.bios_offer.can_scph1001 ? "true" : "false");
|
||||
} else {
|
||||
n = snprintf(msg, sizeof(msg), "{\"op\":\"set_ready\",\"ready\":%s}",
|
||||
ready ? "true" : "false");
|
||||
}
|
||||
if (n < 0 || (size_t)n >= sizeof(msg)) return -1;
|
||||
queue_send(msg);
|
||||
flush_pending();
|
||||
return 0;
|
||||
@@ -2822,16 +3206,21 @@ int psx_lobby_set_ready(int ready)
|
||||
|
||||
int psx_lobby_request_start(const PsxLobbyMatchCaps *match_caps)
|
||||
{
|
||||
char msg[768];
|
||||
char caps_json[512];
|
||||
char msg[896];
|
||||
char caps_json[640];
|
||||
PsxLobbyMatchCaps caps_local;
|
||||
int n;
|
||||
if (!psx_lobby_connected() || !g_lc.in_lobby || !g_lc.is_host) {
|
||||
return -1;
|
||||
}
|
||||
caps_json[0] = '\0';
|
||||
if (match_caps && match_caps->valid) {
|
||||
g_lc.match_caps = *match_caps;
|
||||
append_match_caps_json(caps_json, sizeof(caps_json), match_caps);
|
||||
caps_local = *match_caps;
|
||||
if (!caps_local.session_bios[0])
|
||||
(void)psx_lobby_settle_session_bios(caps_local.session_bios,
|
||||
sizeof(caps_local.session_bios));
|
||||
g_lc.match_caps = caps_local;
|
||||
append_match_caps_json(caps_json, sizeof(caps_json), &caps_local);
|
||||
}
|
||||
n = snprintf(msg, sizeof(msg), "{\"op\":\"start\"%s}", caps_json);
|
||||
if (n < 0 || (size_t)n >= sizeof(msg)) return -1;
|
||||
|
||||
Reference in New Issue
Block a user