mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
b2929aa504
Reinstates the runtime-loaded overlay DLL cache (Layer A) and the offline overlay compilation pipeline that were built on the pruned codex/cache-discovery branch (tip 2bbafc2) and never merged. Ported onto current master; rebuilt rather than history-restored. Runtime (off by default, opt in via [runtime] overlay_cache = true): - overlay_loader.c/.h, overlay_api.h: CRC32-keyed DLL cache at cache/<game_id>/<region>_<crc>.dll. On a dirty-RAM dispatch miss the loader LoadLibrary's the matching DLL, wires runtime callbacks via overlay_init(), enumerates func_XXXXXXXX exports, and dispatches them BEFORE the interpreter. - overlay_capture.c/.h: write-once capture set; records overlay DMA bytes and writes overlay_captures.json (execution-verified seeds) at clean exit. - dma.c: capture + cd_dma_log hook at CD DMA completion (async model). - cdrom.c: cdrom_get_setloc_lba() for disc-position correlation. - dirty_ram_interp.c: g_dirty_ram_exec_pc_table (exec-verified PCs), a decodability guard on JAL-into-dirty targets, and the overlay dispatch hooks. - debug_server.c: cd_read_log + overlay_loader_status TCP commands. - config_loader: [runtime] overlay_cache flag (default false). Recompiler (compile_overlays.py / extract_overlays.py drive these): - main_psx.cpp --overlay-exact: analyze only explicit entries + direct JAL targets and set split_mid_function_targets=false, so overlay basic-block targets are NOT split into bogus standalone functions (the mid-function-seed failure mode that produced black-on-load / blue-on-new-game). - function_analysis.cpp: analyze_exact_entries(); code_generator.cpp: gate the mid-function split pre-pass behind split_mid_function_targets (default true, so normal main-EXE codegen is unchanged - Tomba still regens to 2854 funcs). Deltas from the original: save states dropped (never worked); overlay enable is runtime-config-gated (off by default) instead of a build-time define; loader stderr logging converted to the overlay_loader_status TCP path (CLAUDE.md Rule 3). Recovered from _backups/2026-06-06-consolidation/psxrecomp-all-PRE.bundle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1216 lines
37 KiB
C
1216 lines
37 KiB
C
/*
|
|
* cdrom.c — PS1 CD-ROM controller (adapted from psxrecomp-v3)
|
|
*
|
|
* Registers at 0x1F801800-0x1F801803 with index-based register banking.
|
|
* Reference: nocash PSX specs — CDROM Controller section
|
|
*
|
|
* v4 adaptation: removed event_deliver (HLE), removed CPUState dependency,
|
|
* removed hard-coded RAM writes, removed fprintf. IRQ delivery is via
|
|
* i_stat bit 2 (IRQ_CDROM); the recompiled BIOS exception handler
|
|
* processes events natively.
|
|
*/
|
|
|
|
#include "cdrom.h"
|
|
#include "spu.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
/* C wrappers for the C++ ISOReader (defined in iso_reader_c.cpp) */
|
|
extern void* iso_open(const char* path);
|
|
extern int iso_read_sector(void* handle, uint32_t lba, uint8_t* buffer, int size);
|
|
extern int iso_read_raw_sector(void* handle, uint32_t lba, uint8_t* buffer, int size);
|
|
extern void iso_close(void* handle);
|
|
|
|
/* I_STAT owned by memory.c — set bit 2 for CDROM IRQ */
|
|
extern uint32_t i_stat;
|
|
extern uint32_t g_debug_current_func_addr;
|
|
extern uint32_t g_debug_last_store_pc;
|
|
extern uint64_t s_frame_count;
|
|
|
|
/* CD-ROM state */
|
|
static uint8_t index_reg;
|
|
static uint8_t stat_reg;
|
|
static uint8_t request_reg;
|
|
static uint8_t irq_enable;
|
|
static uint8_t irq_flag;
|
|
|
|
/* Parameter FIFO */
|
|
#define PARAM_FIFO_SIZE 16
|
|
static uint8_t param_fifo[PARAM_FIFO_SIZE];
|
|
static int param_count;
|
|
|
|
/* Response FIFO */
|
|
#define RESPONSE_FIFO_SIZE 16
|
|
static uint8_t response_fifo[RESPONSE_FIFO_SIZE];
|
|
static int response_read;
|
|
static int response_count;
|
|
|
|
/* Data buffer. Whole-sector mode transfers 0x924 bytes starting after
|
|
* the 12 sync bytes: header, subheader, and sector payload. */
|
|
#define SECTOR_SIZE 2048
|
|
#define RAW_SECTOR_SIZE 2352
|
|
#define RAW_USER_DATA_OFFSET 24
|
|
#define WHOLE_SECTOR_OFFSET 12
|
|
#define WHOLE_SECTOR_SIZE (RAW_SECTOR_SIZE - WHOLE_SECTOR_OFFSET)
|
|
#define FALLBACK_SECTOR_HEADER_SIZE 12
|
|
#define FALLBACK_WHOLE_SECTOR_SIZE (FALLBACK_SECTOR_HEADER_SIZE + SECTOR_SIZE)
|
|
#define SECTOR_BUFFER_SIZE WHOLE_SECTOR_SIZE
|
|
static uint8_t sector_buffer[SECTOR_BUFFER_SIZE];
|
|
static int sector_read_pos;
|
|
static int sector_available;
|
|
static int sector_size;
|
|
static uint8_t last_sector_buffer[SECTOR_BUFFER_SIZE];
|
|
static int last_sector_lba;
|
|
static int last_sector_size;
|
|
static uint32_t last_sector_frame;
|
|
static uint8_t last_sector_mode;
|
|
static uint8_t last_sector_have_raw;
|
|
static uint8_t last_sector_raw_mode;
|
|
static uint8_t last_sector_xa_file;
|
|
static uint8_t last_sector_xa_channel;
|
|
static uint8_t last_sector_xa_submode;
|
|
static uint8_t last_sector_xa_coding;
|
|
static CDROMSectorHistoryEntry sector_history[CDROM_SECTOR_HISTORY_CAP];
|
|
static uint64_t sector_history_seq;
|
|
|
|
/* Seek target */
|
|
static uint8_t seek_min, seek_sec, seek_sect;
|
|
static int s_setloc_lba = -1; /* LBA captured at SetLoc time */
|
|
|
|
/* Read state */
|
|
static int reading;
|
|
static int read_min, read_sec, read_sect;
|
|
static uint8_t mode_reg;
|
|
static uint8_t read_cmd;
|
|
static int read_delay;
|
|
static uint8_t filter_file;
|
|
static uint8_t filter_channel;
|
|
static uint8_t cd_muted;
|
|
|
|
#define XA_SUBHEADER_OFFSET 16
|
|
#define XA_DATA_OFFSET 24
|
|
#define XA_SOUND_GROUPS 18
|
|
#define XA_NATIVE_FRAMES (XA_SOUND_GROUPS * 8 * 28)
|
|
#define XA_MAX_44100_FRAMES 9408
|
|
|
|
#define XA_SUBMODE_AUDIO 0x04
|
|
#define XA_SUBMODE_REALTIME 0x40
|
|
#define CDROM_SECTOR_MODE2 0x02
|
|
|
|
#define CDROM_SKIP_NONE 0
|
|
#define CDROM_SKIP_XA_AUDIO_REALTIME 1
|
|
#define CDROM_REQUEST_BFRD 0x80u
|
|
|
|
typedef struct CDROMSectorDelivery {
|
|
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;
|
|
} CDROMSectorDelivery;
|
|
|
|
static int32_t xa_hist_l[2];
|
|
static int32_t xa_hist_r[2];
|
|
static uint8_t xa_stream_file;
|
|
static uint8_t xa_stream_channel;
|
|
static uint8_t xa_stream_coding;
|
|
static int xa_stream_active;
|
|
|
|
/* Operating divisor: 1x during BIOS boot, switches to g_game_divisor
|
|
* when the game's entry point first fires (via cdrom_notify_game_started). */
|
|
static int g_disc_speed_divisor = 1;
|
|
/* Configured target speed — applied post-BIOS. */
|
|
static int g_game_divisor = 1;
|
|
|
|
void cdrom_set_speed(int divisor) {
|
|
g_disc_speed_divisor = divisor;
|
|
}
|
|
|
|
/* Store the configured speed for post-BIOS application. Boot stays at 1x. */
|
|
void cdrom_set_game_speed(int divisor) {
|
|
g_game_divisor = divisor;
|
|
}
|
|
|
|
/* Called by fntrace_record on first game-range dispatch. */
|
|
void cdrom_notify_game_started(void) {
|
|
g_disc_speed_divisor = g_game_divisor;
|
|
}
|
|
|
|
int cdrom_get_setloc_lba(void) { return s_setloc_lba; }
|
|
|
|
/* Minimum cycles between CD-ROM IRQs in fast modes. Must be enough for the
|
|
* interrupt handler to save state, check the IRQ flag, process data, and
|
|
* return. Too low → interrupt fires before the previous one is processed →
|
|
* game hangs. 500 cycles ≈ 15µs at 33MHz, still ~900x faster than authentic. */
|
|
#define CDROM_MIN_DELAY 500
|
|
|
|
static int apply_speed(int delay) {
|
|
/* XA streaming (FMV / CDDA background music): preserve authentic timing.
|
|
* FMVs interleave XA audio + MDEC video — speeding up sector delivery
|
|
* would cause both to play faster than the display refresh rate. */
|
|
if (xa_stream_active) return delay;
|
|
if (g_disc_speed_divisor == 0) return CDROM_MIN_DELAY;
|
|
int d = delay / g_disc_speed_divisor;
|
|
return d < CDROM_MIN_DELAY ? CDROM_MIN_DELAY : d;
|
|
}
|
|
|
|
static int sector_delay_cycles(void) {
|
|
/* PS1 CPU is 33.8688 MHz. CD-ROM sectors arrive at 75 Hz in 1x
|
|
* mode, or twice that rate when SetMode bit 7 enables double speed. */
|
|
int base = (mode_reg & 0x80) ? 225792 : 451584;
|
|
return apply_speed(base);
|
|
}
|
|
|
|
/* Pending command */
|
|
typedef struct {
|
|
uint8_t cmd;
|
|
int pending;
|
|
int delay;
|
|
int phase;
|
|
} PendingCmd;
|
|
static PendingCmd pending;
|
|
|
|
/* ISO reader */
|
|
static void* iso_handle = NULL;
|
|
|
|
static CDROMTraceEntry cdrom_trace[CDROM_TRACE_CAP];
|
|
static uint64_t cdrom_trace_seq;
|
|
|
|
static void trace_cdrom(uint8_t kind, uint32_t addr, uint32_t val, uint8_t width) {
|
|
CDROMTraceEntry *e = &cdrom_trace[cdrom_trace_seq % CDROM_TRACE_CAP];
|
|
e->seq = cdrom_trace_seq++;
|
|
e->kind = kind;
|
|
e->addr = addr;
|
|
e->val = val;
|
|
e->width = width;
|
|
e->func = g_debug_current_func_addr;
|
|
e->pc = g_debug_last_store_pc;
|
|
e->frame = (uint32_t)s_frame_count;
|
|
e->i_stat = i_stat;
|
|
e->index_reg = index_reg;
|
|
e->stat_reg = stat_reg;
|
|
e->request_reg = request_reg;
|
|
e->irq_enable = irq_enable;
|
|
e->irq_flag = irq_flag;
|
|
e->mode_reg = mode_reg;
|
|
e->param_count = (uint8_t)param_count;
|
|
e->response_read = (uint8_t)response_read;
|
|
e->response_count = (uint8_t)response_count;
|
|
e->sector_available = (uint8_t)sector_available;
|
|
e->sector_read_pos = sector_read_pos;
|
|
e->sector_size = sector_size;
|
|
e->pending_cmd = pending.cmd;
|
|
e->pending_pending = (uint8_t)pending.pending;
|
|
e->pending_delay = pending.delay;
|
|
e->reading = (uint8_t)reading;
|
|
e->read_cmd = read_cmd;
|
|
e->read_delay = read_delay;
|
|
}
|
|
|
|
static int xa_is_audio_realtime(const CDROMSectorDelivery *d) {
|
|
return d &&
|
|
d->raw_mode == CDROM_SECTOR_MODE2 &&
|
|
((d->xa_submode & (XA_SUBMODE_AUDIO | XA_SUBMODE_REALTIME)) ==
|
|
(XA_SUBMODE_AUDIO | XA_SUBMODE_REALTIME));
|
|
}
|
|
|
|
static CDROMSectorDelivery classify_raw_sector(const uint8_t *raw_data, int have_raw) {
|
|
CDROMSectorDelivery d;
|
|
memset(&d, 0, sizeof(d));
|
|
if (!have_raw || !raw_data) return d;
|
|
|
|
d.raw_mode = raw_data[15];
|
|
if (d.raw_mode == CDROM_SECTOR_MODE2) {
|
|
d.xa_file = raw_data[XA_SUBHEADER_OFFSET + 0];
|
|
d.xa_channel = raw_data[XA_SUBHEADER_OFFSET + 1];
|
|
d.xa_submode = raw_data[XA_SUBHEADER_OFFSET + 2];
|
|
d.xa_coding = raw_data[XA_SUBHEADER_OFFSET + 3];
|
|
}
|
|
return d;
|
|
}
|
|
|
|
static void record_sector_history(int lba, int size, uint8_t mode, int have_raw,
|
|
const uint8_t *bytes,
|
|
const CDROMSectorDelivery *delivery) {
|
|
CDROMSectorHistoryEntry *e =
|
|
§or_history[sector_history_seq % CDROM_SECTOR_HISTORY_CAP];
|
|
e->seq = sector_history_seq++;
|
|
e->lba = lba;
|
|
e->size = size;
|
|
e->frame = (uint32_t)s_frame_count;
|
|
e->mode = mode;
|
|
e->have_raw = (uint8_t)(have_raw ? 1 : 0);
|
|
e->raw_mode = delivery ? delivery->raw_mode : 0;
|
|
e->xa_file = delivery ? delivery->xa_file : 0;
|
|
e->xa_channel = delivery ? delivery->xa_channel : 0;
|
|
e->xa_submode = delivery ? delivery->xa_submode : 0;
|
|
e->xa_coding = delivery ? delivery->xa_coding : 0;
|
|
e->data_delivered = delivery ? delivery->data_delivered : 0;
|
|
e->xa_audio_delivered = delivery ? delivery->xa_audio_delivered : 0;
|
|
e->skip_reason = delivery ? delivery->skip_reason : CDROM_SKIP_NONE;
|
|
e->bytes_len = (uint16_t)((size < CDROM_SECTOR_HISTORY_BYTES)
|
|
? size : CDROM_SECTOR_HISTORY_BYTES);
|
|
if (e->bytes_len && bytes) {
|
|
memcpy(e->bytes, bytes, e->bytes_len);
|
|
}
|
|
if (e->bytes_len < CDROM_SECTOR_HISTORY_BYTES) {
|
|
memset(e->bytes + e->bytes_len, 0,
|
|
CDROM_SECTOR_HISTORY_BYTES - e->bytes_len);
|
|
}
|
|
}
|
|
|
|
static int has_disc(void) {
|
|
return iso_handle != NULL;
|
|
}
|
|
|
|
/* CD status bits */
|
|
#define CDSTAT_ERROR 0x01
|
|
#define CDSTAT_MOTOR 0x02
|
|
#define CDSTAT_SEEKERR 0x04
|
|
#define CDSTAT_IDERROR 0x08
|
|
#define CDSTAT_SHELL 0x10
|
|
#define CDSTAT_READ 0x20
|
|
#define CDSTAT_SEEK 0x40
|
|
#define CDSTAT_PLAY 0x80
|
|
|
|
/* IRQ types */
|
|
#define CDIRQ_DATA_READY 1
|
|
#define CDIRQ_COMPLETE 2
|
|
#define CDIRQ_ACK 3
|
|
#define CDIRQ_DATA_END 4
|
|
#define CDIRQ_ERROR 5
|
|
|
|
static void response_clear(void) {
|
|
response_read = 0;
|
|
response_count = 0;
|
|
}
|
|
|
|
static void response_push(uint8_t val) {
|
|
if (response_count < RESPONSE_FIFO_SIZE) {
|
|
response_fifo[response_count++] = val;
|
|
}
|
|
}
|
|
|
|
static void set_irq(int type) {
|
|
irq_flag = (uint8_t)type;
|
|
trace_cdrom('I', 0, (uint32_t)type, 0);
|
|
}
|
|
|
|
/* Fire CDROM IRQ into the interrupt controller */
|
|
static void fire_cdrom_irq(void) {
|
|
if (irq_flag && (irq_enable & (1 << (irq_flag - 1)))) {
|
|
i_stat |= (1u << 2); /* IRQ_CDROM */
|
|
trace_cdrom('F', 0, irq_flag, 0);
|
|
} else {
|
|
trace_cdrom('f', 0, irq_flag, 0);
|
|
}
|
|
}
|
|
|
|
static int bcd_to_bin(uint8_t bcd) {
|
|
return (bcd >> 4) * 10 + (bcd & 0x0F);
|
|
}
|
|
|
|
static int msf_to_lba(int m, int s, int f) {
|
|
return (m * 60 + s) * 75 + f - 150;
|
|
}
|
|
|
|
static uint8_t bin_to_bcd(int val) {
|
|
return (uint8_t)(((val / 10) << 4) | (val % 10));
|
|
}
|
|
|
|
static void lba_to_msf(int lba, int pregap, int* m, int* s, int* f) {
|
|
int frames = lba + pregap;
|
|
if (frames < 0) frames = 0;
|
|
*m = frames / (60 * 75);
|
|
frames %= 60 * 75;
|
|
*s = frames / 75;
|
|
*f = frames % 75;
|
|
}
|
|
|
|
static int16_t clamp16_cd(int32_t v) {
|
|
if (v > 32767) return 32767;
|
|
if (v < -32768) return -32768;
|
|
return (int16_t)v;
|
|
}
|
|
|
|
static void xa_reset_decode(void) {
|
|
xa_hist_l[0] = xa_hist_l[1] = 0;
|
|
xa_hist_r[0] = xa_hist_r[1] = 0;
|
|
xa_stream_file = 0xFF;
|
|
xa_stream_channel = 0xFF;
|
|
xa_stream_coding = 0xFF;
|
|
xa_stream_active = 0;
|
|
}
|
|
|
|
static int xa_decode_sector_4bit_stereo(const uint8_t* data, int16_t* out) {
|
|
static const int k0[5] = { 0, 60, 115, 98, 122 };
|
|
static const int k1[5] = { 0, 0, -52, -55, -60 };
|
|
int pair = 0;
|
|
|
|
for (int g = 0; g < XA_SOUND_GROUPS; g++) {
|
|
const uint8_t* grp = data + g * 128;
|
|
for (int blk = 0; blk < 4; blk++) {
|
|
uint8_t hdr_l = grp[4 + blk * 2];
|
|
uint8_t hdr_r = grp[4 + blk * 2 + 1];
|
|
int shift_l = 12 - (int)(hdr_l & 0x0F);
|
|
int shift_r = 12 - (int)(hdr_r & 0x0F);
|
|
int filter_l = (hdr_l >> 4) & 0x03;
|
|
int filter_r = (hdr_r >> 4) & 0x03;
|
|
if (shift_l < 0) shift_l = 0;
|
|
if (shift_r < 0) shift_r = 0;
|
|
|
|
for (int i = 0; i < 28; i++) {
|
|
uint8_t b = grp[16 + blk + i * 4];
|
|
int32_t nib_l = b & 0x0F;
|
|
int32_t nib_r = (b >> 4) & 0x0F;
|
|
if (nib_l >= 8) nib_l -= 16;
|
|
if (nib_r >= 8) nib_r -= 16;
|
|
|
|
int32_t sample_l = (nib_l << shift_l)
|
|
+ ((k0[filter_l] * xa_hist_l[0] + k1[filter_l] * xa_hist_l[1] + 32) >> 6);
|
|
int32_t sample_r = (nib_r << shift_r)
|
|
+ ((k0[filter_r] * xa_hist_r[0] + k1[filter_r] * xa_hist_r[1] + 32) >> 6);
|
|
sample_l = clamp16_cd(sample_l);
|
|
sample_r = clamp16_cd(sample_r);
|
|
xa_hist_l[1] = xa_hist_l[0];
|
|
xa_hist_l[0] = sample_l;
|
|
xa_hist_r[1] = xa_hist_r[0];
|
|
xa_hist_r[0] = sample_r;
|
|
|
|
out[pair * 2 + 0] = (int16_t)sample_l;
|
|
out[pair * 2 + 1] = (int16_t)sample_r;
|
|
pair++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pair;
|
|
}
|
|
|
|
static int xa_decode_sector_4bit_mono(const uint8_t* data, int16_t* out) {
|
|
static const int k0[5] = { 0, 60, 115, 98, 122 };
|
|
static const int k1[5] = { 0, 0, -52, -55, -60 };
|
|
int pair = 0;
|
|
|
|
for (int g = 0; g < XA_SOUND_GROUPS; g++) {
|
|
const uint8_t* grp = data + g * 128;
|
|
for (int blk = 0; blk < 4; blk++) {
|
|
for (int nibble = 0; nibble < 2; nibble++) {
|
|
uint8_t hdr = grp[4 + blk * 2 + nibble];
|
|
int shift = 12 - (int)(hdr & 0x0F);
|
|
int filter = (hdr >> 4) & 0x03;
|
|
if (shift < 0) shift = 0;
|
|
|
|
for (int i = 0; i < 28; i++) {
|
|
uint8_t b = grp[16 + blk + i * 4];
|
|
int32_t sample_nibble = nibble ? ((b >> 4) & 0x0F) : (b & 0x0F);
|
|
if (sample_nibble >= 8) sample_nibble -= 16;
|
|
|
|
int32_t sample = (sample_nibble << shift)
|
|
+ ((k0[filter] * xa_hist_l[0] + k1[filter] * xa_hist_l[1] + 32) >> 6);
|
|
sample = clamp16_cd(sample);
|
|
xa_hist_l[1] = xa_hist_l[0];
|
|
xa_hist_l[0] = sample;
|
|
|
|
out[pair * 2 + 0] = (int16_t)sample;
|
|
out[pair * 2 + 1] = (int16_t)sample;
|
|
pair++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
xa_hist_r[0] = xa_hist_l[0];
|
|
xa_hist_r[1] = xa_hist_l[1];
|
|
return pair;
|
|
}
|
|
|
|
static int xa_resample_to_44100(const int16_t* in, int in_frames,
|
|
int sample_rate, int16_t* out, int max_frames) {
|
|
if (!in || !out || in_frames <= 0 || sample_rate <= 0 || max_frames <= 0) return 0;
|
|
int out_frames = 0;
|
|
int in_pos = 0;
|
|
int phase = 0;
|
|
|
|
while (in_pos < in_frames && out_frames < max_frames) {
|
|
int next_pos = (in_pos + 1 < in_frames) ? in_pos + 1 : in_pos;
|
|
int32_t cur_l = in[in_pos * 2 + 0];
|
|
int32_t cur_r = in[in_pos * 2 + 1];
|
|
int32_t next_l = in[next_pos * 2 + 0];
|
|
int32_t next_r = in[next_pos * 2 + 1];
|
|
out[out_frames * 2 + 0] = (int16_t)(cur_l + ((next_l - cur_l) * phase) / 44100);
|
|
out[out_frames * 2 + 1] = (int16_t)(cur_r + ((next_r - cur_r) * phase) / 44100);
|
|
out_frames++;
|
|
|
|
phase += sample_rate;
|
|
while (phase >= 44100) {
|
|
phase -= 44100;
|
|
in_pos++;
|
|
}
|
|
}
|
|
|
|
return out_frames;
|
|
}
|
|
|
|
static int maybe_deliver_xa_audio(const uint8_t* raw_data,
|
|
const CDROMSectorDelivery *delivery) {
|
|
if (!(mode_reg & 0x40u) || !raw_data || !delivery || cd_muted) return 0;
|
|
if (!xa_is_audio_realtime(delivery)) return 0;
|
|
|
|
uint8_t file = delivery->xa_file;
|
|
uint8_t channel = delivery->xa_channel;
|
|
uint8_t coding = delivery->xa_coding;
|
|
|
|
if ((mode_reg & 0x08u) &&
|
|
(file != filter_file || channel != filter_channel)) {
|
|
trace_cdrom('a', 0, ((uint32_t)file << 16) | ((uint32_t)channel << 8) | coding, 0);
|
|
return 0;
|
|
}
|
|
|
|
int stereo = (coding & 0x01u) != 0;
|
|
int rate_code = (coding >> 2) & 0x03;
|
|
int depth_code = (coding >> 4) & 0x03;
|
|
int sample_rate = (rate_code == 0) ? 37800 : ((rate_code == 1) ? 18900 : 0);
|
|
if (depth_code != 0 || sample_rate == 0) {
|
|
trace_cdrom('X', 0, ((uint32_t)file << 16) | ((uint32_t)channel << 8) | coding, 0);
|
|
return 0;
|
|
}
|
|
|
|
if (!xa_stream_active ||
|
|
xa_stream_file != file ||
|
|
xa_stream_channel != channel ||
|
|
xa_stream_coding != coding) {
|
|
xa_reset_decode();
|
|
xa_stream_file = file;
|
|
xa_stream_channel = channel;
|
|
xa_stream_coding = coding;
|
|
xa_stream_active = 1;
|
|
}
|
|
|
|
int16_t native[XA_NATIVE_FRAMES * 2];
|
|
int16_t pcm_44100[XA_MAX_44100_FRAMES * 2];
|
|
int native_frames = stereo
|
|
? xa_decode_sector_4bit_stereo(raw_data + XA_DATA_OFFSET, native)
|
|
: xa_decode_sector_4bit_mono(raw_data + XA_DATA_OFFSET, native);
|
|
int out_frames = xa_resample_to_44100(native, native_frames, sample_rate,
|
|
pcm_44100, XA_MAX_44100_FRAMES);
|
|
spu_cd_audio_push(pcm_44100, out_frames);
|
|
trace_cdrom('A', 0,
|
|
((uint32_t)file << 24) | ((uint32_t)channel << 16) |
|
|
((uint32_t)coding << 8) | ((uint32_t)(out_frames / 32) & 0xFFu),
|
|
0);
|
|
return 1;
|
|
}
|
|
|
|
static int read_sector_at(int min, int sec, int sect) {
|
|
int lba = msf_to_lba(min, sec, sect);
|
|
uint8_t user_data[SECTOR_SIZE];
|
|
uint8_t raw_data[RAW_SECTOR_SIZE];
|
|
int have_raw = 0;
|
|
CDROMSectorDelivery delivery;
|
|
const uint8_t *history_bytes = user_data;
|
|
int history_size = SECTOR_SIZE;
|
|
|
|
if (iso_handle) {
|
|
have_raw = iso_read_raw_sector(iso_handle, lba, raw_data, RAW_SECTOR_SIZE);
|
|
if (have_raw) {
|
|
memcpy(user_data, raw_data + RAW_USER_DATA_OFFSET, SECTOR_SIZE);
|
|
} else if (!iso_read_sector(iso_handle, lba, user_data, SECTOR_SIZE)) {
|
|
memset(user_data, 0, sizeof(user_data));
|
|
}
|
|
} else {
|
|
memset(user_data, 0, sizeof(user_data));
|
|
}
|
|
|
|
delivery = classify_raw_sector(raw_data, have_raw);
|
|
delivery.xa_audio_delivered =
|
|
(uint8_t)maybe_deliver_xa_audio(raw_data, &delivery);
|
|
delivery.data_delivered = 1;
|
|
if (delivery.xa_audio_delivered ||
|
|
((mode_reg & 0x08u) && xa_is_audio_realtime(&delivery))) {
|
|
delivery.data_delivered = 0;
|
|
delivery.skip_reason = CDROM_SKIP_XA_AUDIO_REALTIME;
|
|
}
|
|
|
|
memset(sector_buffer, 0, sizeof(sector_buffer));
|
|
if (delivery.data_delivered && (mode_reg & 0x20)) {
|
|
if (have_raw) {
|
|
memcpy(sector_buffer, raw_data + WHOLE_SECTOR_OFFSET, WHOLE_SECTOR_SIZE);
|
|
sector_size = WHOLE_SECTOR_SIZE;
|
|
history_bytes = sector_buffer;
|
|
history_size = sector_size;
|
|
} else {
|
|
sector_buffer[0] = bin_to_bcd(min);
|
|
sector_buffer[1] = bin_to_bcd(sec);
|
|
sector_buffer[2] = bin_to_bcd(sect);
|
|
sector_buffer[3] = 0x02; /* Mode 2 sector. */
|
|
memcpy(sector_buffer + FALLBACK_SECTOR_HEADER_SIZE, user_data, SECTOR_SIZE);
|
|
sector_size = FALLBACK_WHOLE_SECTOR_SIZE;
|
|
history_bytes = sector_buffer;
|
|
history_size = sector_size;
|
|
}
|
|
} else if (delivery.data_delivered) {
|
|
memcpy(sector_buffer, user_data, SECTOR_SIZE);
|
|
sector_size = SECTOR_SIZE;
|
|
history_bytes = sector_buffer;
|
|
history_size = sector_size;
|
|
} else {
|
|
sector_size = 0;
|
|
}
|
|
|
|
sector_read_pos = 0;
|
|
sector_available = delivery.data_delivered ? 1 : 0;
|
|
if (delivery.data_delivered) {
|
|
memcpy(last_sector_buffer, sector_buffer, (size_t)sector_size);
|
|
} else {
|
|
uint32_t copy_size = history_size < SECTOR_BUFFER_SIZE ? (uint32_t)history_size
|
|
: SECTOR_BUFFER_SIZE;
|
|
memcpy(last_sector_buffer, history_bytes, copy_size);
|
|
if (copy_size < SECTOR_BUFFER_SIZE) {
|
|
memset(last_sector_buffer + copy_size, 0, SECTOR_BUFFER_SIZE - copy_size);
|
|
}
|
|
}
|
|
last_sector_lba = lba;
|
|
last_sector_size = delivery.data_delivered ? sector_size : history_size;
|
|
last_sector_frame = (uint32_t)s_frame_count;
|
|
last_sector_mode = mode_reg;
|
|
last_sector_have_raw = (uint8_t)(have_raw ? 1 : 0);
|
|
last_sector_raw_mode = delivery.raw_mode;
|
|
last_sector_xa_file = delivery.xa_file;
|
|
last_sector_xa_channel = delivery.xa_channel;
|
|
last_sector_xa_submode = delivery.xa_submode;
|
|
last_sector_xa_coding = delivery.xa_coding;
|
|
record_sector_history(lba, history_size, mode_reg, have_raw,
|
|
history_bytes, &delivery);
|
|
trace_cdrom('S', 0, (uint32_t)lba, 0);
|
|
if (!delivery.data_delivered) {
|
|
trace_cdrom('s', 0,
|
|
((uint32_t)delivery.xa_file << 24) |
|
|
((uint32_t)delivery.xa_channel << 16) |
|
|
((uint32_t)delivery.xa_submode << 8) |
|
|
delivery.xa_coding,
|
|
0);
|
|
}
|
|
return delivery.data_delivered ? 1 : 0;
|
|
}
|
|
|
|
static void advance_msf(int* m, int* s, int* f) {
|
|
(*f)++;
|
|
if (*f >= 75) { *f = 0; (*s)++; }
|
|
if (*s >= 60) { *s = 0; (*m)++; }
|
|
}
|
|
|
|
static void clear_sector_buffer(void) {
|
|
sector_read_pos = 0;
|
|
sector_size = 0;
|
|
sector_available = 0;
|
|
request_reg &= (uint8_t)~CDROM_REQUEST_BFRD;
|
|
}
|
|
|
|
static void start_read_stream(uint8_t cmd) {
|
|
clear_sector_buffer();
|
|
if (mode_reg & 0x40u) {
|
|
xa_reset_decode();
|
|
spu_cd_audio_reset();
|
|
}
|
|
read_min = seek_min;
|
|
read_sec = seek_sec;
|
|
read_sect = seek_sect;
|
|
read_cmd = cmd;
|
|
read_delay = sector_delay_cycles();
|
|
reading = 1;
|
|
stat_reg |= CDSTAT_READ;
|
|
}
|
|
|
|
static void stop_read_stream(void) {
|
|
reading = 0;
|
|
read_cmd = 0;
|
|
read_delay = 0;
|
|
}
|
|
|
|
static int deliver_read_sector(void) {
|
|
int delivered = read_sector_at(read_min, read_sec, read_sect);
|
|
advance_msf(&read_min, &read_sec, &read_sect);
|
|
if (!delivered) return 0;
|
|
response_clear();
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_DATA_READY);
|
|
fire_cdrom_irq();
|
|
return 1;
|
|
}
|
|
|
|
static void exec_command(uint8_t cmd) {
|
|
trace_cdrom('C', 0, cmd, 0);
|
|
response_clear();
|
|
|
|
switch (cmd) {
|
|
case 0x01: /* GetStat */
|
|
if (has_disc()) {
|
|
stat_reg |= CDSTAT_MOTOR;
|
|
} else {
|
|
stat_reg |= CDSTAT_SHELL;
|
|
}
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x02: /* SetLoc */
|
|
if (param_count >= 3) {
|
|
seek_min = bcd_to_bin(param_fifo[0]);
|
|
seek_sec = bcd_to_bin(param_fifo[1]);
|
|
seek_sect = bcd_to_bin(param_fifo[2]);
|
|
s_setloc_lba = msf_to_lba(seek_min, seek_sec, seek_sect);
|
|
}
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x06: /* ReadN */
|
|
if (!has_disc()) {
|
|
response_push(stat_reg | CDSTAT_ERROR);
|
|
set_irq(CDIRQ_ERROR);
|
|
break;
|
|
}
|
|
start_read_stream(cmd);
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x09: /* Pause */
|
|
stop_read_stream();
|
|
xa_reset_decode();
|
|
spu_cd_audio_reset();
|
|
stat_reg &= ~CDSTAT_READ;
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
pending.cmd = 0x09;
|
|
pending.pending = 1;
|
|
pending.delay = apply_speed(10000);
|
|
pending.phase = 1;
|
|
break;
|
|
|
|
case 0x0A: /* Init */
|
|
stop_read_stream();
|
|
spu_cd_audio_reset();
|
|
xa_reset_decode();
|
|
stat_reg = has_disc() ? CDSTAT_MOTOR : CDSTAT_SHELL;
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
pending.cmd = 0x0A;
|
|
pending.pending = 1;
|
|
pending.delay = apply_speed(50000);
|
|
pending.phase = 1;
|
|
break;
|
|
|
|
case 0x0B: /* Mute */
|
|
cd_muted = 1;
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x0C: /* Demute */
|
|
cd_muted = 0;
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x0D: /* SetFilter */
|
|
if (param_count >= 2) {
|
|
filter_file = param_fifo[0];
|
|
filter_channel = param_fifo[1];
|
|
xa_reset_decode();
|
|
}
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x0E: /* SetMode */
|
|
if (param_count >= 1) {
|
|
mode_reg = param_fifo[0];
|
|
if (!(mode_reg & 0x40u)) {
|
|
xa_reset_decode();
|
|
spu_cd_audio_reset();
|
|
}
|
|
}
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x0F: /* Getparam */
|
|
response_push(stat_reg);
|
|
response_push(mode_reg);
|
|
response_push(0x00);
|
|
response_push(filter_file);
|
|
response_push(filter_channel);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x10: { /* GetlocL */
|
|
int lba = (last_sector_lba >= 0)
|
|
? last_sector_lba
|
|
: msf_to_lba(read_min, read_sec, read_sect);
|
|
int m, s, f;
|
|
lba_to_msf(lba, 150, &m, &s, &f);
|
|
response_push(bin_to_bcd(m));
|
|
response_push(bin_to_bcd(s));
|
|
response_push(bin_to_bcd(f));
|
|
response_push(last_sector_raw_mode ? last_sector_raw_mode : CDROM_SECTOR_MODE2);
|
|
response_push(last_sector_xa_file);
|
|
response_push(last_sector_xa_channel);
|
|
response_push(last_sector_xa_submode);
|
|
response_push(last_sector_xa_coding);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
}
|
|
|
|
case 0x11: { /* GetlocP */
|
|
int lba = (last_sector_lba >= 0)
|
|
? last_sector_lba
|
|
: msf_to_lba(read_min, read_sec, read_sect);
|
|
int rm, rs, rf;
|
|
int am, as, af;
|
|
lba_to_msf(lba, 0, &rm, &rs, &rf);
|
|
lba_to_msf(lba, 150, &am, &as, &af);
|
|
response_push(0x01); /* single data track */
|
|
response_push(0x01);
|
|
response_push(bin_to_bcd(rm));
|
|
response_push(bin_to_bcd(rs));
|
|
response_push(bin_to_bcd(rf));
|
|
response_push(bin_to_bcd(am));
|
|
response_push(bin_to_bcd(as));
|
|
response_push(bin_to_bcd(af));
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
}
|
|
|
|
case 0x13: /* GetTN */
|
|
response_push(stat_reg);
|
|
response_push(0x01);
|
|
response_push(0x01);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x15: /* SeekL */
|
|
if (!has_disc()) {
|
|
response_push(stat_reg | CDSTAT_ERROR | CDSTAT_SEEKERR);
|
|
set_irq(CDIRQ_ERROR);
|
|
break;
|
|
}
|
|
xa_reset_decode();
|
|
spu_cd_audio_reset();
|
|
stat_reg |= CDSTAT_SEEK;
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
pending.cmd = 0x15;
|
|
pending.pending = 1;
|
|
pending.delay = apply_speed(20000);
|
|
pending.phase = 1;
|
|
break;
|
|
|
|
case 0x1A: /* GetID */
|
|
/* GetID always sends INT3 (ACK) first, then a pending
|
|
* second response: INT2 (COMPLETE) with disc ID if present,
|
|
* or INT5 (ERROR) if no disc / lid open. */
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
pending.cmd = 0x1A;
|
|
pending.pending = 1;
|
|
pending.delay = apply_speed(30000);
|
|
pending.phase = 1;
|
|
break;
|
|
|
|
case 0x1B: /* ReadS */
|
|
if (!has_disc()) {
|
|
response_push(stat_reg | CDSTAT_ERROR);
|
|
set_irq(CDIRQ_ERROR);
|
|
break;
|
|
}
|
|
start_read_stream(cmd);
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
break;
|
|
|
|
case 0x1E: /* ReadTOC */
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
pending.cmd = 0x1E;
|
|
pending.pending = 1;
|
|
pending.delay = apply_speed(100000);
|
|
pending.phase = 1;
|
|
break;
|
|
|
|
case 0x19: /* Test */
|
|
if (param_count >= 1 && param_fifo[0] == 0x20) {
|
|
response_push(0x97);
|
|
response_push(0x01);
|
|
response_push(0x10);
|
|
response_push(0xC2);
|
|
set_irq(CDIRQ_ACK);
|
|
} else {
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_ACK);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
response_push(stat_reg | CDSTAT_ERROR);
|
|
set_irq(CDIRQ_ERROR);
|
|
break;
|
|
}
|
|
|
|
param_count = 0;
|
|
|
|
/* Fire the CDROM IRQ for the immediate response.
|
|
* Delayed responses (pending) fire from process_pending(). */
|
|
fire_cdrom_irq();
|
|
}
|
|
|
|
static void process_pending(uint32_t cycles) {
|
|
if (!pending.pending) return;
|
|
|
|
if (cycles == 0) return;
|
|
pending.delay -= (int)cycles;
|
|
if (pending.delay > 0) return;
|
|
|
|
pending.pending = 0;
|
|
response_clear();
|
|
|
|
switch (pending.cmd) {
|
|
case 0x06: /* ReadN data ready */
|
|
case 0x1B: /* ReadS data ready */
|
|
if (reading) {
|
|
int delivered = read_sector_at(read_min, read_sec, read_sect);
|
|
advance_msf(&read_min, &read_sec, &read_sect);
|
|
if (delivered) {
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_DATA_READY);
|
|
fire_cdrom_irq();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x09: /* Pause complete */
|
|
stat_reg &= ~CDSTAT_READ;
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_COMPLETE);
|
|
fire_cdrom_irq();
|
|
break;
|
|
|
|
case 0x0A: /* Init complete */
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_COMPLETE);
|
|
fire_cdrom_irq();
|
|
break;
|
|
|
|
case 0x15: /* SeekL complete */
|
|
stat_reg &= ~CDSTAT_SEEK;
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_COMPLETE);
|
|
fire_cdrom_irq();
|
|
break;
|
|
|
|
case 0x1A: { /* GetID result */
|
|
if (!has_disc()) {
|
|
response_push(stat_reg | CDSTAT_IDERROR);
|
|
response_push(0x80);
|
|
set_irq(CDIRQ_ERROR);
|
|
} else {
|
|
response_push(stat_reg);
|
|
response_push(0x00);
|
|
response_push(0x20);
|
|
response_push(0x00);
|
|
response_push('S');
|
|
response_push('C');
|
|
response_push('E');
|
|
response_push('I');
|
|
set_irq(CDIRQ_COMPLETE);
|
|
}
|
|
fire_cdrom_irq();
|
|
break;
|
|
}
|
|
|
|
case 0x1E: /* ReadTOC complete */
|
|
response_push(stat_reg);
|
|
set_irq(CDIRQ_COMPLETE);
|
|
fire_cdrom_irq();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void process_read_stream(uint32_t cycles) {
|
|
if (!reading) return;
|
|
|
|
if (cycles > 0) {
|
|
read_delay -= (int)cycles;
|
|
}
|
|
|
|
if (sector_available && irq_flag != 0) {
|
|
return;
|
|
}
|
|
|
|
if (read_delay <= 0) {
|
|
if (sector_available) {
|
|
trace_cdrom('O', 0, (uint32_t)sector_read_pos, 0);
|
|
}
|
|
deliver_read_sector();
|
|
read_delay += sector_delay_cycles();
|
|
}
|
|
}
|
|
|
|
void cdrom_init(const char* cue_path) {
|
|
memset(param_fifo, 0, sizeof(param_fifo));
|
|
memset(response_fifo, 0, sizeof(response_fifo));
|
|
memset(sector_buffer, 0, sizeof(sector_buffer));
|
|
memset(last_sector_buffer, 0, sizeof(last_sector_buffer));
|
|
|
|
index_reg = 0;
|
|
request_reg = 0;
|
|
irq_enable = 0x1F;
|
|
irq_flag = 0;
|
|
param_count = 0;
|
|
response_read = 0;
|
|
response_count = 0;
|
|
sector_read_pos = 0;
|
|
sector_size = 0;
|
|
sector_available = 0;
|
|
last_sector_lba = -1;
|
|
last_sector_size = 0;
|
|
last_sector_frame = 0;
|
|
last_sector_mode = 0;
|
|
last_sector_have_raw = 0;
|
|
last_sector_raw_mode = 0;
|
|
last_sector_xa_file = 0;
|
|
last_sector_xa_channel = 0;
|
|
last_sector_xa_submode = 0;
|
|
last_sector_xa_coding = 0;
|
|
stop_read_stream();
|
|
mode_reg = 0;
|
|
filter_file = 0;
|
|
filter_channel = 0;
|
|
cd_muted = 0;
|
|
xa_reset_decode();
|
|
spu_cd_audio_reset();
|
|
pending.pending = 0;
|
|
seek_min = seek_sec = seek_sect = 0;
|
|
cdrom_debug_clear_sector_history();
|
|
|
|
if (cue_path) {
|
|
iso_handle = iso_open(cue_path);
|
|
}
|
|
|
|
stat_reg = has_disc() ? CDSTAT_MOTOR : CDSTAT_SHELL;
|
|
cdrom_debug_clear_trace();
|
|
trace_cdrom('N', 0, has_disc() ? 1u : 0u, 0);
|
|
}
|
|
|
|
uint32_t cdrom_read(uint32_t addr) {
|
|
uint32_t ret = 0;
|
|
switch (addr) {
|
|
case 0x1F801800: {
|
|
uint8_t s = index_reg & 0x03;
|
|
s |= (1 << 2); /* ADPCM empty */
|
|
if (param_count == 0) s |= (1 << 3);
|
|
if (param_count < PARAM_FIFO_SIZE) s |= (1 << 4);
|
|
if (response_read < response_count) s |= (1 << 5);
|
|
if (request_reg & CDROM_REQUEST_BFRD) s |= (1 << 6);
|
|
ret = s;
|
|
break;
|
|
}
|
|
|
|
case 0x1F801801:
|
|
if (response_read < response_count) {
|
|
ret = response_fifo[response_read++];
|
|
}
|
|
break;
|
|
|
|
case 0x1F801802:
|
|
if ((request_reg & CDROM_REQUEST_BFRD) && sector_available && sector_read_pos < sector_size) {
|
|
ret = sector_buffer[sector_read_pos++];
|
|
if (sector_read_pos >= sector_size) {
|
|
sector_available = 0;
|
|
request_reg &= (uint8_t)~CDROM_REQUEST_BFRD;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x1F801803:
|
|
if (index_reg == 0 || index_reg == 2) {
|
|
ret = irq_enable;
|
|
} else {
|
|
ret = irq_flag | 0xE0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = 0;
|
|
break;
|
|
}
|
|
trace_cdrom('R', addr, ret, 1);
|
|
return ret;
|
|
}
|
|
|
|
void cdrom_write(uint32_t addr, uint32_t value) {
|
|
uint8_t val = (uint8_t)value;
|
|
trace_cdrom('W', addr, val, 1);
|
|
|
|
switch (addr) {
|
|
case 0x1F801800:
|
|
index_reg = val & 0x03;
|
|
break;
|
|
|
|
case 0x1F801801:
|
|
if (index_reg == 0) {
|
|
exec_command(val);
|
|
fire_cdrom_irq();
|
|
}
|
|
break;
|
|
|
|
case 0x1F801802:
|
|
if (index_reg == 0) {
|
|
if (param_count < PARAM_FIFO_SIZE) {
|
|
param_fifo[param_count++] = val;
|
|
}
|
|
} else if (index_reg == 1) {
|
|
irq_enable = val & 0x1F;
|
|
}
|
|
break;
|
|
|
|
case 0x1F801803:
|
|
if (index_reg == 0) {
|
|
request_reg = val;
|
|
if (!(request_reg & CDROM_REQUEST_BFRD)) {
|
|
sector_read_pos = 0;
|
|
}
|
|
} else if (index_reg == 1) {
|
|
irq_flag &= ~(val & 0x1F);
|
|
if (val & 0x40) {
|
|
param_count = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void cdrom_advance(uint32_t cycles) {
|
|
process_pending(cycles);
|
|
process_read_stream(cycles);
|
|
}
|
|
|
|
void cdrom_tick(void) {
|
|
cdrom_advance(33868u);
|
|
}
|
|
|
|
uint32_t cdrom_dma_read(void) {
|
|
uint32_t val = 0;
|
|
if ((request_reg & CDROM_REQUEST_BFRD) && sector_available && sector_read_pos + 4 <= sector_size) {
|
|
memcpy(&val, sector_buffer + sector_read_pos, 4);
|
|
sector_read_pos += 4;
|
|
if (sector_read_pos >= sector_size) {
|
|
sector_available = 0;
|
|
request_reg &= (uint8_t)~CDROM_REQUEST_BFRD;
|
|
}
|
|
}
|
|
trace_cdrom('D', 0, val, 4);
|
|
return val;
|
|
}
|
|
|
|
int cdrom_dma_ready(void) {
|
|
return (request_reg & CDROM_REQUEST_BFRD) &&
|
|
sector_available &&
|
|
(sector_read_pos + 4 <= sector_size);
|
|
}
|
|
|
|
void cdrom_debug_snapshot(CDROMDebugState* out) {
|
|
if (!out) return;
|
|
memset(out, 0, sizeof(*out));
|
|
out->seq = cdrom_trace_seq;
|
|
out->index_reg = index_reg;
|
|
out->stat_reg = stat_reg;
|
|
out->request_reg = request_reg;
|
|
out->irq_enable = irq_enable;
|
|
out->irq_flag = irq_flag;
|
|
out->mode_reg = mode_reg;
|
|
out->seek_min = seek_min;
|
|
out->seek_sec = seek_sec;
|
|
out->seek_sect = seek_sect;
|
|
out->pending_cmd = pending.cmd;
|
|
out->has_disc = has_disc();
|
|
out->param_count = param_count;
|
|
out->response_read = response_read;
|
|
out->response_count = response_count;
|
|
out->sector_read_pos = sector_read_pos;
|
|
out->sector_available = sector_available;
|
|
out->sector_size = sector_size;
|
|
out->reading = reading;
|
|
out->read_min = read_min;
|
|
out->read_sec = read_sec;
|
|
out->read_sect = read_sect;
|
|
out->read_cmd = read_cmd;
|
|
out->filter_file = filter_file;
|
|
out->filter_channel = filter_channel;
|
|
out->muted = cd_muted;
|
|
out->read_delay = read_delay;
|
|
out->pending_pending = pending.pending;
|
|
out->pending_delay = pending.delay;
|
|
out->pending_phase = pending.phase;
|
|
out->i_stat = i_stat;
|
|
out->last_sector_lba = last_sector_lba;
|
|
out->last_sector_size = last_sector_size;
|
|
out->last_sector_frame = last_sector_frame;
|
|
out->last_sector_mode = last_sector_mode;
|
|
out->last_sector_have_raw = last_sector_have_raw;
|
|
}
|
|
|
|
uint64_t cdrom_debug_get_trace(const CDROMTraceEntry** out_entries) {
|
|
if (out_entries) *out_entries = cdrom_trace;
|
|
return cdrom_trace_seq;
|
|
}
|
|
|
|
void cdrom_debug_clear_trace(void) {
|
|
memset(cdrom_trace, 0, sizeof(cdrom_trace));
|
|
cdrom_trace_seq = 0;
|
|
}
|
|
|
|
uint64_t cdrom_debug_get_sector_history(const CDROMSectorHistoryEntry** out_entries) {
|
|
if (out_entries) *out_entries = sector_history;
|
|
return sector_history_seq;
|
|
}
|
|
|
|
void cdrom_debug_clear_sector_history(void) {
|
|
memset(sector_history, 0, sizeof(sector_history));
|
|
sector_history_seq = 0;
|
|
}
|
|
|
|
uint32_t cdrom_debug_copy_last_sector(uint32_t offset, uint32_t len,
|
|
uint8_t* out,
|
|
CDROMSectorDebugState* state) {
|
|
if (state) {
|
|
memset(state, 0, sizeof(*state));
|
|
state->current_available = sector_available;
|
|
state->current_read_pos = sector_read_pos;
|
|
state->current_size = sector_size;
|
|
state->last_lba = last_sector_lba;
|
|
state->last_size = last_sector_size;
|
|
state->last_frame = last_sector_frame;
|
|
state->last_mode = last_sector_mode;
|
|
state->last_have_raw = last_sector_have_raw;
|
|
}
|
|
|
|
if (!out || last_sector_size <= 0 || offset >= (uint32_t)last_sector_size) {
|
|
return 0;
|
|
}
|
|
uint32_t avail = (uint32_t)last_sector_size - offset;
|
|
if (len > avail) len = avail;
|
|
memcpy(out, last_sector_buffer + offset, len);
|
|
return len;
|
|
}
|