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.

SimusPlayer y el regreso a un antiguo proyecto

Hace una buen tiempo he estado viendo videos de Phils Computer Lab en donde muestra distintas tarjetas de sonido antiguas, comparando como suenan los archivos MIDI en cada una de ellas, y además de esos videos ha hecho algunos experimentos interesantes como simular un módulo MIDI externo usando un tablet conectado al PC que corre los juegos. En todos sus experimentos existe un factor común, que es un reproductor MIDI para Windows que tiene una interfaz de usuario muy funcional y al mismo tiempo muy atractiva para mí, el SoundFont MIDI player.

Todo lo que viene a continuación comenzó con la pregunta «y habrá algo así para Linux«? Me puse a buscar y encontré un montón de proyectos similares abandonados y también otro gran grupo de poderosas herramientas MIDI pero más orientadas a la producción musical, por ejemplo instrumentos virtuales o secuenciadores, pero yo quería simplemente un reproductor MIDI para Linux de escritorio que tuviera un aspecto similar a SoundFont MIDI player, no tan completo como éste, pero sí con algunas características claves como visualización de las pistas, notas, listas de reproducción, selección de dispositivo y patches.

A medida que seguí dándole vueltas al tema – en la ducha como muchos saben – la idea comenzó a convertirse en algo que va mucho más allá de un MIDI Player para Linux. Ya que vamos a hacer un reproductor de música, por qué tendría que ser sólo MIDI? Si bien sobran maneras de escuchar música en formato audio «normal» como Spotify, iTunes y otros, hay una gran cantidad de material interesante que se encuentra en otros formatos, como los formatos usados por los ModTrackers (MOD, S3M, XM, etc), o los formatos para música de consolas y computadores (SID, Pokey, VGM, etc).

Vaya, que hay todo un universo más allá de la música distribuida en forma comercial.

El enfoque es similar a lo que hace RetroArch con los emuladores, el motor y el frontend es uno solo, pero lo que cambia son los reproductores, en este caso de música. En realidad, el concepto no es tan nuevo, ya lo hacía Winamp a través de sus «input plugins».

Al soportar más formatos también se puede pensar en un display diferente para cada uno de ellos, quizás no específicamente por formato, pero si por el tipo de información disponible. Por ejemplo en música de trackers hay acceso a la forma de onda, y eso se podría desplegar cuando está disponible como se puede ver en la imagen de arriba.

El MIDI Player ya no es solamente un reproductor MIDI, sino que también es un reproductor de múltiples formatos de música digital, y pese a que es algo más complejo de implementar, desde el punto de vista del usuario es algo más simple de usar porque oculta las complejidades de los distintos formatos como si fueran uno solo. El player se llama SimusPlayer o (Simple Music Player), es de código abierto y ya se encuentra en github en su fase inicial.

Si expandimos más el modelo, también podemos agregar skins, procesadores de audio y un largo etc, pero vamos con calma que hay otro aspecto interesante en este proyecto.

MusicTrans y RetroX para sistemas de escritorio

Con ese título podrían pensar que cambié radicalmente de tema, pero como verán está más relacionado de lo que pueda parecer. Cuando hice MusicTrans para Android y lo comencé a usar para mi práctica de guitarra, vi que tener una versión para escritorio (Linux/Mac/Windows) sería bastante conveniente, y como la parte principal del código estaba hecha en C y el resto era Java para Android, ya tenía al menos algo avanzado. Lo que necesitaba hacer era cambiar la parte específica de Android por algún equivalente que funcionara en los sistemas de escritorio.

Lo que hice en esa ocasión fue implementar un «mini Android» que me permitió reutilizar gran parte del código de MusicTrans para Android, y en menos de un mes la versión de MusicTrans para Escritorio ya estaba publicada, terminó siendo prácticamente igual a la de Android en cuanto a aspecto y funcionalidad, y me abrió un mercado no despreciable de nuevos usuarios amantes de MusicTrans que también lo querían en sus escritorios.

