#pragma once
/*
* Resident disc resources for title-owned seamless-loading adapters.
*
* A seamless loader serves a game's blocking loads from memory instead of the
* emulated drive, with the game's own code still consuming every byte. The
* title-independent half lives here: read the listed files from the effective
* (mod-patched) disc once, optionally derive data from them with the title's
* own decoders, verify everything with SHA-256, and keep a cache that is
* reused only by the exact same mod plan. The adapter keeps its loader hooks,
* its catalog and its decoders.
*
* Cache key: SHA-256 over the active mod plan fingerprint, the adapter's
* format id and the complete spec (paths, stock sizes/hashes, policy). Every
* blob is re-verified when a cache is loaded; a mismatch rebuilds it. Packs
* live in LOCALAPPDATA (or XDG_CACHE_HOME / ~/.cache)/
/seamless, or in
* PSX_RESIDENT_CACHE when set. Only files named -*.pack are pruned.
*
* Call psx_resident_prepare() from an activation callback (it reads the disc
* through psx_mod_read_disc_file). Accessors are emulation-thread only.
* A cache that cannot be written (no cache directory, read-only or full disk)
* does not fail preparation: the pack is kept in memory for this launch, so
* the outcome depends only on the effective disc and the plan (two netplay
* peers with the same plan always take the same loader path).
* TCP: {"cmd":"resident_status"} lists every prepared pack;
* {"cmd":"resident_events"} returns the always-on service ring.
*/
#include
#ifdef __cplusplus
extern "C" {
#endif
enum {
/* Serve mod-effective content. Files that differ from their stock hash
* are counted (psx_resident_file_stock() == 0), never replaced. */
PSX_RESIDENT_ALLOW_MODIFIED = 0,
/* Fail preparation when any file with a stock hash differs: for adapters
* whose hooks depend on the original layout. The title then loads
* normally. */
PSX_RESIDENT_REQUIRE_STOCK = 1
};
typedef struct PSXResidentFile {
const char* path; /* ISO 9660 path, e.g. "CD/TOMBA2.IMG" */
uint32_t stock_size; /* original byte size; 0 = not catalogued */
const char* stock_sha256; /* lowercase hex over the zero-padded sectors of
* the original file; NULL = not catalogued */
} PSXResidentFile;
/* Complete a verified pure-data resident span through the shared CD-DMA RAM
* path. Requires the original ReadN ACK and pristine first-sector ownership;
* busy/media/changed payloads decline without RAM/controller mutation. Keeps
* the actual final GetlocL header, retained FIFO and next drive cursor. The
* trusted title still supplies its original SDK completion and Pause ack. */
int psx_mod_resident_cd_read(uint32_t address, const void* data,
uint32_t lba, uint32_t sectors);
/* Deliver exactly one stock data sector through the actual controller FIFO,
* IRQ and original guest Ready/DMA path. The ReadN stream remains active and
* its next sector keeps ordinary timing. Busy/changed/media sectors decline
* without mutation. No host pointer or transfer cursor is retained; ordinary
* controller snapshots contain every accepted effect.
*
* Trusted title code must prove the file/span/code/callback and a free next
* destination, including queued GPU source pointers and active GPU/SPU DMA.
* The controller checks CD ownership only. Do not use for XA/FMV or to bypass
* original consumers, publish completion, or replace a final Pause ack. */
int psx_mod_resident_cd_sector(uint32_t lba, const void* user_data);
typedef struct PSXResidentSink PSXResidentSink;
/* Store one derived blob (decoded asset, table, ...) for source file `file`.
* `tag` and `meta` are the adapter's own; blobs are kept in emission order and
* identical bytes are stored once. Returns 0 on failure (preparation fails). */
int psx_resident_emit(PSXResidentSink* sink, uint32_t file, uint32_t tag,
const uint32_t meta[4], const void* bytes, uint32_t size);
/* Called once per file during preparation, in spec order, with the file's
* sector-padded effective content. `stock` is 1 when it matched its stock
* hash. Return 0 to fail preparation (e.g. a stock decode mismatch). */
typedef int (*PSXResidentDeriveFn)(PSXResidentSink* sink, uint32_t file,
const uint8_t* data, uint32_t size,
int stock, void* user);
typedef struct PSXResidentSpec {
uint32_t struct_size; /* sizeof(PSXResidentSpec) */
const char* title; /* cache folder, e.g. "MegaManX6Recomp" */
const char* format; /* pack id; change it whenever the derived
* data's meaning changes */
const PSXResidentFile* files;
uint32_t file_count;
uint32_t policy; /* PSX_RESIDENT_ALLOW_MODIFIED / _REQUIRE_STOCK */
uint32_t max_file_bytes; /* 0 = a full 80-minute CD */
PSXResidentDeriveFn derive; /* optional */
void* derive_user;
uint32_t keep_packs; /* packs of this format kept besides the
* active one; 0 = 3 */
} PSXResidentSpec;
typedef struct PSXResidentPack PSXResidentPack;
/* Load (verified) or build the pack. NULL on failure; the reason is kept for
* resident_status and the resident.prepare_failed mod counter is bumped. A
* later prepare for the same title and format releases the earlier pack. */
const PSXResidentPack* psx_resident_prepare(const PSXResidentSpec* spec);
uint32_t psx_resident_file_count(const PSXResidentPack* pack);
/* Sector-padded effective bytes. `size` receives the file size, `padded` the
* padded length (both optional). NULL for an invalid index. */
const uint8_t* psx_resident_file(const PSXResidentPack* pack, uint32_t file,
uint32_t* size, uint32_t* padded);
/* Effective-disc LBA of the file's first sector (relocations by mods apply). */
uint32_t psx_resident_file_lba(const PSXResidentPack* pack, uint32_t file);
int psx_resident_file_stock(const PSXResidentPack* pack, uint32_t file);
uint32_t psx_resident_modified_files(const PSXResidentPack* pack);
/* `bytes` starting at effective LBA `lba`, entirely within one file's padded
* content; NULL otherwise. `file` (optional) receives the file index. */
const uint8_t* psx_resident_find_lba(const PSXResidentPack* pack, uint32_t lba,
uint32_t bytes, uint32_t* file);
uint32_t psx_resident_derived_count(const PSXResidentPack* pack);
/* Derived blob `index` in emission order. Optional out-params. */
const uint8_t* psx_resident_derived(const PSXResidentPack* pack, uint32_t index,
uint32_t* file, uint32_t* tag,
uint32_t meta[4], uint32_t* size);
/* Lowercase hex SHA-256 of the guest bytes in [lo, hi) of each range, hashed
* in order, compared with `sha256`. Guards an adapter's code contract. */
typedef struct PSXResidentRange { uint32_t lo, hi; } PSXResidentRange;
int psx_resident_guest_ranges_match(const PSXResidentRange* ranges,
uint32_t count, const char* sha256);
/* Debug server: JSON array describing every prepared pack. */
int psx_resident_status_json(char* out, uint32_t capacity);
/* Always-on service ring (observability, not logging). Adapters record every
* loader request they complete from memory (served = 1) or hand to the
* original loader (served = 0), so a load window is measured afterwards from
* the ring instead of by arming a trace. `op` must be a string literal of the
* form "."; file is the pack index or UINT32_MAX. Each
* entry carries the guest frame (as the CD-ROM rings) and the guest cycle.
* Emulation-thread only; the oldest entries are evicted. */
void psx_resident_record(const char* op, uint32_t file, uint32_t lba,
uint32_t bytes, int served);
/* Debug server: entries with frame_lo <= frame <= frame_hi (inclusive; pass
* 0 and UINT64_MAX for all), at most `max` of the newest, oldest first:
* {"total":n,"capacity":c,"entries":[{seq,frame,cycle,op,file,lba,bytes,
* served}]}. Returns 0 when `capacity` is too small. */
int psx_resident_events_json(char* out, uint32_t capacity, uint64_t frame_lo,
uint64_t frame_hi, uint32_t max);
#ifdef __cplusplus
}
#endif