Blog Home  Home Feed your aggregator (RSS 2.0)  
MAME WIP - Trivial Pursuit (Spanish) - Manuel Abadia's ASP.NET stuff
 
# Sunday, 05 March 2006

This post is first in Spanish and then in English.


Esta es la segunda noticia relacionada con el Trivial Pursuit en español. Si no has leido la primera puedes hacerlo aquí:

http://www.manuelabadia.com/blog/PermaLink,guid,bce41855-2806-40e2-b956-e45ba07b3215.aspx

Voy a comentar en detalle el proceso que he seguido para ir reduciendo las combinaciones. Si no teneís conocimientos de arquitectura de computadores y ensamblador probablemente mucho de lo siguiente os suene a chino, pero bueno, igual a alguien le interesa o saca algo en claro... Si no os interesa, más abajo hay algunas fotos del juego.

Lo primero a hacer es comparar las ROMs con las de otro trivial para ver las semejanzas. Para ello el MAME trae una utilidad muy buena llamada ROMCMP. Comparando el trivial en español con los distintos clones y siempre he obtenido los mismos resultados:

romcmp.png

La ROM de sonido es similar, y 2 de las 3 de gráficos iguales. La otra es similar en un 99.81% por lo que no hay ningún problema en mapear dichas ROMs. El problema lo tenemos con las 8 de programa. Ninguna tiene una concordancia mayor del 10%, y normalmente una concordancia tan baja es por tener muchos ceros o unos en alguna parte de la ROM. Normalmente si la concordancia no es mayor del 70%, no podemos afirmar nada, así que no partimos de nada a la hora de mapear las 8 ROMs de programa.

He probado colocando las ROMs de programa en orden ascendente (de la TP_A1.BIN a la TP_A8.BIN) y descendente pero no ha habido suerte. Por lo tanto, hay que buscar opciones para reducir el número de combinaciones posibles (40320).

El Trivial Pursuit usa como CPU principal un Motorola 6809 (CPU de 8 bits). Este procesador al arrancar empieza a ejecutar instrucciones en la dirección apuntada por el vector de reset, que está en 0xfffe-0xffff.

Si observais el driver, el sistema utiliza bancos de 8 KB para ir paginando por las ROMs, puesto que el espacio de direcciones de la CPU es de 64 KB y se necesita acceder a 128 KB de datos. Por lo tanto el objetivo es encontrar una ROM que se mapee en el rango de direcciones 0xe000-0xffff. Como cada ROM es de 0x4000 bytes, en cada ROM hay 2 bancos, por lo que hay que observar la parte final de cada banco a ver si contienen vectores válidos. No solo está el vector de reset, si no que hay varios como muestra la siguiente tabla:

FFF0H to FFF1H           |Reserved by Motorola               |
FFF2H to FFF3H           |SWI3 instruction interrupt vector  |
FFF4H to FFF5H           |SWI2 instruction interrupt vector  |
FFF6H to FFF7H           |Fast hardware int. vector (FIRQ)   |
FFF8H to FFF9H           |Hardware interrupt vector (IRQ)    |
FFFAH to FFFBH           |SWI instruction interrupt vector   |
FFFCH to FFFDH           |Non-maskable interrupt vector (NMI)|
FFFEH to FFFFH           |Reset vector                       |

A continuación muestro los últimos datos de cada posible banco de las ROMs asociadas a la CPU. Si os habeis enterado de la parrafada anterior sería un buen ejercicio tratar de adivinar que banco contiene el vector de arranque sin mirar la solución que está después de la imagen.

Los datos no dan lugar a dudas. Los vectores están en el primer banco de la ROM A3:

F382 F382 F382 FDA7 FBC6 F382 F382 F382

En muchos juegos basados en el M6809 el vector de reset coincide con el del SWI, SWI2 y SWI3 como pasa en el trivial.

Gracias a esto ya podemos mapear una ROM y tenemos la siguiente configuración:
0x10000-0x13fff ???
0x14000-0x17fff ???
0x18000-0x1bfff ???
0x1c000-0x1ffff ???
0x20000-0x23fff ???
0x24000-0x27fff ???
0x28000-0x2bfff ???
0x2c000-0x2ffff TP_A3.BIN

