A8Tools y cómo mejorar Screaming Wings para Atari 800

Viendo un especial de shooters para Atari 8-bit en el canal de Atariado recordé la existencia del juego Screaming Wings, una especie de 1942 que si bien es una buena versión para Atari, los colores usados y algunos de sus gráficos impiden ver bien y lo hacen muy difícil de jugar. Por ejemplo los enemigos son muy difíciles de ver, y las balas se confunden con las olas del mar, pensé que con unos pequeños ajustes este juego podría ser mucho mejor, bastaría con una tarde y listo.

Pero, como todo proyecto entretenido al final no tomó una tarde, sino que varios días! El resultado final es, primero, un juego notablemente mejorado – es casi otro juego – y segundo, me vi en la obligación de crear algunas herramientas para ayudar en la tarea de hacer ingeniería inversa a juegos y programas en assembler para Atari, pero eso es tema para más adelante en este mismo post. Vamos primero al juego.

Aquí van unas capturas del juego original.

Y ahora unas capturas de la versión modificada

La recomendación viene de cerca pero jugar esta nueva versión es realmente otra cosa. Pueden descargar Screaming Wings 2020 desde este enlace.

Si quieres saber cómo se hizo esta mejora y cómo puedes hacer las tuyas a este juego o a cualquier otro, continúa leyendo. Si al leer te pierdes o no entiendes una parte, envíame tu pregunta o espera a que publique mis videos tutoriales de programación en bajo nivel en donde todo esto será explicado en detalle.

Antes de continuar y quizás después de leer lo que viene te preguntarás… y para qué sirve todo esto? Primero, para entretenerse haciendo cambios a juegos que podrían haber sido mejores, y segundo y más importante para mi, porque estas herramientas (patcher y disasm) ayudarán a hacer ports de juegos existentes de Atari a mi computador CLC-88 Compy.

Cambio de colores

Todo esto fue un proceso evolutivo, no había gran plan y en cualquier momento podía desistir, asi que comencé con un modo de trabajo muy sencillo pero que terminó siendo muy efectivo.  El primer paso consistió en ajustar algunos colores y el ciclo de trabajo fue básicamente:

  1. Cargar el juego en el emulador (atari800 en Linux)
  2. Presionar F8 para entrar en modo monitor
  3. Buscar la parte donde se asignan colores
  4. Modificar un color con un valor absurdo
  5. Ver qué parte del juego cambiaba
  6. Volver al punto 2

Poco a poco empecé a encontrar todos los colores, los del avión propio, el avión grande, enemigos verticales, horizontales, las islas, etc. Fue algo bastante directo y sólo tuve problemas en aquellas partes del código que eran modificadas por otras partes del código mientras el juego corre, pero la mayoría bastaba con una modificación directa.

Para el paso 3, buscar asignación de colores, lo primero que hice fue buscar en todas las partes donde se modificaban los registros de color. Usando el Mapping the Atari sabemos que los colores del escenario se escriben en COLPF0 – COLPF3 y COLBK cuyas direcciones van desde $D016 a $D01A. Acá hay un ejemplo de como usar el comando s (search) del monitor para encontrar el uso de esas direcciones a lo largo de toda la ram ($0000-$FFFF)

Ahora, esos registros de color no son los únicos a buscar. El chip de video lee estos registros en todo momento y si el valor de uno de ellos cambia mientras se está dibujando la pantalla, el cambio de color se produce inmediatamente y no en el siguiente cuadro (frame) como uno esperaría. Para evitar este cambio desordenado y sincronizar el cambio de colores con el dibujado de cada cuadro, cada vez que se inicia un cuadro una rutina de la ROM lee los colores desde otra zona de memoria $2C4-$2C8 (COLOR0-COLOR4) y los pone en los registros $D016 – $D01A. A estas direcciones que están en RAM que se ocupan como respaldo de lo que se escribirá más tarde en los registros se les llama «registros shadow» (sombra). Por lo tanto también necesitamos buscar las modificaciones que se hacen de las posiciones de memoria $2C4-$2C8 en donde estarán los colores que se asignarán en cada frame.

Antes de cambiar los valores sólo me debía asegurar que efectivamente se tratara de código valido. Por ejemplo el siguiente es uno de los segmentos en donde se asignan colores:

442B: A9 35     LDA #$35
442D: 8D C4 02  STA $02C4   ;COLOR0
4430: 8D C7 02  STA $02C7   ;COLOR3
4433: A9 0E     LDA #$0E
4435: 8D C6 02  STA $02C6   ;COLOR2
4438: 8D C8 02  STA $02C8   ;COLOR4

En este ejemplo se está asignando el color $35 (rojo) a COLOR0 y COLOR3 y el color 0E (blanco) a COLOR2 y COLOR4. Tienen toda la pinta de ser los colores usados en la presentación: Fondo blanco con letras rojas que dicen Red Rat.

Para modificar los colores y probar, bastaba usar el comando c (change) del monitor. Por ejemplo cambiar los colores por azul y negro de el código de arriba es así:

C 442C 92
C 4434 00
CONT

El comando «cont» reanuda la emulación. Luego para probar este cambio en la presentación se debe jugar el juego, morir, volver a la presentación y ver cómo quedó.

Una vez comprobado que esos eran los colores de la presentación, para experimentar con distintos colores uno puede cargar la presentación y modificar directamente COLOR0/COLOR3 y COLOR2/COLOR4 para ver como quedan.

C 02C4 66
C 02C7 66
C 02C6 7F
C 02C8 7F
CONT

(mirar y espantarse)

C 02C4 44
C 02C7 44
C 02C6 00
C 02C8 00
CONT

Mucho mejor!

A medida que probaba iba anotando en un archivo de texto lo que iba encontrando y poco a poco comencé a descubrir todos los colores del juego. Sólo unos pocos fueron algo más complejos por ser código automodificable, pero el resto fue bastante directo. Acá se puede ver el archivo final con todos los colores encontrados.

Ahora, los colores explicados aquí corresponden a una parte solamente, y aquí conviene hacer una distinción en la forma en que el computador Atari de 8 bit genera lo que se ve en pantalla: Como se trata de un computador diseñado para juegos, el Atari incluye un poco de ayuda via hardware para superponer sobre el fondo imágenes como enemigos, aviones, monos que saltan etc. En la jerga Atari se habla del playfield, los players y lo misiles. El playfield es básicamente el fondo de la pantalla y se usan 4 colores más el color del borde que ya explicamos arriba. En este caso el mar, el portaviones y las islas son todos elementos del playfield y sus colores están definidos por los registros COLPF0-COLPF3 donde COLPF significa COLor PlayField. Los Player son elementos gráficos que se suponen al playfield, tienen 8 pixeles de ancho y pueden ocupar todo el alto de la pantalla, en este juego se usan para todos los aviones. Finalmente los misiles son como los players pero de sólo 2 pixels de ancho, y en este juego se usan para las balas. Tanto players como misiles usan los mismos registros de color y van desde $D012 a $D015, llamados COLPM0-COLPM1 donde COLPM = COLor Player Missile. Buscando modificaciones a estos registros se encuentran los colores de todos los aviones y las balas.

Cambio de gráficos

A medida que fui ajustando los colores vi que se podía ir un poco más allá y modificar algunos gráficos, específicamente las olas del agua que eran muy «ruidosas» y las ventanas de los aviones verticales que se veían muy gruesas. Sólo debía encontrar dónde estaba la definición de esos gráficos.

