Skip to content

Commit f88a393

Browse files
committed
fix: real-BIOS CD boot — trampoline, buffer, static reset
Three fixes that take real-BIOS CD boot from 0/9 to 5/9 passing: 1. Boot stub trampoline ($080000): The BIOS always does JSR $080000 after authentication, but most games' boot stubs load at $004000, $006000, or $124000. When the load address differs from $080000, install a JMP trampoline at $080000 pointing to the actual address. 2. Boot stub buffer (256KB -> 600KB): Battle Morph's boot stub is 414KB — exceeds the old 256KB buffer. Matches the HLE path's 600KB. 3. Static flag reset: cdAuthBypassInstalled and cdBootStubInjected are now module-level statics reset in JaguarReset() via JaguarResetCDHooks(), preventing stale state across core reloads. Also adds frozen-OOB diagnostic snapshots to test_cd_bios_boot.c: captures registers, prev-PC bytes, stack, A0/A1 memory at the exact moment the PC first leaves valid memory, before OP/blitter can corrupt the post-mortem evidence. Real-BIOS baseline (600 frames, 9 CUE discs): PASS: BrainDead 13, Dragon's Lair, Highlander, Hover Strike, Space Ace FAIL: Baldies (BIOS init OOB), Battle Morph (BIOS init OOB), Iron Soldier 2 (self-loop $006AC0), Primal Rage (self-loop at CD_read $003616) Made-with: Cursor
1 parent 1271317 commit f88a393

2 files changed

Lines changed: 122 additions & 10 deletions

File tree

src/jaguar.c

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,19 @@ void JaguarDumpPCHistoryStderr(int count)
168168
*
169169
* Installed lazily on the first virtual-pregap read served by cdintf.c so
170170
* the BIOS has finished decrypting and copying its code into RAM. */
171+
static bool cdAuthBypassInstalled = false;
172+
static bool cdBootStubInjected = false;
173+
174+
void JaguarResetCDHooks(void)
175+
{
176+
cdAuthBypassInstalled = false;
177+
cdBootStubInjected = false;
178+
}
179+
171180
void JaguarInstallCDAuthBypass(void)
172181
{
173-
static bool installed = false;
174182
const uint32_t bneAddr = 0x050AA0;
175-
if (installed)
183+
if (cdAuthBypassInstalled)
176184
return;
177185

178186
if (jaguarMainRAM[bneAddr] != 0x66 || jaguarMainRAM[bneAddr + 1] != 0x00
@@ -182,13 +190,13 @@ void JaguarInstallCDAuthBypass(void)
182190
bneAddr,
183191
jaguarMainRAM[bneAddr], jaguarMainRAM[bneAddr + 1],
184192
jaguarMainRAM[bneAddr + 2], jaguarMainRAM[bneAddr + 3]);
185-
installed = true;
193+
cdAuthBypassInstalled = true;
186194
return;
187195
}
188196
jaguarMainRAM[bneAddr] = 0x4E; jaguarMainRAM[bneAddr + 1] = 0x71;
189197
jaguarMainRAM[bneAddr + 2] = 0x4E; jaguarMainRAM[bneAddr + 3] = 0x71;
190198
LOG_INF("[CD-AUTH] Installed BNE.W $0504EC -> 2x NOP at $%06X\n", bneAddr);
191-
installed = true;
199+
cdAuthBypassInstalled = true;
192200
}
193201

194202
void JaguarDumpMemWindow(uint32_t centerPC, uint32_t before, uint32_t after)
@@ -542,19 +550,42 @@ void M68KInstructionHook(void)
542550