Con lo que las combinaciones posibles pasan de 40320 a 5040.

Como el trivial pursuit es un juego muy simple de programar, el código del programa está todo en esta ROM por lo que si ejecutamos el juego poniendo cualquier combinación en el resto de huecos ya podemos ver algo:

Sin embargo, el resto de ROMs necesitan estar bien mapeadas para ver todos los gráficos correctamente y sobre todo para que las preguntas y respuestas se muestren correctamente. Al probar distintas combinaciones con el resto de ROMs se observa que los gráficos de la presentación y del tablero van cambiando, por lo que tras unas pocas pruebas se consigue ver la presentación y el tablero correctamente:

La ROM TP_A5.BIN contiene información sobre los gráficos del tablero y la TP_A1.BIN información sobre los gráficos de la presentación.

De momento la configuración de las ROMs es:
0x10000-0x13fff ???
0x14000-0x17fff ???
0x18000-0x1bfff ???
0x1c000-0x1ffff TP_A5.BIN
0x20000-0x23fff ???
0x24000-0x27fff TP_A1.BIN
0x28000-0x2bfff ???
0x2c000-0x2ffff TP_A3.BIN

Con lo que las combinaciones posibles pasan de 5040 a 120. No está mal para no haberse metido a ver el código todavía.

Si vamos probando unas cuantas combinaciones vemos que no hay manera de hacer que las preguntas y las respuestas salgan bien, probablemente porque hay algún tipo de dependencia entre las ROMs que impide ver nada a no ser que la combinación introducida sea la correcta.

Si podemos averiguar que es lo que contiene cada ROM que no sabemos mapear, tenemos más puntos a nuestro favor para encontrar la combinación correcta. Si buscamos textos en las ROMs no encontramos nada inteligible, por lo que estarán codificados de alguna forma que no coincide con los caracteres ASCII (cosa que por otro lado tiene sentido, ya que si se pueden ver las preguntas y las respuestas fácilmente sería facil piratear el juego en otras plataformas puesto que la lógica del juego es mínima).

Por lo tanto, como no voy a probar las 120 combinaciones, no queda otra opción que bucear en el ensamblador... La gran mayoría de juegos tienen al principio una serie de comprobaciones de memoria y de ROMs (para comprobar que todo funciona bien) que sirven para descubrir cómo se mapean las ROMs y en qué orden. Sin embargo, el trivial tan solo tiene las comprobaciones de memoria y no las de las ROMs por lo que tan solo nos queda ir entendiendo código hasta encontrar alguna rutina que nos oriente en la colocación de las ROMs. Como he comentado antes, interesa saber que hay en cada ROM, por lo que si vamos estudiando código, llegamos a la rutina que muestra por pantalla el mensaje "Un momento por favor":

Esta rutina nos desvela que las frases se guardan de la siguiente forma:
1 byte de color + 2 bytes de dirección en pantalla + n bytes de la frase (1 byte por caracter) + 0xff (marcador de fín de frase)

Cada caracter de la frase está codificado de la siguiente forma:

0x00-0x09 -> números del 0 al 9
0x0a-0x23 -> caracteres de la 'a' a la 'z'
0x32-0x4b -> caracteres de la 'A' a la 'Z'
hay algunos caracteres más para símbolos de puntuación, acentos, eñes, etc que no especifico, pero espero que capteis la idea.

Me he creado un simple programa en C# al que le pasas una ROM y te busca todas las frases posibles en este formato (no he tenido en cuenta acentos, eñes y demás):

using System;

using System.Collections.Generic;

using System.IO;

using System.Text;

 

namespace Trivial

{

    class Program1

    {

        static void oldMain(string[] args)

