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

This post is first in Spanish and then in English.


Esta es la última noticia relativa al Trivial Pursuit en español. Si no has leído las noticias anteriores puedes hacerlo aquí:

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

http://www.manuelabadia.com/blog/PermaLink,guid,8e5e772a-5769-4741-b59a-d99a89e3e285.aspx

De la última semana teníamos la siguiente configuración de las ROMs:
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

y ya sabíamos como iba la rutina que mostraba las frases en pantalla.

Si continuamos entendiendo el código, llegamos al bucle principal del juego, donde se realiza una serie de tareas determinadas por el estado actual.
Estudiando los diferentes estados del juego podemos averiguar cualquier cosa que queramos sobre él. Os adjunto una captura de los distintos estados del juego:

Si estudiamos el estado cuando la variable vale 0x04, es decir, cuando se muestra una pregunta, llegamos al fin a la rutina que muestra las preguntas por pantalla (dicha rutina está en $f390). Esta rutina es bastante larga y compleja, por lo que no la voy a detallar. Sin embargo, resulta curioso saber cómo se guardan las preguntas y las respuestas del juego.

Las preguntas se guardan con 2 punteros. El primero indica el tipo de pregunta que es y el segundo la pregunta en si. Un ejemplo deja esto más claro: La pregunta: "¿Cual fue el primer monarca europeo que visitó china?" tiene un puntero a "Cual" y a "fue el primer monarca europeo que visitó china". Gracias a este esquema se ahorran unos cuantos bytes puesto que todas las preguntas empiezan por un conjunto reducido de frases del tipo Cuándo, Qué, Quién, De quienes, etc. Esto es solo la primera parte, puesto que los punteros apuntan a frases que tienen los caracteres codificados en base 40, de forma que en 2 bytes se pueden meter 3 caracteres. De nuevo un ejemplo es lo mejor para entender esto.
 
Si tenemos los bytes:
0x42 0x80 0x55 0x27
 
los agrupamos de 2 en 2 y para cada 2 bytes obtenemos los 3 dígitos que componen ese número en base 40. Por si no andáis muy puestos en cambios de base, si quisiéramos obtener los dígitos que componen el número 379 en base 10, obtendríamos 3, 7 y 9, puesto que 379 dividido entre 10^2 da 3 y de resto 79. 79 dividido entre 10^1 da 7 y de resto 9, y 9 entre 10^0 da 9, es decir, 379 = 3*10^2 + 7*10^1 + 9*10^0.

Para base 40 es similar pero dividiendo entre 40. Para el número 0x4280 obtenemos 10, 25 y 24 (en hexadecimal 0x0a, 0x19 y 0x18), y para 0x5527 obtenemos 13, 24 y 39 (en hexadecimal 0x0d, 0x18 y 0x27).

Una vez que se obtienen los 3 dígitos de los 2 bytes, se mandan a la rutina que vimos la semana pasada para mostrar frases por pantalla, aunque se hace un ligero ajuste al principio. Si recordáis lo que dije la semana pasada, la rutina para mostrar caracteres por pantalla hacía la siguiente conversión:
0x00-0x09 -> números del 0 al 9
0x0a-0x23 -> caracteres de la 'a' a la 'z'
0x27-> espacio
0x32-0x4b -> caracteres de la 'A' a la 'Z'

Si comprobamos los valores que habíamos obtenido anteriormente, tenemos:
0x0a -> a, 0x19 -> p, 0x18 -> o, 0x0d -> d, 0x18 -> o, 0x27 -> espacio en blanco
por lo que los bytes 0x42 0x80 0x55 0x27 codifican "apodo ".

Como el juego guarda los caracteres a imprimir en base 40, tan solo se dispone de 40 caracteres distintos para representar las preguntas. Sin embargo, los números más las letras en minúscula ya hacen un total de 36 valores distintos, por lo que no habría forma de mostrar mayúsculas ni todos los acentos así como signos de puntuación. Para evitar este problema, el juego utiliza el valor 0x24 como marcador para indicar que el siguiente byte que se lea se escribirá sumándole 0x28, de forma que si quisiéramos escribir Apodo en vez de apodo deberíamos tener los valores:
0x28 0x0a 0x19 0x18 0x0d 0x18

