Skip to content

Commit ccc30c7

Browse files
committed
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 6b282a2 commit ccc30c7

6 files changed

Lines changed: 842 additions & 181 deletions

File tree

libretro.c

Lines changed: 143 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"
@@ -38,6 +48,8 @@ static retro_environment_t environ_cb;
3848
retro_audio_sample_batch_t audio_batch_cb;
3949

4050
static bool libretro_supports_bitmasks = false;
51+
static bool jaguar_cd_mode = false;
52+
static char cd_image_path[4096] = {0};
4153

4254
void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
4355
void retro_set_audio_sample(retro_audio_sample_t cb) { (void)cb; }
@@ -335,6 +347,17 @@ static void check_variables(void)
335347
vjs.hardwareTypeNTSC = true;
336348
}
337349

350+
var.key = "virtualjaguar_cd_bios_type";
351+
var.value = NULL;
352+
353+
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
354+
{
355+
if (strcmp(var.value, "dev") == 0)
356+
vjs.cdBiosType = CDBIOS_DEV;
357+
else
358+
vjs.cdBiosType = CDBIOS_RETAIL;
359+
}
360+
338361
var.key = "virtualjaguar_alt_inputs";
339362
var.value = NULL;
340363
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
@@ -712,6 +735,14 @@ static void update_input(void)
712735
}
713736
}
714737

738+
static bool has_extension(const char *path, const char *ext)
739+
{
740+
const char *dot = strrchr(path, '.');
741+
if (!dot)
742+
return false;
743+
return strcasecmp(dot + 1, ext) == 0;
744+
}
745+
715746
static void extract_basename(char *buf, const char *path, size_t size)
716747
{
717748
char *ext = NULL;
@@ -746,8 +777,8 @@ void retro_get_system_info(struct retro_system_info *info)
746777
#define GIT_VERSION ""
747778
#endif
748779
info->library_version = "v2.1.0" GIT_VERSION;
749-
info->need_fullpath = false;
750-
info->valid_extensions = "j64|jag";
780+
info->need_fullpath = true;
781+
info->valid_extensions = "j64|jag|cue";
751782
}
752783

753784
void retro_get_system_av_info(struct retro_system_av_info *info)
@@ -852,10 +883,7 @@ bool retro_load_game(const struct retro_game_info *info)
852883
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
853884

854885
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
855-
{
856-
//fprintf(stderr, "Pixel format XRGB8888 not supported by platform, cannot use.\n");
857886
return false;
858-
}
859887

860888
videoWidth = 320;
861889
videoHeight = 240;
@@ -869,9 +897,26 @@ bool retro_load_game(const struct retro_game_info *info)
869897
// Emulate BIOS
870898
vjs.hardwareTypeNTSC = true;
871899
vjs.useJaguarBIOS = false;
900+
vjs.useCDBIOS = false;
901+
vjs.cdBiosType = CDBIOS_RETAIL;
872902

873903
check_variables();
874904

905+
// Detect CD content
906+
jaguar_cd_mode = false;
907+
cd_image_path[0] = '\0';
908+
909+
if (info->path && has_extension(info->path, "cue"))
910+
{
911+
jaguar_cd_mode = true;
912+
strncpy(cd_image_path, info->path, sizeof(cd_image_path) - 1);
913+
cd_image_path[sizeof(cd_image_path) - 1] = '\0';
914+
915+
// For CD mode, force BIOS on -- CD games require the BIOS
916+
vjs.useJaguarBIOS = true;
917+
vjs.useCDBIOS = true;
918+
}
919+
875920
// Get eeprom path info
876921
// > Handle Windows nonsense...
877922
#if defined(_WIN32)
@@ -899,9 +944,40 @@ bool retro_load_game(const struct retro_game_info *info)
899944
}
900945