        {

            if (args.Length != 2){

                Console.WriteLine("Error, se necesitan 2 argumentos: <rom> <salida.txt>");

 

                return;

            }

 

            FileInfo input = new FileInfo(args[0]);

            FileInfo output = new FileInfo(args[1]);

 

            StreamWriter writer = output.CreateText();

 

            FileStream fileStream = input.Open(FileMode.Open);

            BinaryReader br = new BinaryReader(fileStream);

            byte[] romData = br.ReadBytes(0x4000);

            br.Close();

 

            StringBuilder sb = new StringBuilder();

 

            // recorre la ROM grabando las frases que encuentre

            for (int i = 0; i < 0x4000; i++) {

                // 0xff marca el fín de la frase, por lo que la escribe

                if (romData[i] == 0xff) {

                    if (sb.Length > 3) {

                        writer.WriteLine(String.Format("{0:X}: ({1:X}, {2:X}{3:X}) -> {4}", i - sb.Length, (int)sb[0], (int)sb[1], (int)sb[2], sb.ToString(3, sb.Length - 3)));

                        sb.Length = 0;

                    }

 

                    continue;

                }

 

                // añade caracteres a la frase

                sb.Append(Convertir(romData[i]));

            }

            writer.WriteLine(sb.ToString());

 

            writer.Close();

        }

 

        // se encarga de convertir un byte al código ASCII correspondiente

        private static char Convertir(byte data)

        {

            // números

            if ((data >= 0x00) && (data <= 0x09)){

                return (char)(((int)'0') + (data - 0x00));

            }

 

            // letras minúsculas

            if ((data >= 0x0a) && (data <= 0x23)){

                return (char)(((int)'a') + (data - 0x0a));

            }

 

            // espacio

            if (data == 0x27) {

                return ' ';

            }

 

            // letras mayúsculas

            if ((data >= 0x32) && (data <= 0x4b)){

                return (char)(((int)'A') + (data - 0x32));

            }

 

            return '#';

        }

    }

}

 

Desgraciadamente, las preguntas y respuestas del juego no están codificadas de esta forma. Las frases que están codificadas de esta forma son:

D21E: (23, 6E77) -> Jugadores Magistrales
D237: (23, 236F) ->  Pulse Start
D248: (23, 2323) -> Creditos
D254: (23, 2338) -> Introduzca Moneda   
D26D: (23, 2323) -> 1 Moneda 1 Juego
D281: (23, 2323) -> 2 Monedas 1 Juego
D296: (23, 2323) -> 2 Monedas 1 Juego
D2AB: (23, 2323) -> 1 Moneda 2 Juegos
D2C0: (68, 4F23) -> Juego Terminado
D2D3: (68, 686F) -> Usted est# entre los mejores
D2F3: (68, 6D77) -> C#ales son sus inicales
D30E: (68, 206F) -> Puntos
D318: (23, 2323) -> Muy bien Continue as#
D331: (53, 2323) -> Correcto o incorrecto
D34B: (23, 3943) -> Categor#a
D358: (23, 6620) -> Escenario y Pantalla
D370: (23, 6623) -> Gente y Recuerdos
D386: (23, 6623) -> Dias de Colegio
D399: (30, 3030) -> 0000
D3A1: (23, 6623) -> Ultimas Noticias
D3B6: (23, 2323) -> # 1987 Bally Sente
D3CC: (42, 586B) -> Lo siento la respuesta correcta es
D3F2: (23, 2323) -> La pr#xima vez lo sabr#
D40D: (23, 2323) -> Felicitaciones usted
D425: (23, 4523) -> es realmente un
D438: (23, 4B76) -> Maestro en Trivial Pursuit
D456: (23, 4F73) -> Su tiempo se ha acabado
D471: (23, 2023) -> Tiempo
D47B: (23, 2333) -> Baron Von Rightoften
D493: (23, 237A) -> Billie Genio
D4A4: (23, 2351) -> Cleopatra
D4B2: (53, 2323) -> Spartacus
D4BF: (53, 2375) -> Apriete Roja para cambiar de letra
D4E5: (23, 2375) -> Apriete Verde para fijar la letra
D50A: (23, 2367) -> Apriete Verde para elegir personaje
D531: (53, 236B) -> Apriete Rojo para cambiar
D54E: (53, 236B) -> al siguiente personaje
D568: (23, 6E7A) -> Seleccione su personaje
D583: (53, 2375) -> Apriete Rojo o Verde
D59B: (23, 2323) -> Juego gratis
D5AB: (23, 6B23) ->            
D5BB: (23, 7023) -> Adaptacion del juego
D5D3: (23, 7520) -> y Software dise#o
D5E8: (23, 4223) -> Richard Adam
D5F8: (23, 4723) -> A#Anton Toca 
D60A: (23, 4C23) -> J#Fernandez S#
D61D: (23, 5723) ->             
D62E: (23, 2342) ->  MAIBESA 1987
D640: (23, 2346) ->     
D649: (53, 2323) ->             
D65A: (23, 2323) -> TRIVIAL PURSUIT  
D670: (23, 2323) -> Volumen I I I        
D68A: (23, 237A) ->                     
D6A3: (68, 6923) -> Trivial Pursuit
D6B6: (23, 5179) -> Su puntuaci#n Magistral
D6D1: (23, 5723) -> es
D6D7: (53, 2323) -> Un momento por favor
D6EF: (53, 2041) -> Jugador
D6FA: (23, 2345) -> ARCADE

y están todas en la ROM TP_A3.BIN, por lo que no hemos avanzado mucho en este sentido.

He seguido desensamblando código hasta encontrar la rutina principal del juego, que será la que nos desvele el misterio de cómo están codificadas las preguntas y las respuestas, pero eso ya será en otro rato libre que pueda encontrar. La semana que viene terminamos con la emulación del juego.


This is the second post about Trivial Pursuit Spanish. If you haven't read the first one you can do it here:

http://www.manuelabadia.com/blog/PermaLink,guid,bce41855-2806-40e2-b956-e45ba07b3215.aspx

I'm going to explain a little bit the process to reduce the number of possible combinations. This will be a little technical but it may be interesting to somebody. If you're not interested there're some screenshots below.

The first thing to do its to compare the ROMs with other sets to spot similarities. There is a pretty good utility that comes with MAME called ROMCMP that does this. The results are:

romcmp.png

The sound ROMs and graphic ROMs are similar so we now where to map them but the program ROMs are completely different (10% of similarity or so isn’t significant. We need at least 70% to consider them similar) so we don’t have a clue for mapping the program ROMs.

I tried in ascendant order (from TP_A1.BIN to TP_A8.BIN) and in descendant order but the game doesn’t do anything, so we have to make something to reduce the total number of combinations (40320).

The game uses a Motorola 6809 (an 8 bit CPU) as the main CPU. This chip starts fetching instructions from the reset vector located at 0xfffe-0xffff.

If you look at the driver (balsente.c), the SAC-1 system uses 8 KB banks to handle all ROM data, as the CPU can access to only 64 KB at a time and the ROMs have 128 KB of data. We need to find out which ROM maps to 0xe000-0xffff to start reverse engineering code in order to reduce the number of combinations. As each ROM is 0x4000 bytes long, we have 2 possible banks per ROM. We have to check the last bytes in each bank to see if it has valid vectors that map in 0xe000-0xffff. The full vector table for the M6809 is:

FFF0H to FFF1H           |Reserved by Motorola               |
FFF2H to FFF3H           |SWI3 instruction interrupt vector  |
FFF4H to FFF5H           |SWI2 instruction interrupt vector  |
FFF6H to FFF7H           |Fast hardware int. vector (FIRQ)   |
FFF8H to FFF9H           |Hardware interrupt vector (IRQ)    |
FFFAH to FFFBH           |SWI instruction interrupt vector   |
FFFCH to FFFDH           |Non-maskable interrupt vector (NMI)|
FFFEH to FFFFH           |Reset vector                       |

I’m showing here the last bytes of each bank so you can try to identify them yourself if you’re following me:

Without a doubt the vectors are in the first bank of the ROM TP_A3.BIN:

F382 F382 F382 FDA7 FBC6 F382 F382 F382

In a lot of games based on a M6809, the reset vector is the same as SWI, SWI2 and SWI3 as happens with this game.

Thanks to this we have one ROM mapped so for now we have:
0x10000-0x13fff ???
0x14000-0x17fff ???
0x18000-0x1bfff ???
0x1c000-0x1ffff ???
0x20000-0x23fff ???
0x24000-0x27fff ???
0x28000-0x2bfff ???
0x2c000-0x2ffff TP_A3.BIN

So the possible number of combinations decreases from 40320 to 5040.