En Atari hay varias formas de definir los gráficos de un playfield, puede ser a través de un modo de video pixmap en donde cada pixel se controla en forma independiente, o en modo caracteres donde se definen bloques de pixels y el chip de video se encarga de dibujarlos. En el modo pixmap de dos colores, por cada byte que escribimos en memoria de video, cada uno de los 8 bits de ese byte representará un pixel encendido si es 1 o un pixel apagado si el bit es 0. Por ejemplo si «-» es pixel apagado y «O» es pixel encendido veremos estos gráficos para estos bytes / bits:

$55 = 01010101 = -O-O-O-O
$67 = 01100111 = -OO--OOO
$FF = 11111111 = OOOOOOOO
$F8 = 11111000 = OOOOO---

En el modo pixmap se pueden hacer gráficos muy detallados, pero el procesador del Atari es lento, y dibujar una pantalla completa en este modo se demora demasiado, por lo que muchos juegos usan otro modo, el modo de caracteres.

En modo de caracteres cada byte escrito no define pixels, sino que indica cual caracter o grupo de pixels se desplegará en pantalla. El modo de texto con el que parte el Atari es un modo de caracteres de dos colores, cada caracter es de 8×8 pixels. Como podrán suponer, cada linea de 8 pixels es un byte donde cada bit indica si el pixel está encendido o no. Por ejemplo la secuencia 00 18 3C 66 7E 66 66 00 representaría la letra A, de la siguiente forma:

$00 = 00000000 = --------
$18 = 00011000 = ---OO---
$3C = 00111100 = --OOOO--
$66 = 01100110 = -OO--OO-
$7E = 01111110 = -OOOOOO-
$66 = 01100110 = -OO--OO-
$66 = 01100110 = -OO--OO-
$00 = 00000000 = --------

En Atari la letra A es el caracter número 33, a nivel de programación solo escribimos el valor 33 en memoria y el chip de video automáticamente dibujará la secuencia de pixeles de arriba. Cómo lo sabe? Porque hay un registro llamado CHBASE ($D409) que indica en qué parte de la memoria comienza la definición de caracteres y simplemente calculando 8*C encuentra la definición de 8 bytes para el caracter C.

Por ejemplo si CHBASE = $23, la secuencia para el caracter 33 estará en $2300 + 33*8 = $2300 + 264 = $2408

Además de este modo de dos colores, Atari permite modos con 4 colores usando dos bits por cada pixel, de la siguiente forma:

00 = color 0
01 = color 1
10 = color 2
11 = color 3

Luego los siguientes bytes generarán los siguientes pixels de colores que van del 0 al 3:

$55 = 01010101 = 1111
$67 = 01100111 = 1213
$FF = 11111111 = 3333
$F8 = 11111000 = 3320

Como son dos bits por cada pixel sólo tenemos 4 pixeles por cada byte. En modo caracteres ahora en vez de tener 8×8 pixeles tendremos solamente 4×8 pixeles. Éste es el modo que usa Screaming Wings y muchos otros juegos.

Con CHBASE podemos obtener la ubicación de memoria de todos los gráficos del playfield. Nuevamente CHBASE es un registro directo, para coordinar la generación de cada cuadro hay un registro shadow en $2F4 (CHBAS). En la siguiente captura podemos ver el valor de 2F4 ($38) y luego los gráficos que se encuentran en esa zona $3800.

Podriamos decodificar todos los gráficos hasta encontrar los que buscamos, pero sin una herramienta eso puede ser un poco tedioso. Lo que hice fue hacer que el juego dibujara una pantalla y luego buscar la memoria de video en donde estaba esa pantalla.

Sin extender mucho esta parte de la explicación, el chip de video lee una serie de instrucciones (display list) y una de ellas dice en qué parte de la memoria está la pantalla. En la posición SDLIST ($230 y $231) se indica donde están esas instrucciones. Para encontrar los gráficos del mar me puse a jugar hasta que la pantalla se llenó de mar y presioné F8 para volver al monitor. En la captura podemos ver que las instrucciones que lee el chip de video (display list) se encuentran en $9C20 y están destacadas en blanco.

