mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Merge pull request #428 from RetroPortingToolKit/fix/rematch-session-start
Run the full session start on the lobby rematch
This commit is contained in:
+25
-14
@@ -534,10 +534,11 @@ there, netplay or offline, re-enters the emulator in the same process. The
|
||||
session before a rematch is therefore always a netplay match, which ran with
|
||||
the plan cleared, so no plugin activated in it.
|
||||
|
||||
The reset runs immediately before activation in the first session, and on the
|
||||
rematch path after its commit or netplay clear. What it fixes today is the
|
||||
netplay local viewport's Fit and fixed aspect carrying from a match into an
|
||||
offline rematch. The rest of the table is defensive, for the state listed here.
|
||||
The reset is one step of the session start that every session runs,
|
||||
immediately before activation (see *A rematch is a full session start* below).
|
||||
What it fixes today is the netplay local viewport's Fit and fixed aspect
|
||||
carrying from a match into an offline rematch. The rest of the table is
|
||||
defensive, for the state listed here.
|
||||
|
||||
| State | Setter | Reset to | When |
|
||||
|---|---|---|---|
|
||||
@@ -576,7 +577,8 @@ Not reset, and why:
|
||||
`psx_mod_set_bezel_artwork` force) are launcher controls. A rematch takes
|
||||
both from the lobby launcher, which is seeded from the live values. For the
|
||||
aspect, the rematch path then re-applies the Settings clamp (4:3, since
|
||||
widescreen is mod-owned) before the netplay local viewport, so a match's
|
||||
widescreen is mod-owned) before its session start, where a plugin's
|
||||
activation or the netplay local viewport can still replace it, so a match's
|
||||
16:9 or 21:9 does not carry into an offline rematch. A renderer forced by a
|
||||
plugin would be carried the same way, but no plugin activates in the session
|
||||
before a rematch.
|
||||
@@ -586,15 +588,24 @@ Not reset, and why:
|
||||
for the process. A bank ID is read only from packets in a plugin's own arena,
|
||||
which stock game code does not use.
|
||||
|
||||
**A rematch is not a full session start.** It re-enters below the first
|
||||
session's setup block, so it does not run `mod_runtime_activate_plugins()` and
|
||||
does not reset controller overrides and policies, load acceleration or disc
|
||||
speed (the first session resets those before activation). An offline rematch
|
||||
with mods enabled therefore applies the plan's main-EXE and disc patches and
|
||||
runs its VBlank callbacks, but no activation callback runs and no
|
||||
function-entry hook runs. A VBlank callback must not assume its plugin's
|
||||
activation ran in the same session. If the rematch path ever activates
|
||||
plugins, the reset must run before that activation.
|
||||
**A rematch is a full session start.** It re-enters below the first session's
|
||||
setup block, so after its commit or netplay clear it runs the same sequence as
|
||||
the first session:
|
||||
|
||||
1. Clear controller-mode overrides and presentation policies, load
|
||||
acceleration and disc speed.
|
||||
2. Reset the mod-owned state in the table above.
|
||||
3. Run `mod_runtime_activate_plugins()`: the plan's activation callbacks, then
|
||||
its function-entry hook table.
|
||||
4. Apply what activation chose: the netplay local viewport, controller-mode
|
||||
overrides and load acceleration. Disc speed is read when the session boots.
|
||||
5. Mount the plan's derived disc image if it built one, else the stock disc.
|
||||
|
||||
An offline rematch with mods enabled therefore runs like a first launch with
|
||||
those mods: activation callbacks, function-entry hooks, VBlank callbacks and
|
||||
the main-EXE and disc patches. A netplay rematch clears the plan, so none of
|
||||
them run and the match stays vanilla. Either way activation still precedes
|
||||
renderer and window creation, which every session reaches only when it boots.
|
||||
|
||||
A plugin should establish what it needs in its activation callback and not rely
|
||||
on state from an earlier session; its own static variables are its
|
||||
|
||||
+99
-90
@@ -1403,10 +1403,10 @@ extern "C" int psx_mod_set_adaptive_display_aspect(
|
||||
/*
|
||||
* Mod-owned session state: what a trusted plugin's psx_mod_* setters (or the
|
||||
* netplay local viewport, through the same setters) change and no launcher
|
||||
* control owns. Called at every session start, BEFORE activation: immediately
|
||||
* before mod_runtime_activate_plugins() in the first-boot session block, and
|
||||
* on the lobby rematch path after its commit or netplay clear. Anything added
|
||||
* to the rematch path that activates plugins must come after this call.
|
||||
* control owns. Called at every session start, BEFORE activation: by
|
||||
* start_mod_session() in main(), immediately before
|
||||
* mod_runtime_activate_plugins(). The first boot and the lobby rematch both
|
||||
* run that sequence after their commit or netplay clear.
|
||||
*
|
||||
* The rematch is the only in-process second session, and the session before
|
||||
* it is always a netplay match: an offline session ends the process, and only
|
||||
@@ -1418,11 +1418,8 @@ extern "C" int psx_mod_set_adaptive_display_aspect(
|
||||
* renderer and fixed aspect are launcher controls; guest/GPU-DMA memory,
|
||||
* texture-packet arenas and defined texture banks live for the process).
|
||||
*
|
||||
* The rematch path jumps past the first-boot block (goto session_reboot), so
|
||||
* it does not re-run mod_runtime_activate_plugins() or the controller, load
|
||||
* and disc-speed resets there. Function-entry hooks need nothing here: the
|
||||
* commit or netplay clear empties upstream's hook table, and only activation
|
||||
* rebuilds it.
|
||||
* Function-entry hooks need nothing here: the commit or netplay clear empties
|
||||
* upstream's hook table, and only activation rebuilds it.
|
||||
*
|
||||
* The first call captures the pre-activation scalars (mod_session_baseline.h)
|
||||
* and changes nothing that is not already at its initial value, so the first
|
||||
@@ -1470,6 +1467,19 @@ static void reset_mod_owned_presentation(void) {
|
||||
g_frame_interpolation_blend = g_frame_interpolation_blend_default;
|
||||
}
|
||||
|
||||
/* The disc a session mounts: a disc-patching mod's private patched image when
|
||||
* the committed plan built one, else the stock disc, which stays untouched
|
||||
* (master behaviour). Every session start calls it after its commit. */
|
||||
static std::string session_disc_path(const std::filesystem::path& stock_disc) {
|
||||
const std::filesystem::path& mod_disc =
|
||||
PSXRecompV4::mod_runtime_effective_disc_path();
|
||||
if (mod_disc.empty()) return stock_disc.string();
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: stock disc remains %s; mounting private mod cache %s\n",
|
||||
stock_disc.string().c_str(), mod_disc.string().c_str());
|
||||
return mod_disc.string();
|
||||
}
|
||||
|
||||
extern "C" int psx_mod_set_native_vblank_rate(
|
||||
uint32_t frames_per_second) {
|
||||
if (frames_per_second != 0 &&
|
||||
@@ -14706,54 +14716,65 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/* Activation callbacks are re-run after every launcher session. Clear
|
||||
* game-owned controller overrides/policies first so disabling a package
|
||||
* cannot leave its prior state latched across a soft return. */
|
||||
g_mod_controller_mode_override.fill(-1);
|
||||
for (auto& policy : g_mod_controller_policy)
|
||||
policy = ModControllerPresentationPolicy{};
|
||||
g_mod_load_wall_multiplier = -1;
|
||||
g_mod_load_release_frames = -1;
|
||||
g_mod_disc_speed_divisor = -1;
|
||||
g_mod_disc_instant_rate = -1;
|
||||
g_turbo_audio_sink_enabled = g_turbo_audio_sink_config_enabled;
|
||||
g_turbo_load_wall_multiplier = 0;
|
||||
g_turbo_load_release_frames = TURBO_LOADS_RELEASE_FRAMES;
|
||||
if (!turbo_loads_offered)
|
||||
g_turbo_loads_enabled = 0;
|
||||
/* The helper restores interpolation and Skip FMVs to their pre-activation
|
||||
* values, which is only right while no launcher control owns them. */
|
||||
static_assert(!frame_interpolation_offered && !skip_fmv_offered,
|
||||
"reset_mod_owned_presentation() would clobber a launcher "
|
||||
"setting; restore only when the feature is mod-owned");
|
||||
reset_mod_owned_presentation();
|
||||
mod_runtime_activate_plugins();
|
||||
apply_netplay_local_viewport_aspect(net_cfg.enabled);
|
||||
for (int i = 0; i < PSX_MAX_PLAYERS; ++i) {
|
||||
if (g_mod_controller_mode_override[i] >= 0)
|
||||
player_mode[i] = g_mod_controller_mode_override[i];
|
||||
}
|
||||
if (g_mod_load_wall_multiplier >= 0) {
|
||||
g_turbo_loads_enabled = 1;
|
||||
g_turbo_load_wall_multiplier = g_mod_load_wall_multiplier;
|
||||
g_turbo_load_release_frames = g_mod_load_release_frames;
|
||||
/* Fast Loading advances the guest at a host rate greater than real
|
||||
* time. Keep the canonical SPU/CD stream running, but discard the
|
||||
* accelerated presentation-side audio until pacing resumes; otherwise
|
||||
* the SDL bridge overflows and the load becomes observably unstable. */
|
||||
g_turbo_audio_sink_enabled = g_turbo_load_wall_multiplier > 1;
|
||||
if (g_turbo_load_wall_multiplier) {
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: mod selected %dx load acceleration "
|
||||
"(%d release frames)\n",
|
||||
g_turbo_load_wall_multiplier, g_turbo_load_release_frames);
|
||||
} else {
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: mod selected uncapped load acceleration "
|
||||
"(%d release frames)\n",
|
||||
g_turbo_load_release_frames);
|
||||
/* Session start: every session runs this after its mod commit or netplay
|
||||
* clear -- the first boot here, and the lobby rematch, which re-enters at
|
||||
* session_reboot below this block and so calls it itself. Both paths reach
|
||||
* renderer and window creation only after session_reboot, so activation
|
||||
* precedes them either way. A netplay clear leaves no plan, so nothing
|
||||
* activates and the session stays vanilla. */
|
||||
auto start_mod_session = [&](bool netplay) {
|
||||
/* Clear game-owned controller overrides/policies and load/disc-speed
|
||||
* choices first so disabling a package cannot leave its prior state
|
||||
* latched across a soft return. */
|
||||
g_mod_controller_mode_override.fill(-1);
|
||||
for (auto& policy : g_mod_controller_policy)
|
||||
policy = ModControllerPresentationPolicy{};
|
||||
g_mod_load_wall_multiplier = -1;
|
||||
g_mod_load_release_frames = -1;
|
||||
g_mod_disc_speed_divisor = -1;
|
||||
g_mod_disc_instant_rate = -1;
|
||||
g_turbo_audio_sink_enabled = g_turbo_audio_sink_config_enabled;
|
||||
g_turbo_load_wall_multiplier = 0;
|
||||
g_turbo_load_release_frames = TURBO_LOADS_RELEASE_FRAMES;
|
||||
if (!turbo_loads_offered)
|
||||
g_turbo_loads_enabled = 0;
|
||||
/* The helper restores interpolation and Skip FMVs to their
|
||||
* pre-activation values, which is only right while no launcher
|
||||
* control owns them. */
|
||||
static_assert(!frame_interpolation_offered && !skip_fmv_offered,
|
||||
"reset_mod_owned_presentation() would clobber a launcher "
|
||||
"setting; restore only when the feature is mod-owned");
|
||||
reset_mod_owned_presentation();
|
||||
mod_runtime_activate_plugins();
|
||||
apply_netplay_local_viewport_aspect(netplay);
|
||||
for (int i = 0; i < PSX_MAX_PLAYERS; ++i) {
|
||||
if (g_mod_controller_mode_override[i] >= 0)
|
||||
player_mode[i] = g_mod_controller_mode_override[i];
|
||||
}
|
||||
}
|
||||
if (g_mod_load_wall_multiplier >= 0) {
|
||||
g_turbo_loads_enabled = 1;
|
||||
g_turbo_load_wall_multiplier = g_mod_load_wall_multiplier;
|
||||
g_turbo_load_release_frames = g_mod_load_release_frames;
|
||||
/* Fast Loading advances the guest at a host rate greater than real
|
||||
* time. Keep the canonical SPU/CD stream running, but discard the
|
||||
* accelerated presentation-side audio until pacing resumes;
|
||||
* otherwise the SDL bridge overflows and the load becomes
|
||||
* observably unstable. */
|
||||
g_turbo_audio_sink_enabled = g_turbo_load_wall_multiplier > 1;
|
||||
if (g_turbo_load_wall_multiplier) {
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: mod selected %dx load acceleration "
|
||||
"(%d release frames)\n",
|
||||
g_turbo_load_wall_multiplier, g_turbo_load_release_frames);
|
||||
} else {
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: mod selected uncapped load acceleration "
|
||||
"(%d release frames)\n",
|
||||
g_turbo_load_release_frames);
|
||||
}
|
||||
}
|
||||
};
|
||||
start_mod_session(net_cfg.enabled);
|
||||
|
||||
/* Re-apply the resolved language to the translation layer. text_xlate_init
|
||||
* (at config load) only saw the game.toml default; this folds in the
|
||||
@@ -14785,17 +14806,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
std::string bios_path_str = resolved_bios.string();
|
||||
std::string memcard_dir_str = memcard_dir.string();
|
||||
/* A disc-patching mod builds a private patched image; mount that instead of
|
||||
* the stock disc, leaving the user's original untouched (master behaviour). */
|
||||
const std::filesystem::path& mod_disc =
|
||||
PSXRecompV4::mod_runtime_effective_disc_path();
|
||||
std::string disc_path_str =
|
||||
(mod_disc.empty() ? resolved_disc : mod_disc).string();
|
||||
if (!mod_disc.empty()) {
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: stock disc remains %s; mounting private mod cache %s\n",
|
||||
resolved_disc.string().c_str(), mod_disc.string().c_str());
|
||||
}
|
||||
std::string disc_path_str = session_disc_path(resolved_disc);
|
||||
|
||||
session_reboot:
|
||||
/* Rematch after lobby soft-return re-enters here with updated net_cfg. */
|
||||
@@ -16361,14 +16372,10 @@ soft_return_lobby:
|
||||
player_device[i]) <= 1) {
|
||||
player_device[i] = "gamepad";
|
||||
}
|
||||
/* Same resolution as the first launcher-exit path, and the
|
||||
* mod-override arm matters HERE specifically: `goto
|
||||
* session_reboot` re-enters the emulator BELOW the block
|
||||
* that applies g_mod_controller_mode_override, so a soft
|
||||
* return from the lobby never re-runs it. Before this
|
||||
* helper existed, an override survived a rematch only
|
||||
* because it round-tripped through ls.pad_mode[]; a bare
|
||||
* lock clamp here would have silently dropped it. */
|
||||
/* Same resolution as the first launcher-exit path. The
|
||||
* override here is the previous session's; the rematch's
|
||||
* start_mod_session() below clears it, re-runs activation
|
||||
* and applies the new session's override over this. */
|
||||
player_mode[i] =
|
||||
PSXRecompV4::resolve_player_mode_after_launcher(
|
||||
ls.pad_mode[i], ctrl_lock_mode,
|
||||
@@ -16537,7 +16544,9 @@ soft_return_lobby:
|
||||
* clobber whatever the Fast Loading / CD Speed / Tweaks plugins
|
||||
* decided with a stale pre-activation value — turning a player's
|
||||
* enabled mod silently back off on the first in-game Apply. The
|
||||
* offered flags are false for both, so leave both globals alone. */
|
||||
* offered flags are false for both, so leave both globals alone;
|
||||
* the rematch's start_mod_session() below resets and re-decides
|
||||
* both. */
|
||||
if (skip_fmv_offered) g_auto_skip_fmv = ls.auto_skip_fmv ? 1 : 0;
|
||||
if (turbo_loads_offered) g_turbo_loads_enabled = ls.turbo_loads ? 1 : 0;
|
||||
g_fullscreen = ls.fullscreen != 0;
|
||||
@@ -16648,24 +16657,24 @@ soft_return_lobby:
|
||||
}
|
||||
}
|
||||
/* `goto session_reboot` re-enters below the first-boot session
|
||||
* block, so redo its mod-owned presentation reset here, after the
|
||||
* commit/clear above (which also emptied the function-entry hook
|
||||
* table; only activation rebuilds it). Only a netplay match
|
||||
* returns here, so the reachable leak is the Fit and fixed aspect
|
||||
* its local viewport set. The launcher round-trips the previous
|
||||
* session's aspect through ls.aspect_index; widescreen is
|
||||
* mod-owned on PSX, so the Settings aspect is the 4:3 clamp
|
||||
* applied at startup. Unlike the first-boot block, this path does
|
||||
* not run activation or the controller, load and disc-speed
|
||||
* resets: an offline rematch with mods applies the plan's patches
|
||||
* and runs its VBlank callbacks without activation, and no
|
||||
* function-entry hook runs. */
|
||||
* block, so run the same session start here, after the
|
||||
* commit/clear above: the controller, load and disc-speed resets,
|
||||
* the mod-owned presentation reset, activation (which rebuilds the
|
||||
* function-entry hook table the commit/clear emptied), then what
|
||||
* activation chose. A netplay rematch has no plan, so it stays
|
||||
* vanilla. Only a netplay match returns here, so the reachable
|
||||
* leak the reset closes is the Fit and fixed aspect its local
|
||||
* viewport set. The launcher round-trips the previous session's
|
||||
* aspect through ls.aspect_index; widescreen is mod-owned on PSX,
|
||||
* so re-apply the Settings 4:3 clamp from startup first, where a
|
||||
* plugin's activation or the netplay local viewport can still
|
||||
* replace it. */
|
||||
if (!ws_offered) {
|
||||
g_video_aspect_num = 4;
|
||||
g_video_aspect_den = 3;
|
||||
}
|
||||
reset_mod_owned_presentation();
|
||||
apply_netplay_local_viewport_aspect(net_cfg.enabled);
|
||||
start_mod_session(net_cfg.enabled);
|
||||
disc_path_str = session_disc_path(resolved_disc);
|
||||
std::printf("psxrecomp: rematch from lobby (netplay=%d)\n",
|
||||
net_cfg.enabled ? 1 : 0);
|
||||
std::fflush(stdout);
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
// 2. An ACTIVE mod controller-mode override outranks the lock. The first
|
||||
// version of the clamp was an unconditional `if (lock) mode = locked`,
|
||||
// which is correct for (1) and silently wrong here: `goto session_reboot`
|
||||
// re-enters the emulator BELOW the block that applies
|
||||
// g_mod_controller_mode_override, so a soft return from the lobby never
|
||||
// re-runs it. The override used to survive a rematch only because it
|
||||
// round-tripped through ls.pad_mode[]; the clamp cut that path.
|
||||
// re-enters the emulator BELOW the first-boot block that applied
|
||||
// g_mod_controller_mode_override, so a soft return from the lobby did not
|
||||
// re-run it (the rematch now runs start_mod_session() itself). The
|
||||
// override survived a rematch only because it round-tripped through
|
||||
// ls.pad_mode[]; the clamp cut that path.
|
||||
//
|
||||
// 3. Whatever this returns is what gets persisted (seed.p_mode[] /
|
||||
// us.p_mode[]), so a mode the game does not support must never come out of
|
||||
|
||||
@@ -127,10 +127,12 @@ assert calls[1] < MAIN.index(PERSIST_US), (
|
||||
)
|
||||
|
||||
# ---- the mod override still cannot be lost across a soft return ----------
|
||||
# `goto session_reboot` re-enters the emulator BELOW the block that applies
|
||||
# g_mod_controller_mode_override, so the soft-return readback is the only place
|
||||
# an active override can be re-asserted. That is precisely why the resolution
|
||||
# helper takes it as an argument instead of the clamp being unconditional.
|
||||
# `goto session_reboot` re-enters the emulator BELOW the first-boot block, so
|
||||
# the rematch calls start_mod_session() itself: it clears the override,
|
||||
# re-runs activation and applies the new session's override after the
|
||||
# soft-return readback. The readback still passes the live override so the
|
||||
# resolution helper never clamps an active one away, which is why it takes it
|
||||
# as an argument instead of the clamp being unconditional.
|
||||
APPLY = "player_mode[i] = g_mod_controller_mode_override[i];"
|
||||
assert APPLY in MAIN, "the mod controller-mode override apply block is gone"
|
||||
assert "goto session_reboot;" in MAIN
|
||||
|
||||
@@ -54,12 +54,14 @@ assert 'f << "turbo_loads = "' not in CONFIG_CPP
|
||||
assert "if (turbo_loads_offered) g_turbo_loads_enabled = ls.turbo_loads ? 1 : 0;" in MAIN
|
||||
assert "if (skip_fmv_offered) g_auto_skip_fmv = ls.auto_skip_fmv ? 1 : 0;" in MAIN
|
||||
|
||||
# These run inside main.cpp's start_mod_session(), which every session start
|
||||
# (first boot and lobby rematch) calls, hence the lambda-body indentation.
|
||||
reset = """g_mod_load_wall_multiplier = -1;
|
||||
g_mod_load_release_frames = -1;"""
|
||||
g_mod_load_release_frames = -1;"""
|
||||
disc_reset = """g_mod_disc_speed_divisor = -1;
|
||||
g_mod_disc_instant_rate = -1;"""
|
||||
g_mod_disc_instant_rate = -1;"""
|
||||
disable_mod_owned_baseline = """if (!turbo_loads_offered)
|
||||
g_turbo_loads_enabled = 0;"""
|
||||
g_turbo_loads_enabled = 0;"""
|
||||
activate = "mod_runtime_activate_plugins();"
|
||||
apply = "if (g_mod_load_wall_multiplier >= 0) {"
|
||||
assert reset in MAIN
|
||||
|
||||
@@ -45,8 +45,8 @@ assert MAIN.index("g_auto_skip_fmv = 0;") < MAIN.index("mod_runtime_activate_plu
|
||||
|
||||
# Session hygiene: mod-owned presentation state must not outlive the session
|
||||
# that set it. The one in-process second session is the lobby rematch after a
|
||||
# netplay match; it jumps past the first-boot block, so the reset runs on both
|
||||
# paths. The reachable leak is the netplay local viewport's Fit/aspect into an
|
||||
# netplay match; it jumps past the first-boot block, so both paths call the
|
||||
# same session start, which resets before activating. The reachable leak is the netplay local viewport's Fit/aspect into an
|
||||
# offline rematch; the rest of the list is defensive. The first-call capture
|
||||
# itself is exercised by mod_session_baseline_test (behavioural), which cannot
|
||||
# see main.cpp; this guard pins main.cpp's glue to that helper (the baseline it
|
||||
@@ -162,34 +162,63 @@ assert set_bezel.index("if (s_bezel_tex) { glDeleteTextures(1, &s_bezel_tex); s_
|
||||
"gl_renderer_set_bezel(NULL, ...) must delete and forget the texture"
|
||||
assert "if (!s_bezel_tex || ww <= 0 || wh <= 0) return;" in GL
|
||||
|
||||
# First-boot session block: reset immediately before activation.
|
||||
assert "reset_mod_owned_presentation();\n mod_runtime_activate_plugins();" in MAIN, \
|
||||
"reset must run immediately before mod activation"
|
||||
# Session start: one sequence for every session, after its commit or netplay
|
||||
# clear. It clears the controller, load and disc-speed choices, resets
|
||||
# mod-owned presentation state, activates, then applies what activation chose.
|
||||
session_start = MAIN.index("auto start_mod_session = [&](bool netplay) {")
|
||||
session = MAIN[session_start:MAIN.index("\n };\n", session_start)]
|
||||
order = [session.index(step) for step in (
|
||||
"g_mod_controller_mode_override.fill(-1);",
|
||||
"policy = ModControllerPresentationPolicy{};",
|
||||
"g_mod_load_wall_multiplier = -1;",
|
||||
"g_mod_disc_speed_divisor = -1;",
|
||||
"g_mod_disc_instant_rate = -1;",
|
||||
"g_turbo_load_wall_multiplier = 0;",
|
||||
"reset_mod_owned_presentation();\n mod_runtime_activate_plugins();",
|
||||
"apply_netplay_local_viewport_aspect(netplay);",
|
||||
"player_mode[i] = g_mod_controller_mode_override[i];",
|
||||
"g_turbo_load_wall_multiplier = g_mod_load_wall_multiplier;",
|
||||
)]
|
||||
assert order == sorted(order), \
|
||||
"session start must reset, then activate, then apply what activation chose"
|
||||
# Nothing activates or resets outside that sequence.
|
||||
assert MAIN.count("mod_runtime_activate_plugins();") == 1, \
|
||||
"plugins must activate only in start_mod_session"
|
||||
assert MAIN.count("reset_mod_owned_presentation();") == 1, \
|
||||
"the mod-owned reset must run only in start_mod_session"
|
||||
|
||||
# First boot: commit (or netplay clear), then the session start, then the
|
||||
# disc the plan selects, all above session_reboot.
|
||||
reboot = MAIN.index("\nsession_reboot:\n")
|
||||
first_commit = MAIN.index("mod_runtime_commit(resolved_disc, &mod_error)")
|
||||
first_start = MAIN.index("start_mod_session(net_cfg.enabled);")
|
||||
first_disc = MAIN.index("std::string disc_path_str = session_disc_path(resolved_disc);")
|
||||
assert first_commit < session_start < first_start < first_disc < reboot, \
|
||||
"first boot must commit, start the session, then mount the plan's disc"
|
||||
|
||||
# Soft return (rematch) re-enters below that block via `goto session_reboot`,
|
||||
# so the rematch path must reset too, after its commit / netplay clear.
|
||||
# so the rematch path runs the same session start after its commit / netplay
|
||||
# clear, and mounts the disc the new plan selects.
|
||||
rematch = MAIN[MAIN.index('"psxrecomp: cannot clear mods for netplay "'):]
|
||||
rematch = rematch[:rematch.index("goto session_reboot;")]
|
||||
commit = rematch.index("mod_runtime_commit(resolved_disc,")
|
||||
reset_at = rematch.index("reset_mod_owned_presentation();")
|
||||
viewport = rematch.index("apply_netplay_local_viewport_aspect(net_cfg.enabled);")
|
||||
assert commit < reset_at < viewport, \
|
||||
"rematch must reset mod-owned state after its commit, before netplay aspect"
|
||||
# If the rematch path ever activates plugins, the reset must come first, or it
|
||||
# would undo what activation just set.
|
||||
if "mod_runtime_activate_plugins();" in rematch:
|
||||
assert reset_at < rematch.index("mod_runtime_activate_plugins();"), \
|
||||
"rematch must reset mod-owned state before activation"
|
||||
rematch_session = rematch.index("start_mod_session(net_cfg.enabled);")
|
||||
rematch_disc = rematch.index("disc_path_str = session_disc_path(resolved_disc);")
|
||||
assert commit < rematch_session < rematch_disc, \
|
||||
"rematch must commit, start the session, then mount the plan's disc"
|
||||
assert MAIN.count("start_mod_session(net_cfg.enabled);") == 2, \
|
||||
"exactly the first boot and the rematch start a session"
|
||||
|
||||
# Rematch 4:3 re-clamp: the launcher round-trips the previous match's aspect
|
||||
# (16:9/21:9 from the netplay local viewport) through ls.aspect_index. While
|
||||
# widescreen is mod-owned the Settings aspect is 4:3, so the rematch path
|
||||
# re-applies that clamp after the launcher, before the local viewport.
|
||||
# re-applies that clamp after the launcher, before the session start, where
|
||||
# a plugin's activation or the netplay local viewport can still replace it.
|
||||
clamp = "if (!ws_offered) {\n g_video_aspect_num = 4;\n g_video_aspect_den = 3;\n }"
|
||||
assert rematch.count(clamp) == 1, "rematch must re-clamp the aspect to 4:3"
|
||||
clamp_at = rematch.index(clamp)
|
||||
assert commit < clamp_at < viewport, \
|
||||
"4:3 re-clamp must follow the commit and precede the netplay local viewport"
|
||||
assert commit < clamp_at < rematch_session, \
|
||||
"4:3 re-clamp must follow the commit and precede the session start"
|
||||
rematch_start = MAIN.index('"psxrecomp: cannot clear mods for netplay "')
|
||||
assert MAIN.rfind("case 1: g_video_aspect_num = 16; g_video_aspect_den = 9; break;",
|
||||
0, rematch_start) > MAIN.index("soft_return_lobby:"), \
|
||||
|
||||
@@ -393,6 +393,27 @@ int main() {
|
||||
check(active_entry_hits == 4 && disabled_entry_hits == 0 &&
|
||||
unselected_entry_hits == 0,
|
||||
"re-activation restores exactly the active plan's hooks");
|
||||
/* The lobby rematch, in the order main.cpp's start_mod_session() drives
|
||||
* it: a netplay match (plan cleared, then activated) and an offline
|
||||
* rematch that commits and activates exactly like a first boot. */
|
||||
check(PSXRecompV4::mod_runtime_clear_for_netplay(&error), error.c_str());
|
||||
mod_runtime_activate_plugins();
|
||||
mod_runtime_on_vblank();
|
||||
psx_mod_function_entry(&entry_cpu, 0x80003000u);
|
||||
check(activation_calls == 2 && plugin_calls == 1 &&
|
||||
g_psx_mod_function_entry_hooks == 0 && active_entry_hits == 4,
|
||||
"a netplay session must stay vanilla: no activation, VBlank or hook");
|
||||
check(PSXRecompV4::mod_runtime_commit(cue_path, &error), error.c_str());
|
||||
mod_runtime_activate_plugins();
|
||||
check(activation_calls == 3,
|
||||
"an offline rematch after netplay must run activation callbacks");
|
||||
mod_runtime_on_vblank();
|
||||
check(plugin_calls == 2,
|
||||
"an offline rematch after netplay must run VBlank plugins");
|
||||
psx_mod_function_entry(&entry_cpu, 0x80003000u);
|
||||
check(g_psx_mod_function_entry_hooks == 1 && active_entry_hits == 5 &&
|
||||
disabled_entry_hits == 0 && unselected_entry_hits == 0,
|
||||
"an offline rematch after netplay must arm exactly its plan's hooks");
|
||||
|
||||
ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4;
|
||||
ram[0x1100] = 0; ram[0x1101] = 0;
|
||||
|
||||
Reference in New Issue
Block a user