Skip to content

Commit dfaf29a

Browse files
claudeJoeMatt
authored andcommitted
Add Jaguar CD support: CUE/BIN disc image loading, BIOS boot, and Butch emulation
Implements the foundation for Jaguar CD game support based on the spike research in docs/spike-jaguar-cd-support.md. This covers Phases 1-4 of the implementation plan. Phase 1 - Disc Image Loading: - Complete CUE/BIN parser in cdintf.c with session/track/MSF parsing - CDIntfReadBlock reads raw 2352-byte sectors from BIN files - CDIntfGetSessionInfo/GetTrackInfo return proper TOC data - CDIntfOpenImage/CloseImage manage disc image lifecycle Phase 2 - CD BIOS Boot: - retro_load_game detects .cue files and enters CD mode - Loads 256KB CD BIOS (retail or developer) at $E00000 - Reads boot vectors from BIOS for proper 68K initialization - Forces BIOS-on mode for CD games (required by hardware) - ROM loading via file path (need_fullpath=true for CD support) Phase 3 - Butch Emulation: - Enables BUTCHExec with FIFO half-full and DSARX interrupt generation - Routes Butch interrupts through JERRY/DSP EXT1 to GPU - FIFO_DATA and I2SDAT2 reads deliver sector data from disc image - Proper BUTCH status register read with interrupt pending flags - $5400 command returns actual session count from disc Phase 4 - CD Audio: - Simplified GetWordFromButchSSI reads audio sectors directly - SetSSIWordsXmittedFromButch delivers L/R samples to DAC - Removed legacy two-sector kludge workaround Also adds: - CD BIOS Type core option (retail vs developer) - Valid extensions updated to include .cue - Proper cleanup of CD resources on unload - All existing cartridge regression tests pass https://claude.ai/code/session_017594R2HVUZmGUxyQp9328w
1 parent dd44259 commit dfaf29a

6 files changed

Lines changed: 861 additions & 180 deletions

File tree

libretro.c

Lines changed: 163 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@
88
#include <compat/posix_string.h>
99
#include <compat/strl.h>
1010

11+
// Forward declarations for file stream functions used in CD loading
12+
RFILE* rfopen(const char *path, const char *mode);
13+
int rfclose(RFILE* stream);
14+
int64_t rfseek(RFILE* stream, int64_t offset, int origin);
15+
int64_t rftell(RFILE* stream);
16+
int64_t rfread(void* buffer, size_t elem_size, size_t elem_count, RFILE* stream);
17+
1118
#include "file.h"
1219
#include "jagbios.h"
1320
#include "jagbios2.h"
21+
#include "jagcdbios.h"
22+
#include "jagdevcdbios.h"
1423
#include "jaguar.h"
24+
#include "cdintf.h"
1525
#include "dac.h"
1626
#include "dsp.h"
1727
#include "joystick.h"
@@ -55,6 +65,8 @@ retro_audio_sample_batch_t audio_batch_cb;
5565

5666
static bool libretro_supports_bitmasks = false;
5767
static bool save_data_needs_unpack = false;
68+
static bool jaguar_cd_mode = false;
69+
static char cd_image_path[4096] = {0};
5870

5971
void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
6072
void retro_set_audio_sample(retro_audio_sample_t cb) { (void)cb; }
@@ -352,6 +364,17 @@ static void check_variables(void)
352364
vjs.hardwareTypeNTSC = true;
353365
}
354366

367+
var.key = "virtualjaguar_cd_bios_type";
368+
var.value = NULL;
369+
370+
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
371+
{
372+
if (strcmp(var.value, "dev") == 0)
373+
vjs.cdBiosType = CDBIOS_DEV;
374+
else
375+
vjs.cdBiosType = CDBIOS_RETAIL;
376+
}
377+
355378
var.key = "virtualjaguar_alt_inputs";
356379
var.value = NULL;
357380
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
@@ -735,6 +758,34 @@ static void update_input(void)
735758
}
736759
}
737760