Esto tampoco era algo nuevo, de hecho resolver el problema de soportar 3 de sistemas de escritorio diferentes en forma casi nativa ya estaba resuelto por SWT del proyecto Eclipse, y es lo que usé como base para hacer este mini-Android, es una idea que ya andaba rondando hace varios años atrás cuando daba mis primeros pasos en Linux, y que finalmente pude poner en práctica y disfrutar de sus resultados.

Vi que contar con un framework que me permitiera crear aplicaciones casi nativas para Android y sistemas de escritorio usando casi el mismo código no sólo me da un gran espacio de libertad para publicar aplicaciones, sino que incluso me permite rápidamente crear aplicaciones para escritorio con las mismas habilidades que uso normalmente para Android (Java y C).

Es así como el año 2016 comencé a crear una biblioteca similar a la que usé en MusicTrans pero esta vez orientada a cualquier tipo de aplicaciones y bajo la modalidad de código abierto. Bajo el nombre de FTS lo publiqué en GitHub y senté las bases de lo que sería un gran framework para mí, pero por motivos que ya no logro recordar el proyecto quedo congelado en el tiempo y sólo alcancé a hacer una aplicación de prueba que abre una ventana y nada más.

FTS es agnóstico respecto a el mecanismo de despliegue, FTS sabe qué es un cuadro de texto (TextBox) pero no sabe cómo se dibuja, sabe lo que significa que un objeto gráfico se dibuje al lado de otro, pero no sabe cómo traducir eso en la pantalla. Esto nuevamente no es nuevo, pero es una idea muy flexible: Existe un «driver» para FTS que es el que sabe cómo traducir estas intenciones en algo concreto. En el 2016 sólo hice pruebas con un solo driver, y es un driver que utiliza SWT para dibujar en pantalla, que es lo que tenía a mano en su momento y me sirvió para probar los conceptos fundamentales. El problema es que para Android no puedo usar SWT, y por ahora ese driver no continuará en desarrollo, por lo que tuve que buscar un driver que fuera común denominador entre Android y los sistemas de escritorio, y ese driver es basado en OpenGL ES.

Nuevamente, no es primera vez que planteaba algo similar. Cuando estuve haciendo RetroX para que corriera en forma nativa en Raspberry Pi sin utilizar X.org sino que dibujando directo en el framebuffer, hice un framework llamado SGL (Simple Graphics Library) que esta basado en OpenGL y que me permitió desarrollar en mi escritorio Linux con X.org y ejecutar en Raspberry Pi via framebuffer. SGL fue específico para RetroX y quedó congelado una vez que abandoné la versión de RetroX para Linux/Raspberry Pi, para enfocarme sólo en Android.

Finalmente, todo este código converge y como muchas veces he dicho, no hay que tener miedo de escribir código ni de «perderlo» porque siempre de alguna u otra forma ese aprendizaje o incluso el mismo código servirá. Es así como por ejemplo las mejoras que he hecho a la versión de RetroArch que incluye RetroX usan los conocimientos que adquirí desarrollando SGL, y ese mismo conocimiento ahora se integra a FTS.

Entonces, para resumir, FTS (2016) ha sido descongelado y usará un driver basado en OpenGL ES rescatando parte del código de SGL (2014), y será la base de RetroX para sistemas de escritorio (2020)

Y qué tiene que ver todo esto con el SimusPlayer? Pues SimusPlayer será el conejillo de indias que me permitirá poner a prueba FTS con algo mucho más pequeño y menos riesgoso que hacerlo con RetroX, y de paso me permitirá hacer que el SimusPlayer para Linux también pueda correr en Mac, Windows, Android y .. Android TV!

Y aquí la cosa se pone wena wena.

Más que un simple reproductor de música

