mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
netplay: host relay for online rooms (amends §108 "online is always SFU")
An online room this build hosts asks the lobby server for transport "host" (match_caps.relay): the host carries the match on its own UDP port and guests dial it; the server's relay (SFU) is the fallback when a guest's probe fails (recomp-net-server WS_LOBBY.md "Host relay"). The mechanics are recomp-net's recomp_net/host_relay.h, pinned here (lib/recomp-net b0751b7); this engine's own lobby-client copy gets the same glue as rnet_lobby_client: the cap (emitted only when asked), the launch's transport -> PsxLobbyJoinInfo.transport_host (force_input_relay 0; the host accepts first / hubs instead of dialling the guest's LAN address), each seat's published path / path_fresh, host_relay_step() from psx_lobby_pump, and unmap on leave / disconnect. psx_netplay: transport_host takes the LAN / hub path instead of the "MotK online needs the SFU" rule. main.cpp: relay_host_get/set + relay_status for recomp-ui (Network Settings / MATCH SETTINGS / ROOM "Match Relay"), transport_host on the launch and into PsxNetplayConfig, and the host==guest-advertise SFU inference skipped on a host launch; all under RECOMP_LAUNCHER_HAS_HOST_RELAY so older recomp-ui pins still build. Compile-checked against workspace recomp-ui 58604b0 (MOTK scratch configure: full build and link). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+1
-1
Submodule lib/recomp-net updated: c58f125a3c...b0751b7b8f
@@ -106,6 +106,11 @@ typedef struct PsxLobbyMember {
|
||||
/* Account key behind this seat; "" for a guest. See
|
||||
* PsxLobbyOnlinePlayer.account. */
|
||||
char account[PSX_LOBBY_ID_LEN];
|
||||
/* Host-relay room: this guest's latest path_report ("direct" | "fail" |
|
||||
* "relay"), "" when none, and whether the server still trusts it at start
|
||||
* (recomp-net-server WS_LOBBY.md "Host relay"). */
|
||||
char path[8];
|
||||
int path_fresh;
|
||||
} PsxLobbyMember;
|
||||
|
||||
/*
|
||||
@@ -174,6 +179,11 @@ typedef struct PsxLobbyMatchCaps {
|
||||
int input_delay; /* recomp-net delay frames (D) */
|
||||
int input_prediction; /* invent runway frames (P); rollback only */
|
||||
int force_input_relay; /* 0/1 — server input relay (vs P2P) */
|
||||
/* 0/1 -- host: ask for the HOST RELAY (match_caps.relay = "host"): the
|
||||
* host carries the match on its own UDP port and guests dial it; the
|
||||
* server falls back to its relay unless every guest proved the path
|
||||
* (recomp_net/host_relay.h). Guests read it to know the room asks. */
|
||||
int relay_host;
|
||||
int force_turn; /* 0/1 — ICE relay-only (Force TURN for UDP) */
|
||||
int rollback; /* 0/1 — invent/rollback netplay (default on) */
|
||||
/* DualShock-on-multitap-tap hack (0/1). Host-authoritative for the match. */
|
||||
@@ -223,6 +233,11 @@ typedef struct PsxLobbyJoinInfo {
|
||||
/* Launch: the host watches from the gallery but runs the match from
|
||||
* session slot 0 (pad muted); player seats sit at lobby seat + 1. */
|
||||
int host_spectates;
|
||||
/* Launch: transport "host" -- no relay_endpoint; the host carries the
|
||||
* match on host_endpoint (its advertised public port); guests dial it.
|
||||
* force_input_relay is 0. The host binds bind_hostport and accepts the
|
||||
* guest (2 seats) or hubs (3+). */
|
||||
int transport_host;
|
||||
/* A server code (need_password | bad_password | …) or, from
|
||||
* psx_lobby_set_last_error, one sentence the launcher shows as is. */
|
||||
char last_error[192];
|
||||
@@ -348,6 +363,17 @@ const PsxLobbyMatchCaps *psx_lobby_match_caps(void);
|
||||
/* Host: push updated caps while in lobby (clears ready via lobby_update). */
|
||||
int psx_lobby_set_match_caps(const PsxLobbyMatchCaps *caps);
|
||||
|
||||
/* Host relay (recomp_net/host_relay.h), driven from psx_lobby_pump while
|
||||
* seated online in a room whose caps ask for it: the host holds the game port
|
||||
* (UPnP / NAT-PMP / STUN), advertises it with set_host_endpoint and answers
|
||||
* probes; guests probe it and send path_report. The pref is the HOST's
|
||||
* default for rooms it creates (recomp-ui owns the setting; default on). */
|
||||
void psx_lobby_set_relay_host_pref(int on);
|
||||
int psx_lobby_relay_host_pref(void);
|
||||
struct RNetHostRelayStatus;
|
||||
int psx_lobby_host_relay_status(struct RNetHostRelayStatus *out);
|
||||
void psx_lobby_host_relay_release_port(void);
|
||||
|
||||
/* Live member table from lobby_update (and create/join). */
|
||||
int psx_lobby_member_count(void);
|
||||
int psx_lobby_member_get(int index, PsxLobbyMember *out);
|
||||
|
||||
@@ -79,6 +79,10 @@ typedef struct PsxNetplayConfig {
|
||||
/* Rollback invent runway (phase_lock / P). Clamped 2..16. Unused in delay-sync. */
|
||||
int input_prediction;
|
||||
int force_input_relay; /* 1 = lobby UDP SFU star (from launch) */
|
||||
/* 1 = the launch said transport "host": an online room whose host
|
||||
* carries the match on bind_hostport; guests dial peer_hostport. Takes
|
||||
* the LAN / hub transport path, not the server-relay one. */
|
||||
int transport_host;
|
||||
int force_turn; /* 1 = ICE relay-only (Force TURN for UDP) */
|
||||
/* 0 = auto (MotK room → ICE, else LAN), 1 = force ICE, 2 = force LAN.
|
||||
* Env PSX_NET_TRANSPORT=lan|ice overrides. */
|
||||
|
||||
+90
-3
@@ -62,6 +62,7 @@ extern "C" void psx_event_step_conservative_env_init(void);
|
||||
#include "psx_netplay_rb.h"
|
||||
#include "psx_selfcheck.h"
|
||||
#include "psx_lobby_client.h"
|
||||
#include "recomp_net/host_relay.h" /* RNetHostRelayStatus for the launcher relay line */
|
||||
#include "netplay_bios_settle.h"
|
||||
#include "netplay_exit_reason.h"
|
||||
#include "host_time.h"
|
||||
@@ -8497,6 +8498,11 @@ namespace {
|
||||
* relay_endpoint. force_turn is a rollback delay-floor hint only. */
|
||||
int g_lnch_force_input_relay = 0;
|
||||
int g_lnch_force_turn = 0;
|
||||
/* Host relay (2026-10-01): online rooms this build hosts ask the server
|
||||
* for transport "host" (match_caps.relay). recomp-ui owns the setting
|
||||
* (Network Settings / Lobby Settings, persisted there) and pushes it
|
||||
* through relay_host_set; default on. */
|
||||
int g_lnch_relay_host = 1;
|
||||
/* Lobby default on; host “Disable Rollback” clears this → delay_sync. */
|
||||
int g_lnch_rollback = 1;
|
||||
int g_lnch_multitap_analog = 1;
|
||||
@@ -10419,6 +10425,7 @@ namespace {
|
||||
if (caps.input_prediction < 2) caps.input_prediction = 2;
|
||||
if (caps.input_prediction > 16) caps.input_prediction = 16;
|
||||
caps.force_input_relay = g_lnch_force_input_relay != 0;
|
||||
caps.relay_host = g_lnch_relay_host != 0;
|
||||
caps.force_turn = g_lnch_force_turn != 0;
|
||||
caps.rollback = g_lnch_rollback != 0;
|
||||
caps.multitap_analog = g_lnch_multitap_analog != 0;
|
||||
@@ -10461,6 +10468,7 @@ namespace {
|
||||
if (caps.input_prediction < 2) caps.input_prediction = 2;
|
||||
if (caps.input_prediction > 16) caps.input_prediction = 16;
|
||||
caps.force_input_relay = g_lnch_force_input_relay != 0;
|
||||
caps.relay_host = g_lnch_relay_host != 0;
|
||||
caps.force_turn = g_lnch_force_turn != 0;
|
||||
caps.rollback = g_lnch_rollback != 0;
|
||||
caps.multitap_analog = g_lnch_multitap_analog != 0;
|
||||
@@ -10914,6 +10922,69 @@ namespace {
|
||||
}
|
||||
return g_lnch_force_turn;
|
||||
}
|
||||
/* Host relay preference and live state (recomp_launcher.h relay_host_*). */
|
||||
int ae_np_relay_host_get(void*) {
|
||||
if (g_lnch_hosting_lan || g_lnch_joined_lan) return 0;
|
||||
if (psx_lobby_in_lobby()) {
|
||||
const PsxLobbyMatchCaps* caps = psx_lobby_match_caps();
|
||||
if (caps && caps->valid) return caps->relay_host ? 1 : 0;
|
||||
}
|
||||
return g_lnch_relay_host;
|
||||
}
|
||||
int ae_np_relay_host_set(void*, int on) {
|
||||
g_lnch_relay_host = on ? 1 : 0;
|
||||
psx_lobby_set_relay_host_pref(g_lnch_relay_host);
|
||||
if (!g_lnch_hosting_lan && !g_lnch_joined_lan)
|
||||
ae_np_push_match_caps(nullptr); /* no-op unless hosting online */
|
||||
return 0;
|
||||
}
|
||||
int ae_np_relay_status(void*, char* out, size_t out_cap) {
|
||||
if (!out || !out_cap) return 0;
|
||||
out[0] = '\0';
|
||||
if (g_lnch_hosting_lan || g_lnch_joined_lan || !psx_lobby_in_lobby()) return 0;
|
||||
RNetHostRelayStatus st;
|
||||
if (!psx_lobby_host_relay_status(&st)) return 0;
|
||||
if (st.role == 1) {
|
||||
int guests = 0, proven = 0;
|
||||
const int n = psx_lobby_member_count();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
PsxLobbyMember mem;
|
||||
if (!psx_lobby_member_get(i, &mem) || mem.is_spectator) continue;
|
||||
if (std::strcmp(mem.player_id, psx_lobby_host_player_id()) == 0) continue;
|
||||
guests++;
|
||||
if (std::strcmp(mem.path, "direct") == 0 && mem.path_fresh) proven++;
|
||||
}
|
||||
if (!st.port.done)
|
||||
std::snprintf(out, out_cap, "You carry the match. Opening UDP port %u (%s)...",
|
||||
(unsigned)st.port.local_port,
|
||||
st.port.stage[0] ? st.port.stage : "starting");
|
||||
else if (!st.port.endpoint[0])
|
||||
std::snprintf(out, out_cap, "%s", st.port.detail);
|
||||
else
|
||||
std::snprintf(out, out_cap,
|
||||
"You carry the match at %s (%s). Guests who can reach you: %d of %d%s",
|
||||
st.port.endpoint, st.port.how, proven, guests,
|
||||
guests && proven < guests
|
||||
? ". Any guest who cannot sends the match through the lobby "
|
||||
"server's relay."
|
||||
: ".");
|
||||
return 1;
|
||||
}
|
||||
if (st.role == 2) {
|
||||
if (st.probing || !st.last_report[0])
|
||||
std::snprintf(out, out_cap, "The host carries the match. Checking you can reach %s...",
|
||||
st.probed[0] ? st.probed : "the host");
|
||||
else if (std::strcmp(st.last_report, "direct") == 0)
|
||||
std::snprintf(out, out_cap, "The host carries the match; you reach it directly (%s).",
|
||||
st.probed);
|
||||
else
|
||||
std::snprintf(out, out_cap,
|
||||
"The host's port did not answer (%s): the lobby server will relay "
|
||||
"this match. Retrying.", st.probed);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int ae_np_force_turn_set(void*, int force) {
|
||||
if (g_lnch_hosting_lan || g_lnch_joined_lan)
|
||||
return 0; /* LAN/Direct IP — no online delay-floor hint */
|
||||
@@ -12830,13 +12901,18 @@ namespace {
|
||||
for (int i = 0; i < RECOMP_LAUNCHER_NETPLAY_MAX_MEMBERS + 1; ++i)
|
||||
out->slot_port[i] = plan.port[i];
|
||||
}
|
||||
/* §108: online launch always SFU. Prefer caps.force_input_relay from
|
||||
/* Online launch: the server's relay (SFU) unless the launch said
|
||||
* transport "host" (the host carries the match; 2026-10-01 amends
|
||||
* §108's "always SFU"). Prefer caps.force_input_relay from the
|
||||
* relay_endpoint rewrite; also infer when host==guest advertise. */
|
||||
out->force_input_relay =
|
||||
(g_lnch_hosting_lan || g_lnch_joined_lan)
|
||||
(g_lnch_hosting_lan || g_lnch_joined_lan || ji->transport_host)
|
||||
? 0
|
||||
: (caps->force_input_relay ? 1 : 0);
|
||||
if (!out->force_input_relay && !g_lnch_hosting_lan &&
|
||||
#if defined(RECOMP_LAUNCHER_HAS_HOST_RELAY)
|
||||
out->transport_host = ji->transport_host ? 1 : 0;
|
||||
#endif
|
||||
if (!out->force_input_relay && !ji->transport_host && !g_lnch_hosting_lan &&
|
||||
!g_lnch_joined_lan && ji->host_endpoint[0] &&
|
||||
ji->guest_endpoint[0] &&
|
||||
std::strcmp(ji->host_endpoint, ji->guest_endpoint) == 0) {
|
||||
@@ -13047,6 +13123,11 @@ namespace {
|
||||
/* Append-only members past the positional initializer. */
|
||||
#if defined(RECOMP_LAUNCHER_HAS_NETPLAY_HANDOFF)
|
||||
g_lnch_netplay_callbacks.ingest_launch = ae_np_ingest_launch;
|
||||
#endif
|
||||
#if defined(RECOMP_LAUNCHER_HAS_HOST_RELAY)
|
||||
g_lnch_netplay_callbacks.relay_host_get = ae_np_relay_host_get;
|
||||
g_lnch_netplay_callbacks.relay_host_set = ae_np_relay_host_set;
|
||||
g_lnch_netplay_callbacks.relay_status = ae_np_relay_status;
|
||||
#endif
|
||||
g_lnch_netplay_callbacks.memcard_offer_set = ae_np_memcard_offer_set;
|
||||
g_lnch_netplay_callbacks.guest_memcard_get = ae_np_guest_memcard_get;
|
||||
@@ -15248,6 +15329,9 @@ int main(int argc, char** argv) {
|
||||
net_cfg.input_delay = ls.netplay_launch.input_delay;
|
||||
net_cfg.input_prediction = ls.netplay_launch.input_prediction;
|
||||
net_cfg.force_input_relay = ls.netplay_launch.force_input_relay ? 1 : 0;
|
||||
#if defined(RECOMP_LAUNCHER_HAS_HOST_RELAY)
|
||||
net_cfg.transport_host = ls.netplay_launch.transport_host ? 1 : 0;
|
||||
#endif
|
||||
net_cfg.force_turn = ls.netplay_launch.force_turn ? 1 : 0;
|
||||
net_cfg.rollback = ls.netplay_launch.rollback ? 1 : 0;
|
||||
net_cfg.guest_memcard = ls.netplay_launch.guest_memcard ? 1 : 0;
|
||||
@@ -17140,6 +17224,9 @@ soft_return_lobby:
|
||||
net_cfg.input_delay = ls.netplay_launch.input_delay;
|
||||
net_cfg.input_prediction = ls.netplay_launch.input_prediction;
|
||||
net_cfg.force_input_relay = ls.netplay_launch.force_input_relay ? 1 : 0;
|
||||
#if defined(RECOMP_LAUNCHER_HAS_HOST_RELAY)
|
||||
net_cfg.transport_host = ls.netplay_launch.transport_host ? 1 : 0;
|
||||
#endif
|
||||
net_cfg.force_turn = ls.netplay_launch.force_turn ? 1 : 0;
|
||||
net_cfg.rollback = ls.netplay_launch.rollback ? 1 : 0;
|
||||
/* Same fold as the first-boot path: rematch must not lose
|
||||
|
||||
@@ -44,6 +44,10 @@ int psx_lobby_create(const char *a, const char *b, const char *c, const char *d
|
||||
int psx_lobby_join(const char *a, const char *b, const char *c)
|
||||
{ (void)a; (void)b; (void)c; return -1; }
|
||||
int psx_lobby_leave(void) { return -1; }
|
||||
void psx_lobby_set_relay_host_pref(int on) { (void)on; }
|
||||
int psx_lobby_relay_host_pref(void) { return 0; }
|
||||
int psx_lobby_host_relay_status(struct RNetHostRelayStatus *out) { (void)out; return 0; }
|
||||
void psx_lobby_host_relay_release_port(void) {}
|
||||
int psx_lobby_kick(int slot) { (void)slot; return -1; }
|
||||
void psx_lobby_set_allow_spectators(int allow) { (void)allow; }
|
||||
int psx_lobby_allow_spectators_pref(void) { return 0; }
|
||||
@@ -183,6 +187,7 @@ void psx_lobby_clear_launch_pending(void) {}
|
||||
#include "rnet_ws.h"
|
||||
#include "rnet_sha1.h"
|
||||
#include "recomp_net/address.h"
|
||||
#include "recomp_net/host_relay.h"
|
||||
#include "recomp_net/ice.h"
|
||||
#include "recomp_net/ice_rtt.h"
|
||||
#include "recomp_net/lan_beacon.h"
|
||||
@@ -1098,6 +1103,74 @@ static void match_caps_clear(PsxLobbyMatchCaps *c)
|
||||
c->guest_memcard = 1;
|
||||
}
|
||||
|
||||
/* ---- host relay (recomp_net/host_relay.h) -------------------------------
|
||||
* The same wiring as recomp-net's own lobby client (rnet_lobby_client.c):
|
||||
* this file is psxrecomp's copy of that client, so the orchestration object
|
||||
* is recomp-net's and only the glue lives here. */
|
||||
static RNetHostRelay *g_host_relay;
|
||||
static int g_relay_host_pref = 1;
|
||||
|
||||
void psx_lobby_set_relay_host_pref(int on) { g_relay_host_pref = on ? 1 : 0; }
|
||||
int psx_lobby_relay_host_pref(void) { return g_relay_host_pref; }
|
||||
|
||||
static int host_relay_send(const char *json, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (!psx_lobby_connected() || !g_lc.in_lobby) return -1;
|
||||
queue_send(json);
|
||||
flush_pending();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lobby_bind_port(void)
|
||||
{
|
||||
const char *colon = strrchr(g_lc.my_bind, ':');
|
||||
const int port = colon ? atoi(colon + 1) : 0;
|
||||
return port > 0 && port < 65536 ? port : 0;
|
||||
}
|
||||
|
||||
/* Every pump: active only while seated in an online room whose published
|
||||
* caps ask for the host relay (the host's caps are echoed back to it, so one
|
||||
* rule serves both roles) and before the launch, which releases the port for
|
||||
* the game. A spectator is not on the relay's path and reports nothing. */
|
||||
static void host_relay_step(void)
|
||||
{
|
||||
RNetHostRelayView v;
|
||||
if (!g_host_relay) {
|
||||
g_host_relay = rnet_host_relay_create();
|
||||
if (!g_host_relay) return;
|
||||
}
|
||||
memset(&v, 0, sizeof(v));
|
||||
v.is_host = g_lc.is_host ? 1 : 0;
|
||||
v.active = psx_lobby_connected() && g_lc.in_lobby && !g_lc.launch_pending &&
|
||||
!g_lc.join.local_is_spectator &&
|
||||
g_lc.match_caps.valid && g_lc.match_caps.relay_host;
|
||||
v.bind_port = (unsigned short)lobby_bind_port();
|
||||
if (v.is_host && !v.bind_port) v.active = 0;
|
||||
v.host_endpoint = g_lc.join.host_endpoint;
|
||||
v.send_json = host_relay_send;
|
||||
rnet_host_relay_update(g_host_relay, &v);
|
||||
}
|
||||
|
||||
int psx_lobby_host_relay_status(struct RNetHostRelayStatus *out)
|
||||
{
|
||||
RNetHostRelayStatus st;
|
||||
if (!out) return 0;
|
||||
rnet_host_relay_status(g_host_relay, &st);
|
||||
*out = st;
|
||||
return st.role != 0;
|
||||
}
|
||||
|
||||
void psx_lobby_host_relay_release_port(void)
|
||||
{
|
||||
rnet_host_relay_release_port(g_host_relay);
|
||||
}
|
||||
|
||||
static void host_relay_leave(void)
|
||||
{
|
||||
rnet_host_relay_leave(g_host_relay);
|
||||
}
|
||||
|
||||
static int json_extract_object(const char *json, const char *key, char *out, size_t out_cap);
|
||||
static void parse_match_caps_object(const char *obj, PsxLobbyMatchCaps *out);
|
||||
static void ingest_match_caps_from_json(const char *json);
|
||||
@@ -1481,6 +1554,12 @@ static void parse_match_caps_object(const char *obj, PsxLobbyMatchCaps *out)
|
||||
if (out->input_prediction < 2) out->input_prediction = 2;
|
||||
if (out->input_prediction > 16) out->input_prediction = 16;
|
||||
out->force_input_relay = json_get_bool(obj, "force_input_relay", 0);
|
||||
{
|
||||
char relay[12];
|
||||
relay[0] = '\0';
|
||||
json_get_str(obj, "relay", relay, sizeof(relay));
|
||||
out->relay_host = strcmp(relay, "host") == 0 ? 1 : 0;
|
||||
}
|
||||
out->force_turn = json_get_bool(obj, "force_turn", 0);
|
||||
/* Absent field → delay-sync (older hosts). New hosts always publish explicit. */
|
||||
out->rollback = json_get_bool(obj, "rollback", 0);
|
||||
@@ -1538,7 +1617,7 @@ static int append_match_caps_json(char *dst, size_t dst_cap, const PsxLobbyMatch
|
||||
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,"
|
||||
"\"auto_skip_fmv\":%s,\"input_delay\":%d,\"input_prediction\":%d,%s"
|
||||
"\"force_input_relay\":%s,\"force_turn\":%s,\"rollback\":%s,"
|
||||
"\"multitap_analog\":%s,\"guest_memcard\":%s,"
|
||||
"\"guest_memcard_active\":%s,"
|
||||
@@ -1551,6 +1630,8 @@ static int append_match_caps_json(char *dst, size_t dst_cap, const PsxLobbyMatch
|
||||
caps->auto_skip_fmv ? "true" : "false",
|
||||
caps->input_delay,
|
||||
caps->input_prediction,
|
||||
/* only when asked: absent reads as "the SFU, as always" */
|
||||
caps->relay_host ? "\"relay\":\"host\"," : "",
|
||||
caps->force_input_relay ? "true" : "false",
|
||||
caps->force_turn ? "true" : "false",
|
||||
caps->rollback ? "true" : "false",
|
||||
@@ -1629,7 +1710,7 @@ static int using_server_input_relay(const PsxLobbyJoinInfo *j)
|
||||
static void fill_peer_bind_from_join(void)
|
||||
{
|
||||
PsxLobbyJoinInfo *j = &g_lc.join;
|
||||
const int force_relay = using_server_input_relay(j);
|
||||
const int force_relay = !j->transport_host && using_server_input_relay(j);
|
||||
const int seats = j->player_count >= 2 ? j->player_count : j->max_slots;
|
||||
const int host_hub = (g_lc.is_host && seats >= 3 && !force_relay) ? 1 : 0;
|
||||
memset(j->bind_hostport, 0, sizeof(j->bind_hostport));
|
||||
@@ -1642,9 +1723,12 @@ static void fill_peer_bind_from_join(void)
|
||||
j->host_endpoint, j->guest_endpoint, NULL);
|
||||
} else if (g_lc.is_host) {
|
||||
strncpy(j->bind_hostport, g_lc.my_bind, sizeof(j->bind_hostport) - 1);
|
||||
if (!host_hub) {
|
||||
if (!host_hub && !j->transport_host) {
|
||||
/* 2P P2P: dial guest when they advertised a fixed port. Online
|
||||
* guests often join with :0 — leave peer empty (accept-first). */
|
||||
* guests often join with :0 — leave peer empty (accept-first).
|
||||
* Host relay: the guest's advertised address is its LAN bind,
|
||||
* which this host cannot dial across the guest's NAT; the guest
|
||||
* dials us (it proved it can), so accept-first always. */
|
||||
if (j->guest_endpoint[0] && !endpoint_port_is_zero(j->guest_endpoint))
|
||||
strncpy(j->peer_hostport, j->guest_endpoint, sizeof(j->peer_hostport) - 1);
|
||||
}
|
||||
@@ -1724,6 +1808,10 @@ static int parse_seat_array(const char *json, const char *key, int is_spectator,
|
||||
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));
|
||||
g_lc.members[n].path[0] = '\0';
|
||||
json_get_str(chunk, "path", g_lc.members[n].path,
|
||||
sizeof(g_lc.members[n].path));
|
||||
g_lc.members[n].path_fresh = json_get_bool(chunk, "path_fresh", 0) ? 1 : 0;
|
||||
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);
|
||||
@@ -2960,10 +3048,19 @@ static void handle_server_json(const char *json)
|
||||
}
|
||||
if (strcmp(op, "launch") == 0) {
|
||||
char relay_endpoint[PSX_LOBBY_ENDPOINT_LEN];
|
||||
char transport_kind[16];
|
||||
json_get_str(json, "host_endpoint", g_lc.join.host_endpoint, sizeof(g_lc.join.host_endpoint));
|
||||
json_get_str(json, "guest_endpoint", g_lc.join.guest_endpoint, sizeof(g_lc.join.guest_endpoint));
|
||||
relay_endpoint[0] = '\0';
|
||||
json_get_str(json, "relay_endpoint", relay_endpoint, sizeof(relay_endpoint));
|
||||
/* "host": the host carries the match (WS_LOBBY.md "Host relay"): no
|
||||
* relay_endpoint, host_endpoint is the host's advertised port. The
|
||||
* waiting-room socket on that port goes now so the game can bind it;
|
||||
* the router mapping stays for the game. */
|
||||
transport_kind[0] = '\0';
|
||||
json_get_str(json, "transport", transport_kind, sizeof(transport_kind));
|
||||
g_lc.join.transport_host = strcmp(transport_kind, "host") == 0 ? 1 : 0;
|
||||
rnet_host_relay_release_port(g_host_relay);
|
||||
g_lc.join.player_count = json_get_int(json, "player_count", g_lc.join.player_count);
|
||||
g_lc.join.max_slots = json_get_int(json, "max_slots", g_lc.join.max_slots);
|
||||
g_lc.join.session_id = (uint32_t)json_get_int(json, "session_id", (int)g_lc.join.session_id);
|
||||
@@ -3000,7 +3097,8 @@ static void handle_server_json(const char *json)
|
||||
* Legacy ice_p2p from an old server is refused (need SFU). */
|
||||
{
|
||||
char transport[24];
|
||||
const int force_relay = using_server_input_relay(&g_lc.join);
|
||||
const int force_relay =
|
||||
!g_lc.join.transport_host && using_server_input_relay(&g_lc.join);
|
||||
const int seats = g_lc.join.player_count >= 2 ? g_lc.join.player_count
|
||||
: g_lc.join.max_slots;
|
||||
const int host_hub =
|
||||
@@ -3582,6 +3680,7 @@ int psx_lobby_connect(const char *ws_url)
|
||||
|
||||
void psx_lobby_disconnect(void)
|
||||
{
|
||||
host_relay_leave();
|
||||
/* Never block the UI on DNS/connect — cancel and let pump reap. */
|
||||
lobby_cancel_connect_async();
|
||||
automatch_on_connection_reset();
|
||||
@@ -3934,6 +4033,9 @@ void psx_lobby_pump(void)
|
||||
{
|
||||
char buf[4096];
|
||||
automatch_probe_poll();
|
||||
/* Host relay: hold / probe / report to match the room. Before the
|
||||
* connected() check so a dropped WS releases the port the same pump. */
|
||||
host_relay_step();
|
||||
#if defined(_WIN32)
|
||||
int n;
|
||||
#else
|
||||
@@ -4198,6 +4300,7 @@ int psx_lobby_join(const char *lobby_id, const char *password, const char *guest
|
||||
|
||||
int psx_lobby_leave(void)
|
||||
{
|
||||
host_relay_leave(); /* unmap the router, close the port, forget reports */
|
||||
queue_send("{\"op\":\"leave\"}");
|
||||
flush_pending();
|
||||
g_lc.ice_rtt_suspended = 0;
|
||||
|
||||
@@ -3594,6 +3594,17 @@ static int resolve_use_ice(const PsxNetplayConfig *cfg)
|
||||
* relay_endpoint (or equal host/guest advertise). Match ICE / ice_p2p
|
||||
* selection was removed; waiting-room ICE RTT may still run for delay
|
||||
* hints only. Direct IP / LAN file lobby (no MotK seat) stays LAN UDP. */
|
||||
/* Host relay (2026-10-01): an online room whose host carries the match
|
||||
* launches transport "host" -- no server relay. Host: bind the advertised
|
||||
* port, peer empty (accept-first / hub); guest: dial host_endpoint. That
|
||||
* is the LAN path below, so it is excluded from the SFU rule here. */
|
||||
if (cfg->transport_host) {
|
||||
fprintf(stderr,
|
||||
"psx_netplay: host relay — LAN transport (%s)\n",
|
||||
cfg->peer_hostport && cfg->peer_hostport[0] ? "dial host" : "host binds");
|
||||
fflush(stderr);
|
||||
return 0;
|
||||
}
|
||||
if (cfg->force_input_relay || in_motk_room) {
|
||||
if (!cfg->peer_hostport || !cfg->peer_hostport[0]) {
|
||||
fprintf(stderr,
|
||||
|
||||
Reference in New Issue
Block a user