Una vez entendido esto, no tenemos más que creamos un programa en C# que muestre todas las frases que encuentre en las ROMs:

using System;

using System.Collections.Generic;

using System.IO;

using System.Text;

 

namespace Trivial

{

    class Program2

    {

        protected static bool ignorar;

 

        static void Main(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();

 

            int i = 0;

            int startOffset = 0;

 

            // recorre la ROM grabando las frases que encuentre

            while (i < (0x4000 - 1)) {

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

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

                    if (sb.Length > 0) {

                        writer.WriteLine(String.Format("{0:X}: {1}", startOffset, sb.ToString()));

                        sb.Length = 0;

                    }

 

                    i++;

                    startOffset = i;

 

                    continue;

                }

 

                // convierte 2 bytes en 3 digitos en base 40

                int num = (romData[i] << 8) | (romData[i + 1]);

                byte[] caracteres = CambioBase(num, 40, 3);

 

                // escribe los dígitos

                foreach (byte caracter in caracteres) {

                    sb.Append(Convertir(caracter));

                }

 

                i += 2;

            }

 

            writer.WriteLine(sb.ToString());

            writer.Close();

        }

 

        // devuelve un array de bytes con los dígitos del cambio de base

        private static byte[] CambioBase(int valor, int baseNum, int numDigitos)

        {

            byte[] resultado = new byte[numDigitos];

 

            int pos = numDigitos - 1;

 

            do {

                byte digito = (byte)(valor % baseNum);

                valor = valor / baseNum;

                resultado[pos] = digito;

                pos--;

            } while (pos >= 0);

 

            return resultado;

        }

 

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

        private static string Convertir(byte data)

        {

            string str = "#";

 

            // números

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

                str = new string((char)(((int)'0') + (data - 0x00)), 1);

            }

 

            // letras minúsculas

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

                str = new string((char)(((int)'a') + (data - 0x0a)), 1);

            }

 

            // usado para convertir a mayúsculas o para poner acentos

            if (data == 0x24) {

                return "";

            }

 

            // espacio o fín de entrada

            if (data == 0x27) {

                if (ignorar) {

                    return "";

                } else {

                    ignorar = true;

                    return " ";

                }

            }

 

            ignorar = false;

 

            return str;

        }

    }

}

 

Al ejecutar el programa obtenemos muchas preguntas y respuestas en varias ROMs:

[...]

39a: nombre dieron los romanos a las islas canarias
3bb: llamamos en espa#ol a la ciudad alemana de koln
3de: de los ap3stoles alfab1ticamente es el primero
3ff: lugar est0 #la creaci3n del hombre# de miguel angel
426: de los evangelios cuenta que un ni#o le llev3 a jes4s 5 panes y 2 peces
45b: dos paises hay cadenas monta#osas llamadas sierra nevada
484: tienen como patr3n a san nicol0s
49d: hero2na se abras3 en 1431
4b0: continente no tiene ciudades capitales

[...]

8FB: australiana
904: ava gardner
90F: sara montiel
91A: gina lollobrigida
929: brigitte bardot
936: susana estrada
943: arthur miller
94E: hern0ndez mancha
95D: john ford
966: juan pablo ii
973: ernesto iv

[...]

Por lo que ya tenemos un poco más claro que hay en las ROMs que no tenemos mapeadas correctamente:
TP_A2.BIN -> preguntas
TP_A4.BIN -> respuestas
TP_A6.BIN -> ni preguntas ni respuestas, sino que hay datos de otro tipo
TP_A7.BIN -> preguntas y respuestas
TP_A8.BIN -> preguntas y respuestas

