mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-08 17:30:20 -10:00
fix(cd): populate TOC track durations -- Primal Rage CD music plays
Root cause of the Primal Rage CD-audio silence: the $2C00 boot-stub TOC left bytes [5..7] (track duration as MSF) zeroed in every entry. The game's music player computes its DSP playback countdown from those bytes ([5]*4500 + [6]*75 + [7] sectors -> $F1B278); with duration 0 the DSP flagged end-of-track ($F1B27C) after one sector and the 68K turned the CD mix off ~13 ms after turning it on. New CDIntfGetTrackDuration() (lengthLBA minus pregap) fills the bytes in both TOC writers (BIOS boot stub + HLE). Also revert Pause/Pause-Release DSA completion to $0400: the game's own CD driver (RAM $BE42) masks the response to its high byte and accepts ONLY $04xx -- the Philips error-status word with code $00 (none) is the generic success ack, not an error. The previous change ($0100 Found) misread the MiSTer response table and made every Pause/Unpause read as failed. Diagnostics added to the [CDDA] layer while chasing this: INT1 C_JERENA edge log (tom.c), DSP mailbox write log + env-gated RAM snapshot (dsp.c, VJ_CDDA_SNAPDIR). Verified: headless BIOS-mode Primal Rage -- mix-ON mailbox cmd sticks, LRXD reads 5 -> 900k+ with live music data, host audio RMS 1000-4500 sustained from music start (was 0). Full make test green; CD boot sweeps: HLE 6/9 identical to pre-change baseline (same 3 pre-existing failures), BIOS 8/9 (baseline 7/9, only documented Battle Morph pc_escape remains). Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
0f22facd50
commit
746cf4a7a5
8 files changed
+153
-8
No files matched your search
@@ -1,4 +1,33 @@
|
||||
# Primal Rage CD-audio silence — diagnosis in progress
|
||||
# Primal Rage CD-audio silence — RESOLVED (2026-07-16)
|
||||
|
||||
**Root cause:** the boot-stub TOC at $2C00 left bytes [5..7] of every
|
||||
track entry (track duration as MSF) zeroed. Primal Rage's music player
|
||||
(RAM $3A2D6, transient overlay) computes its DSP playback countdown from
|
||||
exactly those bytes — `[5]*4500 + [6]*75 + [7]` sectors — and stores it
|
||||
to the synth-DSP's sector counter at $F1B278. With duration 0 the DSP's
|
||||
per-588-sample countdown ($F1B23A) underflowed on the first sector,
|
||||
set the done flag at $F1B27C, and the 68K service loop ($3A3CC) sent
|
||||
mailbox cmd 2 (mix OFF) within ~13 ms of cmd 1 (mix ON). The mix gate
|
||||
(bank-0 r20) was never the problem — the 68K opened it every attempt and
|
||||
immediately closed it because the track "ended".
|
||||
|
||||
**Fix:** `CDIntfGetTrackDuration()` (lengthLBA minus pregap, as MSF) now
|
||||
fills TOC bytes [5..7] in both TOC writers (jagcd_bios.c boot stub,
|
||||
jagcd_hle.c). Also reverted the Pause/Pause-Release DSA response to
|
||||
$0400: the game's own CD driver ($BE42) masks the response high byte and
|
||||
treats only $04xx (error-status, code $00 = none) as success — $0400 is
|
||||
the Philips-protocol completion ack, not an error.
|
||||
|
||||
**Verified:** headless BIOS-mode run — mailbox cmd 1 arrives, no cmd 2
|
||||
follows, LRXD reads go from 5 to 900 000+ with live waveform data, and
|
||||
per-second host audio RMS holds 1 000–4 500 from music start (was 0).
|
||||
Old "68K enable-gate" hypothesis below is obsolete: Primal Rage never
|
||||
sets INT1 C_JERENA at all (all INT1 writes are $100/$101); its CD flow
|
||||
is poll-driven, no 68K CD interrupt needed.
|
||||
|
||||
---
|
||||
|
||||
Historical notes (pre-resolution):
|
||||
|
||||
**State (2026-07-15, late):** the entire playback mechanism is decoded
|
||||
end-to-end; the remaining unknown is the 68K's precondition for enabling
|
||||
|
||||
@@ -1170,6 +1170,43 @@ uint8_t CDIntfGetTrackInfo(uint32_t track, uint32_t offset)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns one byte of the track's playing duration as MSF (offset 0/1/2 =
|
||||
// minutes/seconds/frames). Duration is a sector-count delta, NOT a disc
|
||||
// position, so no 150-frame lead-in offset applies. The pregap (startLBA
|
||||
// to dataLBA) is excluded: playback and the $2C00 TOC both start at
|
||||
// INDEX 01. Games size their CD-audio playback from this: Primal Rage's
|
||||
// music player loads TOC bytes [5..7] (length MSF) into its DSP sector
|
||||
// countdown at $F1B278 -- a zero length there stops the track after one
|
||||
// sector (~13 ms).
|
||||
uint8_t CDIntfGetTrackDuration(uint32_t track, uint32_t offset)
|
||||
{
|
||||
uint32_t pregap, durLBA;
|
||||
uint8_t m, s, f;
|
||||
|
||||
if (!disc.loaded || track < 1 || track > disc.numTracks)
|
||||
return 0;
|
||||
|
||||
pregap = disc.tracks[track - 1].dataLBA
|
||||
? (disc.tracks[track - 1].dataLBA - disc.tracks[track - 1].startLBA)
|
||||
: 0;
|
||||
durLBA = (disc.tracks[track - 1].lengthLBA > pregap)
|
||||
? (disc.tracks[track - 1].lengthLBA - pregap)
|
||||
: 0;
|
||||
MSFFromLBA(durLBA, &m, &s, &f);
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
case 0:
|
||||
return m;
|
||||
case 1:
|
||||
return s;
|
||||
case 2:
|
||||
return f;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the session number (1-based) for a given track
|
||||
uint8_t CDIntfGetTrackSession(uint32_t track)
|
||||
{
|
||||
|
||||
@@ -70,6 +70,7 @@ uint32_t CDIntfGetCurrentDrive(void);
|
||||
const uint8_t * CDIntfGetDriveName(uint32_t driveNum);
|
||||
uint8_t CDIntfGetSessionInfo(uint32_t session, uint32_t offset);
|
||||
uint8_t CDIntfGetTrackInfo(uint32_t track, uint32_t offset);
|
||||
uint8_t CDIntfGetTrackDuration(uint32_t track, uint32_t offset);
|
||||
uint8_t CDIntfGetTrackSession(uint32_t track);
|
||||
|
||||
// Returns true if the given disc-image LBA falls within a session 2 track
|
||||
|
||||
+13
-7
@@ -1336,13 +1336,19 @@ void CDROMWriteWord(uint32_t offset, uint16_t data, uint32_t who/*=UNKNOWN*/)
|
||||
* $70nn echo and the game wedged polling for it. */
|
||||
uint16_t resp;
|
||||
if ((data & 0xFF00) == 0x0400 || (data & 0xFF00) == 0x0500)
|
||||
/* Pause / Pause-Release complete with Found ($01) in the
|
||||
* Philips CDD-family DSA protocol (mister_ground_truth.h
|
||||
* response table has no $04xx/$05xx completion; $04 is the
|
||||
* ERROR response). Returning the old $0400 fallthrough made
|
||||
* Primal Rage read "error" after Unpause and instantly
|
||||
* re-Pause -- CD-audio test silent, match start blocked. */
|
||||
resp = 0x0100;
|
||||
/* Pause / Pause-Release complete with the error-status word
|
||||
* $04nn, nn = error code, $00 = none -- i.e. $0400 is the
|
||||
* generic "done, no error" ack in the Philips CDD-family DSA
|
||||
* protocol, NOT an error indicator. Ground truth: Primal
|
||||
* Rage's own 68K CD driver (RAM $BE42, from the Atari CD
|
||||
* library) masks the response to its high byte and treats
|
||||
* ONLY $04xx as success for Stop/Mute/Pause/Unpause-class
|
||||
* commands, and its multi-word collector ($C20C) uses an
|
||||
* $04xx word as the end-of-response terminator. A previous
|
||||
* change made these complete with Found ($0100) after
|
||||
* misreading mister_ground_truth.h's DSA_RSP_ERROR -- that
|
||||
* made the game flag every Pause/Unpause as failed. */
|
||||
resp = 0x0400;
|
||||
else if ((data & 0xFF00) == 0x1500)
|
||||
resp = 0x1700 | (data & 0xFF); /* Mode Status */
|
||||
else if ((data & 0xFF00) == 0x1800)
|
||||
|
||||
@@ -147,6 +147,14 @@ static bool bios_instruction_hook(uint32_t m68kPC)
|
||||
* 1-based sessions. */
|
||||
jaguarMainRAM[tocAddr + 4] =
|
||||
(uint8_t)((tsess >= 1) ? (tsess - 1) : 0);
|
||||
/* bytes[5..7] = track duration as MSF. Primal Rage's
|
||||
* music player multiplies these into a sector count
|
||||
* for its DSP playback countdown ($F1B278); zeroes
|
||||
* here made every CD-audio track "finish" after one
|
||||
* sector and the game silenced the mix instantly. */
|
||||
jaguarMainRAM[tocAddr + 5] = CDIntfGetTrackDuration(t, 0);
|
||||
jaguarMainRAM[tocAddr + 6] = CDIntfGetTrackDuration(t, 1);
|
||||
jaguarMainRAM[tocAddr + 7] = CDIntfGetTrackDuration(t, 2);
|
||||
|
||||
maxTrack = (uint8_t)t;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,11 @@ static void HLEPopulateTOC(uint32_t addr)
|
||||
jaguarMainRAM[off + 2] = CDIntfGetTrackInfo(t, 1);
|
||||
jaguarMainRAM[off + 3] = CDIntfGetTrackInfo(t, 2);
|
||||
jaguarMainRAM[off + 4] = (uint8_t)((sess >= 1) ? (sess - 1) : 0);
|
||||
/* bytes[5..7] = track duration as MSF -- games size CD-audio
|
||||
* playback from these (Primal Rage DSP countdown at $F1B278). */
|
||||
jaguarMainRAM[off + 5] = CDIntfGetTrackDuration(t, 0);
|
||||
jaguarMainRAM[off + 6] = CDIntfGetTrackDuration(t, 1);
|
||||
jaguarMainRAM[off + 7] = CDIntfGetTrackDuration(t, 2);
|
||||
|
||||
if (sess >= 2 || t >= numTracks - 4)
|
||||
HLE_LOG("TOC: track %2u session=%u(0-based %u) MSF=%02u:%02u:%02u at $%04X\n",
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
#include "dsp.h"
|
||||
#include "dsp_acc40.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dac.h"
|
||||
#include "gpu.h"
|
||||
#include "jaguar.h"
|
||||
#include "jerry.h"
|
||||
#include "log.h"
|
||||
#include "m68000/m68kinterface.h"
|
||||
#include "settings.h"
|
||||
|
||||
@@ -574,6 +576,38 @@ void DSPWriteWord(uint32_t offset, uint16_t data, uint32_t who/*=UNKNOWN*/)
|
||||
|
||||
if ((offset >= DSP_WORK_RAM_BASE) && (offset < DSP_WORK_RAM_BASE+0x2000))
|
||||
{
|
||||
/* CDDA-DIAG: see DSPWriteLong -- Primal Rage synth mailbox.
|
||||
* Rate-capped: other titles may use this RAM range as data. */
|
||||
if (offset >= 0xF1B270 && offset <= 0xF1B277)
|
||||
{
|
||||
static uint32_t mboxWrites = 0;
|
||||
mboxWrites++;
|
||||
if (mboxWrites <= 40 || (mboxWrites % 10000) == 0)
|
||||
LOG_INF("[CDDA] DSP mailbox write.w $%06X = $%04X who=%u 68kpc=$%06X\n",
|
||||
offset, data, who, m68k_get_reg(NULL, M68K_REG_PC));
|
||||
/* One-shot main-RAM snapshot at the mix-ON edge so the transient
|
||||
* music-player overlay around the writer PC can be disassembled.
|
||||
* Enabled only when VJ_CDDA_SNAPDIR is set (diagnostic builds). */
|
||||
if (offset == 0xF1B276 && data == 0x0001)
|
||||
{
|
||||
const char *dir = getenv("VJ_CDDA_SNAPDIR");
|
||||
static int snapped = 0;
|
||||
if (dir && !snapped)
|
||||
{
|
||||
char path[1024];
|
||||
FILE *f;
|
||||
snapped = 1;
|
||||
snprintf(path, sizeof(path), "%s/mixon_mainram.bin", dir);
|
||||
f = fopen(path, "wb");
|
||||
if (f)
|
||||
{
|
||||
fwrite(jaguarMainRAM, 1, 0x200000, f);
|
||||
fclose(f);
|
||||
LOG_INF("[CDDA] snapshot: %s\n", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
offset -= DSP_WORK_RAM_BASE;
|
||||
dsp_ram_8[offset] = data >> 8;
|
||||
dsp_ram_8[offset+1] = data & 0xFF;
|
||||
@@ -613,6 +647,18 @@ void DSPWriteLong(uint32_t offset, uint32_t data, uint32_t who/*=UNKNOWN*/)
|
||||
|
||||
if (offset >= DSP_WORK_RAM_BASE && offset <= DSP_WORK_RAM_BASE + 0x1FFF)
|
||||
{
|
||||
/* CDDA-DIAG: Primal Rage's synth-DSP command mailbox lives at
|
||||
* $F1B274 (cmd 1 = CD mix ON, 2 = OFF, 3 = reset, 4 = exit) --
|
||||
* log external writes so we can see who opens the mix gate.
|
||||
* Rate-capped: other titles may use this RAM range as data. */
|
||||
if (offset >= 0xF1B270 && offset <= 0xF1B277)
|
||||
{
|
||||
static uint32_t mboxWritesL = 0;
|
||||
mboxWritesL++;
|
||||
if (mboxWritesL <= 40 || (mboxWritesL % 10000) == 0)
|
||||
LOG_INF("[CDDA] DSP mailbox write $%06X = $%08X who=%u 68kpc=$%06X\n",
|
||||
offset, data, who, m68k_get_reg(NULL, M68K_REG_PC));
|
||||
}
|
||||
offset -= DSP_WORK_RAM_BASE;
|
||||
SET32(dsp_ram_8, offset, data);
|
||||
//CC only!
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
#include "event.h"
|
||||
#include "gpu.h"
|
||||
#include "jaguar.h"
|
||||
#include "log.h"
|
||||
#include "m68000/m68kinterface.h"
|
||||
#include "op.h"
|
||||
#include "perf_counters.h"
|
||||
@@ -1246,6 +1247,18 @@ void TOMWriteByte(uint32_t offset, uint8_t data, uint32_t who)
|
||||
offset &= 0x3FFF;
|
||||
if (offset == INT1)
|
||||
TOMClearPendingIRQs(data);
|
||||
if (offset == INT1 + 1)
|
||||
{
|
||||
/* CDDA-DIAG: INT1 bit 4 (C_JERENA) is the TOM-side gate for
|
||||
* JERRY -> 68K delivery (BUTCH CD IRQs route through it, see
|
||||
* cdrom.c BUTCHExec). Edges are rare; log unconditionally. */
|
||||
static uint8_t tomPrevInt1Ena = 0;
|
||||
if ((tomPrevInt1Ena ^ data) & 0x10)
|
||||
LOG_INF("[CDDA] INT1 C_JERENA %s (INT1=$%02X who=%u 68kpc=$%06X)\n",
|
||||
(data & 0x10) ? "ON" : "OFF", data, who,
|
||||
m68k_get_reg(NULL, M68K_REG_PC));
|
||||
tomPrevInt1Ena = data;
|
||||
}
|
||||
tomRam8[offset] = data;
|
||||
if (offset == INT1 || offset == (INT1 + 1))
|
||||
TOMAssertEnabledIRQs();
|
||||
|
||||
Reference in new issue
Block a user