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.

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.

 

El motor de una micro-revolución: MOS 6502

A principios de los ’70 un grupo de ingenieros de Motorola estaba convencido de que era posible obtener un microprocesador barato si se planteaba un diseño simple y un proceso de fabricación mejorado respecto a los estándares de la época. Sin contar con el apoyo de la compañía, este grupo crearía una de las familias de chips que sería el catalizador de la revolución de la computación hogareña, se trata del 6502 y sus sucesores, principal motor de las primeras máquinas de Atari y Apple, entre otros.

Chuck Peddle era uno de los ingenieros líderes en el desarrollo del procesador 6800 de Motorola, en sus reuniones con importantes clientes industriales, se dio cuenta de que la tecnología era apreciada, pero demasiado cara para ser usada en forma masiva. En conversaciones con sus clientes llegó a la conclusión de que el costo del procesador tenía que bajar de los USD$300 que costaba el 6800 a sólo USD$25 de la época, conclusión que no alegró para nada a los directivos de Motorola que no tenían ninguna motivación para buscar reducir el precio de una tecnología que ya tenía buenas ventas.

Para Chuck y otros ingenieros, se trataba de un interesante desafío técnico y sin contar con el apoyo de Motorola comenzaron a trabajar en los cambios necesarios para mejorar su procesador. Por una parte el equipo estaba descontento por el escaso apoyo de Motorola, y por otra parte la compañía estaba molesta por la porfía de Chuck y su equipo, finalmente la gota que rebalsó el vaso fue la petición formal de Motorola para detener el proyecto, lo que hizo que Chuck y su equipo decidieran abandonar la compañía, para seguir trabajando en su microprocesador por su propia cuenta.

El grupo se integró a una compañía de bajo perfil llamada MOS, en donde llegaron a un acuerdo para participar en las ganancias que podrían obtener con el nuevo microprocesador, un acuerdo que desde el punto de vista de MOS era conveniente por la baja rentabilidad que significaba en ese tiempo la producción de estos chips. Este acuerdo, junto a la renuncia en masa de Motorola era una demostración de la confianza que tenía el equipo de ingenieros en el producto que estaban desarrollando.

El 6501 demuestra a Motorola que estaban equivocados

MOS 6501

El primer procesador de la serie 6500 fue el 6501. Un hecho inédito que rodeó el desarrollo de este chip fue que Chuck lo diseñó completamente a mano y funcionó en el primer intento, cuando lo normal en esos años era pasar por un proceso de más de 10 intentos fallidos. Este procesador fue bastante escaso ya que no se hizo con el fin de ser vendido, sino como una forma de demostrar a Motorola lo equivocados que estaban, y que sí se podía construir un microprocesador útil y barato.

Uno de los factores que influyeron en la reducción de los costos de producción fue una innovación de estos ingenieros respecto al proceso de fabricación. En esos tiempos, la tasa de fallas en la producción era de un 70%, es decir, sólo 7 de cada diez procesadores fabricados iban directo a la basura. Esto ocurría principalmente debido a que el proceso consistía en crear un gran molde del procesador e ir aplicando reducciones hasta llegar al tamaño real, en estas reducciones siempre ocurrían errores y los ingenieros en MOS encontraron una forma de aplicar correcciones al molde en cada reducción, logrando con este cambio una tasa de éxito de un 70%, es decir, 7 de cada 10 procesadores resultaban perfectos.

El 6501 era totalmente compatible con las placas del Motorola 6800 y podía ser usado como un reemplazo directo a nivel de hardware, lo que motivó una demanda de Motorola por supuesta violación de patentes de sus ex-ingenieros. Supuesta porque en realidad lo que estaba en juego era la propiedad intelectual, ya que se estaba aplicando el conocimiento de tecnologías que en realidad no estaban patentadas. Aún así, se llegó a un acuerdo con Motorola y se pagó una suma de unos USD$200.000 para detener el pleito.

MOS presenta el 6502 desafiando a Intel y Motorola

Jobs y Wozniak en 1976

