gameboy-sharp

Un emulador de Game Boy (DMG) en C#. Un núcleo sin dependencia del frontend, de modo que toda la máquina funciona y se verifica headless con ROMs de prueba estándar.

gameboy-sharp
TL;DR

Un emulador de Game Boy (modelo DMG) escrito en C#. El núcleo está separado del frontend y se verifica headless contra ROMs de prueba estándar, para que los juegos ajenos corran bit a bit.

Descripción general

gameboy-sharp es un emulador del Game Boy original, el modelo DMG, escrito en C#. La tarea parece simple: hacer que juegos lanzados hace treinta años funcionen sin cambiar un solo byte. La dificultad se esconde en la palabra "fielmente" - el emulador debe imitar el hardware con la precisión suficiente para que el código original no note nada.

Construimos el núcleo para que no sepa nada de una ventana ni del sonido. Toda la máquina es una biblioteca pura que devuelve un buffer de imagen y muestras de audio, y solo el frontend las convierte en lo que ves y oyes. Eso es lo que permite ejecutar toda la consola sin pantalla y comparar su resultado con lo que debería salir.

Los errores en un emulador casi nunca revientan con estruendo. El juego arranca, el juego corre - solo el sonido está un pelín demasiado agudo, o el personaje se desplaza un píxel de más. No hay excepción, no hay cuelgue, solo un comportamiento sutilmente erróneo que "a ojo" no distingues del correcto.

Eso cambia toda la forma de trabajar. No puedes fiarte de la impresión de que "parece bien", porque es justo esa impresión la que miente más a menudo. Lo único en lo que se puede confiar es en comparar el estado de la máquina con un valor conocido de antemano - y es en torno a eso que está construido todo el proyecto.

El corazón de la máquina: el procesador

El corazón de la consola es el procesador del Game Boy, una variante del Z80. Cada instrucción es la lectura de un byte de opcode y la ejecución exacta de lo que haría el original, junto con el número de ciclos que cuesta esa instrucción. El timing no es un detalle - es el que decide si el sonido y la imagen están sincronizados como en el hardware real.

Cpu.cs · csharp
byte opcode = ReadByte(PC++);

switch (opcode) {
    case 0x00: cycles += 4; break;
    case 0x3E: A = ReadByte(PC++); cycles += 8; break;
    case 0xC3: PC = ReadWord(); cycles += 16; break;
    default: cycles += Execute(opcode); break;
}

Una instrucción y su coste en ciclos

OpcodeInstrucciónCiclos
0x00NOP4
0x3ELD A, d88
0xC3JP a1616
i
Nota

El núcleo no sabe nada de una ventana ni del sonido - es una máquina pura. El frontend solo recibe un buffer de imagen y muestras de audio. Eso es lo que permite ejecutar toda la consola sin pantalla y comparar el resultado con lo esperado, ciclo a ciclo.

Pruebas al ciclo exacto

La comunidad de la emulación mantiene conjuntos de ROMs de prueba que verifican registros concretos, banderas y el timing al ciclo. Las ejecutamos headless y comparamos el estado de la máquina con lo que debería salir. Es la única forma honesta de decir que un emulador es correcto, y no solo que "funciona en mi máquina".

Precisamente por eso el núcleo se separó del frontend. Si la máquina necesitara una ventana, cada prueba habría que pincharla a mano. Como es una biblioteca pura, las ROMs de prueba pasan de forma automática, y el veredicto es binario: coincide o no.

El resultado: los juegos ajenos bit a bit

Lo que sale es un emulador que ejecuta los juegos ajenos y pasa las ROMs de prueba estándar, y no solo uno que muestra el logo de Nintendo y se detiene. El sonido está donde debe, el personaje se desplaza el número de píxeles que debe, y la corrección queda demostrada por pruebas, no por una impresión. Es un proyecto nacido del amor por el hardware antiguo y llevado a un estado en el que se nota.

Más proyectos

Más trabajos de la misma categoría - mira cómo abordamos retos parecidos.

¿Tiene un proyecto similar?

Escríbenos - el presupuesto es gratuito y llega en una hora.