La misma rutina que decodifica las preguntas utiliza 2 bancos distintos para obtener los punteros que forman la preguntas y sus respuestas, 4 bancos distintos para obtener el texto de las preguntas y otros 4 bancos distintos para obtener el texto de las respuestas. Como cada ROM tiene 2 bancos, gracias a la información obtenida tenemos:

La ROM TP_A6.BIN ya está mapeada correctamente.
Para las preguntas tenemos 4 opciones y para las respuestas otras 4 opciones por lo que el número total de combinaciones queda reducido a 8. El único problema es que la máquina tiene un generador de números aleatorios, por lo que si nos decantamos por la opción de probar las 8 combinaciones distintas, tenemos que trucar el generador de números aleatorios para que siempre estemos probando colocar las ROMs para la misma pregunta.

Tras 3 o 4 pruebas al fín obtenemos la combinación correcta:

0x10000-0x13fff TP_A2.BIN
0x14000-0x17fff TP_A7.BIN
0x18000-0x1bfff TP_A4.BIN
0x1c000-0x1ffff TP_A5.BIN
0x20000-0x23fff TP_A8.BIN
0x24000-0x27fff TP_A1.BIN
0x28000-0x2bfff TP_A6.BIN
0x2c000-0x2ffff TP_A3.BIN

Como para acertarla sin haber estudiado el juego a fondo...

Aquí tenéis unas pantallas con el juego funcionando correctamente:

Le doy los últimos repasos al driver y lo envío a la lista del MAME.

Espero que disfrutéis del juego muy pronto. Probablemente sea mi último driver para el MAME.


This is the third post about Trivial Pursuit Spanish. If you haven't read the previous ones you can do it here:

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

http://www.manuelabadia.com/blog/PermaLink,guid,8e5e772a-5769-4741-b59a-d99a89e3e285.aspx

Last week we had the following ROM 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

If we continue understanding code we reach the main loop of the game, where the code jumps to a routine based on the current state. Here’s the method I’m talking about:

If we carefully study what happens in the state 0x04, where the game shows a question, we finally found how the questions and answers are coded (routine at $f390). That routine is large so I’m not going to show it but it’s curious to know how the questions and answers were stored.

A question is defined by 2 pointers. The first one is the start of the question and the second one the core of the question. An example will explain this better than me: The question: “Who was the first king that visited china?” has the first pointer pointing to the sentence “Who” and the second one to “was the first king that visited china”. Thanks to this schema they save a few bytes per question.

This is only the first part as the characters for the sentences are coded in base 40. 3 characters are coded in 2 bytes. Again an example is the best to explain this:
 
If we have the following bytes:
0x42 0x80 0x55 0x27
 
We take 2 bytes at a time and extract 3 base 40 digits so for 0x4280 we’ve got 10, 25 and 24 (0x0a, 0x19 and 0x18 in hexadecimal), and for 0x5527 we’ve got 13, 24 and 39 (0x0d, 0x18 and 0x27 in hexadecimal).

After having the values, they’re sent to the routine we saw last week that printed characters. That routine did the following conversion:
0x00-0x09 -> numbers from 0 to 9
0x0a-0x23 -> characters from 'a' to 'z'
0x27-> space
0x32-0x4b -> characters from 'A' to 'Z'

If we check what we obtained previously:
0x0a -> a, 0x19 -> p, 0x18 -> o, 0x0d -> d, 0x18 -> o, 0x27 -> space
So the following bytes: 0x42 0x80 0x55 0x27 store "apodo " (spanish word for nickname).

As the game stores the characters using base 40 codification, we can store only 40 characters, but the digits and lowercase letters make a total of 36 characters and we are still missing punctuation marks, uppercase letters, etc. To solve that problem, the game uses the value 0x24 as a marker to indicate that the character will be written adding 0x28 to the next value. If we want to write Apodo instead of apodo we would need the values:
0x28 0x0a 0x19 0x18 0x0d 0x18

With this information we can write a C# program that searchs all sentences in a ROM:

using System;

using System.Collections.Generic;

using System.IO;

using System.Text;

 

namespace Trivial

{

    class Program2

    {

        protected static bool ignorar;

 

        static void Main(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();

 

            int i = 0;

            int startOffset = 0;

 

            // recorre la ROM grabando las frases que encuentre

            while (i < (0x4000 - 1)) {

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

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

                    if (sb.Length > 0) {

                        writer.WriteLine(String.Format("{0:X}: {1}", startOffset, sb.ToString()));

                        sb.Length = 0;

                    }

 

                    i++;

                    startOffset = i;

 

                    continue;

                }

 

                // convierte 2 bytes en 3 digitos en base 40

                int num = (romData[i] << 8) | (romData[i + 1]);

                byte[] caracteres = CambioBase(num, 40, 3);

 

                // escribe los dígitos

                foreach (byte caracter in caracteres) {

                    sb.Append(Convertir(caracter));

                }

 

                i += 2;

            }

 

            writer.WriteLine(sb.ToString());

            writer.Close();

        }

 

        // devuelve un array de bytes con los dígitos del cambio de base

        private static byte[] CambioBase(int valor, int baseNum, int numDigitos)

        {

            byte[] resultado = new byte[numDigitos];

 

            int pos = numDigitos - 1;

 

            do {

                byte digito = (byte)(valor % baseNum);

                valor = valor / baseNum;

                resultado[pos] = digito;

                pos--;

            } while (pos >= 0);

 

            return resultado;

        }

 

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

        private static string Convertir(byte data)

        {

            string str = "#";

 

            // números

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

                str = new string((char)(((int)'0') + (data - 0x00)), 1);

            }

 

            // letras minúsculas

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

                str = new string((char)(((int)'a') + (data - 0x0a)), 1);

            }

 

            // usado para convertir a mayúsculas o para poner acentos

            if (data == 0x24) {

                return "";

            }

 

            // espacio o fín de entrada

            if (data == 0x27) {

                if (ignorar) {

                    return "";

                } else {

                    ignorar = true;

                    return " ";

                }

            }

 

            ignorar = false;

 

            return str;

        }

    }

}

 

If we run the program we get a lot of questions and answers in several ROMs:

[...]

39a: nombre dieron los romanos a las islas canarias
3bb: llamamos en espa#ol a la ciudad alemana de koln
3de: de los ap3stoles alfab1ticamente es el primero
3ff: lugar est0 #la creaci3n del hombre# de miguel angel
426: de los evangelios cuenta que un ni#o le llev3 a jes4s 5 panes y 2 peces
45b: dos paises hay cadenas monta#osas llamadas sierra nevada
484: tienen como patr3n a san nicol0s
49d: hero2na se abras3 en 1431
4b0: continente no tiene ciudades capitales

[...]

8FB: australiana
904: ava gardner
90F: sara montiel
91A: gina lollobrigida
929: brigitte bardot
936: susana estrada
943: arthur miller
94E: hern0ndez mancha
95D: john ford
966: juan pablo ii
973: ernesto iv

[...]

So now we know what is inside the ROMs we don’t know how to map:
TP_A2.BIN -> questions
TP_A4.BIN -> answers
TP_A6.BIN -> unknown data
TP_A7.BIN -> questions and answers
TP_A8.BIN -> questions and answers

The same routine that decodes the questions and answers uses up to 4 different banks to select a question, up to 4 different banks to select an answer and 2 banks to get the pointers to the questions and answers, so thanks to that information we can map ROM TP_A6.BIN and reduce the number of possible combinations to 8.

After a few tries we finally found the proper combination:

0x10000-0x13fff TP_A2.BIN
0x14000-0x17fff TP_A7.BIN
0x18000-0x1bfff TP_A4.BIN
0x1c000-0x1ffff TP_A5.BIN
0x20000-0x23fff TP_A8.BIN
0x24000-0x27fff TP_A1.BIN
0x28000-0x2bfff TP_A6.BIN
0x2c000-0x2ffff TP_A3.BIN

Some screenshots of the game running:

I’ll tidy up the code and send it to the MAMEDEV list.

