Arreglando Galaga

Galaga_flyer.jpg

Los más viejos del lugar y los aficionados a la informática retro seguro que conocen Galaga, un videojuego que aunque tuvo mucho éxito, no lo tuvo tanto como su predecesor Galaxian. Fue creado por la empresa Namco en 1981.

Como suele pasar, con prácticamente cualquier tipo de software, Galaga también tuvo sus fallos de programación, que los jugadores más audaces eran capaz de descubrir y aprovecharse de ellos para conseguir llegar hasta el final del juego, aunque en esto caso al igual que en el caso pantalla partida del PacMan el final sea un poco impredecible y depende del nivel de dificultad con el que estemos jugando.

En el caso que nos ocupa, Galaga tenía un fallo que, si dejamos una de las abejas, o si lo prefieres marcianos, concretamente de la columna izquierda viva y esperamos unos 15 minutos, dicha abeja deja de disparar, pero además esto se extiende al resto del juego, es decir, por el resto del juego las abejas ya no disparan por lo que tienes vía libre para llegar tan lejos como quieras o hasta que cuelgues el juego :)

En Computer Archeology han hecho un completo y detallado análisis del por qué se produce el error e incluso van más allá y proveen un parche para solucionarlo.

El juego (la máquina recreativa) tenía 3 CPUs: CPU 1 dónde se encontraba el código principal del juego, CPU 2 contenía varias rutinas de ayuda para liberar a la CPU 1 carga de trabajo y CPU 3 encargada exclusivamente al procesado del sonido.

El autor del artículo a través de ingeniería inversa que centra en el ciclo de vida del disparo de las abejas. Desde que se inicia el disparo, hasta que éste desaparece de la pantalla.

La primera rutina que se analiza es (llamada por el autor): InitiateBeeShot (inicio del disparo de la abeja). En esta rutina una de las cosas a destacar es que existe una estructura de datos en la posición de memoria $8868 que es dónde se guardan los disparos “activos”. Esta estructura está compuesta de 8 posiciones de 2 bytes cada una. Cuando el primer byte es 80 indica posición vacía, por lo tanto se puede producir otro disparo y cuando el primer byte es 06 indica posición ocupada, con lo que la rutina principal tendrá que buscar en el resto de las posiciones de dicha estructura y si encuentra alguna posición que empiece con 80 se producirá un disparo, en caso contrario no se produce ningún disparo. De aquí podemos deducir que sólo pueden haber 8 disparos (de abejas) a la vez activos o en pantalla. 0D78: 21 68 88 LD HL,$8868 ; Shot pointers 0D7B: 06 08 LD B,$08 ; 8 shots 0D7D: 7E LD A,(HL) ; Get shot info 0D7E: FE 80 CP $80 ; Shot active? 0D80: 28 06 JR Z,$D88 ; No – use it 0D82: 2C INC L ; Try … 0D83: 2C INC L ; … next slot. 0D84: 10 F7 DJNZ $D7D ; Try all slots

Esta estructura es punto clave y lo siguiente que hace al autor es, con la ayuda de una técnica conocida como Instrumentación. Esta técnica se usa para interceptar/analizar ciertas partes del código y es muy usada en cosas como cobertura de código, profiling, etc. Básicamente lo que hace el autor es insertar varias instrucciones (realmente sobrescribe la rutina de detección de colisiones) para imprimir en pantalla la estructura anteriormente comentada dónde se guardan los disparos de las abejas.

galaga_shotslots.png

Como podemos ver los “6” indican posición activa y el “0” indica que dicha posición no esta ocupada. Como vemos en la imagen de la izquierda, vemos dos “6” y dos disparos. Después de jugar un rato y dejar a la abeja inferior derecha viva, vemos como no se ven disparos en la pantalla, pero las ocho posiciones de memoria aparecen como ocupadas (“6”). A partir de este momento esa estructura nunca se inicializa y por lo tanto en las siguientes pantallas ya no se producen disparos por parte de las abejas, sólo los tuyos.

La pregunta del millón es ¿Por qué y cómo la estructura se queda en ese estado?

Siguiendo con el análisis, en la rutina MoveBeeFire, el autor identifica algo curioso. Dicha rutina es la que dibuja/mueve el disparo a través de la pantalla, pero existe un caso en el que el disparo es ignorado y no dibuja nada y éste es cuando el sprite (abeja) que inicializa el disparo se encuentra la coordenada X = 0, esto es en el borde izquierdo de la pantalla.

; ; Loop Here 1EC5: 26 8B LD H,$8B ; Sprite color code 1EC7: 7E LD A,(HL) ; Get sprite color 1EC8: FE 30 CP $30 ; Sprite color of a bee shot? 1ECA: 20 39 JR NZ,$1F05 ; Not 30 - skip moving it ; 1ECC: 26 93 LD H,$93 ; Sprite position 1ECE: 7E LD A,(HL) ; Get position 1ECF: A7 AND A ; Set flags 1ED0: 28 33 JR Z,$1F05 ; If it is 0, skip moving it ;

Mirando la rutina completa, ésta sólo se encarga de dibujar el disparo y no de liberar las posición ocupada por el disparo asignada por la función InitiateBeeShot.

¿Entonces como se liberan dichas posiciones? La respuesta está una de las tablas de comandos internas que se encarga de “limpiar” el disparo una vez éste a llegado hacia abajo del todo. Este código se encuentra la dirección $257F:

; Remove item from active duty 257F: CB BD RES 7,L ; 2581: 1A LD A,(DE) ; Type 2582: FE 03 CP $03 ; 2584: 28 0A JR Z,$2590 ; 2586: 3E 80 LD A,$80 ; Flag free slot 2588: 12 LD (DE),A ; Here it is – shots are erased here. 2589: 26 93 LD H,$93 ; Free … 258B: 36 00 LD (HL),$00 ; … sprite 258D: C3 24 24 JP $2424 ; Do next

Por lo tanto cuando el juego comienza todas las abejas se mueven por dentro de la pantalla sin llegar a los bordes, pero cuando quedan sólo 6 abejas en juego, el patrón de movimiento del juego cambia y éste hace que en ciertos momentos, las abejas de la columna de la izquierda, acaban llegando al borde izquierdo (coordenada X = 0) acaban disparando. Esto ocurre con una media de cada 2 minutos, por lo que después de 15 minutos (si contamos desde 0) las 8 posiciones de memoria que guardan el estado de los disparos activos quedan ocupadas para siempre.

Para recapitular, InitiateBeeShot inicia el disparo y pone una de las posiciones de memoria como activa (06), la rutina que pinta en pantalla el disparo MoveBeeFire, si la abeja se encontraba en la posición X = 0, ignora dicho disparo y no dibuja nada, por lo tanto la rutina que se encarga de limpiar el disparo (en la posición $257F) cuando llegue hasta la parte baja de la pantalla y por lo tanto liberar la posición de memoria ocupada por éste, como dicho disparo nunca ha sido pintado en pantalla, nunca es detectado por la misma y la posición de memoria ocupada por dicho disparo nunca es liberada.

El artículo continúa incluso con parche para solucionar con este problema. Échale un vistazo al texto original porque hay muchos más detalles que no os he contado aquí.