761+
static bool has_extension(const char *path, const char *ext)
762+
{
763+
const char *dot = strrchr(path, '.');
764+
if (!dot)
765+
return false;
766+
return strcasecmp(dot + 1, ext) == 0;
767+
}
768+
769+
static void extract_basename(char *buf, const char *path, size_t size)
770+
{
771+
char *ext = NULL;
772+
const char *base = strrchr(path, '/');
773+
if (!base)
774+
base = strrchr(path, '\\');
775+
if (!base)
776+
base = path;
777+
778+
if (*base == '\\' || *base == '/')
779+
base++;
780+
781+
strncpy(buf, base, size - 1);
782+
buf[size - 1] = '\0';
783+
784+
ext = strrchr(buf, '.');
785+
if (ext)
786+
*ext = '\0';
787+
}
788+
738789
/************************************
739790
* libretro implementation
740791
************************************/
@@ -749,8 +800,8 @@ void retro_get_system_info(struct retro_system_info *info)
749800
#define GIT_VERSION ""
750801
#endif
751802
info->library_version = "v2.1.0" GIT_VERSION;
752-
info->need_fullpath = false;
753-
info->valid_extensions = "j64|jag";
803+
info->need_fullpath = true;
804+
info->valid_extensions = "j64|jag|cue";
754805
}
755806

756807
void retro_get_system_av_info(struct retro_system_av_info *info)
@@ -955,10 +1006,7 @@ bool retro_load_game(const struct retro_game_info *info)
9551006
}
9561007

9571008
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
958-
{
959-
//fprintf(stderr, "Pixel format XRGB8888 not supported by platform, cannot use.\n");
9601009
return false;
961-
}
9621010

9631011
videoWidth = 320;
9641012
videoHeight = 240;
@@ -972,16 +1020,64 @@ bool retro_load_game(const struct retro_game_info *info)
9721020
// Emulate BIOS
9731021
vjs.hardwareTypeNTSC = true;
9741022
vjs.useJaguarBIOS = false;
1023+
vjs.useCDBIOS = false;
1024+
vjs.cdBiosType = CDBIOS_RETAIL;
9751025

9761026
check_variables();
9771027

9781028
/* Register EEPROM dirty callback so the save buffer stays in sync */
9791029
eeprom_dirty_cb = eeprom_pack_save_buf;
9801030

1031+
/* Detect CD content */
1032+
jaguar_cd_mode = false;
1033+
cd_image_path[0] = '\0';
1034+
1035+
if (info->path && has_extension(info->path, "cue"))
1036+
{
1037+
jaguar_cd_mode = true;
1038+
strncpy(cd_image_path, info->path, sizeof(cd_image_path) - 1);
1039+
cd_image_path[sizeof(cd_image_path) - 1] = '\0';
1040+
1041+
/* For CD mode, force BIOS on -- CD games require the BIOS */
1042+
vjs.useJaguarBIOS = true;
1043+
vjs.useCDBIOS = true;
1044+
}
1045+
9811046
JaguarInit(); // set up hardware
982-
memcpy(jagMemSpace + 0xE00000,
983-
((vjs.biosType == BT_K_SERIES) ? jaguarBootROM : jaguarBootROM2),
984-
0x20000); // Use the stock BIOS
1047+
1048+
if (jaguar_cd_mode)
1049+
{
1050+
// Load CD BIOS at $E00000 (256 KB = 0x40000 bytes)
1051+
// The CD BIOS is larger than the standard 128 KB boot ROM
1052+
uint8_t *cdBios = (vjs.cdBiosType == CDBIOS_DEV)
1053+
? jaguarDevCDBootROM : jaguarCDBootROM;
1054+
memcpy(jagMemSpace + 0xE00000, cdBios, 0x40000);
1055+
1056+
// Open the disc image
1057+
if (!CDIntfOpenImage(cd_image_path))
1058+
{
1059+
// Failed to open disc image
1060+
JaguarDone();
1061+
if (videoBuffer)
1062+
{
1063+
free(videoBuffer);
1064+
videoBuffer = NULL;
1065+
}
1066+
if (sampleBuffer)
1067+
{
1068+
free(sampleBuffer);
1069+
sampleBuffer = NULL;
1070+
}
1071+
return false;
1072+
}
1073+
}
1074+
else
1075+
{
1076+
// Standard cartridge mode
1077+
memcpy(jagMemSpace + 0xE00000,
1078+
((vjs.biosType == BT_K_SERIES) ? jaguarBootROM : jaguarBootROM2),
1079+
0x20000); // Use the stock BIOS (128 KB)
1080+
}
9851081