El problema con Motorola provocó la creación del 6502, se trataba de un 6501 pero incompatible con el hardware diseñado para el Motorola 6800. El 6502 debutó en WestCon 1975 al precio exacto de USD$25 que se habían propuesto. Al principio los asistentes pensaron que se trataba de algún tipo de estafa, ya que no se concebía crear un procesador tan económico, pero en el mismo día Motorola e Intel bajaron el precio de sus procesadores 6800 y 8080 de USD$179 a sólo USD$69, validando al 6502 que se vendió por cientos.

En esos años, Commodore que estaba en el negocio de las calculadoras, estaba pasando por aprietos económicos ya que el negocio iba en bajada y la competencia era cada vez más fuerte. Como una estrategia para cambiar el rumbo del negocio, compraron a MOS que también estaba pasando por aprietos pero que contaba con la tecnología que ellos necesitaban.

Para Chuck, el negocio de los microprocesadores estaba orientado al mundo industrial, sin embargo mientras visitaba a distintas empresas para tratar de vender el 6502 le dijeron que un par de chicos estaban tratando de usar su procesador para construir un computador personal. El hecho le pareció simpático y accedió a ir a ayudarles a su garage, se trataba nada más y nada menos que de Steve Jobs y Steve Wozniak quienes trabajaban en su primer computador Apple. Chuck pensaba en masificar el 6502 en las industrias y para nada sospechaba que el gran mercado estaba en los computadores personales. Ni en un millón de años! Según sus propias palabras.

El legado del 6502

Atari 800XL

El 6502 es un procesador de 8 bits, esto quiere decir que todas sus operaciones internas maneja números entre 0 y 255, por otra parte posee un bus de direcciones de 16 bits que le permite acceder hasta 64KB de memoria. El 6502 normalmente operaba con un reloj entre 1 y 2 Mhz pero dada una misma frecuencia de reloj era capaz de funcionar mucho más rápido que el 6800 de Motorola, gracias a su astuto diseño que reducía el número de ciclos de reloj necesarios para ejecutar una instrucción. Como pueden ver, ya desde esos años la frecuencia de reloj del procesador se podía prestar para malentendidos.

Entre los computadores que se construyeron en torno al 6502 encontramos el Commodore PET, VIC-20, Apple I y II, BBC Micro y toda la linea de computadores Atari de 8 bits (XL, XE, 400, 800, etc), incluso en otro tipo de dispositivos como la unidad de disco 1541 de Commodore.

En la pionera consola Atari 2600 se utilizó una versión económica del 6502 conocida como 6507, versión que contaba con menos pines y sólo podía acceder a 8KB de memoria. El Commodore 64 también usó un 6502 modificado, se trataba del 6510 en donde se agregó funcionalidades simples que normalmente se implementaban con circuitería adicional.

Sin duda una de las variantes más interesantes del 6502 la encontramos en la consola Nintendo Entertainment System (NES), en donde se usaba un 6502 en que funcionalidad no utilizada en la programación de videojuegos fue reemplazada por generación de sonido, manejo de joysticks y sprites, convirtiendo al procesador en un conveniente sistema integrado en un chip (system on a chip).

El 6502 fue un microprocesador que continuó su legado en el mundo de los 16 bits. Una versión de 16 bit conocida como 65C816 es el corazón de la consola Super Nintendo. También encontramos una versión especial del 6502 con modificaciones orientadas a videojuegos en la consola TurboGrafx-16/PC-Engine, en donde se podía cambiar la velocidad del procesador hasta llevarlo a unos 7Mhz.

Como se puede ver, el 6502 estuvo presente en al menos tres frentes de los inicios de la computación hogareña, desde el lado hi-end en Apple hasta computadores menos ambiciosos pero tremendamente exitosos como los de Commodore y Atari. Lo que impresiona aún mas es ver que el mismo procesador que iniciaba la computación personal fue el que inició el mercado de los videojuegos hogareños con el Atari 2600, y tras la debacle de Atari en 1983 que casi termina con la industria de los videojuegos, vuelve en gloria y majestad en el NES de Nintendo para iniciar un camino sin retorno en este mercado.

Links:
MOS Technology 6502 (Wikipedia)
The rise of MOS Technology & the 6502 (Commodore.ca)

En memoria de Chuck Peddle (R.I.P.)

Publicado originalmente en la serie Bicentenario de FayerWayer. Septiembre, 2019