As the game logic in this game is very simple, all the code is inside TP_A3.BIN so if we run MAME now filling the unknown holes with a random combination we can see something:

Unfortunately some graphics and the questions and answers aren’t showing up properly because we haven’t mapped them in the proper order. If you keep trying a few combinations it’s easy to fix the splash screen and the board graphics. TP_A5.BIN has information about the board and TP_A1.BIN about the splash screen:

So for now we have this ROM mapping configuration:
0x10000-0x13fff ???
0x14000-0x17fff ???
0x18000-0x1bfff ???
0x1c000-0x1ffff TP_A5.BIN
0x20000-0x23fff ???
0x24000-0x27fff TP_A1.BIN
0x28000-0x2bfff ???
0x2c000-0x2ffff TP_A3.BIN

So now the possible combinations are reduced from 5040 to 120. Not bad if we consider we haven’t studied a line of code!

If we keep trying a few more combinations we realize that there’s no way to map the other ROMs in order to see more progress (unless we were lucky enough to find the proper combination).

If we could know what kind of data is in each ROM it would be helpful to find the correct mapping, so I searched for plain text in the ROM but found nothing, so they coded the sentences somehow. So as I was in a dead end and I didn’t want to try 120 different combinations it was time to run the game with the debugger and understand some code…

Unfortunately in the power on self test there isn’t a ROM check as in a lot of games. After some debugging you end up in the routine that writes a sentence to the video memory (comments are in Spanish sorry):

This routine shows how they coded this sentence in the ROM. The format is:
1 byte for color + 2 bytes for destination address + n bytes of data (1 byte per character) + 0xff (end mark)

Each character is coded this way:
0x00-0x09 -> numbers from ‘0’ to ‘9’
0x0a-0x023 -> characters from ‘a’ to ‘z’
0x32-0x4b -> characters from ‘A’ to ‘Z’
There are more entries for for punctuation, accents, tilde, etc but you get the point…

I wrote a simple C# program to find out all the sentences in a ROM to see if I could find out what kind of data a ROM has:

using System;

using System.Collections.Generic;

using System.IO;

using System.Text;

 

namespace Trivial

{

    class Program1

    {

        static void oldMain(string[] args)

        {

            if (args.Length != 2){

                Console.WriteLine("Error, se necesitan 2 argumentos: <rom> <salida.txt>");

 

                return;

            }

 

            FileInfo input = new FileInfo(args[0]);

            FileInfo output = new FileInfo(args[1]);

 

            StreamWriter writer = output.CreateText();

 

            FileStream fileStream = input.Open(FileMode.Open);

            BinaryReader br = new BinaryReader(fileStream);

            byte[] romData = br.ReadBytes(0x4000);

            br.Close();

 

            StringBuilder sb = new StringBuilder();

 

            // recorre la ROM grabando las frases que encuentre

            for (int i = 0; i < 0x4000; i++) {

                // 0xff marca el fín de la frase, por lo que la escribe

                if (romData[i] == 0xff) {

                    if (sb.Length > 3) {

                        writer.WriteLine(String.Format("{0:X}: ({1:X}, {2:X}{3:X}) -> {4}", i - sb.Length, (int)sb[0], (int)sb[1], (int)sb[2], sb.ToString(3, sb.Length - 3)));

                        sb.Length = 0;

                    }

 

                    continue;

                }

 

                // añade caracteres a la frase

                sb.Append(Convertir(romData[i]));

            }

            writer.WriteLine(sb.ToString());

 

            writer.Close();

        }

 

        // se encarga de convertir un byte al código ASCII correspondiente

        private static char Convertir(byte data)

        {

            // números

            if ((data >= 0x00) && (data <= 0x09)){

                return (char)(((int)'0') + (data - 0x00));

            }

 

            // letras minúsculas

            if ((data >= 0x0a) && (data <= 0x23)){

                return (char)(((int)'a') + (data - 0x0a));

            }

 

            // espacio

            if (data == 0x27) {

                return ' ';

            }

 

            // letras mayúsculas

            if ((data >= 0x32) && (data <= 0x4b)){

                return (char)(((int)'A') + (data - 0x32));

            }

 

            return '#';

        }

    }

}

 

