Review 2 (mstan): psx_mod_read_* in a host-output hook now peek RAM /
scratchpad / BIOS ROM (psx_peek_word) and read MMIO as 0, so SIO data / stat
reads no longer pop RX or age the ACK. The CPUState copy's bus pointers are
replaced: reads peek, writes are dropped, so cpu->write_* cannot reach the
shared guest. psx_mod_call_guest_uncharged_counted clears *counted_cycles on
refusal. netplay_view_mods_test now runs the real sio.c behind the bus: the
host-output callback reads SIO_DATA / SIO_STAT through both the mod API and
cpu->read_*, and stores through cpu->write_* (RAM and SIO_CTRL); it checks 0
MMIO bus accesses, SIO stat / ctrl / RX unchanged, RAM unchanged. Other tests
linking mod_runtime.cpp get a psx_peek_word stub.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A raw dump with trailing bytes can be a multiple of 2048 and mounted as a cooked ISO
(Legend of Dragoon disc 1: PVD read as zeros, BIOS SystemError B/906).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Preserve negotiated content and dual-raster pass gates. Retain both fixture assertion sets and HD-off seams. Regenerate both BIOS with actual native emitter. Fix type-only rollback corrections and exclude extra-pad changes from digital-only promote gates.
Load modern texpage/texupload PNG replacements independently of DuckStation source. Integrate pack folders and options with mods, preserve native VRAM, and validate GL rendering, format matching, dump safety, and upload invalidation.
A [[plugin]] may declare netplay = "local_view": it only changes what one
player sees. A netplay session now keeps the player's own features whose every
contribution is such a plugin (no writes, overlays or derived discs) instead of
clearing them, without saving state.toml. They activate as usual, but their
hooks run only inside psx_mod_render_local_view (psx_mod_local_view_scope) and
their vblank/savestate callbacks not at all. With such a plan the widescreen
cull margin is the stock 0 in the shared game and this peer's own margin only
inside the sandboxed own-view draw; squash is not used. The canonical frame,
snapshots and digests stay identical on every peer whatever each player chose,
so nothing is negotiated, and each keeps their own aspect.
Tests: netplay_view_mods_test (flag, plan filter, parked hooks, no vblank,
state.toml untouched), ws_local_view_margin_test (margin scope), render-pass
sandbox asserts the scope around a local view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trusted game code can register entry hooks and consuming filters that run
only in a netplay session (psx_game_register_netplay_function_entry /
_filter). They live outside the mod package plan, which every online
match clears, so a title's netplay support survives it. Offline they are
inert unless PSX_GAME_FILTER_OFFLINE_PROBE=1. Reimplemented on master's
entry-filter path from the R4 Link Battle branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apply committed sector mods during plugin activation, resolve paths through
patched directory records, and bound file size by CD capacity.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Preserve current segmented dispatch, active-plan mod hooks and software scale limits. Extend function filters through the active plan, retain the last-store pointer with overlay ABI 25, and keep default config fingerprints stable. Refresh BIOS emitter stamps after byte-identical regeneration and reconcile regression fixtures.
Only CPU stores are cache-isolated. Mods, the FMV skip, debug pokes, the
bg2d ring fill and the exception sentinel write guest memory from the host;
they now go through psx_host_write_*, which bypasses isc_store the way DMA
does, so a host store inside a FlushCache window reaches memory instead of
rewriting an I-cache tag. The render-pass checkpoint restores the new depth
counter like the DMA depth. isc_store_test covers it (a lane-0 host store in
tag-test mode would otherwise mark a cold line valid).
Docs: the SCPH-1001 residual starts at kernel init 0x598, and without #429
the fix removes 113 of the 126 OpenBIOS flush cycles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lobby rematch re-enters the emulator through goto session_reboot,
below the first-boot session block. After its commit (offline) or
netplay clear it reset mod-owned presentation state, but it never ran
mod_runtime_activate_plugins() or the controller, load-acceleration and
disc-speed resets. So an offline rematch with mods enabled applied the
plan's main-EXE and disc patches and ran its VBlank callbacks, but no
activation callback ran, and the function-entry hook table, which the
commit empties and only activation rebuilds, stayed empty. It also
mounted the stock disc even when the plan had built a derived disc
image.
start_mod_session() now holds the first-boot sequence, unchanged:
clear the controller-mode overrides and presentation policies, load
acceleration and disc speed; run reset_mod_owned_presentation();
activate; then apply the netplay local viewport, the controller-mode
overrides and load acceleration. The first boot and the rematch both
call it after their commit or netplay clear, and both then take the
mounted disc from session_disc_path(), which returns the plan's derived
image when there is one and otherwise the stock disc. Activation still
precedes renderer and window creation on the rematch, because those
happen after session_reboot on every session. The rematch re-applies
its 4:3 Settings clamp before the session start, so a plugin's
activation or the netplay local viewport can still set the aspect.
A netplay rematch clears the plan, so activating it runs nothing and
the match stays vanilla. The first boot's behaviour is unchanged.
Tests: test_mod_owned_display_controls.py now requires activation and
the mod-owned reset to happen only inside start_mod_session, in the
order reset, activate, apply. It requires both paths to call it after
their commit and before the disc mount, and the rematch 4:3 clamp to
come before it. mod_runtime_test adds the lobby rematch sequence:
activating after a netplay clear runs no callback, VBlank plugin or
hook; the offline rematch's commit and activation re-run the
activation callback and VBlank plugin and arm exactly the plan's hook.
test_mod_load_acceleration.py follows the reset block into the lambda body.
Two comments that said a soft return never re-applies the controller-mode
override are corrected: test_launcher_pad_mode_wiring.py and
test_launcher_pad_mode_resolve.cpp. MOD_PACKAGES.md now describes the
rematch as a full session start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adapt the 8 MB RAM work from the rbengine branch into a master-targeted change. The builtin package is disabled by default, activation requests expanded RAM before memory init, and runtime/state/digest/dispatch paths respect the live RAM map.
A plugin that draws its own overlay primitives needs to know where the
right-hand edge of the screen is, and it cannot work that out for itself.
GPUSTAT carries the horizontal-resolution bits, so a plugin can recover
the coarse mode width, but the visible width also depends on the GP1(06h)
X1/X2 range -- and that register is write-only and mirrored nowhere a
plugin can read.
Ape Escape is the worked example. It scans out 384 (368-mode, dot clock 7,
horizontal range 539..3227) while its mode width is 368, and the usual
assumption of 320 is wrong by 64. A HUD row right-aligned to 316 lands 68
pixels short of the edge, which is exactly what the Quick Gadget Select
mod does today.
psx_mod_display_width()/psx_mod_display_height() return what the presenter
uses, straight from gpu_get_display_info(). Zero means the geometry is not
established yet, and the header tells callers to skip drawing rather than
substitute a guess -- an overlay in the wrong place is worse than an
overlay that waits a frame.
mod_runtime_test covers the passthrough with a stubbed GPU, including the
384-vs-368 case that motivates it and the zero case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>