Bajo la premisa de hacer un reproductor simple, al agregar Android TV algo que es tan natural en sistemas de escritorio como lo es abrir un archivo, se puede volver muy complicado para un usuario en una aplicación que corre en un televisor o incluso en un móvil. Esto que parece una dificultad es en realidad una oportunidad: Ya que los archivos de música en estos formatos son pequeños, y existen grandes bibliotecas con miles de archivos ya clasificados, por qué no hacer que el mismo reproductor permita buscar y descargar estas canciones!! Por enésima vez, la idea no es nueva y esta aplicación sería como «un Spotify para MIDI/XM/S3M/MOD, etc». Como se trata de una aplicación muy de nicho y los archivos son bastante pequeños, el costo de transferencia de datos desde un repositorio propio no debería ser un problema, para la infraestructura que ya tengo andando con RetroX sería sólo un costo marginal.

Como en otros proyectos, inicialmente esto sería sólo para mi uso personal, la diversión está en construirlo y luego disfrutar lo construido, pero aún podemos ir más allá!

SimusPlayer module

Hace unos días conversaba con mi amigo Cristian Astudillo (Atariado) por la compra/venta de los parlantes que él fabrica como AsDub y en el conjunto me ofreció un amplificador Pioneer que es parte de su equipo modular. El módulo es bien bonito y me quedé pensando en cómo extenderlo un poco para que no se viera tan solo, y me acordé que tenía un módulo Sony reproductor de CD por ahí sin usar, sería la compañía perfecta.

Pero luego pensé, y…. por qué no convertir SimusPlayer en un módulo de más? Si la aplicación ya está orientada a ser usada en un Android TV, todas las características de «uso doméstico» la dejan lista para ser integrada en un módulo, o sea, convertir SimusPlayer en un hardware modular.

Otra vez, esto no es nuevo! Roland y otros fabricantes construyeron módulos MIDI que permiten reproducir sonidos y música en formato MIDI de forma independeinte, la idea sería similar pero cubriendo mucho más que sólo MIDI. Roland es una gran inspiración en cuanto a diseño y en cuanto a cómo debería funcionar un equipo de estas características, se podría hacer con una pantalla LED/LCD, una Raspberry Pi, y mucha ayuda de otros amigos expertos en hardware como Atariado y Gastón Centeno (Tongas).

En mi imaginación, veo un display themeable, idealmente un boot corto para que el equipo quede operativo en pocos segundos, control remoto via bluetooth y también control remoto via una simple app para Android. Las posibilidades de entretención construyendo este proyecto son infinitas, y quién sabe, quizás se podría convertir en un aparato interesante para otros que disfrutan de este tipo de música.

Finalmente, esta idea tampoco es nueva! A fines de los años ’90 con mi amigo Max Celedón queríamos hacer un proyecto de este tipo y se llamaba Multi-Audio, la idea era tener un equipo independiente que pudiera reproducir archivos MP3, MOD, MIDI, etc. En ese tiempo al menos MP3 necesitaba un equipo poderoso y la única opción era usar un PC, jamás había usado Linux y la única opción factible era modificar la BIOS para simplificar el proceso de boot y hacer unos simples drivers de DOS para manejar la tarjeta de sonido. Al final abandonamos el proyecto porque sospechábamos que en cualquier momento algún fabricante grande lo haría, cosa que jamás ocurrió, sólo agregaron MP3 muchos años después y los otros formatos de música pasaron al olvido.

Los conceptos de Multi-Audio después fueron aplicados a las primeras aplicaciones para televisión que hice, en donde se combinaban múltiples formato de video en vez de sólo audio, y posteriormente el mismo concepto se aplicó para Multi-Emuladores, lo que hoy se conoce como RetroX. Hacer este nuevo proyecto es darse la vuelta completa a los inicios de todo, con la ventaja de que ahora la tecnología disponible lo permite hacer. Tecnología al alcance de todos como es Linux, Raspberry Pi, servicios en la nube, impresoras 3D, componentes electrónicos económicos, cortadoras laser, etc, permiten jugar a ser inventor y disfrutar del proceso. Vamos a ver como resulta todo esto!

