Mikelan98
WaH used to be a bigger place...
Antes de nada, aclarar que pido créditos si usáis esta investigación para modificar vuestra ROM. Así mismo, aclaro que sólo funciona para la versión inglesa (la que funciona con PokePlat) porque me da muchísima flojera portar esto a una ROM española.
Introducción
Me ha costado 24 horas resolverlo, pero he encontrado un método para ampliar la tabla de efectividades entre tipos sin tener que expandir ningún overlay de la ROM. Hay que tener en cuenta que, a la hora de compilar PokePlat, los overlays se recortan automáticamente hasta ocupar el tamaño original, por lo que el método MeroMero no sería aplicable en este caso (además de que hay serias dificultades para hallar espacio libre lo suficientemente grande como para realojar la tabla). Si esto de la "tabla de efectividades" te está sonando a chino, recomiendo que te pases por éste thread para que sepas de lo que hablamos, pues es prácticamente el mismo mecanismo en HGSS. Sin embargo, al no existir espacio libre en la RAM, esta estrategia no nos sirve.
¿Qué estrategia vamos a seguir entonces? Pues una estrategia que funciona muy bien cuando tenemos largas cadenas de datos binarios en el arm9 o los overlays y queremos ampliarlas: la reducción de bytes a nibbles. Cabe destacar que esta estrategia es válida sólo cuando merezca la pena en ciertos casos, es decir, cuando esta reducción sea más práctica que buscar espacios en blanco. Como la tabla de efectividades es muy grande y además queremos ampliarla con bastantes bytes (teóricamente más de 30 bytes) podemos seguir esta estrategia.
Para poder aplicar esta estrategia tenemos que saber muy bien cuándo y cómo se leen esos datos, para modificar todas las rutinas involucradas. Por ello, el tracing nos va a ser de gran utilidad en esta investigación.
Fundamento
Antes de seguir, insisto en que es conveniente repasar el thread de HGSS, pero resumiendo, la tabla se compone de bytes formando tripletes AA | DD | EE, donde AA es el ID del tipo del ataque, DD es el ID del tipo del Pokémon que lo recibe, y EE es la efectividad, la cual puede ser 00=inmune, 05=poco eficaz, 0A=neutro, 14=muy eficaz.
La rutina original para leer los datos de la tabla se activa cuando se lanza un ataque (que no sea de estado) y funciona de la siguiente manera: en primer lugar, el juego almacena en la pila (stack) los valores del tipo de ataque y el/los tipo/s del Pokémon que recibe el ataque. A continuación, el juego se introduce en un bucle en el que el valor más importante a tener en cuenta se localiza en el Registro 0. Ese valor empieza el bucle con el número 0x0226ECD4, que es donde empieza la tabla de efectividades (es decir, donde empieza el primer triplete). En otro registro también hay un número, que comienza en 0 y va aumentando de uno en uno, que bien podríamos definir como un localizador que nos indica el loop actual, pero sólo funciona de apoyo para cálculos de la rutina. Pues bien, el código del juego empieza leyendo el primer byte que nos encontramos en la tabla (que corresponde al tipo de ataque del primer triplete) y lo comprueba con el valor que habíamos guardado previamente en la pila.
¿Es el mismo valor? Entonces hemos encontrado uno de los posibles tripletes que corresponden a lo que busca el juego, y el juego pasa a comparar el segundo valor del triplete para confirmarlo.
¿No corresponde alguno de esos 2 valores? Primero leemos el primer byte del siguiente triplete. Si es FF, abortamos el loop, pues el juego entiende que ya ha terminado de leer toda la tabla. Si es distinto de FF, el juego vuelve a hacer el loop pero sumando +3 al registro que antes tenía el valor 0x0226ECD4, de modo que ahora tenemos la localización del segundo triplete y, de hecho, leeremos el segundo triplete. Así, vamos comparando hasta localizar un triplete cuyos dos primeros valores coincidan con lo que busca el juego, es decir, cuando tengamos la localización del triplete que busca el juego (tipo del ataque y tipo del defensor).
Una vez el juego ha encontrado ese triplete (si no ha encontrado ninguno, se realizará un daño de efectividad neutra), se almacena el valor de la localización del triplete (sí, el valor del registro que empezaba en 0x0226ECD4 y que ahora tiene otro offset: justo el offset en el que se encuentra el triplete necesario). Ese valor se "entierra" en la pila para, poco después, usarlo para leer 2 bytes más allá de ese offset (offset + 0x2), lo que le da al juego el tercer byte del triplete seleccionado: la efectividad. Con eso se hacen cálculos más profundos, pero es algo que no nos interesa.
Por otro lado, la parte gráfica de la Pokédex (para que muestre el tipo hada) necesita también un retoque de ASM. Si miramos tal cual, sin modificar nada, veremos que los Pokémon de tipo hada son mostrados como tipos fantasma.
Nos vamos al overlay 21, al offset 0xE40A. Justo en ese offset se encuentran dos instrucciones que llamo yo "de limpieza", teóricamente no sirven de absolutamente nada, pero son vestigios de ensamblador. Lo que hacen ese par de comandos es hacer shift hacia un lado y luego hacia otro para borrar el halfword superior, pero que en el caso de la tabla con la que va a trabajar, no sirve para nada. Este par de instrucciones es muy probable que esté ahí porque, a la hora de programar en C el juego, se hayan reasignado esos valores de una variable de 32 bit a una de 16 bit.
Pero volvamos al tema que nos interesa. Justo antes de estas dos instrucciones, el programa lee una tabla con 17 posibles valores, cuyos bytes indican un "salto" del registro PC (program counter, la instrucción que está leyendo el procesador en ese momento). Una vez que se hayan "limpiado" esos valores, se le añade al registro PC ese valor, por lo que el programa se puede ramificar en 17 posibles caminos, cada uno simplemente carga un valor concreto a un registro (que será el registro que determine el icono del tipo a cargar).
Pues bien, la tabla que he mencionado tiene un posible valor para el tipo hada, pero es el mismo valor que el tipo fantasma. Por tanto, aunque exista una entrada en la tabla, no existe una ramificación del código para el tipo hada. Pero no os preocupéis en absoluto. Esas ramificaciones ocupan sólo 4 bytes. ¿Entendéis ahora por qué he mencionado esas 2 instrucciones inútiles en el código? Vamos a borrarlas para hacerle hueco a la ramificación del tipo hada.
Modificaciones
Nuestro objetivo será reducir en nibbles alguna parte de la tabla. ¿Cuál? Pues la única que es matemáticamente posible de reducir a nibbles, la de las efectividades. ¿Por qué? Porque si nos damos cuenta, todos los valores son múltiplos de 5. Podemos usar como valores de efectividad 0, 1, 2 y 4 y multiplicarlos con una instrucción. De esta manera, podemos almacenarlos en nibbles. Pues manos a la obra.
En los offsets 0x1A01A y 0x1A074 del overlay 16 (cada uno pertenece a una subrutina, y cada subrutina se encarga de uno de los dos tipos del Pokémon que recibe el ataque) vamos a puentear el código, es decir, hacer un branch y llevarnos el código a otra parte de la ROM, donde haya suficiente hueco libre. En el arm9 hay unas cuantas líneas de 00, que si bien no he comprobado que sea seguro modificarlas, no he encontrado bugs o crasheos al testear el resultado.
A fin de cuentas, la tabla al final se dividirá en dos subtablas, una con pares (en vez de tripletes) AA | DD, y más abajo, todos los valores de efectividad (divididos entre 5) seguidos. Lo podéis ver en la siguiente imagen.
En el offset 0x1A01A del overlay 16 insertaremos el siguiente código:
Código:
C0 46 00 49 88 47 01 94 0F 02 C0 46
Y en el offset 0x1A074, el siguiente:
Código:
00 49 88 47 01 94 0F 02 C0 46 C0 46
De esta forma, estamos redirigiendo al juego al offset 0x020F9400, correspondiente al arm9 en la RAM, donde insertaremos el siguiente código (lo explico para que aprendáis).
STR R0, [SP,#arg_4] . . . . . . . . . (Instrucción cortada al puentear y reescrita aquí)
PUSH {R3-R5} . . . . . . . . . . . . . (Guardamos los registros anteriores para no perderlos y poder operar con ellos)
LDR R3, =byte_226ECD4 . . . . .(Cargamos el offset de la tabla de efectividades)
SUBS R2, R2, R3 . . . . . . . . . . . .(Restamos ése offset al registro 2, que contenía el offset del triplete, de modo que obtenemos el ID del triplete)
LSRS R2, R2, #1 . . . . . . . . . . . . (Multiplicamos este ID x2)
MOVS R4, 0x10C . . . . . . . . . . . (Hay una distancia de 0x10C desde el inicio de la primera subtabla al inicio de la segunda)
ADDS R3, R3, R4 . . . . . . . . . . . (Al sumar estos dos registros, tenemos el offset de inicio de la segunda subtabla)
LSRS R2, R2, #1 . . . . . . . . . . . . (Dividimos el ID entre 2. ¡Paso crítico! Va a haber números impares dividiéndose, estamos haciendo la reducción)
LDRB R3, [R3,R2] . . . . . . . . . . .(Leemos el byte correspondiente de sumar el offset de la segunda subtabla y el ID/2)
BCS loc_20F941E . . . . . . . . . . . (¡IMPORTANTE! Esta función ramifica la rutina dependiendo de si el ID era par o impar)
MOVS R5, #0xF0 . . . . . . . . . . . .(Cargamos en el registro 5 ese valor, que en binario corresponde a 0b11110000)
ANDS R3, R5 . . . . . . . . . . . . . . .(Realizamos un AND del byte leído y del valor 0b11110000, así nos quedamos con el primer nibble, aka la mitad superior del byte)
LSRS R2, R3, #4 . . . . . . . . . . . . (Dividimos este último valor entre 16 para que se quede con la forma 0b0000XXXX)
B loc_20F9424
MOVS R5, #0xF . . . . . . . . . . . . .(Cargamos en el registro 5 ese valor, que en binario corresponde a 0b00001111)
ANDS R3, R5 . . . . . . . . . . . . . . .(Realizamos un AND del byte leído y del valor 0b00001111, así nos quedamos con el segundo nibble, aka la mitad inferior del byte)
MOV R2, R3 . . . . . . . . . . . . . . . .(Como aquí no tenemos que dividir, hay que traspasar directamente el valor al registro 2, sin modificarlo)
MOVS R5, #5
MULS R2, R5 . . . . . . . . . . . . . . .(Multiplicamos x5 el valor de la efectividad, de modo que quede con su valor final)
MOV R5, LR
ADDS R5, #8
MOV LR, R5 . . . . . . . . . . . . . . . .(Estas tres instrucciones mueven el link register un poco más adelante, ya que había datos binarios donde lo he puenteado)
POP {R3-R5} . . . . . . . . . . . . . . . (Recuperamos los valores originales de esos registros antes de la rutina)
MOV R0, R5 . . . . . . . . . . . . . . . .(Instrucción cortada al puentear y reescrita aquí)
MOV R1, R7 . . . . . . . . . . . . . . . .(Instrucción cortada al puentear y reescrita aquí)
LDR R1, =(sub_225B63C+1)
BX R1 ; sub_225B63C . . . . . . . .(Reconducimos al juego a las rutinas originales)
Esta es la churretada de bytes que corresponden a este código, que insertaremos en el offset 0xF9400 de nuestro arm9.bin:
Código:
01 90 38 B4 0C 4B D2 1A 52 08 86 24 64 00 1B 19 52 08 9B 5C 03 D2 F0 25 2B 40 1A 09 02 E0 0F 25 2B 40 1A 46 05 25 6A 43 75 46 06 35 AE 46 38 BC 28 46 39 46 01 49 08 47 D4 EC 26 02 3D B6 25 02
Aún no hemos terminado, pues tenemos que "re-adaptar" todas las funciones de la tabla para que, en vez de sumar +3 al terminar el loop, sólo sumen +2.
En el offset 0x19FB6 del overlay 16 → C046
En el offset 0x1A084 del overlay 16 → 6100 C046
En el offset 0x1A766 del overlay 16 → C046
Y por último, pero no menos importante, la nueva tabla, que va en 0x33B94 del overlay 16. Os dejo directamente la tabla actualizada con el tipo hada.
Código:
00 05 00 08 0A 0A 0A 0B 0A 0C 0A 0F 0A 06 0A 05 0A 10 0A 08 0B 0A 0B 0B 0B 0C 0B 04 0B 05 0B 10 0D 0B 0D 0D 0D 0C 0D 04 0D 02 0D 10 0C 0A 0C 0B 0C 0C 0C 03 0C 04 0C 02 0C 06 0C 05 0C 10 0C 08 0F 0B 0F 0C 0F 0F 0F 04 0F 02 0F 10 0F 08 0F 0A 01 00 01 0F 01 03 01 02 01 0E 01 06 01 05 01 11 01 08 03 0C 03 03 03 04 03 05 03 07 03 08 04 0A 04 0D 04 0C 04 03 04 02 04 06 04 05 04 08 02 0D 02 0C 02 01 02 06 02 05 02 08 0E 01 0E 03 0E 0E 0E 11 0E 08 06 0A 06 0C 06 01 06 03 06 02 06 0E 06 07 06 11 06 08 05 0A 05 0F 05 01 05 04 05 02 05 06 05 08 07 00 07 0E 07 11 07 08 07 07 10 10 10 08 11 01 11 0E 11 07 11 11 11 08 08 0A 08 0B 08 0D 08 0F 08 05 08 08 FE FE 00 07 01 07 10 09 01 09 06 09 11 09 03 09 08 09 09 10 09 01 09 11 09 08 09 0A 09 03 FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF 11 11 44 41 14 41 14 41 41 10 41 14 11 41 14 11 14 14 44 11 44 11 11 44 44 11 11 04 41 40 14 41 44 41 14 41 01 14 11 14 14 14 41 14 41 04 12 44 11 44 12 11 14 41 00 00 11 14 44 44 11 1F FF FF FF FF FF
Si queréis modificarla, os lo vuelvo a explicar. Hay un primer bloque, ordenado en AA | DD, que termina en FF FF, una serie de 00 00 y luego otra vez FF FF. Si queréis insertar más relaciones, quitad el primer FF FF y escribid vuestras nuevas relaciones, pero terminad siempre la última relación con FF FF (para que el juego deje de leer ahí y no empiece a leer los 00).
En la tabla inferior, que comienza después del último FF FF (en 11 11 44 41...) se encuentran las relaciones, correspondientes al mismo orden de los tripletes, donde 0=inefectivo, 1=poco efectivo, 2=neutro, 4=muy efectivo.
Por ejemplo, el segundo triplete es 00 08, que corresponde a Normal | Acero (es decir, un ataque de tipo normal dirigido a un Pokémon de tipo acero). Su efectividad estará en el segundo nibble de la segunda subtabla, es decir, 1 (poco efectivo).
En cuanto al overlay 21 (para que se muestre el tipo hada en la Pokédex) simplemente es borrar 4 bytes, desplazar unos cuantos e insertar 4 bytes. Pero como es más complicado explicaros dónde hay que cortar que daros un puñado de bytes y la dirección de dónde tenéis que sobreescribir, ahí van. Offset 0xE408 del overlay 21:
Código:
49 88 8F 44 22 00 26 00 2A 00 2E 00 32 00 36 00 3A 00 3E 00 42 00 66 00 46 00 4A 00 4E 00 52 00 56 00 5A 00 5E 00 62 00 00 20 70 47 06 20 70 47 0E 20 70 47 0A 20 70 47 08 20 70 47 05 20 70 47 0B 20 70 47 07 20 70 47 09 20 70 47 01 20 70 47 03 20 70 47 02 20 70 47 04 20 70 47 0F 20 70 47 0D 20 70 47 10 20 70 47 0C 20 70 47 12 20 70 47
Cabe mencionar que tenéis que añadir los recursos gráficos correspondientes en resource/zukan (el icono del tipo hada debe aparecer el último, por detrás de esa extraña barra amarilla).
A partir de aquí os toca: modificar el icono ??? que aparece en el menú Pokémon, modificar los datos de los Pokémon para asignarle el tipo hada a los que haya que asignárselo, editar los ataques para lo mismo, y modificar los colores de la movebox. Eso lo dejo en vuestras manos, y especialmente en las de Mimi y Bag que son los que más se han esforzado en conseguir esto, pese a que no han podido seguir por culpa del ASM hasta que a mí me ha dado la gana de hacerlo.
Se aceptan sugerencias para modificar el thread. Gracias y buenas noches. Sed buenos.
Última edición: