Herramientas

Qué hace cada programa del proyecto. Todas son Python 3 sin dependencias externas, salvo las de openMSX que son scripts TCL.

El camino principal

Esto es lo que make ejecuta, en orden:

tsx_parse.py — abrir la cinta

python3 tools/tsx_parse.py cinta.tsx extracted

Lee el fichero TSX, identifica sus bloques y saca los programas que contiene. Reconoce las cabeceras de fichero del MSX, así que sabe decirte que un bloque es "un binario llamado SLOTS que se carga en 0xC350 y se ejecuta en 0xC58F".

También escribe un manifiesto para poder reconstruir la cinta después.

z80trace.py — separar el código de los datos

python3 tools/z80trace.py juego.bin 0x4000 entradas.txt salida [zonas.nocode]

Éste es el corazón del proyecto. Desensamblar 40 KB de corrido no funciona: en cuanto te topas con un gráfico, los píxeles se interpretan como instrucciones y a partir de ahí todo queda desalineado.

Así que en vez de leer de corrido, sigue el flujo del programa: empieza en un punto de entrada conocido, decodifica cada instrucción, y sigue los saltos y las llamadas. Lo que se alcanza así es código; el resto, datos.

Tiene dos particularidades que costaron sangre:

Los saltos indirectos (JP (HL), tablas de punteros) no se pueden seguir sin ejecutar el programa. El trazador los marca como punto ciego y los lista, para que se resuelvan a mano.

mkasm.py — montar el listado comentado

python3 tools/mkasm.py juego.bin 0x4000 trazado.json notas.txt simbolos.sym salida.asm "Titulo"

Junta el binario, el mapa de código/datos y las anotaciones, y produce el .asm.

Usa z80dasm solo para los mnemónicos. Las etiquetas de la BIOS las añade él como comentario, y solo en instrucciones de salto: si se dejara a z80dasm poner símbolos, convertiría cualquier número que coincidiera con una dirección conocida, y salían cosas como ld bc,CHRGTR donde el código dice en realidad ld bc,0x0010 — una longitud, no una dirección.

verify_build.sh — la comprobación que lo sostiene todo

./tools/verify_build.sh listado.asm original.bin 0x4000

Ensambla el listado y compara con el binario original. Si no salen idénticos, algo se ha interpretado mal.

check_trace.py — cazar la cobertura falsa

python3 tools/check_trace.py trazado.json zonas.nocode

Comprueba que las zonas que sabemos que son datos no hayan quedado marcadas como código.

Es la comprobación que verify_build.sh no puede hacer: si un dibujo se lee como instrucciones los bytes no cambian, solo su interpretación, y el reensamblado sigue cuadrando.

coverage.py — el presupuesto de bytes

python3 tools/coverage.py trazado.json notas.txt 40449 0x4000

Cuenta cuántos bytes están explicados —o como código trazado, o dentro de un rango de datos identificado— y lista los huecos que quedan.

Es la métrica que de verdad mide el avance. El porcentaje de código no sirve: este juego es 87% datos, así que "12,9% de código" suena a desensamblado a medias cuando en realidad está completo.

Para mirar el juego

render_maps.py — dibujar las pantallas

python3 tools/render_maps.py juego.bin docs/mapas

Dibuja los 29 mapas como PNG, usando la fuente y los colores del propio juego. Escribe los PNG a mano con zlib, sin librerías.

Sirve de comprobación visual: si el formato de mapa estuviera mal interpretado, saldría ruido.

Respeta un detalle del hardware que es fácil pasar por alto: el color 0 del MSX no es negro, es transparente, y por debajo se ve el color de fondo. Por eso el nivel 4 se dibuja sobre verde y el resto sobre negro.

render_vram.py — dibujar lo que se ve ahora mismo

python3 tools/render_vram.py volcado.vram salida.png

Igual que el anterior, pero partiendo de un volcado de la memoria de vídeo del emulador: es lo que está literalmente en pantalla en ese instante.

charset.py — leer los textos

python3 tools/charset.py juego.bin 0x4000 8

El juego no usa ASCII. Esto tiene su tabla de caracteres y busca cadenas con ella.

inventario.py, make_gallery.py

Generan el listado de rutinas identificadas y la galería de pantallas.

Para verificar de verdad: los scripts de openMSX

Aquí está la diferencia entre suponer y saber. En vez de deducir qué hace una rutina leyéndola, se pone el juego a correr y se observa.

TEMPT_TSX="$PWD/cinta.tsx" TEMPT_OUT="$PWD/dump" \
  openmsx -machine Philips_VG_8020-20 -script tools/omsx_load.tcl
ScriptPara qué
omsx_load.tclCarga la cinta entera y vuelca la memoria en cada etapa. Sirvió para usar el cargador original del juego como decodificador: en vez de reimplementar el turbo loader, se deja que él haga el trabajo y se captura el resultado.
omsx_run.tclGuarda un savestate justo cuando arranca el juego. A partir de ahí las pruebas son instantáneas en vez de esperar siete minutos.
omsx_watch.tclWatchpoints: dice qué código lee o escribe una dirección. Así se identificó el contador de vidas: no por deducción, sino viendo que solo una rutina lo lee y que esa rutina lo pinta en la casilla exacta del marcador.
omsx_pcsample.tclMuestrea el contador de programa mientras el juego corre. Cada dirección capturada es código ejecutado de verdad.
omsx_huecos.tclVigila varios rangos a la vez. Con esto se resolvieron los últimos 642 bytes sin explicar.
omsx_screen.tclVuelca la memoria de vídeo para ver qué hay en pantalla.
omsx_poke.tclEscribe en memoria durante la partida, para probar hipótesis.
omsx_testpoke.tclPrueba el POKE de vidas infinitas forzando la rutina de morir con y sin el parche.
omsx_final.tclFuerza la pantalla final, opcionalmente con la trampa anti-tramposos activada.
omsx_fugapila.tclProvoca varios game over seguidos y mide cómo se pierde la pila.
omsx_demo.tclLanza el juego para jugarlo, acelerando la carga ×10.

Reconstruir la cinta

python3 tools/build_tape.py build build/Temptations_rebuild.tsx

Ensambla los listados, remonta los bloques con sus cabeceras y checksums, y escribe un TSX nuevo. El resultado es idéntico byte a byte al original.

tsx_build.py hace la parte de bajo nivel: reconstruir el fichero desde el manifiesto.

Auxiliares

gen_msx_syms.pySaca la tabla de rutinas de la BIOS del MSX de los headers de MSXgl, en vez de escribirla de memoria
dasm_slice.pyDesensambla por secciones con orígenes distintos. Hace falta para SLOTS, que se copia a sí mismo a otras direcciones antes de ejecutarse
find_tables.pyBusca tablas de punteros. Da falsos positivos: sirve para decidir dónde mirar, no como fuente de verdad