mirror of
https://github.com/N64Recomp/N64ModernRuntime.git
synced 2026-05-10 19:01:53 +00:00
pi: mirror ROM into kseg1 region of rdram
Adds recomp::mirror_rom_to_kseg1(rdram) that copies the loaded ROM
into rdram + 0x30000000 with XOR-3-byte-swapped storage so direct
MIPS reads of cart vaddrs return the expected bytes.
Why:
The recompiler's MEM_W formula maps kseg1 vaddrs into the second
512 MiB of the rdram allocation:
MEM_W(addr) = *(int32_t*)(rdram + (addr - 0xFFFFFFFF80000000))
For addr=0xB0000000, that's rdram + 0x30000000. Previously, ROM
bytes were only available via osPiStartDma -> do_rom_read; direct
reads of cart vaddrs (e.g. `lw $t0, 0xE38($t9)` with $t9=0xB000_0000)
hit never-written rdram and returned garbage.
Visible symptom:
Pokemon Stadium's Game_DoCopyProtection at 0x80028FA0 reads
*(u32*)0xB0000E38 and compares the low 16 bits against 0x828A.
Without the mirror that read returned 0, the magic check tripped,
and the function returned -0x10 = 0xFFFFFFF0 — a sentinel state
that the main state-machine switch doesn't handle. Title screen
flashed for 1-2 frames then reverted to intro on every run.
After the fix: copyprot returns input state unchanged, title
screen stays up cleanly. Verified via Stadium harness:
gCurrentGameState transitions 0x01 -> 0x02 and stays there
through 25s of idle with zero copyprot trips logged.
Generality:
Any N64 game with a ROM-checksum / magic-word check (Stadium,
many original-IP games, anti-piracy code in others) needs this.
CIC-NUS-6103 / 6105 / 6106 boot also reads ROM via virtual
addresses. The mirror is install-once at game-start time, after
load_stored_rom populates the rom vector.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0bb76b0fc7
commit
e43892bb5f
3 changed files with 53 additions and 0 deletions
|
|
@ -78,6 +78,11 @@ namespace recomp {
|
|||
bool is_rom_loaded();
|
||||
void set_rom_contents(std::vector<uint8_t>&& new_rom);
|
||||
std::span<const uint8_t> get_rom();
|
||||
// Mirror ROM into rdram's kseg1 region so direct MIPS reads of
|
||||
// cart vaddrs (e.g. lw $t1, 0xB0000E38) return correct bytes.
|
||||
// Call once after rdram is allocated. Without this, ROM-checksum
|
||||
// / copy-protection routines see garbage. See pi.cpp for detail.
|
||||
void mirror_rom_to_kseg1(uint8_t* rdram);
|
||||
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes);
|
||||
void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr);
|
||||
const Version& get_project_version();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,44 @@ void recomp::set_rom_contents(std::vector<uint8_t>&& new_rom) {
|
|||
rom = std::move(new_rom);
|
||||
}
|
||||
|
||||
// Mirror ROM into the kseg1 region of rdram so that direct MIPS
|
||||
// reads of cart vaddrs (e.g. `lw $t1, 0xB0000E38`) return the
|
||||
// correct ROM bytes. Some games read ROM via cart vaddrs without
|
||||
// going through osPiStartDma — most notably copy-protection /
|
||||
// CIC-checksum routines that read a known ROM word and compare
|
||||
// against an expected value (e.g. Pokemon Stadium's
|
||||
// Game_DoCopyProtection at *(u32*)0xB0000E38).
|
||||
//
|
||||
// Without this mirror, MEM_W of a cart vaddr reads rdram bytes
|
||||
// that were never written, returning garbage. The check trips
|
||||
// and the game falls into a copy-protection error path
|
||||
// (e.g. state = -0x10 in Stadium, which then bypasses the
|
||||
// state-machine switch and the game appears to "stutter back to
|
||||
// intro").
|
||||
//
|
||||
// MEM_W formula: rdram + (vaddr - 0xFFFFFFFF80000000). For
|
||||
// kseg1 cart base 0xB0000000, that's rdram + 0x30000000.
|
||||
// We use the recompiler's BE-byte-swapped storage convention
|
||||
// (XOR-3 byte index) so that a host-native int32_t read of the
|
||||
// mirrored bytes returns the BE word that Stadium expects.
|
||||
//
|
||||
// Cost: one-time copy at startup, ~32 MiB worst case (size of
|
||||
// the ROM image). Negligible vs. the rest of the recompile.
|
||||
void recomp::mirror_rom_to_kseg1(uint8_t* rdram) {
|
||||
if (rom.empty()) {
|
||||
fprintf(stderr,
|
||||
"[mirror] mirror_rom_to_kseg1 called with empty rom — "
|
||||
"kseg1 cart reads will return garbage\n");
|
||||
fflush(stderr);
|
||||
return;
|
||||
}
|
||||
constexpr size_t kKseg1RomOffset = 0x30000000;
|
||||
const size_t n = rom.size();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
rdram[(kKseg1RomOffset + i) ^ 3] = rom[i];
|
||||
}
|
||||
}
|
||||
|
||||
std::span<const uint8_t> recomp::get_rom() {
|
||||
return rom;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -646,6 +646,16 @@ bool wait_for_game_started(uint8_t* rdram, recomp_context* context) {
|
|||
ultramodern::error_handling::message_box("Error opening stored ROM! Please restart this program.");
|
||||
}
|
||||
|
||||
// Mirror ROM into the kseg1 region of rdram so direct
|
||||
// MIPS reads of cart vaddrs (e.g. CIC-checksum /
|
||||
// copy-protection code reading *(u32*)0xB000XXXX)
|
||||
// return the expected bytes. Without this, MEM_W of
|
||||
// a cart vaddr lands on never-written rdram bytes
|
||||
// and games with ROM-magic checks (e.g. Pokemon
|
||||
// Stadium's Game_DoCopyProtection at *(u32*)0xB0000E38)
|
||||
// trip and fall into error paths.
|
||||
recomp::mirror_rom_to_kseg1(rdram);
|
||||
|
||||
auto find_it = game_roms.find(current_game.value());
|
||||
const recomp::GameEntry& game_entry = find_it->second;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue