Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Operación Rebirth: Disección de la Rutina de Inicialización y Watchdog de HALT
Resumen
Este Step implementa la "Operación Rebirth" para diseccionar la rutina de inicialización de Pokémon Red
donde se desactivan las interrupciones. El análisis del Step 0274 reveló que el juego ejecuta DI
(0xF3) en PC:1F54 y escribe 0x00 en 0xFFFF (IE) en PC:1F58,
causando un "suicidio técnico" que bloquea el juego en un estado de coma permanente.
Se añadió instrumentación en tres puntos críticos: (1) Sniper Trace de la zona de muerte (1F54-1F60) para capturar la secuencia exacta de opcodes, (2) Monitor de Salto de Banco (Bank Watcher) para detectar cambios de banco MBC que puedan desorientar el rastreo, y (3) Watchdog de "HALT of Death" para detectar cuando la CPU entra en HALT con IE=0 e IME=0, un estado de huelga permanente. El objetivo es entender por qué el juego no reactiva las interrupciones después de desactivarlas.
Concepto de Hardware
El sistema de interrupciones de la Game Boy es crítico para el funcionamiento del juego. Cuando un juego
desactiva todas las interrupciones (tanto IME como IE), entra en un estado de "aislamiento sensorial" donde
ningún evento externo puede despertar la CPU. Esto es especialmente peligroso cuando se combina con la
instrucción HALT.
1. La Instrucción HALT (0x76)
La instrucción HALT pone la CPU en un estado de bajo consumo donde deja de ejecutar instrucciones
hasta que ocurre una interrupción. Sin embargo, hay un comportamiento especial conocido como "HALT bug":
- Si IME=1: La CPU entra en HALT y espera una interrupción. Cuando ocurre, la CPU sale de HALT y procesa la interrupción normalmente.
- Si IME=0 pero hay interrupción pendiente (IE & IF != 0): La CPU NO entra en HALT. Simplemente continúa ejecutando la siguiente instrucción. Este es el "HALT bug" documentado en Pan Docs.
- Si IME=0 e IE=0: La CPU entra en HALT y nunca sale. Este es un estado de "huelga permanente" que bloquea el juego completamente.
2. El Peligro de los Estados de Espera Infinitos
Cuando un juego desactiva todas las interrupciones (IE=0x00) y luego ejecuta HALT, la CPU entra
en un estado de coma permanente. Ningún evento externo puede despertarla porque:
- IME=0: La CPU no procesa interrupciones incluso si están habilitadas en IE.
- IE=0: Ninguna interrupción está habilitada, incluso si el hardware las solicita.
- HALT activo: La CPU está en estado de espera y no ejecuta instrucciones.
Este es un estado de "muerte técnica" del juego. El único escape sería que el hardware resetee la consola, pero en un emulador esto no ocurre automáticamente.
3. Cambios de Banco ROM (MBC)
Los juegos con múltiples bancos ROM (MBC1, MBC2, MBC3, MBC5) pueden cambiar de banco escribiendo en el rango
0x2000-0x3FFF. Cuando esto ocurre, el mismo PC apunta a código diferente. Si el juego cambia de
banco justo después de desactivar interrupciones, el rastreo puede perderse porque el código que se espera
ver en un banco puede estar en otro.
Es crítico monitorear estos cambios de banco para entender el flujo de ejecución real del juego.
Implementación
Se implementaron tres sistemas de instrumentación para diseccionar la rutina de inicialización y detectar estados de bloqueo permanente:
1. Sniper Trace de la Zona de Muerte (1F54-1F60)
Se añadió un bloque de instrumentación al final del método step() en CPU.cpp que
captura el estado de la CPU cuando el PC está en el rango 0x1F50-0x1F65. Esta zona contiene la
secuencia crítica donde se ejecuta DI y se escribe 0x00 en 0xFFFF.
El trace captura:
- PC actual y los siguientes 3 opcodes
- Estado de todos los registros (AF, BC, DE, HL)
- Estado del IME (0 o 1)
- Valor de IE (0xFFFF) e IF (0xFF0F)
Se limita a 100 trazas para evitar saturar los logs, pero es suficiente para ver la secuencia completa de la rutina de inicialización.
2. Monitor de Salto de Banco (Bank Watcher)
Se añadió un bloque de instrumentación en el método write() de MMU.cpp que detecta
cualquier escritura en el rango 0x2000-0x3FFF, que es el área de control del MBC para cambios
de banco ROM.
El monitor imprime:
- Valor escrito (el nuevo banco solicitado)
- PC desde el cual se ejecutó la escritura
- Banco ROM actual antes del cambio
Esto permite rastrear si el juego cambia de banco justo después de desactivar interrupciones, lo que podría explicar por qué el rastreo se pierde.
3. Watchdog de "HALT of Death"
Se añadió un bloque de detección en el case 0x76 (HALT) de CPU.cpp que detecta
cuando la CPU intenta entrar en HALT con IE=0 e IME=0. Este es el estado de
"huelga permanente" que bloquea el juego.
El watchdog imprime una advertencia crítica con el PC donde ocurrió el HALT, permitiendo identificar exactamente dónde el juego se suicida técnicamente.
Decisiones de diseño
Límite de 100 trazas para Sniper-INIT: La zona de inicialización se ejecuta una vez al inicio del juego, por lo que 100 trazas son más que suficientes para capturar toda la secuencia. Esto evita saturar los logs con información redundante.
Sin límite para Bank Watcher: Los cambios de banco pueden ocurrir en cualquier momento durante la ejecución, pero son relativamente infrecuentes. No se limita el número de logs para asegurar que no se pierda ningún cambio crítico.
Detección temprana de HALT of Death: El watchdog se ejecuta antes de que la CPU entre en HALT, permitiendo capturar el PC exacto donde ocurre el problema. Esto es crítico para el diagnóstico.
Archivos Afectados
src/core/cpp/CPU.cpp- Añadido Sniper Trace de zona de muerte (1F54-1F60) y Watchdog de HALT of Deathsrc/core/cpp/MMU.cpp- Añadido Monitor de Salto de Banco (Bank Watcher)
Tests y Verificación
La instrumentación se valida mediante ejecución del emulador con Pokémon Red y análisis de los logs generados. Los logs deben mostrar:
- [SNIPER-INIT]: La secuencia exacta de opcodes en la zona de muerte (1F54-1F60),
incluyendo el
DI(0xF3) y la escritura a IE (0xFFFF). - [MBC-WRITE]: Cualquier cambio de banco ROM que ocurra durante o después de la desactivación de interrupciones.
- [CRITICAL WARNING]: Si el juego entra en HALT con IE=0 e IME=0, confirmando el "suicidio técnico".
Comando de prueba:
python main.py roms/pkmn.gb
Validación esperada: Los logs deben revelar:
- La secuencia exacta de opcodes que acompañan al apagado de interrupciones.
- Si el juego cambia de banco ROM justo después de desactivar interrupciones.
- Si el juego entra en HALT con IE=0 e IME=0, confirmando el bloqueo permanente.
Validación de módulo compilado C++: La compilación debe completarse sin errores y los logs deben aparecer durante la ejecución del emulador. Los logs [SNIPER-INIT] deben aparecer al inicio de la ejecución cuando el PC pasa por la zona de inicialización.
Fuentes Consultadas
- Pan Docs: CPU Instruction Set - Comportamiento de la instrucción HALT y el HALT bug
- Pan Docs: Interrupts Section - Sistema de interrupciones y condiciones para procesar interrupciones
- Pan Docs: Memory Bank Controllers - Control de bancos ROM mediante escrituras en 0x2000-0x3FFF
- Análisis del Step 0274: Identificación de PC:1F54 (DI) y PC:1F58 (IE=0x00) como zona de muerte
Integridad Educativa
Lo que Entiendo Ahora
- HALT bug y estados de bloqueo: La instrucción HALT tiene un comportamiento especial cuando IME=0. Si hay interrupciones pendientes, la CPU no entra en HALT (bug documentado). Pero si IE=0 e IME=0, la CPU entra en HALT y nunca sale, causando un bloqueo permanente.
- Zona de muerte (1F54-1F60): Esta zona contiene la secuencia crítica donde el juego desactiva todas las interrupciones. Capturar esta secuencia es esencial para entender por qué el juego no reactiva las interrupciones después.
- Cambios de banco ROM: Los juegos pueden cambiar de banco ROM en cualquier momento, lo que puede desorientar el rastreo si no se monitorea. Es crítico detectar estos cambios para entender el flujo real de ejecución.
Lo que Falta Confirmar
- Secuencia exacta de opcodes: Los logs [SNIPER-INIT] deben revelar la secuencia completa de opcodes en la zona de muerte. Esto permitirá desensamblar el código exacto que el juego ejecuta y entender por qué no reactiva las interrupciones.
- Cambios de banco: Los logs [MBC-WRITE] deben mostrar si el juego cambia de banco justo después de desactivar interrupciones. Si esto ocurre, puede explicar por qué el rastreo se pierde.
- Confirmación de HALT of Death: Si aparece [CRITICAL WARNING], confirmaremos que el juego entra en HALT con IE=0 e IME=0, causando el bloqueo permanente. Esto confirmaría la hipótesis del "suicidio técnico".
Hipótesis y Suposiciones
Hipótesis principal: El juego desactiva interrupciones en la zona de muerte (1F54-1F58) para realizar tareas críticas (como limpiar VRAM), pero luego no las reactiva antes de entrar en un bucle de espera. Esto causa que el juego espere una interrupción que nunca se puede procesar, bloqueando el juego permanentemente.
Suposición sobre cambios de banco: Asumimos que el juego no cambia de banco durante la rutina de inicialización, pero el Bank Watcher confirmará o refutará esto. Si hay cambios de banco, puede explicar por qué el rastreo se pierde.
Suposición sobre HALT: Asumimos que el juego puede entrar en HALT con IE=0 e IME=0, causando el bloqueo. El watchdog confirmará si esto ocurre y en qué PC exacto.
Próximos Pasos
- [ ] Ejecutar el emulador con Pokémon Red y analizar los logs generados
- [ ] Desensamblar la secuencia de opcodes capturada por [SNIPER-INIT] para entender el flujo exacto
- [ ] Verificar si hay cambios de banco ROM durante la rutina de inicialización ([MBC-WRITE])
- [ ] Confirmar si el juego entra en HALT con IE=0 e IME=0 ([CRITICAL WARNING])
- [ ] Si se identifica el problema, implementar corrección o ajuste en el emulador