La secuencia de instrucciones que nos interesa es la que dice C2 4D 93, y nos indica que la memoria de video que representa la pantalla actual que es casi puro mar comienza en $934D, veamos la siguiente captura:

Si eso es pura agua se ve claramente que los caracteres para el agua son $39 y $3A, y si CHBASE apunta a $3800 entonces es cosa de ir a mirar a $3800 + $39*8 y $380 + $3A*8, que resulta ser $39C8 y $39D0. A modo de prueba podemos modificar esos gráficos para convertirlos en simples lineas verticales.

En mi parche hice un cambio similar, pero en vez de lineas verticales básicamente reduje la cantidad de pixeles para las olas y con eso mejoró bastante la visibilidad.

Cambio de gráficos en aviones (players)

Para encontrar los gráficos de players es un poco más simple, hay un registro llamado PMBASE ($D407) que indica donde está la definición de estos gráficos, como son 8 pixeles de ancho con sólo dos colores: encendido o transparente, cada byte representa una linea, sin entrar en más detalles así se ve la definición de un avión en la memoria apuntada por PMBASE. El avión son dos players superpuestos, uno es el color de fondo del avión y el otro la parte iluminada.

El chip de video dibujará cualquier cosa que encuentre en esa memoria por lo que ésta siempre está cambiando, si modificamos directamente ahí el juego lo sobre escribirá porque es así como realiza la animación del player.  Lo que hay que hacer para modificar esos gráficos es buscar en donde se encuentran los datos originales del avión que después son copiados a esta zona:

Patcher tool

Todos estos cambios los fui probando y documentando en un archivo de texto, pero cómo aplicarlos al archivo original para generar una nueva versión?

Lo que hice fue una pequeña utilidad en PHP que lee el archivo XEX del juego y procesa una lista de comandos desde un archivo de texto. Los comandos son los mismos «c» que ya usamos en el monitor y agregué un sencillo comando de search and replace, para buscar una secuencia de bytes y reemplazarla por otra. El archivo de cambios que ejecuta el patcher en Screaming Wings se puede ver aquí. Un sencillo ejemplo es:

c 442c 42 ; red rat
c 4434 00 ; background
c 4456 08 ; software
c 4354 ff ; text color

; big plane color (90) search / replace
s A9 26 8D C3 02 8D C2 02 A9 03 8D 0A D0 8D 0B D0
r A9 90 8D C3 02 8D C2 02 A9 03 8D 0A D0 8D 0B D0

Si en algún momento quiero continuar haciendo cambios, o quiero experimentar rápidamente otros colores o gráficos, simplemente edito mi archivo de patch y corro el patcher tool para generar un nuevo ejecutable.

Disasm tool

Después de hacer estos cambios quedé con ganas de hacer otras mejoras como por ejemplo agregar más colores al avión gigante o cambiar la horrible música, pero estos cambios ya no eran tan sencillos como cambiar un byte por aquí y otro por allá, lo que necesitaba era agregar trozos de código y modificar el código original.

Mientras aún pensaba en una forma práctica de hacer eso, justo aparece Abel «ascrnet» y me envía el listado de Screaming Wings generado con un densensamblador. Si bien el código no era usable como tal, era el empujón para darme cuenta del camino: Lo conveniente era desensamblar el juego y usar eso como base, en vez del código binario solamente.

Y aquí me acordé de un proyecto que había estado haciendo hace mucho tiempo atrás, la idea era crear una herramienta de ingenieria inversa para Atari en Linux que me permitiera partir desde un archivo ejecutable y poco a poco reconstruir en lo posible el código fuente original. Esta herramienta estaba pensada como herramienta gráfica y por algún motivo no la seguí desarrollando. Ahora con este experimento me di cuenta de que no era necesario tener algo gráfico, simplemente me bastaba con poder reconstruir el código a partir de un archivo de texto con instrucciones al desensamblador. Instrucciones del tipo: esta parte es código, esta parte son datos, aquí hay un puntero, pon esta etiqueta aqui, esta variable se llama de esta forma, agreguemos un comentario acá etc.

