This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Reboot Loop and MBC1 Investigation
Summary
This Step implements advanced instrumentation to detect if Pokémon Red is trapped in aReset Loop. Analysis of Step 0278 revealed that over 300,000 delay loop exits were detected in just 12 seconds, strongly suggesting that the game is continually restarting. After exiting the delay, the game will likely encounter an error condition (such as a corrupt stack or a poorly mapped ROM bank) and jump back to 0x0000 or run a RST 00.
Three critical monitors were added: (1) detector passing through the reset vectors (0x0000 and 0x0100) to confirm the Reset Loop theory, (2) trace the V-Blank handler (0x0040) to check if interrupts are processed correctly, and (3) mode change monitor MBC1 to detect if the memory mapping becomes corrupted and shifts Bank 0 out of 0x0000-0x3FFF, breaking the interrupt vectors.
Hardware Concept
Reboot loops are a common problem in emulators when there are errors in the emulation of critical hardware. These loops occur when game code attempts to execute an instruction or access memory that is unavailable or corrupted, causing the game to crash to the restart vector (0x0000 or 0x0100) and restart execution from the beginning.
1. Game Boy Reset Vectors
The Game Boy has two main reset vectors:
- 0x0000:Boot ROM vector. If the system has an active Boot ROM, this vector points to the start of the Boot ROM. If there is no Boot ROM, this vector points directly to the cartridge code.
- 0x0100:Cartridge entry vector. This is the standard entry point for the game code after the Boot ROM ends (or if there is no Boot ROM).
When the PC reaches 0x0000 or 0x0100, the game is restarting. If this happens repeatedly, the game is stuck in a restart loop.
2. MBC1 and Memory Mapping
The MBC1 (Memory Bank Controller 1) is the most common memory controller in Game Boy cartridges. It has two modes of operation:
- Mode 0 (ROM Banking):Bank 0 is always mapped to 0x0000-0x3FFF. The upper banks are mapped to 0x4000-0x7FFF. This is the standard and most common mode.
- Mode 1 (RAM Banking):Bank 0 can be replaced by a RAM bank at 0x0000-0x3FFF. This mode is rarely used and only on cartridges with large RAM.
Critical problem:If MBC1 is accidentally switched to Mode 1, ROM Bank 0 could disappear from 0x0000-0x3FFF. This would break the interrupt vectors (0x0000, 0x0040, 0x0048, 0x0050, 0x0058, 0x0060) because these vectors are in Bank 0. If the game tries to jump to one of these vectors and Bank 0 is not mapped, it will read incorrect or garbage data, causing a crash or reboot.
Fountain:Pan Docs - "MBC1": Mode 0/1 controls whether 0x0000-0x3FFF is ROM or RAM. In mode 1, ROM Bank 0 may not be available at 0x0000-0x3FFF, breaking interrupt vectors.
3. Interruption Vectors
The Game Boy has 5 interrupt vectors, all located in ROM Bank 0 (0x0000-0x3FFF):
- 0x0040:V-Blank Interrupt (Vertical Blanking)
- 0x0048:LCD STAT Interrupt
- 0x0050:Timer Interrupt
- 0x0058:Serial Interrupt
- 0x0060:Joypad Interrupt
If Bank 0 is not correctly mapped to 0x0000-0x3FFF, these vectors will point to incorrect data, causing interrupts. run corrupt or junk code, which can lead to a system reboot.
Implementation
Three instrumentation monitors were implemented to detect and diagnose the reset loop:
1. Restart Monitor in CPU.cpp
Added a detector that monitors when the PC goes through the reset vectors (0x0000 or 0x0100). This monitor captures:
- The original PC (before the instruction fetch)
- A restart counter (to see how many times it occurs)
- The Stack Pointer (SP) to detect stack corruption
- The current ROM bank (to check if the mapping is correct)
- The status of IME (Interrupt Master Enable)
- The IE (Interrupt Enable) and IF (Interrupt Flag) registers
// --- Step 0279: Reset Monitor (Reset Loop Detection) ---
if (original_pc == 0x0000 || original_pc == 0x0100) {
static uint32_t reset_count = 0;
printf("[RESET-WATCH] Passing through PC:0x%04X (Counter: %u) | SP:0x%04X Bank:%d | IME:%d IE:%02X IF:%02X\n",
original_pc, ++reset_count, regs_->sp, mmu_->get_current_rom_bank(),
ime_? 1 : 0, mmu_->read(0xFFFF), mmu_->read(0xFF0F));
}
2. V-Blank Handler Tracking
Added a monitor that detects when the code enters the V-Blank handler (0x0040). This monitor captures:
- The Stack Pointer (to check if the stack is valid when entering the handler)
- The HL register (to see what data the handler is manipulating)
- Record A (to view data status)
- The current ROM bank (to verify that the vector points to the correct code)
// --- Step 0279: V-Blank Handler Tracking ---
if (original_pc == 0x0040) {
printf("[VBLANK-ENTRY] Vector 0x0040 reached. SP:0x%04X | HL:0x%04X | A:0x%02X | Bank:%d\n",
regs_->sp, regs_->get_hl(), regs_->a, mmu_->get_current_rom_bank());
}
3. MBC1 Mode Change Monitor in MMU.cpp
Added a monitor that detects when MBC1 changes modes (0x6000-0x7FFF). This monitor captures:
- The old mode and the new mode (0 = ROM Banking, 1 = RAM Banking)
- The PC where the change occurs (to identify which code causes it)
- The current bank 0 and bank N (to verify mapping)
// --- Step 0279: MBC1 Mode Change Monitor ---
uint8_t new_mode = value & 0x01;
if (mbc1_mode_ != new_mode) {
printf("[MBC1-MODE] Mode change detected: %d -> %d on PC:0x%04X | Bank0:%d BankN:%d\n",
mbc1_mode_, new_mode, debug_current_pc, bank0_rom_, bankN_rom_);
}
mbc1_mode_ = new_mode;
update_bank_mapping();
Design Decisions
Use oforiginal_pc:The original PC is used (captured at the beginning ofstep()before fetch)
instead of the current PC, because the PC can change during the execution of the instruction. This ensures that we detect the passage through the
vectors even if the instruction modifies the PC.
Static counters:Static variables are used for counters (such asreset_count) to maintain
the state between calls tostep(). This allows you to track how many times an event occurs without the need for external storage.
Detailed logging:Complete system state information (SP, banks, interrupts) is captured to facilitate the diagnosis. This allows you to identify patterns in reboots and correlate them with changes in system state.
Affected Files
src/core/cpp/CPU.cpp- Added restart monitors (0x0000/0x0100) and V-Blank trace (0x0040)src/core/cpp/MMU.cpp- Added MBC1 mode change monitor in the range 0x6000-0x7FFF
Tests and Verification
This implementation is purely for instrumentation and diagnosis. No unit tests were added because monitors are tools debug functions that are activated during the execution of the emulator with real ROMs.
Validation:The monitors will be validated by running Pokémon Red and observing the logs on the console. If the game is in a
restart loop, we should see multiple messages[RESET-WATCH]with the counter incrementing. If MBC1 changes mode
incorrectly, we should see messages[MBC1-MODE]indicating the change.
Execute command:The monitors are automatically activated during normal emulator execution. To view the logs, run the emulator with Pokémon Red and observe the console output.
Compiled C++ module validation:The changes require rebuilding the Cython extension. Execute:
python setup.py build_ext --inplace
Sources consulted
- Bread Docs:https://gbdev.io/pandocs/- "MBC1" section (operation modes and memory mapping)
- Bread Docs:https://gbdev.io/pandocs/- "Interrupts" section (interrupt vectors at 0x0040-0x0060)
- Bread Docs:https://gbdev.io/pandocs/- "Reset Vectors" section (reset vectors at 0x0000 and 0x0100)
Educational Integrity
What I Understand Now
- Reset Loops:They occur when the game repeatedly jumps to the reset vectors (0x0000 or 0x0100), usually due to errors in critical hardware emulation (corrupt stack, incorrect memory mapping, broken interruption).
- MBC1 Mode 1:If MBC1 is switched to Mode 1 (RAM Banking), ROM Bank 0 may disappear from 0x0000-0x3FFF, breaking the interruption vectors. This can cause interrupts to execute corrupt or garbage code, leading to a system reboot.
- Interruption Vectors:All interrupt vectors (0x0040-0x0060) are in Bank 0 of ROM. If he Bank 0 is not mapped correctly, these vectors will point to incorrect data, causing crashes or reboots.
What remains to be confirmed
- Reboot frequency:How often do reboots occur? Is it constant or intermittent? The logs of
[RESET-WATCH]They will tell us this. - Cause of reboot:Is it the MBC1 changing modes, a corrupted battery, or something else? The logs of
[MBC1-MODE]and SP status in[RESET-WATCH]They will help us identify the cause. - V-Blank handler status:Does the V-Blank handler run correctly, or is it never reached? The logs of
[VBLANK-ENTRY]They will tell us if the vector 0x0040 is accessible.
Hypotheses and Assumptions
Main hypothesis:The game is in a restart loop caused by one of these issues:
- MBC1 accidentally switches to Mode 1, shifting Bank 0 out of 0x0000-0x3FFF and breaking the interrupt vectors.
- The stack becomes corrupted, causing a RET or POP to execute corrupted code that jumps to 0x0000.
- An unimplemented or poorly emulated opcode causes unexpected behavior leading to a jump to 0x0000.
The implemented monitors will allow us to confirm or refute these hypotheses by observing the logs during execution.
Next Steps
- [ ] Run Pokémon Red with the monitors active and analyze the logs
[RESET-WATCH]to confirm if there is a reset loop - [ ] Check for messages
[MBC1-MODE]indicating incorrect mode changes - [ ] Analyze the logs
[VBLANK-ENTRY]to check if the V-Blank handler runs correctly - [ ] If a reset loop is confirmed, identify the root cause (MBC1, stack, opcode) and correct it
- [ ] If MBC1 is switching modes incorrectly, investigate what game code is causing it and why