Skip to content

Commit 2259731

Browse files
JoeMattclaude
andcommitted
Add external CD BIOS loading, fix boot vector setup
The embedded CD BIOS data (jaguarCDBootROM) is scrambled and does not contain valid 68K reset vectors, so CD games cannot boot with it. Changes: - Add load_external_cd_bios() to load a real BIOS dump from the system directory (looks for jaguarcd_bios.bin, jagcd_bios.bin, etc.) - Validate the BIOS by checking that the initial PC points into the BIOS ROM range ($E00000-$E3FFFF) - Move CD BIOS boot vector setup AFTER JaguarReset() since JaguarReset() overwrites RAM[0..7] when jaguarCartInserted is false - Re-pulse the 68K reset after setting vectors so it picks them up - Add test/test_cd_boot.c diagnostic harness for CD boot testing Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 1e777d8 commit 2259731

2 files changed

Lines changed: 316 additions & 19 deletions

File tree

libretro.c

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ int64_t rfread(void* buffer, size_t elem_size, size_t elem_count, RFILE* stream)
2828
#include "settings.h"
2929
#include "tom.h"
3030
#include "state.h"
31+
#include "m68000/m68kinterface.h"
3132

3233
#define SAMPLERATE 48000
3334
#define BUFPAL 1920
@@ -69,6 +70,8 @@ static bool libretro_supports_bitmasks = false;
6970
static bool save_data_needs_unpack = false;
7071
static bool jaguar_cd_mode = false;
7172
static char cd_image_path[4096] = {0};
73+
static bool cd_bios_loaded_externally = false;
74+
static uint8_t external_cd_bios[0x40000]; /* 256 KB */
7275

7376
void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
7477
void retro_set_audio_sample(retro_audio_sample_t cb) { (void)cb; }
@@ -944,6 +947,66 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code)
944947
(void)code;
945948
}
946949

950+
/* Try to load a CD BIOS from the system directory.
951+
* Looks for several common filenames. Returns true if loaded. */
952+
static bool load_external_cd_bios(void)
953+
{
954+
const char *system_dir = NULL;
955+
/* Common filenames for the Jaguar CD BIOS (256 KB) */
956+
static const char *bios_names[] = {
957+
"jaguarcd_bios.bin",
958+
"jagcd_bios.bin",
959+
"jaguarcd.bin",
960+
"jagcd.bin",
961+
NULL
962+
};
963+
964+
if (!environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir) || !system_dir)
965+
return false;
966+
967+
for (int i = 0; bios_names[i]; i++)
968+
{
969+
char path[4096];
970+
RFILE *f;
971+
972+
snprintf(path, sizeof(path), "%s/%s", system_dir, bios_names[i]);
973+
f = rfopen(path, "rb");
974+
if (!f)
975+
continue;
976+
977+
rfseek(f, 0, SEEK_END);
978+
int64_t size = rftell(f);
979+
rfseek(f, 0, SEEK_SET);
980+
981+
if (size != 0x40000) /* Must be exactly 256 KB */
982+
{
983+
rfclose(f);
984+
continue;
985+
}
986+
987+
if (rfread(external_cd_bios, 1, 0x40000, f) != 0x40000)
988+
{
989+
rfclose(f);
990+
continue;
991+
}
992+
rfclose(f);
993+
994+
/* Validate: first 8 bytes should be valid 68K vectors.
995+
* Initial PC should be in the BIOS ROM range $E00000-$E3FFFF. */
996+
{
997+
uint32_t pc = (external_cd_bios[4] << 24) | (external_cd_bios[5] << 16)
998+
| (external_cd_bios[6] << 8) | external_cd_bios[7];
999+
if (pc >= 0xE00000 && pc <= 0xE3FFFF)
1000+
{
1001+
cd_bios_loaded_externally = true;
1002+
return true;
1003+
}
1004+
}
1005+
}
1006+
1007+
return false;
1008+
}
1009+
9471010
bool retro_load_game(const struct retro_game_info *info)
9481011
{
9491012
unsigned i;
@@ -1043,6 +1106,16 @@ bool retro_load_game(const struct retro_game_info *info)
10431106
/* For CD mode, force BIOS on -- CD games require the BIOS */
10441107
vjs.useJaguarBIOS = true;
10451108
vjs.useCDBIOS = true;
1109+
1110+
/* Try to load an external CD BIOS from the system directory.
1111+
* The embedded CD BIOS data is scrambled and non-functional;
1112+
* a real BIOS dump is required for CD games to boot. */
1113+
cd_bios_loaded_externally = false;
1114+
if (!load_external_cd_bios())
1115+
{
1116+
/* No external BIOS found -- CD games won't boot.
1117+
* We still allow loading so users see a diagnostic screen. */
1118+
}
10461119
}
10471120

10481121
/* For CD mode, open the disc image BEFORE JaguarInit() so that
@@ -1070,10 +1143,17 @@ bool retro_load_game(const struct retro_game_info *info)
10701143

10711144
if (jaguar_cd_mode)
10721145
{
1073-
/* Load CD BIOS at $E00000 (256 KB = 0x40000 bytes) */
1074-
uint8_t *cdBios = (vjs.cdBiosType == CDBIOS_DEV)
1075-
? jaguarDevCDBootROM : jaguarCDBootROM;
1076-
memcpy(jagMemSpace + 0xE00000, cdBios, 0x40000);
1146+
/* Load CD BIOS at $E00000 (256 KB = 0x40000 bytes).
1147+
* Prefer the external BIOS file (real dump); fall back to
1148+
* embedded data (which is scrambled and won't boot). */
1149+
if (cd_bios_loaded_externally)
1150+
memcpy(jagMemSpace + 0xE00000, external_cd_bios, 0x40000);
1151+
else
1152+
{
1153+
uint8_t *cdBios = (vjs.cdBiosType == CDBIOS_DEV)
1154+
? jaguarDevCDBootROM : jaguarCDBootROM;
1155+
memcpy(jagMemSpace + 0xE00000, cdBios, 0x40000);
1156+
}
10771157
}
10781158
else
10791159
{
@@ -1092,21 +1172,6 @@ bool retro_load_game(const struct retro_game_info *info)
10921172

10931173
if (jaguar_cd_mode)
10941174
{
1095-
// For CD mode, the BIOS handles boot
1096-
// Set the stack pointer and boot from BIOS
1097-
SET32(jaguarMainRAM, 0, 0x00200000);
1098-
1099-
// The BIOS entry vectors are in the CD BIOS ROM itself
1100-
// Read the reset vector from the BIOS: first long = initial SP, second long = initial PC
1101-
{
1102-
uint8_t *biosBase = jagMemSpace + 0xE00000;
1103-
uint32_t initialSP = GET32(biosBase, 0);
1104-
uint32_t initialPC = GET32(biosBase, 4);
1105-
1106-
SET32(jaguarMainRAM, 0, initialSP);
1107-
SET32(jaguarMainRAM, 4, initialPC);
1108-
}
1109-
11101175
jaguarCartInserted = false;
11111176
}
11121177
else
@@ -1147,6 +1212,17 @@ bool retro_load_game(const struct retro_game_info *info)
11471212

11481213
JaguarReset();
11491214

1215+
if (jaguar_cd_mode)
1216+
{
1217+
/* Set up CD BIOS boot vectors AFTER JaguarReset(), because
1218+
* JaguarReset() overwrites RAM[0..7] with jaguarRunAddress
1219+
* when jaguarCartInserted is false. */
1220+
uint8_t *biosBase = jagMemSpace + 0xE00000;
1221+
SET32(jaguarMainRAM, 0, GET32(biosBase, 0)); /* Initial SP */
1222+
SET32(jaguarMainRAM, 4, GET32(biosBase, 4)); /* Initial PC */
1223+
m68k_pulse_reset(); /* Re-reset 68K to pick up new vectors */
1224+
}
1225+
11501226
/* The frontend will load .srm data into our save buffer (returned by
11511227
* retro_get_memory_data) after this function returns but before the
11521228
* first retro_run(). We unpack it on the first frame. */

test/test_cd_boot.c

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/* test_cd_boot.c -- Minimal test harness for CD boot diagnostics.
2+
* Build: make -j4 && cc -o test/test_cd_boot test/test_cd_boot.c -L. -lvirtualjaguar_libretro -Wl,-rpath,.
3+
* Actually, just link against the dylib directly:
4+
* cc -o test/test_cd_boot test/test_cd_boot.c -ldl
5+
* Or use the simpler approach: include retro API and call it. */
6+
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <string.h>
10+
#include <dlfcn.h>
11+
#include <stdarg.h>
12+
#include <stdbool.h>
13+
#include "../libretro-common/include/libretro.h"
14+
15+
/* Function pointers for the libretro API */
16+
static void (*p_retro_init)(void);
17+
static void (*p_retro_deinit)(void);
18+
static void (*p_retro_set_environment)(retro_environment_t);
19+
static void (*p_retro_set_video_refresh)(retro_video_refresh_t);
20+
static void (*p_retro_set_audio_sample)(retro_audio_sample_t);
21+
static void (*p_retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
22+
static void (*p_retro_set_input_poll)(retro_input_poll_t);
23+
static void (*p_retro_set_input_state)(retro_input_state_t);
24+
static bool (*p_retro_load_game)(const struct retro_game_info *);
25+
static void (*p_retro_unload_game)(void);
26+
static void (*p_retro_run)(void);
27+
static void (*p_retro_get_system_info)(struct retro_system_info *);
28+
static void (*p_retro_get_system_av_info)(struct retro_system_av_info *);
29+
30+
static unsigned frame_count = 0;
31+
static uint32_t last_frame_hash = 0;
32+
static unsigned width_seen = 0, height_seen = 0;
33+
static bool got_video = false;
34+
35+
static void video_refresh(const void *data, unsigned width, unsigned height, size_t pitch)
36+
{
37+
if (!data) return;
38+
got_video = true;
39+
width_seen = width;
40+
height_seen = height;
41+
42+
/* Simple hash of video buffer to detect changes */
43+
const uint32_t *pixels = (const uint32_t *)data;
44+
uint32_t hash = 0;
45+
unsigned total = width * height;
46+
for (unsigned i = 0; i < total; i += 97) /* sample every 97th pixel */
47+
hash = hash * 31 + pixels[i];
48+
49+
if (hash != last_frame_hash)
50+
{
51+
/* Check if frame is all black (or near-black) */
52+
unsigned nonblack = 0;
53+
for (unsigned i = 0; i < total; i += 37)
54+
{
55+
uint32_t p = pixels[i] & 0x00FFFFFF;
56+
if (p > 0x010101)
57+
nonblack++;
58+
}
59+
printf(" Frame %u: %ux%u, hash=0x%08X, nonblack_samples=%u/%u\n",
60+
frame_count, width, height, hash, nonblack, total / 37);
61+
last_frame_hash = hash;
62+
}
63+
}
64+
65+
static void audio_sample(int16_t left, int16_t right) { (void)left; (void)right; }
66+
static size_t audio_sample_batch(const int16_t *data, size_t frames) { (void)data; return frames; }
67+
static void input_poll(void) {}
68+
static int16_t input_state(unsigned port, unsigned device, unsigned index, unsigned id)
69+
{
70+
(void)port; (void)device; (void)index; (void)id;
71+
return 0;
72+
}
73+
74+
static void log_printf(enum retro_log_level level, const char *fmt, ...)
75+
{
76+
va_list ap;
77+
const char *lvl_str[] = {"DEBUG", "INFO", "WARN", "ERROR"};
78+
printf("[%s] ", lvl_str[level < 4 ? level : 3]);
79+
va_start(ap, fmt);
80+
vprintf(fmt, ap);
81+
va_end(ap);
82+
}
83+
84+
static struct retro_log_callback log_cb = { log_printf };
85+
86+
static bool environment(unsigned cmd, void *data)
87+
{
88+
switch (cmd)
89+
{
90+
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
91+
*(struct retro_log_callback *)data = log_cb;
92+
return true;
93+
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
94+
return true;
95+
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
96+
/* Look for BIOS files in test/roms/private or current dir */
97+
*(const char **)data = "test/roms/private";
98+
return true;
99+
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
100+
*(const char **)data = ".";
101+
return true;
102+
case RETRO_ENVIRONMENT_SET_VARIABLES:
103+
case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2:
104+
return true;
105+
case RETRO_ENVIRONMENT_GET_VARIABLE:
106+
{
107+
struct retro_variable *var = (struct retro_variable *)data;
108+
/* Force CD BIOS on */
109+
if (var->key && strcmp(var->key, "virtualjaguar_bios") == 0)
110+
{
111+
var->value = "enabled";
112+
return true;
113+
}
114+
if (var->key && strcmp(var->key, "virtualjaguar_usefastblitter") == 0)
115+
{
116+
var->value = "enabled";
117+
return true;
118+
}
119+
var->value = NULL;
120+
return false;
121+
}
122+
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
123+
*(bool *)data = false;
124+
return true;
125+
default:
126+
return false;
127+
}
128+
}
129+
130+
int main(int argc, char *argv[])
131+
{
132+
if (argc < 2)
133+
{
134+
fprintf(stderr, "Usage: %s <path-to-cue-or-chd> [num_frames]\n", argv[0]);
135+
return 1;
136+
}
137+
138+
const char *image_path = argv[1];
139+
unsigned num_frames = argc > 2 ? atoi(argv[2]) : 300;
140+
141+
/* Load the core */
142+
void *handle = dlopen("./virtualjaguar_libretro.dylib", RTLD_NOW);
143+
if (!handle)
144+
{
145+
fprintf(stderr, "Failed to load core: %s\n", dlerror());
146+
return 1;
147+
}
148+
149+
#define LOAD_SYM(sym) do { \
150+
p_##sym = dlsym(handle, #sym); \
151+
if (!p_##sym) { fprintf(stderr, "Missing symbol: %s\n", #sym); return 1; } \
152+
} while(0)
153+
154+
LOAD_SYM(retro_init);
155+
LOAD_SYM(retro_deinit);
156+
LOAD_SYM(retro_set_environment);
157+
LOAD_SYM(retro_set_video_refresh);
158+
LOAD_SYM(retro_set_audio_sample);
159+
LOAD_SYM(retro_set_audio_sample_batch);
160+
LOAD_SYM(retro_set_input_poll);
161+
LOAD_SYM(retro_set_input_state);
162+
LOAD_SYM(retro_load_game);
163+
LOAD_SYM(retro_unload_game);
164+
LOAD_SYM(retro_run);
165+
LOAD_SYM(retro_get_system_info);
166+
LOAD_SYM(retro_get_system_av_info);
167+
168+
p_retro_set_environment(environment);
169+
p_retro_set_video_refresh(video_refresh);
170+
p_retro_set_audio_sample(audio_sample);
171+
p_retro_set_audio_sample_batch(audio_sample_batch);
172+
p_retro_set_input_poll(input_poll);
173+
p_retro_set_input_state(input_state);
174+
175+
p_retro_init();
176+
177+
struct retro_game_info game = {0};
178+
game.path = image_path;
179+
180+
printf("Loading CD image: %s\n", image_path);
181+
if (!p_retro_load_game(&game))
182+
{
183+
fprintf(stderr, "retro_load_game failed!\n");
184+
p_retro_deinit();
185+
dlclose(handle);
186+
return 1;
187+
}
188+
189+
printf("Game loaded successfully. Running %u frames...\n", num_frames);
190+
191+
/* Check initial RAM state */
192+
/* Access jaguarMainRAM to read vectors */
193+
uint8_t *(*get_ram)(void) = dlsym(handle, "GetRamPtr");
194+
if (get_ram)
195+
{
196+
uint8_t *ram = get_ram();
197+
uint32_t sp = (ram[0]<<24) | (ram[1]<<16) | (ram[2]<<8) | ram[3];
198+
uint32_t pc = (ram[4]<<24) | (ram[5]<<16) | (ram[6]<<8) | ram[7];
199+
printf("Initial vectors: SP=0x%08X, PC=0x%08X\n", sp, pc);
200+
}
201+
202+
for (frame_count = 0; frame_count < num_frames; frame_count++)
203+
{
204+
p_retro_run();
205+
206+
/* Print status at key frames */
207+
if (frame_count == 0 || frame_count == 10 || frame_count == 30 ||
208+
frame_count == 60 || frame_count == 120 || frame_count == 299)
209+
{
210+
if (!got_video)
211+
printf(" Frame %u: no video output\n", frame_count);
212+
}
213+
}
214+
215+
printf("\nDone. Total frames: %u\n", num_frames);
216+
217+
p_retro_unload_game();
218+
p_retro_deinit();
219+
dlclose(handle);
220+
return 0;
221+
}

0 commit comments

Comments
 (0)