A medida que avanzaba el desensamblador fue quedando cada vez más potente, generando código bastante legible… para quien sabe de assembler por supuesto. Vean las siguientes capturas como ejemplo.

Este código documentado se genera automáticamente con el .XEX original más un archivo con anotaciones. Lo bueno es que al ser todo archivo de texto se puede versionar y mantener via github como si fuera código fuente original.

Acá se puede ver el archivo «map» actual para documentar el código generado de Screaming Wings.

Ahora, tampoco es llegar y modificar el código, porque al cambiar algunas posiciones de memoria se puede romper el juego. Eso sí, se pueden hacer parches muy rapidamente, recompilar y probar y así lo estuve haciendo para ir probando qué parte del código hacía qué cosa.

Para parches reales y más complejos apliqué un concepto de «overlay» de código.  Funciona de la siguiente forma: Un archivo XEX es una lista de bloques de datos/código que se van cargando y ejecutando, por ejemplo Screaming Wings tiene 7 bloques, el primero es la pantalla de presentación del crack que se decodifica, el segundo hace que se ejecute el primer bloque, luego el bloque 3 es un código para modificar el juego antes de que parta, el bloque 4 es el más grande y corresponde al juego completo, etc. En vez de modificar directamente uno de estos bloques, lo que hice fue agregar un bloque adicional ANTES del último bloque, porque el último bloque es el que finalmente ejecuta el juego.

Al desensamblar, cada bloque se escribe en un archivo separado, y al final se escribe un archivo principal que une todos esos archivos. En este caso patched_screaming_wings.xex genera el siguiente archivo partched_screaming_wings.asm:

icl "patched_screaming_wings.inc"

icl "patched_screaming_wings_block_1.asm"
icl "patched_screaming_wings_block_2.asm"
icl "patched_screaming_wings_block_3.asm"
icl "patched_screaming_wings_block_4.asm"
icl "patched_screaming_wings_block_5.asm"
icl "patched_screaming_wings_block_6.asm"
icl "patched_screaming_wings_block_7.asm"

Al correr el ensamblador sobre ese archivo, se genera el XEX original. Se debe partir revisando si el XEX generado es idéntico al archivo original del juego antes de continuar, de otro modo estaremos trabajando con una versión diferente del juego.

Para crear una version parchada, copio este archivo .asm y creo un nuevo archivo con mi parche. Por ejemplo «… enemy.asm», entonces mi nuevo archivo .asm principal contiene:

icl "patched_screaming_wings.inc"

icl "patched_screaming_wings_block_1.asm"
icl "patched_screaming_wings_block_2.asm"
icl "patched_screaming_wings_block_3.asm"
icl "patched_screaming_wings_block_4.asm"
icl "patched_screaming_wings_block_5.asm"
icl "patched_screaming_wings_block_6.asm"
icl "patched_screaming_wings_enemy.asm"
icl "patched_screaming_wings_block_7.asm"

La ventaja de este método es que nunca podré arruinar el juego excepto por problemas de programación, y más importante aún, puedo volver a regenerar el código fuente con más comentarios, labels, etc, en cualquier momento, sin perder ningún cambio que haya realizado en mi patch.

Ahora, las malas noticias son que el parche que quería hacer para mejorar el avión no es tan sencillo, porque la CPU no alcanza a hacer todo lo que tiene que hacer y el avión que uno conduce falla al dibujarse. Por otro lado, cambiar la música para que quede bien requiere integrar RMT y hacer la música, se trata de un proyecto de más largo aliento que no quiero que retrase el release de esta nueva versión del juego, además que con todas estas herramientas quizás alguien más se anima a hacerlo.

Todas las herramientas descritas aqui, junto a los parches para el juego y el código asm generado se pueden encontrar en mi repositorio github para a8tools.