Cuando el desarrollo de software se complica

Esta historia aun no ha terminado pero vengo acá sólo para desahogarme. El desarrollo de software a veces puede ser muy cabrón, sobre todo cuando hay que interactuar con componentes sobre los que no se tiene control, y protocolos diseñados por enajenados mentales.

RetroX soporta conectarse a unidades de red (Windows/NAS) para cargar juegos, el protocolo (SMB/CIFS) es muy antiguo y tiene todo tipo de problemas, sin embargo hay bibliotecas como jCIFS que ayudan a implementarlo en Android y aplicaciones Java.

Un usuario reportó un problema de conexión hacia su Shield y efectivamente RetroX arroja error porque el servidor retorna un valor vacío en vez de la lista de elementos compartidos. El error lo reporta la biblioteca y es el típico Null Pointer Exception cuando trata de obtener una lista de recursos compartidos. Parece un easy fix.

Para no alargar la historia, acá un resumen de lo que ha significado diagnosticar el problema, encontrar la causa y aún así, no tener la solución:

  1. Comprobar que el uso de jCIFS esté correcto
  2. Revisar si es o no un bug conocido y corregido en jCIFS
  3. Descartar un problema de seguridad
  4. Descartar un problema de protocolo
  5. Revisar el código y encontrar el punto exacto de la caída para ver si es simple de corregir. Y no lo es.
  6. Asumir que fue corregido y actualizar la biblioteca,pero
    1. Hay 3 forks divergentes y se debe escoger uno
    2. Hay que revisar la historia de cada uno y por qué existe
    3. Compruebo dolorosamente que no todos funcionarán con Android
  7. Escogido uno, hacer lo necesario para que se pueda usar en Android
  8. Una vez actualizado jCIFS, aparece problema de protocolo. Ahora soportan SMB2 pero la Shield no.
  9. Solucionado el problema de protocolo, volvemos al error original!!! Viene un valor vacío desde la Shield.
  10. Consigo un Windows para ver si es un problema exclusivo de la Shield y efectivamente, Windows si se conecta (*)
  11. Usando Samba compruebo que sí me puedo conectar a la Shield y a Windows, mientras que jCIFS solo a Windows.