543551
if (m68kPC == 0x050176)
544552
{
545-
static bool bootStubInjected = false;
546-
if (!bootStubInjected)
553+
if (!cdBootStubInjected)
547554
{
548-
static uint8_t stub[256 * 1024];
555+
static uint8_t stub[600 * 1024];
549556
uint32_t loadAddr = 0, length = 0;
550-
bootStubInjected = true;
557+
cdBootStubInjected = true;
551558
if (CDIntfExtractBootStub(stub, sizeof(stub), &loadAddr, &length))
552559
{
553560
uint32_t i;
554561
for (i = 0; i < length && (loadAddr + i) < 0x200000; i++)
555562
jaguarMainRAM[loadAddr + i] = stub[i];
556563
LOG_INF("[CD-BOOTSTUB] Injected $%X bytes at $%06X\n",
557564
length, loadAddr);
565+
566+
/* Dump the 68K instruction at the injection hook PC so we can
567+
* see whether it's `JSR $080000` or something else. */
568+
LOG_INF("[CD-BOOTSTUB] Bytes at PC=$050176: %02X %02X %02X %02X %02X %02X %02X %02X\n",
569+
jaguarMainRAM[0x050176], jaguarMainRAM[0x050177],
570+
jaguarMainRAM[0x050178], jaguarMainRAM[0x050179],
571+
jaguarMainRAM[0x05017A], jaguarMainRAM[0x05017B],
572+
jaguarMainRAM[0x05017C], jaguarMainRAM[0x05017D]);
573+
LOG_INF("[CD-BOOTSTUB] JSR target at $050178 = $%02X%02X%02X%02X\n",
574+
jaguarMainRAM[0x050178], jaguarMainRAM[0x050179],
575+
jaguarMainRAM[0x05017A], jaguarMainRAM[0x05017B]);
576+
577+
if (loadAddr != 0x080000)
578+
{
579+
LOG_INF("[CD-BOOTSTUB] Boot stub loads at $%06X, not $080000 — "
580+
"installing trampoline at $080000\n", loadAddr);
581+
/* JMP loadAddr (4EF9 xxxx xxxx) */
582+
jaguarMainRAM[0x080000] = 0x4E;
583+
jaguarMainRAM[0x080001] = 0xF9;
584+
jaguarMainRAM[0x080002] = (loadAddr >> 24) & 0xFF;
585+
jaguarMainRAM[0x080003] = (loadAddr >> 16) & 0xFF;
586+
jaguarMainRAM[0x080004] = (loadAddr >> 8) & 0xFF;
587+
jaguarMainRAM[0x080005] = (loadAddr >> 0) & 0xFF;
588+
}
558589
}
559590
}
560591
}
@@ -1058,8 +1089,8 @@ void JaguarReset(void)
10581089
{
10591090
unsigned i;
10601091

1061-
// Contents of local RAM are quasi-stable; we simulate this by randomizing RAM contents.
1062-
// Skip the region occupied by a RAM-loaded executable (ABS/COFF) so it survives reset.
1092+
JaguarResetCDHooks();
1093+
10631094
JaguarSeedPRNG(12345);
10641095
for(i=8; i<0x200000; i+=4)
10651096
{

test/test_cd_bios_boot.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@ struct cd_disc_result {
111111
bool unique_pc_overflow;
112112
size_t ram_nonzero_bytes;
113113
char load_error[256];
114+
115+
/* Frozen snapshot at the moment PC first leaves the valid execute window.
116+
* Captured ONCE so the post-mortem reflects the actual transition rather
117+
* than the OP/blitter scribble that may keep mutating RAM afterwards. */
118+
bool oob_snapshot_captured;
119+
uint32_t oob_pc;
120+
uint32_t oob_prev_pc;
121+
uint32_t oob_frame;
122+
uint32_t oob_regs[16]; /* D0..D7, A0..A7 (SP shadow) */
123+
uint8_t oob_prev_pc_bytes[32]; /* RAM around prev_pc (the JMP/RTS that fired) */
124+
uint8_t oob_sp_bytes[32]; /* RAM at SP (top of stack — likely RTS source) */
125+
uint32_t oob_sp_addr;
126+
uint8_t oob_a0_bytes[32];
127+
uint8_t oob_a1_bytes[32];
114128
};
115129

116130
static bool cd_load_game(const char *path)
@@ -168,6 +182,7 @@ static void cd_run_one_disc(const char *path, unsigned frames,
168182
uint32_t first_oob_pc = 0;
169183
unsigned first_oob_frame = 0;
170184
size_t oob_count = 0;
185+
uint32_t prev_pc = 0;
171186

172187
for (unsigned f = 0; f < frames; f++) {
173188
p_retro_run();
@@ -181,10 +196,49 @@ static void cd_run_one_disc(const char *path, unsigned frames,
181196
if (!first_oob_pc) {
182197
first_oob_pc = oob;
183198
first_oob_frame = f;
199+
200+
/* Freeze diagnostic state immediately — anything we read
201+
* later might be corrupted by OP/Blitter chasing garbage. */
202+
if (!out->oob_snapshot_captured) {
203+
out->oob_snapshot_captured = true;
204+
out->oob_pc = oob;
205+
out->oob_prev_pc = prev_pc;
206+
out->oob_frame = f;
207+
208+
for (int r = 0; r < 16; r++)
209+
out->oob_regs[r] = C.m68k_get_reg(NULL, r);
210+
211+
if (ram) {
212+
uint32_t a0 = out->oob_regs[8];
213+
uint32_t a1 = out->oob_regs[9];
214+
uint32_t sp = C.m68k_get_reg(NULL, M68K_REG_SP);
215+
out->oob_sp_addr = sp;
216+
217+
uint32_t pbase = (prev_pc >= 8 && prev_pc < 0x200000)
218+
? (prev_pc - 8) : 0;
219+
for (int i = 0; i < 32; i++) {
220+
uint32_t a = pbase + i;
221+
out->oob_prev_pc_bytes[i] = (a < 0x200000) ? ram[a] : 0;
222+
}
223+
for (int i = 0; i < 32; i++) {
224+
uint32_t a = sp + i;
225+
out->oob_sp_bytes[i] = (a < 0x200000) ? ram[a] : 0;
226+
}
227+
for (int i = 0; i < 32; i++) {
228+
uint32_t a = a0 + i;
229+
out->oob_a0_bytes[i] = (a < 0x200000) ? ram[a] : 0;
230+
}
231+
for (int i = 0; i < 32; i++) {
232+
uint32_t a = a1 + i;
233+
out->oob_a1_bytes[i] = (a < 0x200000) ? ram[a] : 0;
234+
}
235+
}
236+
}
184237
}
185238
oob_count++;
186239
out->pc_stayed_in_ram = false;
187240
}
241+
prev_pc = pc;
188242
}
189243
}
190244

@@ -383,6 +437,33 @@ TEST(boot_all_discovered_discs_real_bios)
383437
r->unique_pc_count,
384438
r->unique_pc_overflow ? "+" : "",
385439
r->final_pc);
440+
441+
if (r->oob_snapshot_captured) {
442+
fprintf(stderr,
443+
" [OOB-FROZEN] frame=%u prev_pc=$%06X -> oob_pc=$%08X\n",
444+
r->oob_frame, r->oob_prev_pc, r->oob_pc);
445+
fprintf(stderr,
446+
" [OOB-REGS] D0=$%08X D1=$%08X D2=$%08X D3=$%08X "
447+
"A0=$%08X A1=$%08X A6=$%08X SP=$%08X\n",
448+
r->oob_regs[0], r->oob_regs[1], r->oob_regs[2], r->oob_regs[3],
449+
r->oob_regs[8], r->oob_regs[9], r->oob_regs[14], r->oob_sp_addr);
450+
fprintf(stderr, " [OOB-PREVBYTES $%06X]", r->oob_prev_pc & 0xFFFFFF);
451+
for (int i = 0; i < 32; i++)
452+
fprintf(stderr, " %02X", r->oob_prev_pc_bytes[i]);
453+
fprintf(stderr, "\n");
454+
fprintf(stderr, " [OOB-SPBYTES $%06X]", r->oob_sp_addr & 0xFFFFFF);
455+
for (int i = 0; i < 32; i++)
456+
fprintf(stderr, " %02X", r->oob_sp_bytes[i]);
457+
fprintf(stderr, "\n");
458+
fprintf(stderr, " [OOB-A0BYTES $%06X]", r->oob_regs[8] & 0xFFFFFF);
459+
for (int i = 0; i < 32; i++)
460+
fprintf(stderr, " %02X", r->oob_a0_bytes[i]);
461+
fprintf(stderr, "\n");
462+
fprintf(stderr, " [OOB-A1BYTES $%06X]", r->oob_regs[9] & 0xFFFFFF);
463+
for (int i = 0; i < 32; i++)
464+
fprintf(stderr, " %02X", r->oob_a1_bytes[i]);
465+
fprintf(stderr, "\n");
466+
}
386467
}
387468

388469
fprintf(stderr, " --- discs: %zu pass, %zu fail, %zu focus-skip ---\n",

0 commit comments

Comments
 (0)