Add Jaguar CD support with CUE/BIN and CHD image loading#109
Add Jaguar CD support with CUE/BIN and CHD image loading#109JoeMatt wants to merge 31 commits intolibretro:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds Jaguar CD disc image support to the core by integrating libchdr (for CHD), implementing CUE/BIN parsing and sector reads, and updating the libretro frontend to boot via the Jaguar CD BIOS when .cue/.chd content is loaded.
Changes:
- Added CD image loading APIs and implemented CUE/BIN + CHD sector reading in
cdintf.*. - Updated CDROM/Butch emulation paths and added core option for selecting Retail vs Developer CD BIOS.
- Vendored
deps/libchdrand wired it into the build viaMakefile.common(plus upstream CMake/CI files included underdeps/libchdr).
Reviewed changes
Copilot reviewed 65 out of 69 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/settings.h | Adds settings fields/enums for enabling CD BIOS and selecting CD BIOS type. |
| src/cdrom.c | Updates Butch status/interrupt behavior and sector/audio delivery reads. |
| src/cdintf.h | Expands the CD interface API and adds disc image load/unload helpers and data structures. |
| src/cdintf.c | Implements CUE/BIN parsing, CHD parsing/reads, and image-backed sector reads. |
| libretro_core_options.h | Adds core option to select Retail vs Developer Jaguar CD BIOS. |
| libretro.c | Enables .cue/.chd loading, boots via CD BIOS, and loads cart ROMs from path when needed. |
| Makefile.common | Adds libchdr include paths/defines and compiles vendored libchdr sources. |
| deps/libchdr/unity.c | Unity build translation unit for libchdr + bundled deps sources. |
| deps/libchdr/src/link.T | Version-script for symbol visibility when building shared libchdr. |
| deps/libchdr/src/libchdr_huffman.c | Vendored libchdr implementation (Huffman). |
| deps/libchdr/src/libchdr_flac.c | Vendored libchdr implementation (FLAC wrapper). |
| deps/libchdr/src/libchdr_codec_zstd.c | Vendored libchdr codec implementation (Zstd). |
| deps/libchdr/src/libchdr_codec_zlib.c | Vendored libchdr codec implementation (Zlib/miniz). |
| deps/libchdr/src/libchdr_codec_lzma.c | Vendored libchdr codec implementation (LZMA). |
| deps/libchdr/src/libchdr_codec_huff.c | Vendored libchdr codec implementation (Huffman). |
| deps/libchdr/src/libchdr_codec_flac.c | Vendored libchdr codec implementation (FLAC). |
| deps/libchdr/src/libchdr_codec_cdzs.c | Vendored libchdr CD codec implementation (CD + Zstd). |
| deps/libchdr/src/libchdr_codec_cdzl.c | Vendored libchdr CD codec implementation (CD + Zlib). |
| deps/libchdr/src/libchdr_codec_cdlz.c | Vendored libchdr CD codec implementation (CD + LZMA). |
| deps/libchdr/src/libchdr_codec_cdfl.c | Vendored libchdr CD codec implementation (CD + FLAC). |
| deps/libchdr/src/libchdr_bitstream.c | Vendored libchdr bitstream implementation. |
| deps/libchdr/pkg-config.pc.in | pkg-config template for standalone libchdr builds. |
| deps/libchdr/include/libchdr/macros.h | Vendored libchdr public header (macros). |
| deps/libchdr/include/libchdr/huffman.h | Vendored libchdr public header (Huffman). |
| deps/libchdr/include/libchdr/flac.h | Vendored libchdr public header (FLAC). |
| deps/libchdr/include/libchdr/coretypes.h | Vendored libchdr public header (core file callbacks/types). |
| deps/libchdr/include/libchdr/codec_zstd.h | Vendored libchdr public header (Zstd codec). |
| deps/libchdr/include/libchdr/codec_zlib.h | Vendored libchdr public header (Zlib codec). |
| deps/libchdr/include/libchdr/codec_lzma.h | Vendored libchdr public header (LZMA codec). |
| deps/libchdr/include/libchdr/codec_huff.h | Vendored libchdr public header (Huffman codec). |
| deps/libchdr/include/libchdr/codec_flac.h | Vendored libchdr public header (FLAC codec). |
| deps/libchdr/include/libchdr/codec_cdzs.h | Vendored libchdr public header (CDZS codec). |
| deps/libchdr/include/libchdr/codec_cdzl.h | Vendored libchdr public header (CDZL codec). |
| deps/libchdr/include/libchdr/codec_cdlz.h | Vendored libchdr public header (CDLZ codec). |
| deps/libchdr/include/libchdr/codec_cdfl.h | Vendored libchdr public header (CDFL codec). |
| deps/libchdr/include/libchdr/chdconfig.h | Vendored libchdr public header (feature config). |
| deps/libchdr/include/libchdr/chd.h | Vendored libchdr public header (CHD API). |
| deps/libchdr/include/libchdr/cdrom.h | Vendored libchdr public header (CD constants/helpers). |
| deps/libchdr/include/libchdr/bitstream.h | Vendored libchdr public header (bitstream). |
| deps/libchdr/deps/zstd-1.5.7/zstd_errors.h | Bundled Zstd error definitions for libchdr. |
| deps/libchdr/deps/zstd-1.5.7/CMakeLists.txt | CMake build file for bundled zstd static lib. |
| deps/libchdr/deps/miniz-3.1.1/CMakeLists.txt | CMake build file for bundled miniz static lib. |
| deps/libchdr/deps/lzma-25.01/src/LzmaDec.c | LZMA decoder compilation unit wrapper for bundled SDK. |
| deps/libchdr/deps/lzma-25.01/include/real/LzmaDec.h | Bundled LZMA SDK header. |
| deps/libchdr/deps/lzma-25.01/include/real/7zTypes.h | Bundled LZMA SDK types header. |
| deps/libchdr/deps/lzma-25.01/include/LzmaDec.h | Namespacing wrapper header to avoid symbol collisions. |
| deps/libchdr/deps/lzma-25.01/LICENSE | Bundled LZMA SDK license. |
| deps/libchdr/deps/lzma-25.01/CMakeLists.txt | CMake build file for bundled LZMA static lib (incl. optional asm). |
| deps/libchdr/deps/lzma-25.01/Asm/x86/7zAsm.asm | Bundled LZMA x86 asm macros. |
| deps/libchdr/deps/lzma-25.01/Asm/arm64/7zAsm.S | Bundled LZMA arm64 asm macros. |
| deps/libchdr/README.md | libchdr README (vendored). |
| deps/libchdr/LICENSE.txt | libchdr license (vendored). |
| deps/libchdr/CMakeLists.txt | Standalone libchdr CMake build definition. |
| deps/libchdr/.gitignore | Ignore build artifacts within vendored libchdr directory. |
| deps/libchdr/.github/workflows/vita.yml | Vendored libchdr CI workflow (Vita). |
| deps/libchdr/.github/workflows/switch.yml | Vendored libchdr CI workflow (Switch). |
| deps/libchdr/.github/workflows/msys2.yml | Vendored libchdr CI workflow (MSYS2). |
| deps/libchdr/.github/workflows/cross-platform-actions.yml | Vendored libchdr CI workflow (BSD/Haiku/OmniOS). |
| deps/libchdr/.github/workflows/cmake.yml | Vendored libchdr CI workflow (Linux/macOS/Windows). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| int64_t fileSize; | ||
|
|
||
| rfseek(romFile, 0, SEEK_END); | ||
| fileSize = rftell(romFile); | ||
| rfseek(romFile, 0, SEEK_SET); | ||
|
|
||
| romData = (uint8_t *)malloc(fileSize); | ||
| if (romData) | ||
| { | ||
| rfread(romData, 1, fileSize, romFile); | ||
| JaguarLoadFile(romData, fileSize); | ||
| free(romData); |
There was a problem hiding this comment.
When loading cartridge ROMs from info->path, fileSize = rftell(romFile) isn’t validated. If rftell fails (returns < 0) or returns an unexpectedly large value, this can lead to a huge/invalid malloc and subsequent read/overflow behavior. Add error checks for fileSize <= 0 (and optionally a sane max size), and verify rfread returned the expected number of bytes before calling JaguarLoadFile.
| // Calculate the file position | ||
| // The track's fileOffset tells us where track data starts in the file. | ||
| // Then we add the offset for the requested sector within the track. | ||
| filePos = (int64_t)(sector - track->startLBA) * sectorSize + track->fileOffset; | ||
|
|
||
| // For single-BIN CUE sheets, all tracks are in the same file and fileOffset | ||
| // accounts for the absolute position. But for multi-index tracks where INDEX 01 | ||
| // is the actual start, fileOffset is based on INDEX 01's MSF offset. | ||
| // Simpler approach: single BIN file, sectors are sequential. | ||
| // File position = sector * sectorSize (for single-file BIN) | ||
| filePos = (int64_t)sector * sectorSize; | ||
|
|
||
| rfseek((RFILE *)disc.binFile, filePos, SEEK_SET); | ||
| bytesRead = rfread(buffer, 1, 2352, (RFILE *)disc.binFile); | ||
|
|
There was a problem hiding this comment.
CDIntfReadBlock() calculates filePos using track->fileOffset/track->startLBA, but then immediately overwrites it with filePos = (int64_t)sector * sectorSize;, which makes the earlier track-based calculation dead code and breaks any CUE that relies on INDEX offsets/pregaps. Additionally, the read always requests 2352 bytes regardless of sectorSize, which will mis-handle MODE1/2048 (and similar) tracks. Use a single, consistent addressing scheme (either track-relative using fileOffset + (sector-startLBA)*sectorSize, or explicitly constrain support to raw-2352 images) and ensure the read length matches the on-disk sector size (padding/expanding to 2352 as needed).
| // Build full path | ||
| if (dir[0]) | ||
| snprintf(currentBinFile, sizeof(currentBinFile), "%s%s", dir, binName); | ||
| else | ||
| snprintf(currentBinFile, sizeof(currentBinFile), "%s", binName); | ||
|
|
||
| // If we don't have a bin path set yet, set it as the primary | ||
| if (!disc.binPath[0]) | ||
| snprintf(disc.binPath, sizeof(disc.binPath), "%s", currentBinFile); | ||
|
|
||
| fileOffset = 0; | ||
| } |
There was a problem hiding this comment.
ParseCueSheet() tracks the current FILE entry in currentBinFile, but only ever stores/opens disc.binPath (the first FILE) and never updates fileOffset based on previous files. As a result, multi-BIN CUE sheets (one BIN per track) will parse but read from the wrong file/offset. Either explicitly reject multi-FILE CUEs (return false with an error) or extend the track model to store/open per-track file paths/handles and compute offsets across files.
| // bit 12: Command to CD drive pending (trans buffer empty if 1) | ||
| // Always set when we're ready for commands | ||
| butchRead |= (1 << 12); | ||
|
|
||
| // bit 13: Response from CD drive pending (rec buffer full if 1) | ||
| // Set when we have a response ready (always ready in our emulation) | ||
| butchRead |= (1 << 13); | ||
|
|
||
| // Store the read-side status | ||
| cdRam[BUTCH + 2] = (butchRead >> 8) & 0xFF; | ||
| cdRam[BUTCH + 3] = butchRead & 0xFF; | ||
|
|
||
| // Generate interrupts through JERRY -> GPU path | ||
| // Butch interrupts route through JERRY EXT1 to the GPU | ||
| if (butchRead & 0x3E00) // Any interrupt flag pending | ||
| { | ||
| // Check if any enabled interrupt has a pending flag | ||
| bool shouldIRQ = false; | ||
|
|
||
| if ((butchWrite & 0x02) && (butchRead & (1 << 9))) // FIFO half-full | ||
| shouldIRQ = true; | ||
| if ((butchWrite & 0x20) && (butchRead & (1 << 13))) // DSARX (response ready) | ||
| shouldIRQ = true; | ||
|
|
||
| if (shouldIRQ) | ||
| { | ||
| // Route through JERRY to GPU via EXT1 interrupt | ||
| // The GPU ISR at JERRY_ISR handles Butch interrupts | ||
| DSPSetIRQLine(DSPIRQ_EXT1, ASSERT_LINE); | ||
| } |
There was a problem hiding this comment.
BUTCHExec() sets the “response pending” flag (bit 13) unconditionally and then raises DSPIRQ_EXT1 whenever DSARX interrupt enable (bit 5) is set. Since the flag is never cleared on read/ack, this can result in a continuously asserted/pulsed EXT1 interrupt (potentially re-entering the ISR repeatedly). Track response availability explicitly (set bit 13 only when a new response is queued, clear it when DS_DATA is read/consumed) and only assert the interrupt when transitioning to the pending state.
| // FIFO state for Butch data delivery | ||
| #define FIFO_SIZE 32 | ||
| static uint8_t fifoData[FIFO_SIZE]; | ||
| static uint32_t fifoReadPtr = 0; | ||
| static uint32_t fifoWritePtr = 0; | ||
| static uint32_t fifoCount = 0; | ||
| static bool fifoDataReady = false; |
There was a problem hiding this comment.
The FIFO bookkeeping (fifoData, read/write pointers, fifoCount) is currently unused; only fifoDataReady is referenced. Either wire the FIFO up (push sector bytes into the FIFO and have FIFO_DATA reads pop from it) or remove the unused state to avoid confusion and dead code.
| // Forward declarations for file stream functions used in CD loading | ||
| RFILE* rfopen(const char *path, const char *mode); | ||
| int rfclose(RFILE* stream); | ||
| int64_t rfseek(RFILE* stream, int64_t offset, int origin); | ||
| int64_t rftell(RFILE* stream); | ||
| int64_t rfread(void* buffer, size_t elem_size, size_t elem_count, RFILE* stream); |
There was a problem hiding this comment.
libretro.c forward-declares rfopen/rfclose/rfseek/rftell/rfread, but these are already declared by libretro-common/include/streams/file_stream_transforms.h (and that header also ensures the right RETRO_BEGIN_DECLS handling). Prefer including the proper header instead of duplicating prototypes here to avoid drift or signature mismatches.
| info->library_version = "v2.1.0" GIT_VERSION; | ||
| info->need_fullpath = false; | ||
| info->valid_extensions = "j64|jag"; | ||
| info->need_fullpath = true; | ||
| info->valid_extensions = "j64|jag|cue|chd"; |
There was a problem hiding this comment.
Changing info->need_fullpath to true makes the core require filesystem paths for all content types, which is a breaking API change for frontends that previously relied on info->data (in-memory loading) for cartridge ROMs. If possible, keep need_fullpath=false and only require info->path for .cue/.chd loads (returning false with a clear message when a CD image is loaded without a path), while continuing to support memory-loaded .j64/.jag when provided.
c4a8963 to
379ee16
Compare
|
Hi, let me know if you want us to merge this |
c77db9e to
67cd444
Compare
…ch 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
Vendors libchdr (https://github.com/rtissera/libchdr) with its dependencies (lzma, miniz, zstd) to support loading Jaguar CD games from CHD (MAME Compressed Hunks of Data) format, the preferred format for distribution in libretro. Changes: - deps/libchdr/: Vendored libchdr library with lzma, miniz, zstd deps - Makefile.common: Add libchdr sources and include paths, define HAVE_CHD - src/cdintf.c: Add ParseCHD() that reads CHTR/CHTR2 track metadata, CDIntfReadBlockCHD() that reads sectors via hunk-based access with single-hunk caching, updated CDIntfOpenImage/CloseImage/IsImageLoaded to handle CHD alongside CUE/BIN - libretro.c: Add .chd to valid_extensions, detect CHD in load_game The CHD reader extracts track layout from CHD metadata tags, handles both CDROM_TRACK_METADATA and CDROM_TRACK_METADATA2 formats (with pregap/postgap), and reads raw 2352-byte audio sectors from the compressed hunk data. All existing cartridge regression tests pass. https://claude.ai/code/session_017594R2HVUZmGUxyQp9328w
- Remove undeclared cdBuf2/cdBuf3 from CDROMStateSave/Load - Add test/roms/private/ for commercial ROMs (gitignored) Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add cdrom_eeprom_ram[64] array in eeprom.c for Jaguar CD saves - Include CD EEPROM in save state serialization - Extend SRAM buffer to 256 bytes (128 cart + 128 CD EEPROM) - Pack/unpack both arrays for RETRO_MEMORY_SAVE_RAM The CD EEPROM I/O hookup (BUTCH register $DFFF2C) is not yet implemented — this provides the data infrastructure for when it is. Co-Authored-By: Claude Opus 4.6 <[email protected]>
CDROMInit() (called by JaguarInit()) checks CDIntfIsImageLoaded() to set haveCDGoodness. The disc image must be opened before that check runs, otherwise the CD drive is never activated. Co-Authored-By: Claude Opus 4.6 <[email protected]>
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]>
The CD BIOS is not a replacement for the standard boot ROM at $E00000. It is a "cartridge" loaded at $800000 with a Jaguar universal header at $800404 containing entry point $802000. Boot sequence: 1. Standard boot ROM at $E00000 initializes the 68K (SP=0, PC=$E00008) 2. Boot ROM detects "cartridge" (CD BIOS) at $800000 3. Boot ROM reads entry point from $800404 and jumps to $802000 4. CD BIOS code runs, shows intro animation, reads CD TOC The embedded jaguarCDBootROM data is not encrypted -- it contains readable strings (VLM, "ATARI APPROVED DATA HEADER") and valid 68K code at offset $2000. It just doesn't use standard 68K reset vectors because it boots as a cartridge, not a boot ROM. Also adds support for loading external CD BIOS from system directory with the common No-Intro filename convention (.j64 extension). Tested: CD BIOS boots, shows intro animation loop. CD drive protocol responses need further work for games to load. Co-Authored-By: Claude Opus 4.6 <[email protected]>
The retail CD BIOS now passes the session-2 pregap audio authentication and reaches its built-in CD Player interface (verified via headless screenshot at 326x240). Boot flow now requires five hooks in JaguarExecuteNew (gated by vjs.useCDBIOS): $050A9C - JaguarInstallCDAuthBypass (BNE.W $0504EC -> 2x NOP) $050AB2 - DSPWriteLong $F1B4C8 = $80010000 (DSP-result fake) $050B0C - JaguarWriteLong $FB000 = $0A (post-BSR success) $0505FA - JaguarWriteLong $1AE00C = $20010001 (CD response magic) $192E46 - JaguarWriteWord $1A6800 = $0001 (BIOS GPU mailbox) The TryReadAuthRedirect path in cdintf.c serves real TAIRTAIR audio from track 30 BIN for the auth window (LBA 139668-139816). cdintf.c needs `#undef fprintf` after streams/file_stream_transforms.h to prevent fprintf->rfprintf macro substitution from silently eating debug logs. Adds test/headless.py - libretro.py-based local test harness so we can drive the core without round-tripping logs through iOS. Includes optional --screenshot flag to dump the framebuffer as PPM. Game-specific boot (jumping from BIOS CD Player into Primal Rage's own boot.abs) is the next milestone. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Adds one-shot JaguarDumpMemWindow hooks in JaguarExecuteNew() for the game CD-event poll function ($081220), its flag area ($0008B398), and the BIOS service routines the game calls into ($00196446 DSP serial comms, $00194D18 CD-data processing). Also traces writes to the $0008B398 game flag. These dumps decoded the post-auth blocker: the BIOS service at $194D18 expects $001AE034 (data-present) and $001AE032 (bytes-remaining) to be non-zero, kicked by ($001AE00C & 0x2000). Our $0505FA stuff value of $20010001 lacks bit 13, so the kick path never triggers. Also adds .iso to libretro core's valid_extensions and headless.py docs. Co-Authored-By: Claude Opus 4.7 <[email protected]>
New documentation: - BUTCH register map with bit definitions - CD data flow: I2S, FIFO, GPU ISR, boot stub layout - Test infrastructure inventory Co-Authored-By: Claude Opus 4.6 <[email protected]>
CHD support removed — CUE/BIN and CDI formats are sufficient. Add jagcd_hle.c to the source list for HLE CD boot path. Co-Authored-By: Claude Opus 4.6 <[email protected]>
cdintf: rewrite CUE parser for multi-file multi-session discs, add CDI format support, boot stub extraction, auth-zone redirect for redump-style dumps that strip pregap audio. cdrom/jaguar: improve BUTCH FIFO emulation, DSA command handling, add CD auth bypass for stripped-pregap dumps, boot stub injection hooks, GPU data phase intercept for HLE path. libretro: add HLE CD boot fallback when no external BIOS ROM found. Co-Authored-By: Claude Opus 4.6 <[email protected]>
jagcd_hle: high-level emulation of the CD BIOS jump table — extracts boot stub, populates TOC, intercepts CD_read/CD_poll/CD_stop calls to transfer sectors directly from disc image to RAM. Enables CD boot without a real BIOS ROM. test_cd_boot: headless test harness that loads a CUE/BIN via dlsym, runs frames, and dumps 68K register state and RAM contents for debugging the CD boot sequence. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Joseph Mattiello <[email protected]>
Mirror the 20 official Atari Jaguar developer-binder PDFs released into the public domain by Hasbro Interactive in 1999, converted to Markdown via pymupdf4llm so the Tom/Jerry register reference, opcode tables, and hardware-bugs list are greppable next to src/op.c, src/tom.c, src/gpu.c, src/dsp.c, etc. Source PDFs are mirrored from cubanismo/jaguar-sdk and hillsoftware.com. The PDFs themselves are .gitignored to keep the repo small (~73 MB skipped, ~2 MB of Markdown checked in); fetch-pdfs.sh + .convert.py reproduce them locally on demand. The 'Technical Reference v8.md' (Brennan/Dunn/Mathieson, rev 8, 28 Feb 2001) comes from a typeset PDF and is the cleanest source. The numbered binder files (00-17) are scans, so OCR quality varies — README.md notes this and points to the originals when in doubt. Made-with: Cursor
Signed-off-by: Joseph Mattiello <[email protected]>
Signed-off-by: Joseph Mattiello <[email protected]>
Signed-off-by: Joseph Mattiello <[email protected]>
Signed-off-by: Joseph Mattiello <[email protected]>
Signed-off-by: Joseph Mattiello <[email protected]>
Signed-off-by: Joseph Mattiello <[email protected]>
Signed-off-by: Joseph Mattiello <[email protected]>
…rbosity Add src/log.h with LOG_DBG/LOG_INF/LOG_WRN/LOG_ERR macros that use the retro_log_printf_t callback (falls back to stderr for test harnesses). Convert ~107 fprintf(stderr) calls across 7 source files to use log levels: - Debug: hex dumps, per-sector traces, sentinel matches, GPU loop traces - Info: boot progress, CD loading, auth bypass - Warn: missing BIOS, fallback paths - Error: hard failures (rfopen, magic mismatch, bad lengths) Also: increase boot stub buffer from 12 to 32 sectors (fixes Space Ace $FA00 boot stub), use register-based TOM resolution (HDB1/HDE/VDB/VDE), fix JERRY_TRACE_DEBUG and GPU trace guards for audio regression. Co-Authored-By: Claude Opus 4.6 <[email protected]>
The HLE CD_read sentinel scan now iterates across every session-2 track when the scan from the boot-stub-supplied LBA misses, with a single-match fallback for ASCII-tagged sentinels (CODE/STUB/SCOR/TITL). Many discs (Highlander, Battle Morph, BrainDead 13) supply MSF values that point to session-2 lead-in instead of game data; scanning each session-2 track in order locates the sync block reliably. Also: * CD_poll now reports A0 = end+4 (matching the real GPU CD ISR which pre-decrements before each long write), unblocking cmp+bge polling idioms used by Highlander. * Boot stub buffers bumped from 256KB to 600KB to fit Battle Morph's ~414KB stub; both the cdintf raw-sector buffer and the jagcd_hle injection buffer kept in lockstep. * New cdintf accessors: CDIntfGetSession2FirstTrackLBA(), CDIntfGetSession2TrackCount(), CDIntfGetSession2TrackLBA(i). * Test harness test/test_cd_hle_boot.c discovers all .cue/.iso/.cdi under VJ_TEST_CD_ROOT (defaults to test/roms/private), runs each through 300 frames, and asserts PC stays in RAM, escapes self-loops, and visits more than a handful of unique addresses. Defaults to cue-only via VJ_TEST_CD_EXTS. * CHD support removed (libchdr deps deleted, .info dropped chd ext). * test_hle_bios test cd_poll_a0_advances_past_end_after_read renamed + assertion updated to match the end+4 contract. Current CUE baseline: 4 PASS / 5 FAIL. PASS: Battle Morph, Dragon's Lair, Highlander, Space Ace. FAIL: Baldies, BrainDead 13, Hover Strike, Iron Soldier 2, Primal Rage (all blocked on GPU CD ISR streaming or post-load downstream waits). Made-with: Cursor
Per docs/cd-bios-calling-convention.md and the retail BIOS disassembly: "The BIOS does NOT use CD_poll. It polls DSP RAM flag at [\$F1B4C8] — the GPU ISR writes \$FFFFFFFF there when the transfer completes, and the BIOS loops until negative." HLEHandleCDRead now mirrors that contract: clear the flag at the start of the read and write \$FFFFFFFF when the transfer finishes. This is the hardware-correct completion primitive. Game boot stubs that follow the BIOS convention will pick this up automatically. The remaining failing CUE games (Baldies, BrainDead 13, Iron Soldier 2, Primal Rage) do NOT poll [\$F1B4C8] — they either spin in STOP waiting for a JERRY ext IRQ from BUTCH, or they read the BUTCH FIFO data register (\$DFFF24/\$DFFF28) directly from the 68K. Both are separate, larger problems (interrupt-driven streaming and direct-FIFO emulation) tracked for follow-up work. Test diagnostic: the boot smoke test now also dumps 32 bytes of code around each visited PC when fewer than 32 unique PCs are seen, so the wait-loop instruction stream can be decoded without re-running. Result: 4 PASS / 5 FAIL (no regression vs prior baseline). Made-with: Cursor
When the boot smoke test parks on a tiny PC set, we already dump 32
bytes around each visited PC. Add a one-line dump of D0-D3 / A0-A2 /
A6 / SP at the same time so the wait loop's source pointer and target
value are visible without re-running with extra logging.
Example: BrainDead 13 stops at \$12438A executing
CMPA.L (A0)+, D0 ; BEQ ; BRA -4
with A0=\$00851644 and D0=\$41545249 ("ATRI") — i.e. it scans cart
space for the universal boot header instead of using CD_poll, which
means HLE needs to populate the CD cart memory window (or implement
direct BUTCH FIFO data reads) before that path can complete.
Made-with: Cursor
…space Two related fixes for boot stubs that issue multiple CD_read calls: 1. Re-seek (D0 bit 31 set) is now a no-op transfer. Per docs/cd-bios-calling-convention.md, bit 31 means "skip hardware init, just re-seek; the GPU data area is already configured by the prior call." We were treating these as full reads, computing byteCount from A0/A1 (which hold stale or garbage values in re-seek mode) and falling back to a default \$5BC00 transfer that stomped the boot stub's just-loaded code/data with raw audio sectors. Hover Strike previously crashed to PC=\$FFFFFFFF at frame 86 because its 4th and 5th CD_reads (D0=\$80657374, \$80F652B9) overwrote 750KB of memory; with this fix it now runs to a clean wait loop at \$065B36 with 19 unique PCs. 2. Loaded data is now mirrored into cart space at the same offset. On real Jaguar CD hardware the CD cart's onboard buffer maps into cart space (\$800000-\$DFFFFF); some boot stubs scan cart-space addresses (e.g. BrainDead 13 reads A0=\$00851644) for the universal "ATRI" header. Cart space is otherwise empty in HLE mode, so the mirror is harmless when not needed. Test diagnostic upgrade: when the run barely moves we also dump 32 bytes of the current memory at A0/A1 (using the libretro core's jagMemSpace[] symbol so cart space is visible), so the wait loop's read target shows up alongside the PC bytes. Result: 4 PASS / 5 FAIL. Hover Strike no longer crashes (now a wait-loop FAIL); other failures unchanged for now. Made-with: Cursor
When a boot stub re-issues the same CD_read (same D0/D1/A0/A1) without varying parameters, real hardware is still feeding new sectors of disc data through the I2S stream — each call produces a different chunk. Without a notion of "where we left off" the HLE handed the same 5KB to the game over and over (Iron Soldier 2 has been stuck in this loop). We now remember the (D0, D1, dest, end) signature of the prior call plus the post-transfer LBA, and on a matching repeat we resume the sentinel scan from that LBA instead of the boot-stub-supplied MSF. This unblocks the multi-chunk boot pattern but does NOT fix Iron Soldier 2 by itself: its sentinel sync block sits at a single fixed LBA in the boot-stub track, so even after resuming we keep finding the same one. Iron Soldier 2 ultimately stops at \$007416 polling RAM[\$44F4] for a flag updated by an interrupt path we don't yet emulate; further progress needs real interrupt-driven streaming. No PASS regressions; all five PASSes hold (Battle Morph, Dragon's Lair, Highlander, Space Ace, Highlander). Made-with: Cursor
Mirrors test_cd_hle_boot but forces virtualjaguar_cd_boot_mode=bios so
the real Atari Jaguar CD BIOS is loaded from VJ_TEST_CD_ROOT (default
test/roms/private). Discovers all .cue/.iso under that root, runs each
for VJ_TEST_CD_FRAMES (default 600 — enough to clear the BIOS animation
window and watch each disc reach its game-code entry point).
New make target: test-cd-bios-boot (parallel to test-cd-hle-boot, also
intentionally excluded from 'make test''s strict pass/fail loop).
Adds two diagnostic LOG lines around CDIntfOpenImage in retro_load_game
so silent disc-open failures are visible in the test log instead of
just retro_load_game failed.
Current real-BIOS baseline (600 frames):
- All 9 cue discs advance through CD-AUTH bypass into game code
(vs all 9 stuck in BIOS animation at 300 frames)
- 8/9 then PC-OOB into garbage (stack/streaming corruption);
Primal Rage stalls in BIOS at \$003616
Made-with: Cursor
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
Refactor CD boot into pluggable strategy vtable (HLE, real BIOS, cart hybrid) with polymorphic dispatch. Fix real-BIOS CD boot for Dragon's Lair, Iron Soldier 2, and Baldies. Improve HLE sentinel scanning with LBA redirect for session-2 games and relaxed self-loop detection. Build: guard HAVE_NEON for osx/Intel, fix test_blitter_simd rule to use auto-detected SIMD source, add jagcd_bios.c/jagcd_cart.c to Makefile.common, update .gitignore for test artifacts. Tests: add harnesses for audio DAC, blitter, BUTCH CD, boot config, GPU control flow/ctrl/IRQ, memory map, timers, and video modes. Relax HLE boot test criteria for post-boot polling loops. Made-with: Cursor
67cd444 to
4f8a734
Compare
This pull request integrates the
libchdrlibrary as a dependency, enabling CHD disc image support in the project. It adds the library's source code, build configuration, and comprehensive cross-platform CI workflows. The most important changes are summarized below.Build System Integration and Source Inclusion:
Makefile.commonis updated to includelibchdras a dependency, adding its include paths, source files, and necessary compiler flags for CHD support. ([[1]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-f190d29e2cad4f32539c8b73d8af07c77f0aac63b5c6553c06b117c08a70b494R2),[[2]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-f190d29e2cad4f32539c8b73d8af07c77f0aac63b5c6553c06b117c08a70b494R13-R19),[[3]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-f190d29e2cad4f32539c8b73d8af07c77f0aac63b5c6553c06b117c08a70b494R59-R78))libchdr Library Addition:
libchdrsource code and dependencies are added underdeps/libchdr, including license, README, and all necessary files to build and use the library. ([[1]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-abd5ac3bb94c64e105483f5faf36fa21ebe709a176890bead73b2901d88ce55eR1-R7),[[2]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-1b8ba7656932a95a1543b3d24df279d2aa5602ab27c62d896c51cd9b55c15cc6R1-R24),[[3]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-053801adbf44631113c7c7d4a686235a4a256e402db546f444bc8e79b104ca37R1-R3),[[4]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-b906f2c0d7e47818baed3f8a4a02da10dc2b0b763b30d0db06a363109c68287cR1-R181),[[5]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-c5c7fc96865e9fed47725023ad159ba5e5868298365c42f8dc0653bd0c0e0470R1-R341))Build System and Configuration for libchdr:
libchdr, supporting options for static/shared builds, system libraries, and build optimizations. ([deps/libchdr/CMakeLists.txtR1-R172](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-33f96b07c6b48c3495a5fb6a8ab937c028c9060c04f93108c2318b66d2f7b9eaR1-R172))Continuous Integration (CI) Enhancements:
libchdr, covering CMake builds on major platforms (Linux, macOS, Windows), MSYS2 environments, BSD/Haiku/OmniOS, Nintendo Switch, and PlayStation Vita. ([[1]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-d73a63b10a4819b37a9db04c94bfbcbc2d815740f527208817e23b868fb1040bR1-R19),[[2]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-bbfe3353020b26283c4dcf8d4f5538360f0690caf0a0cec92a710c8ae381c529R1-R36),[[3]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-ffc351a06445bfa406becbe0fbe3ec5386f351a5088f74d2f8f668bb4136dec1R1-R45),[[4]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-50f09d4b3c600489a54bfabf0bc622bef16d071b5df9f219fb3330427f521608R1-R17),[[5]](https://github.com/libretro/virtualjaguar-libretro/pull/109/files#diff-c51f1585a38196e56ac6551307e4fce42e171120386984e1cbdcaa4d9c87cdb4R1-R17))These changes collectively enable robust CHD image support, ensure build reliability across platforms, and make future maintenance and integration of
libchdrstraightforward.