mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
dc7a4c1084
Honour cue PREGAP/POSTGAP in the BIN/CUE mount (fixes #388)
353 lines
13 KiB
C
353 lines
13 KiB
C
#ifndef PSXRECOMP_CDROM_H
|
|
#define PSXRECOMP_CDROM_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
void cdrom_init(const char* cue_path);
|
|
|
|
/* Did cdrom_init() actually mount an image? 0 = the drive is empty.
|
|
*
|
|
* cdrom_init() is deliberately non-fatal when iso_open() fails (a BIOS-only
|
|
* target legitimately runs with no disc), so a game that was GIVEN a disc path
|
|
* whose image could not be mounted would otherwise boot straight into an empty
|
|
* drive and render nothing -- a black screen with no explanation. Callers that
|
|
* passed a real path must check this and report. */
|
|
int cdrom_has_disc(void);
|
|
/* Disc license region for GetID ("SCEE"/"SCEA"/"SCEI"); derived from the
|
|
* mounted disc's boot serial at launch. Defaults to "SCEA" (SCPH1001). */
|
|
void cdrom_set_disc_scex(const char scex[4]);
|
|
|
|
/* Set disc speed multiplier immediately. divisor=0 → instant, 1 → 1x, 2 → 2x. */
|
|
void cdrom_set_speed(int divisor);
|
|
/* Store configured game speed (applied post-BIOS via cdrom_notify_game_started). */
|
|
void cdrom_set_game_speed(int divisor);
|
|
/* Called by fntrace on first game-range dispatch; switches to game speed. */
|
|
void cdrom_notify_game_started(void);
|
|
/* LBA of the most recent SetLoc command (-1 if none yet). Used by the DMA
|
|
* ring buffer to correlate each sector transfer with its disc position. */
|
|
int cdrom_get_setloc_lba(void);
|
|
|
|
/* 'instant' per-frame sector-IRQ budget (step 3). Clamped to [1, 4096];
|
|
* the per-sector period additionally floors at CDROM_MIN_DELAY. Writers:
|
|
* game.toml [runtime] instant_max_per_frame, the cdrom_instant_rate TCP
|
|
* command, and the turbo-through-loads predicate (step 4). */
|
|
void cdrom_set_instant_rate(int per_frame);
|
|
int cdrom_get_instant_rate(void);
|
|
|
|
/* Strict, config-driven warm-load route accelerator. Up to 16 routes may be
|
|
* registered after cdrom_init. A route arms only after arm_lba and activates
|
|
* only if the following SetLoc values match lbas in order. Any mismatch
|
|
* restores normal disc timing. Only non-XA read cadence is accelerated. */
|
|
void cdrom_register_warm_route(int arm_lba, const int* lbas, int count,
|
|
int instant_max_per_frame);
|
|
void cdrom_warm_route_set_enabled(int enabled);
|
|
void cdrom_warm_route_stats_json(char* out, int cap);
|
|
|
|
/* Passive physical-deadline -> buffer -> INT1 exposure telemetry (L1.5).
|
|
* Diagnostics only: recording never changes CD scheduling or delivery. */
|
|
void cdrom_timing_reset(void);
|
|
void cdrom_timing_stats_json(char* out, int cap);
|
|
int cdrom_get_delivered_lba(void);
|
|
|
|
/* Per-record view of the same ring, for localising a single lost/skipped
|
|
* sector rather than summarising thousands. */
|
|
typedef struct CdTimingPub {
|
|
uint64_t seq;
|
|
uint64_t due_cycle;
|
|
uint64_t buffer_cycle;
|
|
uint64_t irq_arm_cycle;
|
|
uint64_t intc_cycle;
|
|
uint32_t frame;
|
|
int32_t lba;
|
|
uint8_t flags; /* CDT_* bits, mirrored in the JSON as named fields */
|
|
} CdTimingPub;
|
|
uint64_t cdrom_timing_total(void);
|
|
int cdrom_timing_record(uint64_t seq, CdTimingPub* out);
|
|
/* Notify the guest that the mounted disc was reinserted. The controller stops
|
|
* active transfers, reports an open shell, waits two emulated seconds, then
|
|
* makes the mounted media readable. This call does not mount a different image. */
|
|
void debug_force_cd_reinsert(void);
|
|
|
|
/* Multi-disc support. The roster is the image set this build was made from
|
|
* (game.toml [game] discs), registered once at launch in roster order.
|
|
*
|
|
* cdrom_disc_select() mounts a different image while the game runs, through
|
|
* the same tray-open event as a reinsert, so a title that asks for its other
|
|
* disc can be answered. Returns 0 if the index is outside the roster or the
|
|
* image will not open. Indexes are 1-based, matching [disc] selected. */
|
|
void cdrom_disc_roster_set(const char *const *paths, int count,
|
|
int selected_1based);
|
|
int cdrom_disc_roster_count(void);
|
|
int cdrom_disc_selected(void);
|
|
const char *cdrom_disc_roster_path(int index_1based);
|
|
int cdrom_disc_select(int index_1based);
|
|
|
|
/* TOC of the image currently mounted; 0 when the drive is empty. Two images
|
|
* of a set have different TOCs, so this is how a caller confirms that a disc
|
|
* change reached the reader instead of only moving an index. */
|
|
uint32_t cdrom_mounted_sector_count(void);
|
|
int cdrom_mounted_track_count(void);
|
|
|
|
/* FMV auto-skip detection: cdrom_xa_stream_active() lets the frontend detect
|
|
* that streaming XA (FMV/CDDA) is in progress. The skip itself is done by the
|
|
* frontend via uncapped pacing (it does NOT alter CD timing — flooding XA
|
|
* sectors desyncs and hangs the player). */
|
|
int cdrom_xa_stream_active(void);
|
|
/* True while a CD read is armed with XA/FMV mode bits (or XA already
|
|
* streaming). Netplay uses this to arm no-invent / refuse tip episodes
|
|
* before the first MDEC colour decode — MotK intro invent≠Start at FMV
|
|
* entry opened a tip episode into a matched black wait. */
|
|
int cdrom_fmv_stream_pending(void);
|
|
/* Re-arm host absolute CD deadlines from restored relative delays after
|
|
* boot_state / RB snap load (read stream + pending/present dues from snap).
|
|
* Does NOT clamp delays (unlike accelerate). */
|
|
void cdrom_resync_deadlines_after_restore(void);
|
|
|
|
/* CD load-burst ring (always-on). One record per gap-separated run of
|
|
* delivered data sectors. `out` receives up to `max` records, newest first
|
|
* (layout matches cdrom.c's CdBurst — consumed by debug_server.c only). */
|
|
typedef struct CdBurstRecord {
|
|
uint32_t start_frame, end_frame;
|
|
uint64_t start_ms, end_ms;
|
|
uint32_t sectors;
|
|
uint32_t rate;
|
|
uint32_t divisor;
|
|
} CdBurstRecord;
|
|
int cdrom_get_bursts(void *out, int max);
|
|
uint32_t cdrom_get_burst_total(void);
|
|
|
|
/* True while a data-sector load is in progress (read stream active or a
|
|
* data sector delivered within the burst-gap window). XA streaming is never
|
|
* a load. Drives turbo-through-loads (step 4). */
|
|
int cdrom_load_in_progress(void);
|
|
/* Physical non-XA data-read command state, without the logical load gap
|
|
* bridge used by cdrom_load_in_progress(). Diagnostics only. */
|
|
int cdrom_data_read_active(void);
|
|
|
|
/* boot_state / netplay digest — full controller FSM (sector FIFOs included). */
|
|
uint32_t cdrom_snapshot_bytes(void);
|
|
void cdrom_snapshot_write(uint8_t *p);
|
|
int cdrom_snapshot_read(const uint8_t *p, uint32_t len);
|
|
|
|
/* After savestate restore: clamp long CD second-response / read-start delays
|
|
* and arm a short boost window so post-load ReadTOC/seek/Init waits do not
|
|
* freeze the picture for ~1s+. Completions still fire (IRQs preserved). */
|
|
void cdrom_accelerate_after_savestate(void);
|
|
/* Call once per host vblank while the boost window is armed. */
|
|
void cdrom_savestate_boost_vblank(void);
|
|
/* Non-zero while boost is armed AND a CD wait is outstanding (pending
|
|
* second response or non-XA read). Lets turbo_loads unpace those waits. */
|
|
int cdrom_savestate_cd_wait_active(void);
|
|
/* Remaining boost vblanks (0 when idle). Diagnostics / post-load probe. */
|
|
int cdrom_savestate_boost_vblanks_remaining(void);
|
|
|
|
/* MMIO read/write (0x1F801800-0x1F801803) */
|
|
uint32_t cdrom_read(uint32_t addr);
|
|
void cdrom_write(uint32_t addr, uint32_t value);
|
|
|
|
/* Advance CD-ROM state machine by guest CPU cycles. Sets i_stat IRQ_CDROM
|
|
* when a command completes or data is ready. */
|
|
void cdrom_advance(uint32_t cycles);
|
|
|
|
/* Cycle-budgeted precise event slicing: guest CPU cycles until a DELIVERABLE
|
|
* CD-ROM IRQ (bit2 unmasked in i_mask). UINT32_MAX if none. */
|
|
uint32_t cdrom_cycles_to_irq(uint32_t i_mask);
|
|
|
|
/* Legacy frame-paced entry point for interpreter/oracle builds. */
|
|
void cdrom_tick(void);
|
|
|
|
/* DMA channel 3 interface */
|
|
uint32_t cdrom_dma_read(void);
|
|
int cdrom_dma_ready(void);
|
|
uint32_t cdrom_dma_sector_word_count(void);
|
|
|
|
typedef struct CDROMDebugState {
|
|
uint64_t seq;
|
|
uint8_t index_reg;
|
|
uint8_t stat_reg;
|
|
uint8_t request_reg;
|
|
uint8_t irq_enable;
|
|
uint8_t irq_flag;
|
|
uint8_t mode_reg;
|
|
uint8_t seek_min;
|
|
uint8_t seek_sec;
|
|
uint8_t seek_sect;
|
|
uint8_t pending_cmd;
|
|
uint8_t queued_cmd;
|
|
uint8_t queued_pending;
|
|
uint8_t queued_param_count;
|
|
int has_disc;
|
|
int param_count;
|
|
int response_read;
|
|
int response_count;
|
|
int sector_read_pos;
|
|
int sector_available;
|
|
int sector_size;
|
|
int reading;
|
|
int read_min;
|
|
int read_sec;
|
|
int read_sect;
|
|
uint8_t read_cmd;
|
|
uint8_t filter_file;
|
|
uint8_t filter_channel;
|
|
uint8_t muted;
|
|
int read_delay;
|
|
/* Cycles until a held CD response is presented to INTC (0 = ready). */
|
|
int irq_present_delay;
|
|
int pending_pending;
|
|
int pending_delay;
|
|
int pending_phase;
|
|
/* Operating disc-speed divisor (1 during BIOS; game.toml post-entry). */
|
|
int speed_divisor;
|
|
uint32_t i_stat;
|
|
int last_sector_lba;
|
|
int last_sector_size;
|
|
uint32_t last_sector_frame;
|
|
uint8_t last_sector_mode;
|
|
uint8_t last_sector_have_raw;
|
|
/* Read-stream hold accounting: guest cycles the disc clock was frozen
|
|
* waiting on guest INT-ack / buffer drain (unfaithful pause; real discs
|
|
* never stop). Must stay 0 since the 2026-07-10 continuous-disc fix;
|
|
* nonzero = regression. */
|
|
uint64_t read_hold_cycles;
|
|
uint64_t read_hold_events;
|
|
/* One-deep pended data-ready INT1 accounting (Beetle SetAIP analog). */
|
|
uint64_t int1_pended;
|
|
uint64_t int1_lost;
|
|
/* Accelerated-read flow control: holds where a faster-than-hardware
|
|
* sector was deferred rather than allowed to clobber an unconsumed one.
|
|
* Nonzero is healthy (the guest was busy and the enhancement waited);
|
|
* int1_lost rising while the speed divisor != 1 is the regression. */
|
|
/* Sector-ring tripwires: starved = a drain found its slot exhausted
|
|
* while the writer had moved on (must stay 0); dropped = an unread slot
|
|
* was overwritten because the guest fell a full ring behind. */
|
|
uint64_t ring_starved;
|
|
uint64_t ring_dropped;
|
|
uint64_t accel_consumer_waits;
|
|
uint64_t accel_consumer_wait_cycles;
|
|
uint8_t int1_pending_now;
|
|
} CDROMDebugState;
|
|
|
|
typedef struct CDROMSectorDebugState {
|
|
int current_available;
|
|
int current_read_pos;
|
|
int current_size;
|
|
int last_lba;
|
|
int last_size;
|
|
uint32_t last_frame;
|
|
uint8_t last_mode;
|
|
uint8_t last_have_raw;
|
|
} CDROMSectorDebugState;
|
|
|
|
typedef struct CDROMTraceEntry {
|
|
uint64_t seq;
|
|
uint32_t addr;
|
|
uint32_t val;
|
|
uint32_t func;
|
|
uint32_t pc;
|
|
uint32_t frame;
|
|
uint32_t i_stat;
|
|
uint8_t kind;
|
|
uint8_t width;
|
|
uint8_t index_reg;
|
|
uint8_t stat_reg;
|
|
uint8_t irq_enable;
|
|
uint8_t irq_flag;
|
|
uint8_t param_count;
|
|
uint8_t response_read;
|
|
uint8_t response_count;
|
|
uint8_t sector_available;
|
|
uint8_t request_reg;
|
|
uint8_t mode_reg;
|
|
uint8_t pending_cmd;
|
|
uint8_t pending_pending;
|
|
uint8_t reading;
|
|
uint8_t read_cmd;
|
|
int sector_read_pos;
|
|
int sector_size;
|
|
int pending_delay;
|
|
int read_delay;
|
|
} CDROMTraceEntry;
|
|
|
|
#define CDROM_TRACE_CAP (1 << 16)
|
|
#define CDROM_COMMAND_HISTORY_CAP (1 << 13)
|
|
#define CDROM_SECTOR_HISTORY_CAP (1 << 13)
|
|
#define CDROM_SECTOR_HISTORY_BYTES 128
|
|
|
|
typedef struct CDROMCommandHistoryEntry {
|
|
uint64_t seq;
|
|
/* Guest cycle at which this command was issued/executed. Frame numbers
|
|
* cannot separate "the guest had not asked yet" from "the command was
|
|
* queued behind an unacked INT" -- both look like one frame. */
|
|
uint64_t cycle;
|
|
uint32_t frame;
|
|
uint32_t func;
|
|
uint32_t pc;
|
|
uint32_t i_stat;
|
|
uint8_t kind;
|
|
uint8_t cmd;
|
|
uint8_t param_count;
|
|
uint8_t params[16];
|
|
uint8_t stat;
|
|
uint8_t request;
|
|
uint8_t irq_enable;
|
|
uint8_t irq_flag;
|
|
uint8_t mode;
|
|
uint8_t seek_min;
|
|
uint8_t seek_sec;
|
|
uint8_t seek_sect;
|
|
uint8_t read_min;
|
|
uint8_t read_sec;
|
|
uint8_t read_sect;
|
|
uint8_t read_cmd;
|
|
uint8_t reading;
|
|
uint8_t pending_cmd;
|
|
uint8_t pending_pending;
|
|
uint8_t queued_cmd;
|
|
uint8_t queued_pending;
|
|
/* Immediate response FIFO as the guest will read it (e.g. the GetTD MSF,
|
|
* the GetTN track range), so a TOC can be checked from the ring. */
|
|
uint8_t response_count;
|
|
uint8_t response[16];
|
|
} CDROMCommandHistoryEntry;
|
|
|
|
typedef struct CDROMSectorHistoryEntry {
|
|
uint64_t seq;
|
|
int lba;
|
|
int size;
|
|
uint32_t frame;
|
|
uint8_t mode;
|
|
uint8_t have_raw;
|
|
uint8_t raw_mode;
|
|
uint8_t xa_file;
|
|
uint8_t xa_channel;
|
|
uint8_t xa_submode;
|
|
uint8_t xa_coding;
|
|
uint8_t data_delivered;
|
|
uint8_t xa_audio_delivered;
|
|
uint8_t skip_reason;
|
|
uint16_t bytes_len;
|
|
uint8_t bytes[CDROM_SECTOR_HISTORY_BYTES];
|
|
} CDROMSectorHistoryEntry;
|
|
|
|
void cdrom_debug_snapshot(CDROMDebugState* out);
|
|
uint64_t cdrom_debug_get_trace(const CDROMTraceEntry** out_entries);
|
|
void cdrom_debug_clear_trace(void);
|
|
uint64_t cdrom_debug_get_command_history(const CDROMCommandHistoryEntry** out_entries);
|
|
void cdrom_debug_clear_command_history(void);
|
|
uint32_t cdrom_debug_copy_last_sector(uint32_t offset, uint32_t len,
|
|
uint8_t* out,
|
|
CDROMSectorDebugState* state);
|
|
uint64_t cdrom_debug_get_sector_history(const CDROMSectorHistoryEntry** out_entries);
|
|
void cdrom_debug_clear_sector_history(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSXRECOMP_CDROM_H */
|