9861082
JaguarSetScreenPitch(videoWidth);
9871083
JaguarSetScreenBuffer(videoBuffer);
@@ -990,8 +1086,61 @@ bool retro_load_game(const struct retro_game_info *info)
9901086
for (i = 0; i < videoWidth * videoHeight; ++i)
9911087
videoBuffer[i] = 0xFF00FFFF;
9921088

993-
SET32(jaguarMainRAM, 0, 0x00200000);
994-
JaguarLoadFile((uint8_t*)info->data, info->size);
1089+
if (jaguar_cd_mode)
1090+
{
1091+
// For CD mode, the BIOS handles boot
1092+
// Set the stack pointer and boot from BIOS
1093+
SET32(jaguarMainRAM, 0, 0x00200000);
1094+
1095+
// The BIOS entry vectors are in the CD BIOS ROM itself
1096+
// Read the reset vector from the BIOS: first long = initial SP, second long = initial PC
1097+
{
1098+
uint8_t *biosBase = jagMemSpace + 0xE00000;
1099+
uint32_t initialSP = GET32(biosBase, 0);
1100+
uint32_t initialPC = GET32(biosBase, 4);
1101+
1102+
SET32(jaguarMainRAM, 0, initialSP);
1103+
SET32(jaguarMainRAM, 4, initialPC);
1104+
}
1105+
1106+
jaguarCartInserted = false;
1107+
}
1108+
else
1109+
{
1110+
// Standard cartridge loading (need_fullpath=true, so load from file)
1111+
SET32(jaguarMainRAM, 0, 0x00200000);
1112+
1113+
if (info->data && info->size > 0)
1114+
{
1115+
// Data provided directly
1116+
JaguarLoadFile((uint8_t*)info->data, info->size);
1117+
}
1118+
else if (info->path)
1119+
{
1120+
// Load ROM from file path
1121+
RFILE *romFile;
1122+
romFile = rfopen(info->path, "rb");
1123+
if (romFile)
1124+
{
1125+
uint8_t *romData;
1126+
int64_t fileSize;
1127+
1128+
rfseek(romFile, 0, SEEK_END);
1129+
fileSize = rftell(romFile);
1130+
rfseek(romFile, 0, SEEK_SET);
1131+
1132+
romData = (uint8_t *)malloc(fileSize);
1133+
if (romData)
1134+
{
1135+
rfread(romData, 1, fileSize, romFile);
1136+
JaguarLoadFile(romData, fileSize);
1137+
free(romData);
1138+
}
1139+
rfclose(romFile);
1140+
}
1141+
}
1142+
}
1143+
9951144
JaguarReset();
9961145

9971146
/* The frontend will load .srm data into our save buffer (returned by
@@ -1012,6 +1161,10 @@ bool retro_load_game_special(unsigned game_type, const struct retro_game_info *i
10121161

10131162
void retro_unload_game(void)
10141163
{
1164+
CDIntfCloseImage();
1165+
jaguar_cd_mode = false;
1166+
cd_image_path[0] = '\0';
1167+
10151168
JaguarDone();
10161169
if (videoBuffer)
10171170
free(videoBuffer);

libretro_core_options.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,20 @@ struct retro_core_option_v2_definition option_defs_us[] = {
147147
},
148148
"disabled"
149149
},
150+
{
151+
"virtualjaguar_cd_bios_type",
152+
"CD BIOS Type (Restart)",
153+
NULL,
154+
"Select which Jaguar CD BIOS to use when loading CD images. Retail is the standard BIOS. Dev is the developer BIOS with less strict checks.",
155+
NULL,
156+
NULL,
157+
{
158+
{ "retail", "Retail" },
159+
{ "dev", "Developer" },
160+
{ NULL, NULL },
161+
},
162+
"retail"
163+
},
150164
{
151165
"virtualjaguar_alt_inputs",
152166
"Enable Core Options Remapping",

0 commit comments

Comments
 (0)