mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
985d45ecf2
Review of #561. Guest time now has two entry points (psx_cycles.h): the CPU charge psx_cpu_charge() (instruction base, load fudge/region/completion, i-cache refills, BIOS HLE costs), which applies [timing] guest_cycle_scale, and the unscaled device advance psx_advance_cycles() (DMA, idle skips, mul/div and GTE deadline waits). The emitters' block-mode charges emit psx_cpu_charge; overlay DLLs accumulate it raw and publish through the cpu_charge callback (was advance_cycles, same slot) into the same host function, so nothing is scaled at the DLL boundary. The batched psx_cyc_charge path, the dirty-RAM interpreter, psx_cyc_readmem, i-cache refills and bios_hle.c all charge through it. At scale 1 it is exactly psx_advance_cycles. Gate in psxrecomp: [timing] guest_cycle_scale_gate, RAM predicates judged at every VBlank edge (deterministic, works online, no mod needed), plus psx_mod_set_guest_cycle_scale_gate() in mod_plugins.h for custom logic or to tie the scale to a mod (guest_cycle_scale_gated = true; every mod session reset shuts it). The carried fraction and both gate states are a boot_state section (BS_SEC_GCS, written only when the scale is not 1), so savestates and the netplay rollback snapshot re-simulate the same cycles. psx_guest_cycle_scale_gate_test pins tier identity, the gates and rollback re-simulation against the real psx_cycles.c. OpenBIOS regenerated (emitter stamp); retail SCPH1001 is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
173 lines
9.2 KiB
C
173 lines
9.2 KiB
C
#ifndef PSX_BOOT_STATE_H
|
|
#define PSX_BOOT_STATE_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include "cpu_state.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/*
|
|
* Boot snapshot (a.k.a. fast_boot) — a COMPLETE post-BIOS save-state.
|
|
*
|
|
* Model: first launch (and the first launch after ANY app/recompiler update)
|
|
* runs the real recompiled BIOS normally, logos and all. At the moment the BIOS
|
|
* dispatches into the game's PS-EXE entry_pc, we capture a complete hardware
|
|
* snapshot. Every subsequent launch (same build) restores that snapshot and
|
|
* presents the game's first frame — instant boot, no BIOS, no logos.
|
|
*
|
|
* This is NOT HLE: it persists the REAL hardware state produced by a real BIOS
|
|
* run, then replays it. Nothing about BIOS behaviour is synthesized.
|
|
*
|
|
* Rebuild-proof: the file carries an integrity key (below) that includes the
|
|
* codegen hash + ABI tag + codegen version. A user update changes those, so a
|
|
* stale snapshot can NEVER silently load into a new build — it is rejected and
|
|
* the next boot is a normal boot that recaptures. Graceful, automatic.
|
|
*
|
|
* Completeness is mandatory (v4 no-stub rule): a partial capture that leaves a
|
|
* subsystem at reset while CPU/RAM assume it was configured is a latent stub.
|
|
* Every mutable hardware subsystem gets a section here, and the capture is
|
|
* proven complete by diffing a restored session's frames against a normal-boot
|
|
* session's frames (see the "bootsnap" debug command). Host-side / recompiler-
|
|
* derived state (dirty-RAM bitmap, overlay tables, debug rings) is NOT
|
|
* serialized — it is re-derived from restored guest RAM on load.
|
|
*/
|
|
|
|
#define BOOT_STATE_MAGIC 0x50535842u /* "PSXB" */
|
|
/* v1 = incomplete RAM-only; v2 = full machine but host-struct memcpy (padding);
|
|
* v3 = little-endian field wire (portable Win/Linux/macOS ARM);
|
|
* v4 = v3 + optional zlib on large sections (section pad bit0 = compressed);
|
|
* v5 = v4 + CD-ROM Sub-Q replacement state;
|
|
* v6 = v5 + per-word GPU DMA2 linked-list progress;
|
|
* v7 = v6 + pending XA DATA_END IRQ state. */
|
|
#define BOOT_STATE_VERSION 9u
|
|
/* v8 added allocated enhancement memory (vanilla v8-era writers kept v7); older
|
|
* runtimes must not accept enhanced snapshots while ignoring their arenas. */
|
|
/* v9 persists the BIOS-to-game handoff latch. Engine overlays may have
|
|
* replaced the original entry code by the time a gameplay state is loaded. */
|
|
/* v7 intentionally breaks older savestates after the CD-ROM wire grew. Reject
|
|
* them at the header before any section changes the live machine. */
|
|
#define BOOT_STATE_VERSION_MIN_READ 7u
|
|
/* Section pad bit0: payload is u32 LE uncompressed_len + zlib deflate bytes. */
|
|
#define BOOT_STATE_SEC_ZLIB 1u
|
|
|
|
/*
|
|
* On-disk header (v3): nine little-endian uint32 fields at offset 0 (36 bytes),
|
|
* followed by the section stream. ALL key fields must match the running build
|
|
* or the snapshot is rejected. Do not fwrite() this struct — use pst_wire.
|
|
*/
|
|
typedef struct {
|
|
uint32_t magic; /* BOOT_STATE_MAGIC */
|
|
uint32_t version; /* BOOT_STATE_VERSION */
|
|
/* ---- integrity key (every field must match to accept) ---- */
|
|
uint32_t bios_checksum; /* sum of all uint32 words in the BIOS ROM */
|
|
uint32_t entry_pc; /* game PS-EXE entry PC */
|
|
uint32_t codegen_hash; /* PSX_OVERLAY_CODEGEN_HASH (auto-gen by cmake) */
|
|
int32_t abi_tag; /* PSX_OVERLAY_ABI_TAG (abi version | flavor<<16) */
|
|
uint32_t codegen_ver; /* PSX_OVERLAY_CODEGEN_VER */
|
|
/* ---- layout ---- */
|
|
uint32_t section_count; /* number of sections that follow */
|
|
uint32_t reserved; /* enhancement-memory, RAM and execution cookie */
|
|
} BootStateHeader;
|
|
|
|
#define BOOT_STATE_HEADER_WIRE_BYTES 36u
|
|
|
|
/*
|
|
* Section stream (v3/v4): section_count records, each laid out as
|
|
* uint32_t tag; LE (one of BS_SEC_*)
|
|
* uint32_t pad; LE flags (v3: 0; v4: BOOT_STATE_SEC_ZLIB optional)
|
|
* uint64_t len; LE payload byte count
|
|
* uint8_t payload[len]; (module payloads are LE field wires too)
|
|
* When BOOT_STATE_SEC_ZLIB is set, payload = u32 LE raw_len + zlib(raw).
|
|
* Unknown tags are skipped for forward compatibility. A malformed known section
|
|
* or a missing required section on load is a hard reject (incomplete restore is
|
|
* never allowed) -> normal boot + recapture.
|
|
*/
|
|
enum {
|
|
BS_SEC_GAME_START = 0x12, /* u32 LE boolean: BIOS-to-game handoff completed */
|
|
BS_SEC_MODMEM = 0x11, /* allocated opt-in CPU/GPU enhancement arenas */
|
|
BS_SEC_CPU = 0x01, /* CPUState: gpr/pc/hi/lo/cop0/gte_data/gte_ctrl */
|
|
BS_SEC_RAM = 0x02, /* 2 MB main RAM */
|
|
BS_SEC_SPAD = 0x03, /* 1 KB scratchpad */
|
|
BS_SEC_IRQ = 0x04, /* i_stat / i_mask / cycles_since_vblank (12B; 8B ok) */
|
|
BS_SEC_TIMER = 0x05, /* 3 root counters (counter/mode/target/irq/frac) */
|
|
BS_SEC_CLOCK = 0x06, /* psx_cycle_count */
|
|
BS_SEC_GPU = 0x07, /* GPU regs: display/draw-area/offset/mask/texpage/xfer*/
|
|
BS_SEC_VRAM = 0x08, /* 1 MB VRAM (1024x512x16) */
|
|
BS_SEC_SPU = 0x09, /* SPU regs + 24 voice decode/ADSR state + latches */
|
|
BS_SEC_SPURAM = 0x0A, /* 512 KB SPU RAM */
|
|
BS_SEC_CDROM = 0x0B, /* CD-ROM controller FSM (regs/FIFOs/seek/read/pending)*/
|
|
BS_SEC_DMA = 0x0C, /* DMA channels[7] + dpcr/dicr + async-transfer state */
|
|
BS_SEC_SIO = 0x0D, /* SIO regs + pad-config FSM + memcard FSM */
|
|
BS_SEC_DIRTY = 0x0E, /* dirty-RAM page bitmap (guest-written code pages) */
|
|
BS_SEC_MDEC = 0x0F, /* MDEC command/FIFOs/quant/scale (FMV decode resume) */
|
|
BS_SEC_GCS = 0x13, /* [timing] guest_cycle_scale: carried fraction + gate
|
|
state (3 u32). Written only when the title's
|
|
scale is not 1, so faithful states are unchanged;
|
|
absent => fraction 0, gates as they are. */
|
|
BS_SEC_ICACHE = 0x10, /* R3000A I-cache tag/valid words (1024 u32) — fetch
|
|
cost model. Host-persistent otherwise: a warm load
|
|
without it replays with the pre-load timeline's
|
|
cache, so fetch-miss cycles differ per peer/retry
|
|
and IRQ delivery lands a few wait-loop iterations
|
|
apart (MotK abort@940: fin cyc Δ8, v0 5c83/5c86
|
|
from identical baselines). Optional on load for
|
|
old blobs (left untouched when absent). */
|
|
};
|
|
|
|
/* Save a COMPLETE snapshot at game handoff. Returns 1 on success. */
|
|
int boot_state_save(const CPUState* cpu, uint32_t bios_checksum,
|
|
uint32_t entry_pc, const char* path);
|
|
|
|
/* Same as boot_state_save, but into a malloc'd buffer (caller frees *out_data).
|
|
* Compresses large sections (disk-oriented). */
|
|
int boot_state_save_buffer(const CPUState* cpu, uint32_t bios_checksum,
|
|
uint32_t entry_pc, uint8_t** out_data,
|
|
size_t* out_len);
|
|
|
|
/* In-memory ring snaps: same sections, no zlib. Load accepts either form.
|
|
* Avoids compress2 on ~3.5 MiB RAM+VRAM+SPU every live/resim snap (FPS). */
|
|
int boot_state_save_buffer_raw(const CPUState* cpu, uint32_t bios_checksum,
|
|
uint32_t entry_pc, uint8_t** out_data,
|
|
size_t* out_len);
|
|
|
|
/* §96 telemetry: after the latest save, how many VRAM scanlines were dirty
|
|
* and whether the incremental mirror path patched (vs full memcpy). */
|
|
uint32_t boot_state_last_vram_dirty_rows(void);
|
|
int boot_state_last_vram_incremental(void);
|
|
|
|
/* Drop the §96 VRAM mirror (RB shutdown / before re-enable). */
|
|
void boot_state_vram_mirror_reset(void);
|
|
|
|
/* Load + validate (integrity key) + restore the full machine. On any mismatch
|
|
* or incompleteness returns 0 (caller then boots normally and recaptures). */
|
|
int boot_state_load(const char* path, uint32_t bios_checksum,
|
|
uint32_t entry_pc, CPUState* cpu);
|
|
|
|
/* Same as boot_state_load, but from an already-buffered .pst image (netplay). */
|
|
int boot_state_load_buffer(const uint8_t* file, size_t file_len,
|
|
uint32_t bios_checksum, uint32_t entry_pc,
|
|
CPUState* cpu);
|
|
|
|
/* Header-only integrity check (no section inflate/apply). Returns 1 if this
|
|
* build can load the image; 0 and fills reason (when non-NULL) on reject. */
|
|
int boot_state_check_buffer(const uint8_t* file, size_t file_len,
|
|
uint32_t bios_checksum, uint32_t entry_pc,
|
|
char* reason, size_t reason_cap);
|
|
|
|
/* Register a deferred capture: when boot_state_trigger_capture() fires (from
|
|
* fntrace at game-start), serialize to path. One-shot. */
|
|
void boot_state_set_capture(const char* path, uint32_t bios_checksum,
|
|
uint32_t entry_pc);
|
|
|
|
/* Called from fntrace when the game entry PC first dispatches. */
|
|
void boot_state_trigger_capture(const CPUState* cpu);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSX_BOOT_STATE_H */
|