901946
JaguarInit(); // set up hardware
902-
memcpy(jagMemSpace + 0xE00000,
903-
((vjs.biosType == BT_K_SERIES) ? jaguarBootROM : jaguarBootROM2),
904-
0x20000); // Use the stock BIOS
947+
948+
if (jaguar_cd_mode)
949+
{
950+
// Load CD BIOS at $E00000 (256 KB = 0x40000 bytes)
951+
// The CD BIOS is larger than the standard 128 KB boot ROM
952+
uint8_t *cdBios = (vjs.cdBiosType == CDBIOS_DEV)
953+
? jaguarDevCDBootROM : jaguarCDBootROM;
954+
memcpy(jagMemSpace + 0xE00000, cdBios, 0x40000);
955+
956+
// Open the disc image
957+
if (!CDIntfOpenImage(cd_image_path))
958+
{
959+
// Failed to open disc image
960+
JaguarDone();
961+
if (videoBuffer)
962+
{
963+
free(videoBuffer);
964+
videoBuffer = NULL;
965+
}
966+
if (sampleBuffer)
967+
{
968+
free(sampleBuffer);
969+
sampleBuffer = NULL;
970+
}
971+
return false;
972+
}
973+
}
974+
else
975+
{
976+
// Standard cartridge mode
977+
memcpy(jagMemSpace + 0xE00000,
978+
((vjs.biosType == BT_K_SERIES) ? jaguarBootROM : jaguarBootROM2),
979+
0x20000); // Use the stock BIOS (128 KB)
980+
}
905981

906982
JaguarSetScreenPitch(videoWidth);
907983
JaguarSetScreenBuffer(videoBuffer);
@@ -910,8 +986,61 @@ bool retro_load_game(const struct retro_game_info *info)
910986
for (i = 0; i < videoWidth * videoHeight; ++i)
911987
videoBuffer[i] = 0xFF00FFFF;
912988

913-
SET32(jaguarMainRAM, 0, 0x00200000);
914-
JaguarLoadFile((uint8_t*)info->data, info->size);
989+
if (jaguar_cd_mode)
990+
{
991+
// For CD mode, the BIOS handles boot
992+
// Set the stack pointer and boot from BIOS
993+
SET32(jaguarMainRAM, 0, 0x00200000);
994+
995+
// The BIOS entry vectors are in the CD BIOS ROM itself
996+
// Read the reset vector from the BIOS: first long = initial SP, second long = initial PC
997+
{
998+
uint8_t *biosBase = jagMemSpace + 0xE00000;
999+
uint32_t initialSP = GET32(biosBase, 0);
1000+
uint32_t initialPC = GET32(biosBase, 4);
1001+
1002+
SET32(jaguarMainRAM, 0, initialSP);
1003+
SET32(jaguarMainRAM, 4, initialPC);
1004+
}
1005+
1006+
jaguarCartInserted = false;
1007+
}
1008+
else
1009+
{
1010+
// Standard cartridge loading (need_fullpath=true, so load from file)
1011+
SET32(jaguarMainRAM, 0, 0x00200000);
1012+
1013+
if (info->data && info->size > 0)
1014+
{
1015+
// Data provided directly
1016+
JaguarLoadFile((uint8_t*)info->data, info->size);
1017+
}
1018+
else if (info->path)
1019+
{
1020+
// Load ROM from file path
1021+
RFILE *romFile;
1022+
romFile = rfopen(info->path, "rb");
1023+
if (romFile)
1024+
{
1025+
uint8_t *romData;
1026+
int64_t fileSize;
1027+
1028+
rfseek(romFile, 0, SEEK_END);
1029+
fileSize = rftell(romFile);
1030+
rfseek(romFile, 0, SEEK_SET);
1031+
1032+
romData = (uint8_t *)malloc(fileSize);
1033+
if (romData)
1034+
{
1035+
rfread(romData, 1, fileSize, romFile);
1036+
JaguarLoadFile(romData, fileSize);
1037+
free(romData);
1038+
}
1039+
rfclose(romFile);
1040+
}
1041+
}
1042+
}
1043+
9151044
JaguarReset();
9161045

9171046
return true;
@@ -927,6 +1056,10 @@ bool retro_load_game_special(unsigned game_type, const struct retro_game_info *i
9271056

9281057
void retro_unload_game(void)
9291058
{
1059+
CDIntfCloseImage();
1060+
jaguar_cd_mode = false;
1061+
cd_image_path[0] = '\0';
1062+
9301063
JaguarDone();
9311064
if (videoBuffer)
9321065
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)