mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 04:36:00 -10:00
fix(cd): latch HLE stream status-struct base per CD_read
Battle Morph uploads its own GPU worker code to $F03000-$F031A7, then calls JT_CD_SETUP_CDROM_ISR ($3060) with A0=$F03158 while a 917KB streamed CD_read is still active. HLEHandleISRSetup retargeted hle_gpu_data_base on the spot, so JaguarCDHLEStreamTick's per-halfline transfer-status writes ($F03158/+4/+8/+16) shredded the worker's movei instruction streams at $F0315A-$F03170. The worker then halted without writing the $ABCDEF77 done-magic to $1FFFF0, the 68K spun forever at $5340, and the display froze as static garbage. Latch the status-struct base into hleStream at CD_read arm time: StreamTick and HLEStreamFinish write only to the latched base, and an ISR-setup retarget takes effect for the NEXT CD_read. This matches hardware — the status words come from the BIOS's own GPU ISR, whose data area cannot move mid-transfer, and which dies outright when the game reloads GPU RAM with its own code. Validated: Battle Morph wedge probe runs clean 1200 frames in both blitter modes with the worker region intact; full CD boot matrix shows no backward stage moves; make TEST_EXPORTS=1 test passes; c89-lint clean. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
114dca8a62
commit
8c9356b6fe
1 file changed
+44
-14
+44
-14
@@ -140,6 +140,16 @@ static struct
|
||||
uint32_t written; /* bytes delivered so far */
|
||||
uint32_t accFrac; /* 16.16 fractional byte budget accumulator */
|
||||
uint32_t d1; /* sentinel word (for the GPU data area) */
|
||||
uint32_t statusBase; /* transfer-status struct base, LATCHED at arm time.
|
||||
* All in-flight status writes (StreamTick / Finish)
|
||||
* use this, never the live hle_gpu_data_base: a
|
||||
* mid-stream JT_CD_SETUP_CDROM_ISR retarget must
|
||||
* only affect the NEXT CD_read. Battle Morph
|
||||
* uploads its own GPU worker to $F03000-$F031A7,
|
||||
* then calls the $3060 ISR setup with A0=$F03158
|
||||
* while a 917KB streamed read is active — writing
|
||||
* status words there shreds the worker's movei
|
||||
* streams at $F0315A-$F03170 and wedges the game. */
|
||||
uint32_t sigD0, sigD1, sigA0, sigA1; /* raw args for duplicate detection */
|
||||
uint8_t buf[2352];
|
||||
} hleStream;
|
||||
@@ -613,6 +623,7 @@ hle_cd_read_post_scan:
|
||||
hleStream.written = 0;
|
||||
hleStream.accFrac = 0;
|
||||
hleStream.d1 = d1;
|
||||
hleStream.statusBase = hle_gpu_data_base; /* latch for this transfer */
|
||||
hleStream.sigD0 = d0;
|
||||
hleStream.sigD1 = d1;
|
||||
hleStream.sigA0 = a0;
|
||||
@@ -625,18 +636,18 @@ hle_cd_read_post_scan:
|
||||
|
||||
/* Make the transfer-state structure visible to pollers from the first
|
||||
* frame: write pointer at the start, end address, size, sentinel. */
|
||||
if (hle_gpu_data_base != 0)
|
||||
if (hleStream.statusBase != 0)
|
||||
{
|
||||
/* [+0] mirrors the real GPU ISR's pre-decremented write pointer:
|
||||
* CD_read does `subq.l #4,a0` before storing it ($362C/$3666), so
|
||||
* the pointer runs dest-4 .. end-4 across the transfer. */
|
||||
GPUWriteLong(hle_gpu_data_base + 0, destAddr - 4, 0);
|
||||
GPUWriteLong(hle_gpu_data_base + 4, destAddr + byteCount, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 0, destAddr - 4, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 4, destAddr + byteCount, 0);
|
||||
/* [+8] is the ISR error/status long, NOT progress: real CD_read
|
||||
* zeroes it ($366A move.l #0,(a2)+) and boot stubs ABORT the load
|
||||
* when CD_poll hands it back nonzero in A1 (Baldies $4DA2). */
|
||||
GPUWriteLong(hle_gpu_data_base + 8, 0, 0);
|
||||
GPUWriteLong(hle_gpu_data_base + 16, d1, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 8, 0, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 16, d1, 0);
|
||||
}
|
||||
|
||||
HLE_LOG("CD_read: streaming %u bytes to $%06X-$%06X from LBA %u offset %u\n",
|
||||
@@ -654,6 +665,7 @@ hle_cd_read_complete:
|
||||
hleStream.d1 = d1;
|
||||
hleStream.lba = lba;
|
||||
hleStream.bufOff = 0;
|
||||
hleStream.statusBase = hle_gpu_data_base;
|
||||
HLEStreamFinish();
|
||||
(void)bytesWritten;
|
||||
(void)s;
|
||||
@@ -768,8 +780,11 @@ static void HLEStreamFinish(void)
|
||||
/* Write completion state to the GPU data area.
|
||||
* The boot stub reads [$3074] to find this structure, then checks
|
||||
* [+0] (current write pos) against [+4] (end addr) for completion.
|
||||
* The real GPU ISR pre-decrements dest by 4, so [+0] = A0-4. */
|
||||
if (hle_gpu_data_base != 0)
|
||||
* The real GPU ISR pre-decrements dest by 4, so [+0] = A0-4.
|
||||
* Uses the base LATCHED at arm time (statusBase), not the live
|
||||
* hle_gpu_data_base — a mid-stream ISR-setup retarget only applies
|
||||
* to the next CD_read. */
|
||||
if (hleStream.statusBase != 0)
|
||||
{
|
||||
/* Final [+0]: the real GPU ISR drains 32-byte FIFO batches with a
|
||||
* pre-incremented pointer WHILE ptr <= end, so the last batch
|
||||
@@ -782,12 +797,12 @@ static void HLEStreamFinish(void)
|
||||
uint32_t ptr0 = destAddr - 4;
|
||||
uint32_t span = (destAddr + byteCount) - ptr0;
|
||||
uint32_t final = ptr0 + ((span / 32) + 1) * 32;
|
||||
GPUWriteLong(hle_gpu_data_base + 0, final, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 0, final, 0);
|
||||
}
|
||||
GPUWriteLong(hle_gpu_data_base + 4, destAddr + byteCount, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 4, destAddr + byteCount, 0);
|
||||
/* [+8] = error status, 0 on success — see the arm-time comment. */
|
||||
GPUWriteLong(hle_gpu_data_base + 8, 0, 0);
|
||||
GPUWriteLong(hle_gpu_data_base + 16, d1, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 8, 0, 0);
|
||||
GPUWriteLong(hleStream.statusBase + 16, d1, 0);
|
||||
}
|
||||
|
||||
/* Signal completion to BIOS-style polling code via DSP RAM flag.
|
||||
@@ -875,9 +890,10 @@ void JaguarCDHLEStreamTick(void)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Advance the write pointer the game's poll loop watches. */
|
||||
if (hle_gpu_data_base != 0)
|
||||
GPUWriteLong(hle_gpu_data_base + 0,
|
||||
/* Advance the write pointer the game's poll loop watches — at the
|
||||
* base latched when this read was armed (see statusBase). */
|
||||
if (hleStream.statusBase != 0)
|
||||
GPUWriteLong(hleStream.statusBase + 0,
|
||||
hleStream.dest + hleStream.written, 0);
|
||||
}
|
||||
|
||||
@@ -1010,6 +1026,20 @@ static void HLEHandleISRSetup(uint8_t mode)
|
||||
{
|
||||
uint32_t a0 = m68k_get_reg(NULL, M68K_REG_A0);
|
||||
|
||||
/* Retarget applies to the NEXT CD_read only. An in-flight streamed
|
||||
* transfer keeps writing its status struct at the base latched when
|
||||
* the read was armed (hleStream.statusBase) — on real hardware those
|
||||
* writes come from the BIOS's own GPU ISR, whose data area doesn't
|
||||
* move mid-transfer (and which dies outright when the game reloads
|
||||
* GPU RAM with its own code). Battle Morph calls this vector with
|
||||
* A0=$F03158 — inside its freshly-uploaded GPU worker — while a
|
||||
* 917KB streamed CD_read is active; retargeting live status writes
|
||||
* there shreds the worker code and wedges the game. */
|
||||
if (hleStream.active)
|
||||
HLE_LOG("ISR setup mid-stream: new base $%06X deferred to next "
|
||||
"CD_read (active stream keeps $%06X)\n",
|
||||
a0, hleStream.statusBase);
|
||||
|
||||
hle_gpu_data_base = a0;
|
||||
|
||||
/* $3072: ISR mode flag */
|
||||
|
||||
Reference in new issue
Block a user