This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Operation Rebirth: Dissection of HALT's Initialization Routine and Watchdog
Summary
This Step implements "Operation Rebirth" to dissect Pokémon Red's initialization routine
where interrupts are disabled. Analysis of Step 0274 revealed that the game runsD.I.(0xF3) inPC:1F54and write0x00in0xFFFF(IE) inPC:1F58,
causing "technical suicide" that locks the game into a permanent coma.
Instrumentation was added at three critical points: (1) Kill Zone Sniper Trace (1F54-1F60) to capture the exact sequence of opcodes, (2) Bank Watcher to detect changes of MBC bank that can disorient the tracking, and (3) "HALT of Death" Watchdog to detect when the CPU goes into HALT with IE=0 and IME=0, a permanent strike state. The goal is to understand why the game does not reactivate interrupts after disabling them.
Hardware Concept
The Game Boy's interrupt system is critical to the operation of the game. When a game
disables all interrupts (both IME and IE), enters a "sensory isolation" state where
no external event can wake up the CPU. This is especially dangerous when combined with
instructionHALT.
1. The HALT Instruction (0x76)
The instructionHALTputs the CPU into a low-power state where it stops executing instructions
until an interruption occurs. However, there is a special behavior known as "HALT bug":
- If IME=1:The CPU goes into HALT and waits for an interrupt. When this occurs, the CPU exits HALT and processes the interrupt normally.
- If IME=0 but there is pending interruption (IE & IF != 0):The CPU does NOT go into HALT. Simply continue executing the next statement. This is the "HALT bug" documented in Pan Docs.
- If IME=0 and IE=0:The CPU goes into HALT andnever comes out. This is a state "permanent strike" that blocks the game completely.
2. The Danger of Infinite Waiting States
When a game disables all interrupts (IE=0x00) and then runsHALT, the CPU enters
in a permanent coma. No external event can wake her up because:
- IME=0:The CPU does not process interrupts even if they are enabled in IE.
- IE=0:No interrupts are enabled, even if the hardware requests them.
- HALT active:The CPU is in a standby state and does not execute instructions.
This is a "technical death" state of the game. The only escape would be for the hardware to reset the console, but in an emulator this does not happen automatically.
3. ROM Bank Changes (MBC)
Games with multiple ROM banks (MBC1, MBC2, MBC3, MBC5) can change banks by writing to the range0x2000-0x3FFF. When this happens, the same PC points to different code. If the game changes
bank right after disabling interrupts, the trace may be lost because the expected code
see in one bank may be in another.
It is critical to monitor these bank changes to understand the actual execution flow of the game.
Implementation
Three instrumentation systems were implemented to dissect the initialization routine and detect permanent lock states:
1. Death Zone Sniper Trace (1F54-1F60)
Added an instrumentation block to the end of the methodstep()inCPU.cppthat
Captures CPU status when PC is in range0x1F50-0x1F65. This area contains the
critical sequence where it is executedD.I.and it is written0x00in0xFFFF.
The trace captures:
- Current PC and next 3 opcodes
- Status of all records (AF, BC, DE, HL)
- IME status (0 or 1)
- Value of IE (0xFFFF) and IF (0xFF0F)
It is limited to 100 traces to avoid saturating the logs, but it is enough to see the complete sequence of the initialization routine.
2. Bank Watcher
Added an instrumentation block in the methodwrite()ofMMU.cppthat detects
any write in range0x2000-0x3FFF, which is the MBC control area for changes
ROM bank.
The monitor prints:
- Written value (the new bank requested)
- PC from which the write was executed
- Current ROM bank before change
This allows you to track if the game changes banks right after disabling interruptions, which could explain why the trace is lost.
3. Watchdog from "HALT of Death"
Added a detection block in thecase 0x76(HALT) ofCPU.cppthat detects
when the CPU tries to enter HALT withIE=0andIME=0. This is the state of
"permanent strike" that blocks the game.
The watchdog prints a critical warning to the PC where the HALT occurred, allowing it to identify exactly where the game technically commits suicide.
Design decisions
100 trace limit for Sniper-INIT:The initialization zone is executed once a start of the game, so 100 traces are more than enough to capture the entire sequence. This avoids saturate logs with redundant information.
No limit for Bank Watcher:Bank changes can occur at any time during execution, but are relatively infrequent. The number of logs is not limited to ensure so you don't miss any critical changes.
Early detection of HALT of Death:The watchdog runs before the CPU comes into power. HALT, allowing you to capture the exact PC where the problem occurs. This is critical for diagnosis.
Affected Files
src/core/cpp/CPU.cpp- Added Death Zone Sniper Trace (1F54-1F60) and HALT of Death Watchdogsrc/core/cpp/MMU.cpp- Added Bank Watcher
Tests and Verification
The instrumentation is validated by running the emulator with Pokémon Red and analyzing the logs. generated. The logs should show:
- [SNIPER-INIT]:The exact sequence of opcodes in the kill zone (1F54-1F60),
including the
D.I.(0xF3) and writing to IE (0xFFFF). - [MBC-WRITE]:Any ROM bank change that occurs during or after the Disabling interruptions.
- [CRITICAL WARNING]:If the game enters HALT with IE=0 and IME=0, confirming the "technical suicide."
Test command:
python main.py roms/pkmn.gb
Expected validation:The logs must reveal:
- The exact sequence of opcodes that accompany interrupt shutdown.
- If the game changes ROM bank right after disabling interrupts.
- If the game goes into HALT with IE=0 and IME=0, confirming the permanent lock.
Compiled C++ module validation:The build should complete without errors and the logs should appear during the emulator execution. The [SNIPER-INIT] logs should appear at the beginning of execution when the PC passes through the initialization zone.
Sources consulted
- Bread Docs:CPU Instruction Set- Behavior of the HALT instruction and the HALT bug
- Bread Docs:Interrupts Section- Interrupt system and conditions for processing interrupts
- Bread Docs:Memory Bank Controllers- Control of ROM banks by writing to 0x2000-0x3FFF
- Analysis of Step 0274: Identification of PC:1F54 (DI) and PC:1F58 (IE=0x00) as a kill zone
Educational Integrity
What I Understand Now
- HALT bug and crash states:The HALT instruction has a behavior especially when IME=0. If interrupts are pending, the CPU does not go into HALT (documented bug). But if IE=0 and IME=0, the CPU goes into HALT and never exits, causing a permanent hang.
- Death zone (1F54-1F60):This zone contains the critical sequence where the game disables all interruptions. Capturing this sequence is essential to understand why the game does not reactivate interruptions afterwards.
- ROM bank changes:Games can change ROM banks at any time, which can disorient tracking if not monitored. It is critical to detect these changes to understand the actual flow of execution.
What remains to be confirmed
- Exact sequence of opcodes:The [SNIPER-INIT] logs should reveal the sequence full of opcodes in the kill zone. This will allow you to disassemble the exact code that the game runs and understand why it doesn't reactivate interrupts.
- Bank changes:[MBC-WRITE] logs should show if the game changes banks right after disabling interrupts. If this occurs, it may explain why the trace is lose.
- HALT of Death Confirmation:If [CRITICAL WARNING] appears, we will confirm that the game goes into HALT with IE=0 and IME=0, causing the game to crash permanently. This would confirm the "technical suicide" hypothesis.
Hypotheses and Assumptions
Main hypothesis:The game disables interruptions in the death zone (1F54-1F58) to perform critical tasks (such as clearing VRAM), but then does not reactivate them before entering a waiting loop. This causes the game to wait for an interrupt that can never be processed, blocking the game permanently.
Assumption on bank changes:We assume that the game does not change banks during the initialization routine, but Bank Watcher will confirm or refute this. If there are bank changes, you can explain why the trace is lost.
Assumption about HALT:We assume that the game can enter HALT with IE=0 and IME=0, causing the blockage. The watchdog will confirm if this occurs and on which exact PC.
Next Steps
- [ ] Run the emulator with Pokémon Red and analyze the generated logs
- [ ] Disassemble the opcode sequence captured by [SNIPER-INIT] to understand the exact flow
- [ ] Check for ROM bank changes during boot routine ([MBC-WRITE])
- [ ] Confirm if the game enters HALT with IE=0 and IME=0 ([CRITICAL WARNING])
- [ ] If the problem is identified, implement correction or adjustment in the emulator