mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Preserve CD header and cursor when completing resident data reads
This commit is contained in:
@@ -33,6 +33,16 @@ void cdrom_notify_game_started(void);
|
||||
* ring buffer to correlate each sector transfer with its disc position. */
|
||||
int cdrom_get_setloc_lba(void);
|
||||
|
||||
/* Trusted opt-in resident adapter: fold a verified Mode2/Form1 data span
|
||||
* after its real ReadN ACK, before the first sector has arrived. The commit
|
||||
* callback copies the complete span to RAM and must not advance guest time.
|
||||
* A zero callback result leaves controller state unchanged. Success retains
|
||||
* the actual final header/FIFO and next drive cursor, cancels sector delivery,
|
||||
* and raises no IRQ; the title must finish its audited SDK completion/Pause.
|
||||
* last_user_data must be the verified final2048-byte payload. */
|
||||
int cdrom_resident_complete_read(uint32_t lba, uint32_t sectors,
|
||||
const uint8_t* last_user_data, int (*commit)(void*), void* context);
|
||||
|
||||
/* '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
|
||||
|
||||
@@ -48,6 +48,14 @@ typedef struct PSXResidentFile {
|
||||
* 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);
|
||||
|
||||
typedef struct PSXResidentSink PSXResidentSink;
|
||||
|
||||
/* Store one derived blob (decoded asset, table, ...) for source file `file`.
|
||||
|
||||
@@ -3271,6 +3271,81 @@ int cdrom_data_read_active(void) {
|
||||
return reading && !xa_stream_active;
|
||||
}
|
||||
|
||||
int cdrom_resident_complete_read(uint32_t lba, uint32_t sectors,
|
||||
const uint8_t* last_user_data, int (*commit)(void*), void* context) {
|
||||
uint8_t raw[RAW_SECTOR_SIZE];
|
||||
CDROMSectorDelivery delivery;
|
||||
uint32_t last;
|
||||
int next_m, next_s, next_f;
|
||||
CdSectorBuf* slot;
|
||||
/* Only the already-ACKed, untouched first sector of a normal ReadN can
|
||||
* be replaced. An interrupt, DMA or arriving sector keeps full ownership
|
||||
* on the original path. No defaults or ordinary CD timings change. */
|
||||
if (!commit || !last_user_data || !sectors || !has_disc() ||
|
||||
lba >= iso_sector_count(iso_handle) || lba > 0x7FFFFF00u ||
|
||||
sectors > 0x7FFFFF00u - lba ||
|
||||
sectors > iso_sector_count(iso_handle) - lba ||
|
||||
!reading || read_cmd != 0x06 ||
|
||||
(mode_reg != 0x80u && mode_reg != 0xA0u) ||
|
||||
xa_stream_active || cdda_playing ||
|
||||
s_setloc_lba != (int)lba || setloc_pending ||
|
||||
msf_to_lba(read_min, read_sec, read_sect) != (int)lba ||
|
||||
irq_flag || pending.pending ||
|
||||
queued_cmd.pending || pending_dataready || pending_present_due ||
|
||||
param_count || (response_read < response_count) ||
|
||||
dma_cdrom_transfer_active() || rb_available())
|
||||
return 0;
|
||||
last = lba + sectors - 1u;
|
||||
if (!iso_read_raw_sector(iso_handle, last, raw, sizeof raw) ||
|
||||
raw[15] != CDROM_SECTOR_MODE2 ||
|
||||
memcmp(raw + 16, raw + 20, 4) || !(raw[18] & 0x08u) ||
|
||||
(raw[18] & 0x66u) || /* audio, video, Form2, real-time */
|
||||
memcmp(raw + RAW_USER_DATA_OFFSET, last_user_data, SECTOR_SIZE))
|
||||
return 0;
|
||||
/* RAM delivery validates its entire destination before writing. Neither
|
||||
* it nor overlay-capture bookkeeping executes guest code, so the guarded
|
||||
* controller ownership cannot change before this atomic commit. */
|
||||
if (!commit(context)) return 0;
|
||||
delivery = classify_raw_sector(raw, 1);
|
||||
delivery.data_delivered = 1;
|
||||
stop_read_stream();
|
||||
clear_sector_buffer();
|
||||
s_ring_read = s_ring_write = (int)(sectors % CDROM_NUM_SECTOR_BUFFERS);
|
||||
slot = &s_sector_ring[s_ring_write];
|
||||
if (mode_reg & 0x20u) {
|
||||
memcpy(slot->data, raw + WHOLE_SECTOR_OFFSET, WHOLE_SECTOR_SIZE);
|
||||
slot->size = WHOLE_SECTOR_SIZE;
|
||||
} else {
|
||||
memcpy(slot->data, raw + RAW_USER_DATA_OFFSET, SECTOR_SIZE);
|
||||
slot->size = SECTOR_SIZE;
|
||||
}
|
||||
slot->pos = slot->size;
|
||||
s_data_fifo = *slot;
|
||||
/* PsyQ consumes 12 header/subheader bytes before the 2048 user bytes in
|
||||
* whole-sector mode. Retain the ECC tail and BFRD latch just as that
|
||||
* transfer does; a later explicit BFRD can reload the retained sector. */
|
||||
s_data_fifo.pos = (mode_reg & 0x20u) ? 12 + SECTOR_SIZE : SECTOR_SIZE;
|
||||
request_reg |= CDROM_REQUEST_BFRD;
|
||||
memcpy(last_sector_buffer, slot->data, (size_t)slot->size);
|
||||
last_sector_lba = (int)last;
|
||||
last_sector_size = slot->size;
|
||||
last_sector_frame = (uint32_t)s_frame_count;
|
||||
last_sector_mode = mode_reg;
|
||||
last_sector_have_raw = 1;
|
||||
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;
|
||||
lba_to_msf((int)(lba + sectors), 150, &next_m, &next_s, &next_f);
|
||||
read_min = next_m; read_sec = next_s; read_sect = next_f;
|
||||
setloc_pending = 0;
|
||||
if (subq_replacements_active) update_last_valid_subq(last);
|
||||
record_sector_history((int)last, slot->size, mode_reg, 1, slot->data, &delivery);
|
||||
trace_cdrom('H', lba, sectors, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Savestate post-load: authentic CD second-response delays (ReadTOC ~30M
|
||||
* cycles, Init ~1.1M, far seeks, etc.) leave the restored frame on screen
|
||||
* for up to ~1s+ of wall time. Clamp those timers so the next cdrom_advance
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
* one shared implementation of an effect several titles' adapters need; see
|
||||
* the header for the contract. */
|
||||
#include "mod_plugins.h"
|
||||
#include "mod_resident.h"
|
||||
#include "cpu_state.h"
|
||||
#include "cdrom.h"
|
||||
#include "dma.h"
|
||||
#include "gpu.h"
|
||||
#include "interrupts.h"
|
||||
@@ -15,6 +17,23 @@
|
||||
* guest-mutating service refuses then (docs/NETPLAY.md). */
|
||||
int psx_mod_host_output_scope(void);
|
||||
|
||||
typedef struct ResidentCDCopy {
|
||||
uint32_t address, bytes, lba;
|
||||
const void* data;
|
||||
} ResidentCDCopy;
|
||||
static int resident_cd_copy(void* opaque) {
|
||||
ResidentCDCopy* copy = (ResidentCDCopy*)opaque;
|
||||
return psx_mod_dma_write_ram(copy->address, copy->data, copy->bytes, (int)copy->lba);
|
||||
}
|
||||
int psx_mod_resident_cd_read(uint32_t address, const void* data,
|
||||
uint32_t lba, uint32_t sectors) {
|
||||
if (psx_mod_host_output_scope() || !data || !sectors ||
|
||||
sectors > UINT32_MAX / 2048u || (address & 3u)) return 0;
|
||||
ResidentCDCopy copy = {address, sectors * 2048u, lba, data};
|
||||
return cdrom_resident_complete_read(lba, sectors,
|
||||
(const uint8_t*)data + copy.bytes - 2048u, resident_cd_copy, ©);
|
||||
}
|
||||
|
||||
uint32_t psx_mod_call_guest(struct CPUState* cpu, uint32_t function,
|
||||
uint32_t return_address, uint32_t a0, uint32_t a1,
|
||||
uint32_t a2, uint32_t a3) {
|
||||
|
||||
@@ -5,22 +5,119 @@
|
||||
uint32_t i_stat, g_debug_current_func_addr, g_debug_last_store_pc;
|
||||
uint64_t s_frame_count, psx_cycle_count;
|
||||
static int test_dma_active;
|
||||
static int test_raw_fail, test_copy_result = 1, test_copy_calls;
|
||||
static uint8_t test_submode = 8;
|
||||
int dma_cdrom_transfer_active(void) { return test_dma_active; }
|
||||
void psx_irq_raise(uint32_t bit, uint32_t detail) { (void)detail; i_stat |= bit; }
|
||||
int iso_read_raw_sector(void* h, uint32_t lba, uint8_t* out, int size) {
|
||||
(void)h; memset(out, 0, size); out[15]=2; out[18]=out[22]=8;
|
||||
int m,s,f;
|
||||
(void)h; if (test_raw_fail) return 0;
|
||||
memset(out, 0, size); lba_to_msf((int)lba,150,&m,&s,&f);
|
||||
out[12]=bin_to_bcd(m);out[13]=bin_to_bcd(s);out[14]=bin_to_bcd(f);
|
||||
out[15]=2;out[16]=out[20]=9;out[17]=out[21]=7;
|
||||
out[18]=out[22]=test_submode;
|
||||
memset(out+24, (unsigned char)lba, 2048); return 1;
|
||||
}
|
||||
int iso_read_sector(void* h,uint32_t lba,uint8_t* out,int size) {(void)h;memset(out,(unsigned char)lba,size);return 1;}
|
||||
|
||||
static void setup(void) {
|
||||
test_dma_active = 0;
|
||||
test_raw_fail=0;test_submode=8;test_copy_result=1;test_copy_calls=0;
|
||||
psx_cycle_count = 1000000;
|
||||
cdrom_init("synthetic");
|
||||
setloc_pending = 0;
|
||||
mode_reg = 0x80;
|
||||
reading = 1;
|
||||
read_min = 0; read_sec = 3; read_sect = 25; /* LBA 100 */
|
||||
}
|
||||
static void sector(void);
|
||||
static void ack(void);
|
||||
static void request(void);
|
||||
static void drain(unsigned lba, int words);
|
||||
static int resident_copy(void* context) {
|
||||
CHECK(context == &test_copy_calls);
|
||||
++test_copy_calls;
|
||||
return test_copy_result;
|
||||
}
|
||||
static void resident_setup(uint8_t mode) {
|
||||
setup(); mode_reg=mode; reading=0;
|
||||
seek_min=0; seek_sec=3; seek_sect=25;
|
||||
s_setloc_lba=100; setloc_pending=1;
|
||||
exec_command(6);
|
||||
CHECK(irq_flag==CDIRQ_ACK && response_count==1);
|
||||
(void)cdrom_read(0x1F801801);
|
||||
CHECK(response_read==response_count && response_count!=0);
|
||||
/* ReadN's consumed response and elapsed presentation deadline remain
|
||||
* after the SDK acknowledges it; neither means there is pending work. */
|
||||
psx_cycle_count+=CDROM_IRQ_PRESENT_DELAY;
|
||||
cdrom_advance(0); ack(); i_stat=0;
|
||||
CHECK(cdrom_irq_present_due!=0 && !irq_flag && reading);
|
||||
}
|
||||
static void resident_header_cursor_and_restore(void) {
|
||||
const uint8_t modes[]={0x80,0xA0};
|
||||
for(unsigned n=0;n<sizeof modes;++n) {
|
||||
uint8_t payload[2048]; memset(payload,102,sizeof payload);
|
||||
resident_setup(modes[n]);
|
||||
CHECK(cdrom_resident_complete_read(100,3,payload,resident_copy,&test_copy_calls));
|
||||
CHECK(test_copy_calls==1 && !reading && !irq_flag && !i_stat);
|
||||
CHECK(last_sector_lba==102 && msf_to_lba(read_min,read_sec,read_sect)==103);
|
||||
CHECK(s_setloc_lba==100 && !setloc_pending);
|
||||
CHECK(s_ring_read==s_ring_write && s_ring_write==3);
|
||||
CHECK(s_data_fifo.pos==((modes[n]&0x20)?2060:2048));
|
||||
CHECK(last_sector_xa_file==9 && last_sector_xa_channel==7);
|
||||
const uint32_t size=cdrom_snapshot_bytes();
|
||||
uint8_t* wire=malloc(size);CHECK(wire);
|
||||
cdrom_snapshot_write(wire);
|
||||
setup(); CHECK(cdrom_snapshot_read(wire,size));free(wire);
|
||||
CHECK(last_sector_lba==102 && msf_to_lba(read_min,read_sec,read_sect)==103);
|
||||
CHECK(s_data_fifo.pos==((modes[n]&0x20)?2060:2048));
|
||||
exec_command(0x10);
|
||||
CHECK(response_count==8 && response_fifo[0]==0 && response_fifo[1]==3 &&
|
||||
response_fifo[2]==0x27 && response_fifo[3]==2 &&
|
||||
response_fifo[4]==9 && response_fifo[5]==7 && response_fifo[6]==8);
|
||||
response_clear();ack();
|
||||
exec_command(0x11);
|
||||
CHECK(response_count==8 && response_fifo[5]==0 &&
|
||||
response_fifo[6]==3 && response_fifo[7]==0x28);
|
||||
response_clear();ack();
|
||||
/* Original Pause/ack and a retail read with no new Setloc resume
|
||||
* at the next sector, never the start or last-header sector. */
|
||||
exec_command(9);response_clear();ack();
|
||||
psx_cycle_count+=100000000;process_pending(0);response_clear();ack();
|
||||
mode_reg=0x80;exec_command(6);response_clear();ack();
|
||||
CHECK(msf_to_lba(read_min,read_sec,read_sect)==103);
|
||||
sector();request();drain(103,512);
|
||||
}
|
||||
}
|
||||
static void resident_declines_atomically(void) {
|
||||
for(unsigned reason=0;reason<14;++reason) {
|
||||
uint8_t payload[2048];memset(payload,102,sizeof payload);
|
||||
resident_setup(0xA0);
|
||||
switch(reason) {
|
||||
case 0: reading=0;break;
|
||||
case 1: read_sect++;break; /* A physical sector already arrived. */
|
||||
case 2: irq_flag=3;break;
|
||||
case 3: test_dma_active=1;break;
|
||||
case 4: mode_reg=0xE0;break;
|
||||
case 5: pending.pending=1;break;
|
||||
case 6: queued_cmd.pending=1;break;
|
||||
case 7: test_submode=0x24;break;
|
||||
case 8: test_submode=0x0A;break;
|
||||
case 9: test_raw_fail=1;break;
|
||||
case 10: payload[42]^=1;break;
|
||||
case 11: test_copy_result=0;break;
|
||||
case 12: setloc_pending=1;break;
|
||||
case 13: response_read=0;break; /* Unconsumed command response. */
|
||||
}
|
||||
const uint32_t size=cdrom_snapshot_bytes();
|
||||
uint8_t *before=malloc(size),*after=malloc(size);CHECK(before&&after);
|
||||
cdrom_snapshot_write(before);
|
||||
CHECK(!cdrom_resident_complete_read(100,3,payload,resident_copy,&test_copy_calls));
|
||||
CHECK(test_copy_calls==(reason==11?1:0));
|
||||
cdrom_snapshot_write(after);CHECK(!memcmp(before,after,size));
|
||||
free(before);free(after);
|
||||
}
|
||||
}
|
||||
static void sector(void) {
|
||||
psx_cycle_count += 225792;
|
||||
read_delay = 0;
|
||||
@@ -231,6 +328,8 @@ static void snapshot_setloc_and_paused_head(void) {
|
||||
free(wire);
|
||||
}
|
||||
int main(void) {
|
||||
resident_header_cursor_and_restore();
|
||||
resident_declines_atomically();
|
||||
pause_read_resumes_position();
|
||||
snapshot_setloc_and_paused_head();
|
||||
invalid_setloc_preserves_target();
|
||||
|
||||
Reference in New Issue
Block a user