Todo apunta a un problema de cliente, y como Samba se conecta, ahora es cosa de comparar jhacer pruebas con jCIFS y con Samba para ver cuál es la diferencia entre ambos clientes.

  1. Usando Wireshark reviso toda la comunicación y sólo aparece una diferencia. El server dice File Not Found
  2. Me enfoco en identificar problemas relacionados con nombres. Pero todos los nombres son iguales en ambos casos
  3. Verifico las peticiones antes del error via Wireshark y ambas son idénticas también!!!!!
  4. Desde la Shield no puedo ver nada, excepto que el server instalado ahí dice «Alfresco»
  5. No hay rastros de un server SMB publicado por Alfresco hasta que doy con un comunicado de prensa y se llama JLAN… con un link al código fuente
  6. Instalo el servidor de Alfresco que está con código código fuente y lo puedo intervenir. Esto es mejor que probar con la Shield al menos!
  7. El error se replica! Comienzo a buscar reportes de este problema
  8. Sólo encuentro preguntas sin respuestas o links rotos.
  9. Mostrando la interpretación del protocolo desde Alfresco logro encontrar una diferencia!!! Veo que jCIFS envía un «0x10000» cuando debe enviar «1»
  10. Modifico Alfresco para que siempre crea que viene el valor como «1» y ahora funciona!!!
  11. Me voy a dormir pensando en que solo basta con corregir jCIFS para que envíe el valor correcto.
  12. Dia siguiente. En ninguna parte jCIFS envía ese valor, pero sí envía un «1». Al parecer sería un problema de interpretación de Alfresco o de codificación de jCIFS
  13. Modificando Alfresco encuentro que lee la secuencia «00 00 01 00» en vez de leer «01 00 00 00». Hay dos bytes corridos
  14. Determino dos causas probables:
    1. El string que viene antes jCIFS lo envía con «00 00» al final y Alfresco no espera eso
    2. El byte alignment entre Alfresco y jCIFS no es el mismo y por eso se corre 2 bytes
  15. Busco que dice la especificación de SMB y ohhh my god
    1. No hay una especificación de SMB sino múltiples
    2. La especificación está basada en protocolos heredados (NetBios y hasta X.)
    3. Leer leyes es más fácil que entender esta especificación!! Hay referencias cruzadas para todos lados y no llegas a ninguna parte que realmente sirva para este caso.
    4. Me encuentro con joyas del tipo : «Los strings a veces van con 00 00 al final, y a veces NO»
    5. Y otras como, a veces el alignment es relativo al paquete y otras veces relativo al mensaje
  16. Busco otras referencias / análisis de SMB y encuentro
    1. Un libro sobre CIFS muy bueno pero que no cubre esa parte
    2. Análisis de SMB pero de v2. Gra-cias.
  17. No importa, el código es la primera referencia. Sabemos que Samba lo hace bien, revisemos
  18. Horror! El código de Samba…
    1. Son 3082 archivos en C
    2. Ninguno hace referencia a la llamada que busco, tampoco se ve la parte de codificación del mensaje tras varios minutos de búsqueda
    3. Tiene directorios llamados source2, source3 en donde el contenido está duplicado, y dentro de él duplicado nuevamente. También aparecen carpetas como rpc_client y rpcclient. Cual es cual? No se sabe.  Para buscar es un infierno
  19. Probemos a fuerza bruta entonces y vamos descartando. Compruebo que el string tanto en Alfresco, Samba y jCIFS termina con «00 00» y por lo tanto es un problema de alignment y no de strings terminados en 0.
  20. En Alfresco el alignment es de 4 bytes. Hago el cambio en jCIFS y… tampoco funciona
  21. El problema del alignment es que depende del nombre del host, entonces a veces puede funcionar y a veces no, dependiendo del largo de ese nombre. * Es una posible explicación a por qué para jCIFS o Alfresco este problema ha sido reportado pero no lo han logrado replicar, y por qué algunos usuarios si se pueden conectar a la Shield desde RetroX y otros no.
  22. Reviso el código del alignment en jCIFS y veo que es relativo al mensaje, pero para Alfresco es relativo al contenedor del mensaje. Cuando Alfresco hace alignment al valor, jCIFS no, y vice versa.
  23. Podría pensar que Alfresco tiene el error, pero si Samba logra conectarse entonces se puede codificar el mensaje en forma correcta.
  24. Podría modificar Alfresco, pero ese código corre dentro de la Shield y no es modificable ahí. Puedo implementar quizás un workaround desde el punto de vista del cliente pero…
  25. Si corrijo jCIFS se rompe todo el resto de sus llamadas, porque asume alignment relativo la mensaje, sin embargo esa versión funcionaría con la Shield….

Mientras tanto, los usuarios me siguen preguntando si podrán conectarse a la Shield via SMB, ya que desde otras aplicaciones si pueden… claro, son aplicaciones cuyo único propósito es conectarse a la Shield y probablemente están usando Samba en forma nativa, un terreno al que no me gustaría entrar para algo tan simple. Mejor hago un servidor propio!

Como comentario final, es impresionante el nivel de complejidad de protocolos «corporativos antiguos» como SMB para algo que es relativamente simple. Dentro del protocolo metieron RPC, Named Pipes y lo que tuvieran a mano, pensar que con simple HTTP se puede hacer lo mismo y más.


Actualización 1: Dejaré de luchar un rato, reporté el bug a ver si hay alguna ayuda: https://github.com/AgNO3/jcifs-ng/issues/211

Actualización 2: Logré un workaround que debería servir por mientras (20 de Marzo)

