⚠️ Clean-Room / Educativo

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.

ROM Protection & Interrupt Trace

Fecha: 2025-12-23 Step ID: 0252 Estado: Draft

Resumen

Este Step implementa dos mejoras críticas de integridad: protección de ROM y rastreo de interrupciones. El análisis del Step 0251 reveló que el juego estaba escribiendo en el rango de ROM (`0x0000-0x7FFF`), lo que podría corromper el código del juego en tiempo de ejecución. Además, el misterio de `IME:0` constante requiere instrumentación para detectar quién desactiva las interrupciones (¿es `DI`? ¿o una interrupción que se dispara?).

Concepto de Hardware

Protección de ROM

En una Game Boy real, la ROM del cartucho (`0x0000-0x7FFF`) es físicamente de solo lectura. Intentar escribir en este rango no modifica los datos de la ROM, sino que se envía al MBC (Memory Bank Controller) del cartucho para controlar el cambio de bancos de memoria.

Cartuchos "ROM ONLY" (Type 0x00): Los cartuchos sin MBC (como Tetris) no tienen bancos de memoria. En estos casos, las escrituras en el rango de ROM simplemente se ignoran silenciosamente. El hardware no genera errores, pero tampoco modifica la memoria.

El Problema en Nuestro Emulador: Si nuestra MMU permite escribir directamente en `memory_[addr]` para direcciones en el rango de ROM, estamos permitiendo que el juego se automutile. Si el juego sobrescribe su propio código con valores como `0xFD`, cualquier ejecución futura leerá instrucciones corruptas, causando saltos erráticos, cuelgues y comportamiento extraño.

Fuente: Pan Docs - "Memory Map", "Cartridge Types"

Rastreo de Interrupciones

El sistema de interrupciones de la Game Boy tiene dos estados críticos:

  • IME (Interrupt Master Enable): Flag global que habilita/deshabilita todas las interrupciones.
  • IF (Interrupt Flag): Registro que indica qué interrupciones están pendientes.
  • IE (Interrupt Enable): Registro que indica qué interrupciones están habilitadas.

¿Quién desactiva IME? Hay dos formas principales:

  1. Instrucción `DI` (0xF3): Desactiva IME inmediatamente. Se usa típicamente al inicio de rutinas críticas.
  2. Procesamiento de Interrupción: Cuando se dispara una interrupción, el hardware desactiva IME automáticamente para evitar interrupciones anidadas. El ISR (Interrupt Service Routine) puede reactivar IME con `EI` si lo necesita.

El Misterio del Step 0251: El GPS mostraba `IME:0` constante, a pesar de que vimos un `[EI]`. Necesitamos saber quién apaga las interrupciones para entender por qué el juego no responde a las interrupciones de V-Blank.

Fuente: Pan Docs - "Interrupts", "DI", "EI"

Implementación

1. Protección de ROM en MMU

Se añadió una verificación en el método `MMU::write()` que previene escrituras en el rango de ROM (`0x0000-0x7FFF`). La protección se coloca después de todos los casos especiales (DIV, TIMA, TMA, TAC, P1) pero antes de la escritura directa en `memory_`.

// --- Step 0252: PROTECCIÓN DE ROM ---
if (addr < 0x8000) {
    // ROM es de solo lectura: NO escribir en memory_
    // Los logs de SENTINEL y DMA ya se registraron arriba si aplicaban,
    // pero no modificamos la memoria para evitar corrupción.
    return;
}
// -----------------------------------------

Decisiones de Diseño:

  • Los logs de `SENTINEL` y `DMA` se mantienen para diagnóstico, pero la memoria no se modifica.
  • No generamos errores ni warnings: el hardware real simplemente ignora estas escrituras silenciosamente.
  • Para cartuchos con MBC, esta lógica debería extenderse para manejar el cambio de bancos, pero por ahora es suficiente para cartuchos "ROM ONLY".

2. Rastreo de Interrupciones en CPU

Se añadieron dos puntos de instrumentación:

  1. En `case 0xF3` (DI): Log cuando se ejecuta la instrucción `DI`.
  2. En `handle_interrupts()`: Log cuando se dispara una interrupción y se desactiva IME.
// En case 0xF3 (DI):
printf("[DI] ¡Interrupciones Deshabilitadas en PC:%04X!\n", regs_->pc);

// En handle_interrupts():
printf("[INT] ¡Interrupcion disparada! Tipo: %02X. Saltando a Vector. (IME desactivado)\n", pending);

Formato de los Logs:

  • [DI]: Indica que se ejecutó la instrucción `DI` en la dirección PC especificada.
  • [INT]: Indica que se disparó una interrupción. El valor `Tipo` es el bit de interrupción activado (0x01=V-Blank, 0x02=LCD STAT, 0x04=Timer, 0x08=Serial, 0x10=Joypad).

Archivos Afectados

  • src/core/cpp/MMU.cpp - Añadida protección de ROM en el método write() (líneas ~399-408).
  • src/core/cpp/CPU.cpp - Añadidos logs de rastreo en case 0xF3 (DI) y handle_interrupts().

Tests y Verificación

Para verificar la implementación, se debe ejecutar el emulador con Tetris y analizar los logs:

  1. Recompilar: .\rebuild_cpp.ps1
  2. Ejecutar con redirección: python main.py roms/tetris.gb > debug_252.log 2>&1
  3. Analizar logs:
    • ROM: ¿Desapareció el log Write SENTINEL [0065]? (O al menos, ¿seguro que no corrompe la memoria?)
    • IME: Buscar en el log:
      • ¿Ves [DI] después del [EI]?
      • ¿Ves [INT] ¡Interrupcion disparada!?

Validación Esperada:

  • Los logs de Write SENTINEL [0065] pueden seguir apareciendo (para diagnóstico), pero la memoria ROM no debe corromperse.
  • Si vemos [DI] después de [EI], sabremos que el juego está desactivando interrupciones explícitamente.
  • Si vemos [INT], sabremos que las interrupciones se están disparando, pero IME se desactiva automáticamente.

Validación de módulo compilado C++: Los cambios están en código C++ compilado, por lo que se requiere recompilación antes de probar.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • ROM es de solo lectura: En hardware real, escribir en ROM no modifica los datos, sino que se envía al MBC. Para cartuchos "ROM ONLY", estas escrituras simplemente se ignoran.
  • Corrupción de memoria: Si permitimos que el juego escriba en su propio código, cualquier lectura futura puede devolver datos corruptos, causando saltos erráticos y comportamiento impredecible.
  • IME se desactiva automáticamente: Cuando se dispara una interrupción, el hardware desactiva IME para evitar interrupciones anidadas. El ISR puede reactivar IME con `EI` si lo necesita.

Lo que Falta Confirmar

  • ¿El juego está usando DI?: Los logs de [DI] nos dirán si el juego está desactivando interrupciones explícitamente después de EI.
  • ¿Las interrupciones se están disparando?: Los logs de [INT] nos dirán si las interrupciones se están disparando, pero IME se desactiva automáticamente.
  • ¿La protección de ROM resuelve el problema?: Si la corrupción de ROM era la causa del comportamiento errático, proteger la ROM debería mejorar significativamente la estabilidad del emulador.

Hipótesis y Suposiciones

Hipótesis Principal: La corrupción de ROM (escribir `0xFD` en `0x0065`) estaba causando que el juego lea instrucciones corruptas, lo que provocaba saltos erráticos y el comportamiento extraño que observamos. Proteger la ROM debería prevenir esta corrupción y mejorar la estabilidad.

Suposición sobre IME: Asumimos que el juego está desactivando IME después de activarlo con `EI`, o que las interrupciones se están disparando pero IME se desactiva automáticamente. Los logs nos dirán cuál es el caso real.

Próximos Pasos

  • [ ] Ejecutar el emulador con Tetris y analizar los logs de protección de ROM.
  • [ ] Verificar si los logs de [DI] y [INT] revelan quién desactiva IME.
  • [ ] Si la protección de ROM resuelve el problema, considerar implementar manejo de MBC para cartuchos con bancos de memoria.
  • [ ] Si IME se desactiva automáticamente por interrupciones, verificar que el ISR reactiva IME correctamente.