Sunday, 12 March 2006 01:41:15 (Romance Standard Time, UTC+01:00)  #    Comments [9]   Games  | 
Sunday, 12 March 2006 11:27:20 (Romance Standard Time, UTC+01:00)
Que tío mas grande eres !!!! Por fin ha caído la placa mas deseada. Mención especial al trabajo que te has tomado para enseñarnos el método que has seguido para desentrañar los misterios de sus ROMs., pero espero que esta no sea tu última colaboración como0 desarrollador del MAME Team.

Muuuuuuuuuuuuchas gracias Manuel !!
Sunday, 12 March 2006 17:45:10 (Romance Standard Time, UTC+01:00)
Me llama la atención tu comentario, de que tal vez es tu ultima driver para MAME. Acaso ya no te interesa la emulación?

La experiencia que tienes creo que es de no desperdiciarse, me agradaría que siguieras adelante, tal vez como un hobbie solamente.


De todos modos, gracias por tu gran aportación al mundo de la emulación.


Saludos desde México.
Aztecatl
Sunday, 12 March 2006 18:10:18 (Romance Standard Time, UTC+01:00)
Felicidades Manuel, excelente trabajo "mental", ya no decir de la documentación mostrada y entendible de parte del proceso para deleite de las emuadictos :)

Sinceramente espero que tengas más ganas de emular otro reto, aunque sea dentro de un tiempo, pq la verdad que para muchos aún nos queda muy lejos sacar adelante esta clase de projectos con éxito.

Gracias por tus drivers, yo por mi parte espero revivir esta máquina en breve y entre preguntas que acierte brindaré a tu saud.

Un Saludo
UNK
Sunday, 12 March 2006 21:42:30 (Romance Standard Time, UTC+01:00)
Gracias por vuestros comentarios.

La emulación me sigue atrayendo bastante, pero requiere mucho tiempo y no da dinero, y al precio que están los pisos en España y lo bajos que son los sueldos, pues necesito emplear mi poco tiempo libre en cosas que me reporten beneficio económico. Así que a no ser que alguna persona adinerada me subvencione los proyectos relacionados con los juegos o me toque la lotería, tendré que hacer otras cosas :(

Saludos,
Manu
Sunday, 12 March 2006 23:01:53 (Romance Standard Time, UTC+01:00)
Realmente es una pena lo de que sea probablemente tu último driver para MAME.

Realmente tu trabajo en la emulación de algunos títulos de GAELCO fue excelente.

Creo que si abandonas, la emulación de arcades nacionales quedará seriamente malherida.
Monday, 13 March 2006 12:25:04 (Romance Standard Time, UTC+01:00)
Solo darte la enhorabuena por el excelente articulo y desearte lo mejor, seguro que llegara un momento donde estes mas estabilizado y puedas dedicar de nuevo mas tiempo a tus hobbies. Personalmente me encuentro como tu he tenido que dejar de lado mis "hobbies" intentar poder tener una vida digna en un hogar digno, por que los mayores no nos lo ponen muy facil si siguen especulando con nuestros hogares :/

Lo dicho un saludo y animo! siempre hay un ratito para darte un homenaje, nosotros te lo agradeceremos siempre, ya lo sabes!

Un Saludo
Friday, 17 March 2006 21:27:21 (Romance Standard Time, UTC+01:00)
comantario friki pero serio!

si te pago serias capaz de emular el daytona usa en condiciones?
jordigahan
Saturday, 18 March 2006 01:47:25 (Romance Standard Time, UTC+01:00)
No te podría asegurar que pudiera emularlo ni la velocidad de emulación. Además, te saldría mucho más barato comprarte la máquina...
Tuesday, 24 April 2007 17:22:54 (Romance Daylight Time, UTC+02:00)
Vengo de encontrar esta página via una busqueda por código vbnet y datasourceviews, esto de la emulacion me interesa - le daré un tiempo. =)
All comments require the approval of the site owner before being displayed.
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

[Captcha]Enter the code shown (prevents robots):

Live Comment Preview
Copyright © 2017 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.