La continuación de esta historia es más o menos así: Ya abandonado el branch de SMB para RetroX y dando por congelado el tema, aparece en mi ToDo list una tarea que alguna vez anoté y que dice «Sources: SMB2 via SMBJ». Aha!! Otra hay otra implementación para revisar, vamos a ver!

Hago las pruebas respectivas e implementa sólo SMB2 y SMB3, que no me sirve porque la compatibilidad que necesito es con SMB1. Reviso en mayor profundidad y veo que el protocolo es totalmente diferente así que nada más que hacer. Pero… mientras buscaba la versión compilada de SMBJ en el repositorio de Maven aparece un proyecto relacionado llamado SMBJ-RPC, y se trata justamente de la parte del protocolo que está fallando en SMB1. Paso a enumerar para facilitar el relato:

  • Aprendo que el request que lista los recursos compartidos es parte de un servicio llamado MS-SRVS (y no SMB / LanMan como uno pensaría)
  • DCE-RPC que es la parte que estuve revisando en jCIFS es quien permite invocar los servicios de MS-SRVS. En DCE-RPC se define el alignment de los datos y esa parte la documentan los amigos de SMBJ-RPC.
  • Y para mayor goce SMBJ-RPC tiene el código fuente para NetShareEnumRequest, la llamada que me está dando problemas!
  • En el código además está el link hacia la especificacón de Microsoft para MS-SRVS.
  • La spec dice lo siguiente señoras y señores: ServerName: If this parameter is NULL, the local computer is used.  O sea, que si paso un valor nulo en vez del nombre del servidor, se elimina la dependencia del alignment!!!
  • Reviso el código de SMBJ-RPC y justamente, pasan un valor nulo antes del InfoLevel
// <NDR: pointer[struct]> [in, string, unique] SRVSVC_HANDLE ServerName,
packetOut.writeNull();

// [in, out] LPSHARE_ENUM_STRUCT InfoStruct
// <NDR: unsigned long> DWORD Level
// Alignment: 4 - Already aligned
packetOut.writeInt(getShareEnumLevel().getInfoLevel());
  • Ahí además se ve claramente que InfoLevel debe ir alineado, por lo que jlan está correcto, el que falla en esa parte es jCIFS
  • Problem is: Incluso si aplico el alineado en jCIFS lo calcula mal dependiendo del nombre del servidor pero… el nombre puede ir nulo o vacio!
  • Pruebo con NULL y jCIFS se queda esperando respuesta de jlan, tampoco le gustó. Pero qué pasa si pruebo con string vacío?
 if ( this.servername != null ) {
    // _dst.enc_ndr_string(this.servername);
    _dst.enc_ndr_string("");
 }

FUNCIONA!!!!

Este cambio hace que el alignment ya no dependa del nombre del servidor porque tanto el pointer como el string vacío dejan el valor alineado de InfoLevel a 4 bytes.

Ahora, todavía me queda la duda de quién está calculando mal el alignment, si jlan o jCIFS, pero eso lo dejo en manos de ellos en el bug report de jCIFS.

Por otro lado jlan/alfresco dice:

Anyway, there is really no point working with that library any more, since Alfresco does not support its use in a standalone capacity (standalone library hasn’t had a release since ca. 2009) and will remove the support of CIFS / SMB based shared drives in one of the upcoming versions since Microsoft / WIndows no longer supports the old CIFS / SMB 1.0 dialect provided by JLAN.

Pero el problema es que Nvidia Shield está usando esa biblioteca. La cambiarán?

Actualización 3: Finalmente encontraron el bug en jCIFS, siempre estuvo ahí pero se desconocía la causa. Ya lo corrigieron y al menos en mis pruebas funciona bien. La vida puede continuar!

Turns out the SMB_COM_TRANSACTION padding algorithm has been wrong all the time (padding two 2 bytes among other things). So this was caused by missing padding before the NetShareEnum structure.