Unfortunately the questions and answers aren’t coded this way, so the only readable sentences I got were:
 
D21E: (23, 6E77) -> Jugadores Magistrales
D237: (23, 236F) ->  Pulse Start
D248: (23, 2323) -> Creditos
D254: (23, 2338) -> Introduzca Moneda   
D26D: (23, 2323) -> 1 Moneda 1 Juego
D281: (23, 2323) -> 2 Monedas 1 Juego
D296: (23, 2323) -> 2 Monedas 1 Juego
D2AB: (23, 2323) -> 1 Moneda 2 Juegos
D2C0: (68, 4F23) -> Juego Terminado
D2D3: (68, 686F) -> Usted est# entre los mejores
D2F3: (68, 6D77) -> C#ales son sus inicales
D30E: (68, 206F) -> Puntos
D318: (23, 2323) -> Muy bien Continue as#
D331: (53, 2323) -> Correcto o incorrecto
D34B: (23, 3943) -> Categor#a
D358: (23, 6620) -> Escenario y Pantalla
D370: (23, 6623) -> Gente y Recuerdos
D386: (23, 6623) -> Dias de Colegio
D399: (30, 3030) -> 0000
D3A1: (23, 6623) -> Ultimas Noticias
D3B6: (23, 2323) -> # 1987 Bally Sente
D3CC: (42, 586B) -> Lo siento la respuesta correcta es
D3F2: (23, 2323) -> La pr#xima vez lo sabr#
D40D: (23, 2323) -> Felicitaciones usted
D425: (23, 4523) -> es realmente un
D438: (23, 4B76) -> Maestro en Trivial Pursuit
D456: (23, 4F73) -> Su tiempo se ha acabado
D471: (23, 2023) -> Tiempo
D47B: (23, 2333) -> Baron Von Rightoften
D493: (23, 237A) -> Billie Genio
D4A4: (23, 2351) -> Cleopatra
D4B2: (53, 2323) -> Spartacus
D4BF: (53, 2375) -> Apriete Roja para cambiar de letra
D4E5: (23, 2375) -> Apriete Verde para fijar la letra
D50A: (23, 2367) -> Apriete Verde para elegir personaje
D531: (53, 236B) -> Apriete Rojo para cambiar
D54E: (53, 236B) -> al siguiente personaje
D568: (23, 6E7A) -> Seleccione su personaje
D583: (53, 2375) -> Apriete Rojo o Verde
D59B: (23, 2323) -> Juego gratis
D5AB: (23, 6B23) ->            
D5BB: (23, 7023) -> Adaptacion del juego
D5D3: (23, 7520) -> y Software dise#o
D5E8: (23, 4223) -> Richard Adam
D5F8: (23, 4723) -> A#Anton Toca 
D60A: (23, 4C23) -> J#Fernandez S#
D61D: (23, 5723) ->             
D62E: (23, 2342) ->  MAIBESA 1987
D640: (23, 2346) ->     
D649: (53, 2323) ->             
D65A: (23, 2323) -> TRIVIAL PURSUIT  
D670: (23, 2323) -> Volumen I I I        
D68A: (23, 237A) ->                     
D6A3: (68, 6923) -> Trivial Pursuit
D6B6: (23, 5179) -> Su puntuaci#n Magistral
D6D1: (23, 5723) -> es
D6D7: (53, 2323) -> Un momento por favor
D6EF: (53, 2041) -> Jugador
D6FA: (23, 2345) -> ARCADE

And all were in ROM TP_A3.BIN so this didn’t help us very much.

I continued debugging code until the main game loop, that will tell us all what we need to know to emulate the game properly. However I ran out of time for this week so until the next week I won’t be able to finish this game.

Sunday, 05 March 2006 01:48:24 (Romance Standard Time, UTC+01:00)  #    Comments [3]   Games  |  Tracked by:
"MAME driver complete for Trivial Pursuit (Spanish)" (Manuel Abadia's blog) [Trackback]
http://www.manuelabadia.com/blog/PermaLink,guid,658de6b6-4fae-48cc-bb0a-f5593cf3... [Pingback]
Copyright © 2017 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.