diff --git a/runtime/src/cdrom.c b/runtime/src/cdrom.c index ef040e99..d667ba3f 100644 --- a/runtime/src/cdrom.c +++ b/runtime/src/cdrom.c @@ -1636,10 +1636,16 @@ static void start_read_stream(uint8_t cmd) { xa_reset_decode(); spu_cd_audio_reset(); } + /* Setloc names a new destination only until it is consumed. Pause keeps + * read_* at the next sector; a later ReadN/ReadS without Setloc resumes + * there. Reusing seek_* unconditionally restarts XA dialogue at its first + * sector while the game's subtitle clock resumes where it paused. */ + if (setloc_pending) { + read_min = seek_min; + read_sec = seek_sec; + read_sect = seek_sect; + } setloc_pending = 0; - read_min = seek_min; - read_sec = seek_sec; - read_sect = seek_sect; read_cmd = cmd; read_delay = initial_read_delay_cycles(); s_cd_probe_read_start_count++; @@ -3383,6 +3389,10 @@ static int cdrom_snap_emit(PstW *w, int extended) { WB(s_sector_ring[slot].data); WI(s_sector_ring[slot].pos); WI(s_sector_ring[slot].size); } + if (extended >= 2) { + WU(0x33464443u); /* CDF3: unconsumed Setloc, distinct from drive head */ + W8(setloc_pending); + } } #undef W8 @@ -3452,6 +3462,10 @@ static int cdrom_snap_parse(PstR *r, int extended) { RB(s_sector_ring[slot].data); RI(s_sector_ring[slot].pos); RI(s_sector_ring[slot].size); } + if (extended >= 2) { + RU(u); /* validated CDF3 marker */ + R8(setloc_pending); + } } #undef R8 @@ -3476,9 +3490,10 @@ static uint32_t cdrom_snapshot_legacy_bytes(void) { int cdrom_snapshot_validate(const uint8_t *p, uint32_t len) { const uint32_t legacy = cdrom_snapshot_legacy_bytes(); + const uint32_t current = cdrom_snapshot_bytes(); if (!p) return 0; if (len == legacy) return 1; - if (len != cdrom_snapshot_bytes()) return 0; + if (len != current && len != current - 5u) return 0; /* CDF3 or CDF2 */ PstR r; uint32_t magic; int32_t index, pos, size, delay; @@ -3496,26 +3511,33 @@ int cdrom_snapshot_validate(const uint8_t *p, uint32_t len) { size < 0 || size > SECTOR_BUFFER_SIZE || pos < 0 || pos > size) return 0; } + if (len == current) { + uint8_t pending_loc; + if (!pst_r_u32(&r, &magic) || magic != 0x33464443u || + !pst_r_u8(&r, &pending_loc) || pending_loc > 1) return 0; + } return r.p == r.end; } uint32_t cdrom_snapshot_bytes(void) { PstW w; pst_w_init(&w, NULL, 0); - (void)cdrom_snap_emit(&w, 1); + (void)cdrom_snap_emit(&w, 2); return (uint32_t)w.written; } void cdrom_snapshot_write(uint8_t *p) { PstW w; uint32_t n = cdrom_snapshot_bytes(); pst_w_init(&w, p, n); - (void)cdrom_snap_emit(&w, 1); + (void)cdrom_snap_emit(&w, 2); } int cdrom_snapshot_read(const uint8_t *p, uint32_t len) { PstR r; if (!cdrom_snapshot_validate(p, len)) return 0; pst_r_init(&r, p, len); - if (!cdrom_snap_parse(&r, len != cdrom_snapshot_legacy_bytes())) + const int extended = len == cdrom_snapshot_legacy_bytes() ? 0 : + len == cdrom_snapshot_bytes() ? 2 : 1; + if (!cdrom_snap_parse(&r, extended)) return 0; /* Absolute host deadlines are not on the wire — rebuild from restored * relative read_delay (psx_cycle_count is resynced by the load caller). */ diff --git a/runtime/tests/test_cdrom_sector_fifo.c b/runtime/tests/test_cdrom_sector_fifo.c index ec9de719..44e89231 100644 --- a/runtime/tests/test_cdrom_sector_fifo.c +++ b/runtime/tests/test_cdrom_sector_fifo.c @@ -168,7 +168,71 @@ static void invalid_setloc_preserves_target(void) { CHECK(setloc_pending==pending && setloc_seek_far==seek_far); } } +static void pause_read_resumes_position(void) { + const uint8_t reads[] = {0x06, 0x1B}; + for (unsigned n = 0; n < sizeof reads; ++n) { + setup(); + seek_min = 0; seek_sec = 2; seek_sect = 0; /* stale target, LBA 0 */ + sector(); ack(); /* delivered 100; drive will read 101 next */ + exec_command(0x09); ack(); + CHECK(!reading && msf_to_lba(read_min, read_sec, read_sect) == 101); + exec_command(reads[n]); + CHECK(reading && read_cmd == reads[n] && !setloc_pending); + CHECK(msf_to_lba(read_min, read_sec, read_sect) == 101); + ack(); sector(); request(); drain(101, 512); + ack(); + /* Another pause cannot regress to the original target either. */ + exec_command(0x09); ack(); exec_command(reads[n]); + CHECK(msf_to_lba(read_min, read_sec, read_sect) == 102); + ack(); + /* An explicit new Setloc still takes precedence over the drive. */ + exec_command(0x09); ack(); + param_fifo[0] = 0; param_fifo[1] = 4; param_fifo[2] = 0; param_count = 3; + exec_command(0x02); ack(); exec_command(reads[n]); + CHECK(msf_to_lba(read_min, read_sec, read_sect) == 150); + CHECK(!setloc_pending); + ack(); sector(); request(); drain(150, 512); + /* A completed explicit seek retains its own destination too. */ + ack(); exec_command(0x09); ack(); + param_fifo[0] = 0; param_fifo[1] = 5; param_fifo[2] = 0; param_count = 3; + exec_command(0x02); ack(); exec_command(0x15); ack(); + psx_cycle_count += 100000000; process_pending(0); ack(); + exec_command(reads[n]); + CHECK(msf_to_lba(read_min, read_sec, read_sect) == 225); + } +} +static void snapshot_setloc_and_paused_head(void) { + setup(); sector(); ack(); exec_command(0x09); ack(); + param_fifo[0]=0;param_fifo[1]=4;param_fifo[2]=0;param_count=3; + exec_command(0x02);ack(); + const uint32_t size=cdrom_snapshot_bytes(); + uint8_t* wire=malloc(size);CHECK(wire); + cdrom_snapshot_write(wire); + setloc_pending=0;read_sec=9; + CHECK(cdrom_snapshot_read(wire,size) && setloc_pending); + exec_command(0x1B);ack(); + CHECK(msf_to_lba(read_min,read_sec,read_sect)==150); + sector();ack();exec_command(0x09);ack(); + cdrom_snapshot_write(wire); + CHECK(!setloc_pending); + setloc_pending=1;read_sec=9; + CHECK(cdrom_snapshot_read(wire,size) && !setloc_pending); + exec_command(0x1B);ack(); + CHECK(msf_to_lba(read_min,read_sec,read_sect)==151); + wire[size-1]=2;CHECK(!cdrom_snapshot_validate(wire,size)); + /* CDF2 states remain readable and retain the paused drive position. */ + exec_command(0x09);ack(); + PstW writer;pst_w_init(&writer,wire,size); + CHECK(cdrom_snap_emit(&writer,1) && writer.written==size-5); + read_sec=9; + CHECK(cdrom_snapshot_read(wire,size-5)); + exec_command(0x06); + CHECK(msf_to_lba(read_min,read_sec,read_sect)==151); + free(wire); +} int main(void) { + pause_read_resumes_position(); + snapshot_setloc_and_paused_head(); invalid_setloc_preserves_target(); delayed_notification(); dma_continuation();