Skip to content

Fix two decisions taken from the cartridge's IPL3 - #824

Merged
LibretroAdmin merged 2 commits into
libretro:masterfrom
Pitchoune:fix-ipl3-driven-decisions
Aug 22, 2026
Merged

Fix two decisions taken from the cartridge's IPL3#824
LibretroAdmin merged 2 commits into
libretro:masterfrom
Pitchoune:fix-ipl3-driven-decisions

Conversation

@Pitchoune

Copy link
Copy Markdown
Contributor

Found while porting to a Raspberry Pi 5 (aarch64, GLES 3.1). Both commits
concern code that identifies a cartridge by its IPL3 checksum and acts on
the answer; both were isolated by bisecting on real hardware.

1. The autoselect reads the checksum in the wrong byte order

core_settings_autoselect_rsp_plugin() runs from emu_step_initialize(),
before main_run() byte-swaps the cartridge into host word order.
cic_ipl3_known() reads native uint32, so on a little-endian host it sums
transposed words and matches no retail CIC. Every cartridge looked like
homebrew and went to the LLE core.

Resident Evil 2 (Europe) sums to 0x000000A316ADC55A that way, against the
0x000000D057C85244 the table holds for it.

Most games tolerate cxd4, which is why this is easy to miss. The two that
gave it away paint from RDRAM: RE2 shredded its pre-rendered backgrounds
into repeating horizontal streaks while its polygonal characters stayed
correct, and Donkey Kong 64 lost the HLE-side microcode handling it needs.
Forcing rspplugin=hle fixed both, which is what placed the fault.

2. Only claim the RDRAM is up for an IPL3 that checks

25c769d sets RI_SELECT for every cartridge so libdragon's open IPL3 boots.
Retail IPL3 never reads it: it runs its sizing procedure unconditionally, and
that procedure publishes osMemSize at 0x80000318 and hands the detected
module count to rdram.c through s4. Telling it the RDRAM is already up
makes it skip all of that.

South Park Rally runs 12.3M cycles, derives a memory top from a machine that
reports no memory, and takes a TLB refill on 0x7ffffffc. It never programs
COMPARE afterwards, so COMPARE_INT stays due at count 0 and the dynarec
re-enters gen_interrupt() without running an instruction: one core at 100%,
no output. Donkey Kong 64 fails more quietly and detects 4 MB.

Gating on cic_ipl3_known() puts retail cartridges back on exactly the path
they took before 25c769d.

Verification

South Park Rally boots and Donkey Kong 64 sees 8 MB again; removing either
commit reproduces its failure.

One caveat: the osMemSize write on the unknown-IPL3 branch is not
exercised here, as I have no libdragon cartridge to test with. Happy to drop
it if you would rather keep the change minimal.

fe0cbbf sends a cartridge whose IPL3 checksum is not in the CIC table
to the LLE RSP under "auto".  The check runs in
core_settings_autoselect_rsp_plugin(), reached from
emu_step_initialize(), which is before main_run() byte-swaps the
cartridge into host word order.  cic_ipl3_known() reads native uint32,
so on a little-endian host it sums transposed words.  The sum matches no
retail CIC, and every cartridge - retail or not - went to the LLE core.

Resident Evil 2 (Europe) reads 0x000000A316ADC55A that way against the
0x000000D057C85244 the table holds for it.

The consequence is not a crash: cxd4 is accurate enough that most games
look right, and the two that gave it away were the ones that paint from
RDRAM.  RE2 shredded its pre-rendered backgrounds and language select
into repeating horizontal streaks while its polygonal characters stayed
correct, and Donkey Kong 64 lost the HLE-side microcode handling it
needs.  Setting rspplugin to hle by hand fixed both, which is what
placed the fault here.

Sum a corrected copy of the 0xfc0 bytes rather than the live buffer,
byte-swapping when g_RomWordsLittleEndian says the cartridge has not
been converted yet.  Both games are correct again under "auto", and a
big-endian host is unaffected.
25c769d sets RI_SELECT for every cartridge so that libdragon's open
IPL3, which reads it and otherwise drives RDRAM registers this core does
not model, boots.  Retail IPL3 never reads it: it runs its sizing
procedure unconditionally, and that procedure is what publishes
osMemSize at 0x80000318 and hands the detected module count to rdram.c
through s4.  Telling it the RDRAM is already up makes it skip all of
that, and nothing else in the core fills those in.

South Park Rally shows the result.  It runs 12.3M cycles, derives a
memory top from a machine that reports no memory, and takes a TLB
refill on 0x7ffffffc - 0x80000000 minus four, the last word of an
empty RDRAM.  It never programs COMPARE after that, so COMPARE_INT
stays due at count 0, cycle_count is COUNT on every pass, and the
dynarec re-enters gen_interrupt without ever running an instruction:
one core at 100%, no output, killall required.  Donkey Kong 64 fails
more quietly and detects 4 MB.

Gate the claim on cic_ipl3_known(), the helper fe0cbbf added for the
same distinction: a cartridge whose IPL3 checksum is in the CIC table
runs the retail sequence and needs nothing from us, and takes exactly
the path it took before 25c769d.  An unknown IPL3 keeps the claim, and
since the sizing procedure no longer runs for it, osMemSize is
published here instead.

South Park Rally boots and Donkey Kong 64 sees 8 MB again; removing
the gate reproduces both failures.
@LibretroAdmin
LibretroAdmin merged commit 0df91e8 into libretro:master Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants