Kaiser de Emperana
Called in hand
Bueno en este tutorial, les voy a enseñar como usar C tal y como si fuese ASM. No voy a enseñar a programar en C, para eso, cualquier tutorial que encuentren por internet les va a servir.
La forma de programar es EXCTAMENTE igual a la de programar en una computadora, con el pequeño detalle, de que la computadora no tiene las mismas funciones que un rom de pokemon. Así que no van a poder llamar por ejemplo a "printf".
No necesitan tener conocimientos en ASM para seguir este tutorial ya que voy a ir explicando el código asm que vaya apareciendo, pero tenerlos, probablemente les facilite más las cosas
Pero bueno, vamos al tuto.
Para eso voy a tomar la rutina determinadora del poder oculto de un pokemon escrita por @Javi4315♪ en este tema. La que a simple vista es un engendro del demonio que busca robarse nuestra alma a base de su complejidad.
- Un buen editor de texto (como siempre, yo recomiendo Notepad++)
- Una base para empezar a escribir nuestro código. Por lo que descarguen este repositorio, el cual vamos a modificar.
Ahora borren los archivos "BPRE.ld", "BPEE.ld" y "README.md".
Con eso borramos los rastros del código del repositorio.
En este tuto voy a enseñar tanto a hacer la rutina para FR como para emerald, asi que vamos a hacer la compilación un poco más general. Abran el archivo "Makefile" y cambien la línea:
por
Luego creen un archivo en la carpeta principal llamado "ROM.ld". Más adelante vamos a ver para que sirve este archivo.
Ahora, abran el archivo "main.s" y modifique la linea que dice:
por:
Y pueden modificar el offset de la OTRA línea para cambiar la dirección donde se insertará nuestro código.
Ahora pongan su rom en la carpeta con el nombre "mirom.gba".
Y fnalmente, creen un archivo con el nombre que quieran dentro de la carpeta "src" con la condición de que su nombre termine en ".c". Yo le voy a poner: "poder_oculto.c".
Con eso ya estamos listos.
Empecemos con la traducción:
La parte del push, es obviamente el comienzo de la función.
Luego de eso, lo que javi hace es:
- Cargar el puntero a la variable 0x8000 en r0
- Posteriormente carga el valor de esa variable en el mismo registro (por lo que podemos asumir que a javi no le importaba el puntero sino el valor de la variable).
- Y finalmente carga en r1 el puntero a al equipo pokemon del jugador.
Los que se hayan puesto a ver un códig en C, probablemente se les haya presentado la sigueinte duda: "¡¿Y acá donde se supone qu están los offsets?!". Y bueno respondiendo la duda, están en el archivo "ROM.ld" que acabamos de crear hace un rato. Así que habran el archivo y escriban lo siguiente:
Ahora cuando escribamos nuestro código en C, con simplemente escribir "var_8000" eso será equivalente al valor almacenado en la variable.
Con la parte del código que vimos, todavía no hay mucho que escribir de nuestro código, pero vayamos defieniendo nuestra función:
Lo que estamos incluyendo, son simples definiciones de los tipos u8, u16, u32, s8, etc (sacado de pokeagb).
Bueno, en las ultimas cuatro líneas simplemente se le esta dando valores a los distintos registros.
Por otro lado las primeras tres líneas lo que hacen es, al puntero al equipo pokemon se le suma el valor de la variable 0x8000 multiplicado por 0x64 (la cantidad de memoria que ocupa un pokemon). Por lo que en r1 ahora lo que tenemos no es el puntero al equipo pokemon, sino el puntero al pokemon que elejimos, segun las reglas que javi nos definio para la rutina.
Y finalmente la línea que falta. Ya que a javi lo que le interesaban eran las estadísticas del pokemon, para así calcular su poder oculto, lo que hizo fue sumarle al puntero 0x56, lo que convierte el puntero a un pokemon en el puntero a los bytes que tienen almacenado la vida actual del pokemon.
Y aqui fue cuando me di cuenta que la rutina de javi estaba mal, pues el poder oculto del pokemon se calcula con los IV's no con el total de la estadística xD. Pero esto me viene genial para explicar otra cosa más al final del tuto.
Aún así por ahora vamos a seguir traduciendo la rutina de Javi tal cual está.
Bueno a partir de ahora, vamos a empezar a trabajar con los datos del pokemon, pero como trabajar con punteros como en asm no es muy lindo que digamos, vamos a definir nuestra estructura pokemon Según bulbapedia, nos sería suficiente una estructura tal que así:
De paso también declaramos los tipos de las variables:
- var_8000: que es un numero de 2 bytes.
- pkmn_party: que es un vector con 6 pokemon. (los datos de 6 pokemon seguidos)
Y finalmente agregamos agregamos el código que estuvimos traduciendo:
(Con excepcion del r1 + 0x56, el cual usaremos de otra forma).
Acá, segun nuestro amigo javi nos indica, llegamos a un bucle y podemos ver que su condición es que continuará mientra r4 sea distinto de 6.
Más abajo vemos que javi le suma r3 al puntero a las estadísticas, lo que causa que la primera estadística que dijimos (vida actual) sea ignorada y posteriormente guarda el valor de la estadística en r2. Este es el único lugar donde r3 es usado y se usa para ir pasando de una estadística a la siguiente en el bucle.
Por lo que podemos traducir el código como:
Ya que como veremos proximamente r4 ira cambiando al final de cada vuelta del bucle.
Bueo, yo esta parte la hubiera hecho de una forma diferente, pero veamos lo que javi hizo.
Javi lo que hace es comparar el valor de la estadística con 1 y con 0, si es 0, simplemente aumenta 1 a r4 y sale del bucle 2; si no es ni 0 ni 1 lo que hace es restarle 2 a la estadistica y continuar con el bucle 2 (hasta llegar un punto en que el valor será 1 o 0), básicamente determinando si el número es par o impar.
Finalmente si el valor es 1, la estadística es impar, por lo que procede a aumentar el valor de r0 segun corresponda (para más info en porque hace esto ver...)
Pero bueno, si bien el código es largo es sencillo de comprender, traduciendo, lo que termina haciendo es:
Acá no hay anda raro con excepción de ls línea "swi 0x6". Esta es una llamada a la bios, a la función de division. Citando a gba tek:
Y finalmente cambia el valor de r0 a 0.
Bueno, como podrán notar no agregamos el puntero de la variable 0x8004 en ningun lado, por lo que tenemos que hacerlo. Así que agregamos la siguiente linea al archivo ROM.ld:
Y tambien debemos indicarle al compilador que use la función de division de la bios debemos agregar tambien al mismo archivo las siguientes líneas:
Finalmente agregamos el nuevo código (y tambien la declaración de la variable 0x8004):
Y ya que estamos limpiemos un poco el códio de cosas innecesarias y cambiemos el nombre a las variables:
Bueno aquí nuevamente, lo que hace es cargar el puntero a las estadísticas del pokemon y posteriormente compara el resultado de dividir la primera estadística por 4, Si da distinto a 0 suma 1 a un acumulador. Caso contrario no hace nada.
Por lo que podemos traducir esto como:
Ahora lo que vamos a hacer es corregir este error. Básicamente lo único que tenemos que cambiar son estas dos líneas:
Para que usen los iv's en lugar de los totales.
Los que tengan un poco de idea del tema sabrán que leer los iv's no es tan sencillo como leer los totales, ya que los mismos estan encriptados.
Pero el rom, tiene ya programadas unas funciones que sirven para obtener esos datos, así que lo que vamos a hacer es usar esas.
Y se preguntarán de donde es que vamos a sacar esas funciones...
Bueno, en este foro no nos enteramos de nada pero por si no lo sabían, la gente de pokecommunity tiene las roms bastante investigadas... Y no es que precisamente oculten los datos, todo es bastante público... Si vemos este repositorio podemos ver que hay muchas funciones con sus cabeceras declaradas.
A nosotros la que nos interesa es "pokemon_getattr" la cual se encuentra en este archivo
Ahora, si compilamos este repositorio nos va a aparecer un archivo ld, con los offset de todas estas funciones en fr. Pueden ver una version ya compilada aquí
El offset de la funcion que necesitamos es:
Por si esto fuera poco, podemos buscar información en pokeemerald tambien. Acá la función se llama "GetMonData" y pueden ver su código fuente aquí.
Así que no se pueden quejar de que tenemos poca información...
Que debería de estarse viendo algo así:
Ahora lo que toca es declarar el prototipo de la funcion. Y de paso vamos a cambiar nuestra definición de la estructura pokemon y vamos a usar la que tiene pokemerald (la cual está, junto con muchas otras cosas, en el archivo "pokemon.h" de la carpeta "src/headers"). Así que cambiamos nuestro código tal que así:
Y finalmente cambiamos las condiciones de los if para utilizar llamadas a la función GetMonData. Esta función recibe un puntero a un pokemon, un número que indica el dato que se quiere pedir y un parámetro nulo. En pokemon.h pueden ver los diferentes atributos del pokemon que pueden pedir.
Pero bueno el código terminaría tal que así:
Ahora al compilarlos aparecerá un archivo offset.txt que tiene los offset donde fueron insertadas nuestra/s funcion/es.
Y con eso estaría todo.
Los equivalentes de los nombres en emerald son:
Y bueno, eso sería todo.
Cualquier duda, pregunten. Si hay partes poco claras avisen.
Saludos.
La forma de programar es EXCTAMENTE igual a la de programar en una computadora, con el pequeño detalle, de que la computadora no tiene las mismas funciones que un rom de pokemon. Así que no van a poder llamar por ejemplo a "printf".
No necesitan tener conocimientos en ASM para seguir este tutorial ya que voy a ir explicando el código asm que vaya apareciendo, pero tenerlos, probablemente les facilite más las cosas
Pero bueno, vamos al tuto.
¿Qué vamos a hacer?
Como dije, en este tuto vamos a ver como C es mejor que el ASM y porque es mejor. Así que lo que vamos a hacer va a ser tomar una rutina ASM y ver como hacer lo mismo en C.Para eso voy a tomar la rutina determinadora del poder oculto de un pokemon escrita por @Javi4315♪ en este tema. La que a simple vista es un engendro del demonio que busca robarse nuestra alma a base de su complejidad.
Javi4315♪;378518 dijo:......Código:.align 2 .thumb push {r0-r6,lr} ldr r0, .var_8000 ldrb r0, [r0] ldr r1, .pkmn_party mov r2, #0x64 mul r2, r0 add r1, r1, r2 mov r2, #0x56 add r1, r1, r2 mov r0, #0x0 mov r3, #0x2 mov r4, #0x0 mov r5, #0x2 loop1: cmp r4, #0x6 beq cont add r1, r1, r3 ldrh r2, [r1] loop2: cmp r2, #0x1 beq impar cmp r2, #0x0 beq par sub r2, r2, r5 b loop2 par: add r4, #0x1 b loop1 impar: cmp r4, #0x1 beq corr2 cmp r4, #0x2 beq corr3 cmp r4, #0x3 beq corr4 cmp r4, #0x4 beq corr5 cmp r4, #0x5 beq corr6 mov r6, #0x1 b add corr2: mov r6, #0x2 b add corr3: mov r6, #0x4 b add corr4: mov r6, #0x8 b add corr5: mov r6, #0x10 b add corr6: mov r6, #0x20 add: add r0, r0, r6 add r4, #0x1 b loop1 cont: mov r1, #0x3F mov r2, #0xF mul r0, r2 swi 0x6 ldr r1, .var_8000 strh r0, [r1,#0x8] mov r0, #0x0 ldr r0, .var_8000 ldrb r0, [r0] ldr r4, .pkmn_party mov r2, #0x64 mul r2, r0 add r4, r4, r2 mov r2, #0x58 add r4, r4, r2 mov r5, #0x0 ldrh r0, [r4] mov r1, #0x4 swi 0x6 cmp r3, #0x1 bls jump1 mov r0, #0x1 add r5, r5, r0 jump1: ldrh r0, [r4,#0x2] mov r1, #0x4 swi 0x6 cmp r1, #0x1 bls jump2 mov r0, #0x2 add r5, r5, r0 jump2: ldrh r0, [r4,#0x4] mov r1, #0x4 swi 0x6 cmp r1, #0x1 bls jump3 mov r0, #0x4 add r5, r5, r0 jump3: ldrh r0, [r4,#0x6] mov r1, #0x4 swi 0x6 cmp r1, #0x1 bls jump4 mov r0, #0x8 add r5, r5, r0 jump4: ldrh r0, [r4,#0x8] mov r1, #0x4 swi 0x6 cmp r1, #0x1 bls jump5 mov r0, #0x10 add r5, r5, r0 jump5: ldrh r0, [r4,#0xA] mov r1, #0x4 swi 0x6 cmp r1, #0x1 bls jump6 mov r0, #0x20 add r5, r5, r0 jump6: mov r6, #0x28 mov r1, #0x3F mov r0, #0x0 add r0, r0, r5 mul r0,r6 swi 0x6 mov r6, #0x1E add r0, r0, r6 ldr r1, .var_8000 strh r0, [r1,#0xA] pop {r0-r6,pc} .var_8000: .word 0x020370B8 .pkmn_party: .word 0x02024284
¿Qué vamos a necesitar?
Bueno, para seguir este tuto lo que van a necesitar es:- Un buen editor de texto (como siempre, yo recomiendo Notepad++)
- Una base para empezar a escribir nuestro código. Por lo que descarguen este repositorio, el cual vamos a modificar.
Adaptando el repositorio
Bueno, lo primero que deben hacer es entrar a la carpeta src y borrar el archivo "functions.c".Ahora borren los archivos "BPRE.ld", "BPEE.ld" y "README.md".
Con eso borramos los rastros del código del repositorio.
En este tuto voy a enseñar tanto a hacer la rutina para FR como para emerald, asi que vamos a hacer la compilación un poco más general. Abran el archivo "Makefile" y cambien la línea:
Código:
export ROM_CODE := BPRE
Código:
export ROM_CODE := ROM
Ahora, abran el archivo "main.s" y modifique la linea que dice:
Código:
.open "BPRE0.gba","build/rom.gba", 0x08000000
Código:
.open "mirom.gba","build/rom.gba", 0x08000000
Ahora pongan su rom en la carpeta con el nombre "mirom.gba".
Y fnalmente, creen un archivo con el nombre que quieran dentro de la carpeta "src" con la condición de que su nombre termine en ".c". Yo le voy a poner: "poder_oculto.c".
Con eso ya estamos listos.
Traduciendo el código de ASM a C
Como dije anteriormente, en este tuto vamos a hacer una rutina ya hecha en ASM, en C. Si no tienen una rutina base en ASM, simplemente escriban lo que queiran hacer en C...Empecemos con la traducción:
Código:
push {r0-r6,lr}
ldr r0, .var_8000
ldrb r0, [r0]
ldr r1, .pkmn_party
...
.var_8000: .word 0x020370B8
.pkmn_party: .word 0x02024284
Luego de eso, lo que javi hace es:
- Cargar el puntero a la variable 0x8000 en r0
- Posteriormente carga el valor de esa variable en el mismo registro (por lo que podemos asumir que a javi no le importaba el puntero sino el valor de la variable).
- Y finalmente carga en r1 el puntero a al equipo pokemon del jugador.
Los que se hayan puesto a ver un códig en C, probablemente se les haya presentado la sigueinte duda: "¡¿Y acá donde se supone qu están los offsets?!". Y bueno respondiendo la duda, están en el archivo "ROM.ld" que acabamos de crear hace un rato. Así que habran el archivo y escriban lo siguiente:
Código:
var_8000 = 0x020370B8;
pkmn_party = 0x02024284;
Con la parte del código que vimos, todavía no hay mucho que escribir de nuestro código, pero vayamos defieniendo nuestra función:
Código:
#include "headers/types.h"
void rutina_javi() {
}
Código:
mov r2, #0x64
mul r2, r0
add r1, r1, r2
mov r2, #0x56
add r1, r1, r2
mov r0, #0x0
mov r3, #0x2
mov r4, #0x0
mov r5, #0x2
Por otro lado las primeras tres líneas lo que hacen es, al puntero al equipo pokemon se le suma el valor de la variable 0x8000 multiplicado por 0x64 (la cantidad de memoria que ocupa un pokemon). Por lo que en r1 ahora lo que tenemos no es el puntero al equipo pokemon, sino el puntero al pokemon que elejimos, segun las reglas que javi nos definio para la rutina.
Y finalmente la línea que falta. Ya que a javi lo que le interesaban eran las estadísticas del pokemon, para así calcular su poder oculto, lo que hizo fue sumarle al puntero 0x56, lo que convierte el puntero a un pokemon en el puntero a los bytes que tienen almacenado la vida actual del pokemon.
Y aqui fue cuando me di cuenta que la rutina de javi estaba mal, pues el poder oculto del pokemon se calcula con los IV's no con el total de la estadística xD. Pero esto me viene genial para explicar otra cosa más al final del tuto.
Aún así por ahora vamos a seguir traduciendo la rutina de Javi tal cual está.
Bueno a partir de ahora, vamos a empezar a trabajar con los datos del pokemon, pero como trabajar con punteros como en asm no es muy lindo que digamos, vamos a definir nuestra estructura pokemon Según bulbapedia, nos sería suficiente una estructura tal que así:
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
}
u16 var_8000;
struct Pokemon pkmn_party[6];
void rutina_javi() {
}
- var_8000: que es un numero de 2 bytes.
- pkmn_party: que es un vector con 6 pokemon. (los datos de 6 pokemon seguidos)
Y finalmente agregamos agregamos el código que estuvimos traduciendo:
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* r1;
u32 r0,r3,r4,r5;
r1 = &pkmn_party[var_8000];
r0 = 0;
r3 = 2;
r4 = 0;
r5 = 2;
}
Código:
loop1:
cmp r4, #0x6
beq cont
add r1, r1, r3
ldrh r2, [r1]
Más abajo vemos que javi le suma r3 al puntero a las estadísticas, lo que causa que la primera estadística que dijimos (vida actual) sea ignorada y posteriormente guarda el valor de la estadística en r2. Este es el único lugar donde r3 es usado y se usa para ir pasando de una estadística a la siguiente en el bucle.
Por lo que podemos traducir el código como:
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* r1;
u32 r0,r3,r4,r5;
u16 r2;
r1 = &pkmn_party[var_8000];
r0 = 0;
r3 = 2;
r4 = 0;
r5 = 2;
// loop1:
while (r4 != 6) {
r2 = r1->estadisticas[1 + r4];
}
}
Código:
loop2:
cmp r2, #0x1
beq impar
cmp r2, #0x0
beq par
sub r2, r2, r5
b loop2
par:
add r4, #0x1
b loop1
impar:
cmp r4, #0x1
beq corr2
cmp r4, #0x2
beq corr3
cmp r4, #0x3
beq corr4
cmp r4, #0x4
beq corr5
cmp r4, #0x5
beq corr6
mov r6, #0x1
b add
corr2:
mov r6, #0x2
b add
corr3:
mov r6, #0x4
b add
corr4:
mov r6, #0x8
b add
corr5:
mov r6, #0x10
b add
corr6:
mov r6, #0x20
add:
add r0, r0, r6
add r4, #0x1
b loop1
Javi lo que hace es comparar el valor de la estadística con 1 y con 0, si es 0, simplemente aumenta 1 a r4 y sale del bucle 2; si no es ni 0 ni 1 lo que hace es restarle 2 a la estadistica y continuar con el bucle 2 (hasta llegar un punto en que el valor será 1 o 0), básicamente determinando si el número es par o impar.
Finalmente si el valor es 1, la estadística es impar, por lo que procede a aumentar el valor de r0 segun corresponda (para más info en porque hace esto ver...)
Pero bueno, si bien el código es largo es sencillo de comprender, traduciendo, lo que termina haciendo es:
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* r1;
u32 r0,r4;
u16 r2;
r1 = &pkmn_party[var_8000];
r0 = 0;
r4 = 0;
// loop1:
while (r4 != 6) {
r2 = r1->estadisticas[1 + r4];
// loop2:
while (1) {
if (r2 == 0) {
r4++;
break;
} else if (r2 == 1) {
switch (r4) {
case 0:
r0 += 1;
break;
case 1:
r0 += 2;
break;
case 2:
r0 += 4;
break;
case 3:
r0 += 8;
break;
case 4:
r0 += 0x10;
break;
case 5:
r0 += 0x20;
break;
}
r4++;
break;
}
r2 -= 2;
}
}
}
Código:
cont:
mov r1, #0x3F
mov r2, #0xF
mul r0, r2
swi 0x6
ldr r1, .var_8000
strh r0, [r1,#0x8]
mov r0, #0x0
Básicamente lo que hace javi es guardar en la variable 0x8004 el valor de (r0 * 0xF / 0x3F).Gba Tek dijo:SWI 06h (GBA) or SWI 09h (NDS7/NDS9/DSi7/DSi9) - Div
Signed Division, r0/r1.
r0 signed 32bit Number
r1 signed 32bit Denom
Return:
r0 Number DIV Denom ;signed
r1 Number MOD Denom ;signed
r3 ABS (Number DIV Denom) ;unsigned
Y finalmente cambia el valor de r0 a 0.
Bueno, como podrán notar no agregamos el puntero de la variable 0x8004 en ningun lado, por lo que tenemos que hacerlo. Así que agregamos la siguiente linea al archivo ROM.ld:
Código:
var_8004 = 0x020370B8 + 8;
Código:
__aeabi_idiv = 0x081E3B68|1;
__aeabi_idivmod = 0x081E3B68|1;
__aeabi_uidiv = 0x081E3B68|1;
__aeabi_uidivmod = 0x081E3B68|1;
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
u16 var_8004;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* r1;
u32 r0,r3,r4,r5;
u16 r2;
r1 = &pkmn_party[var_8000];
r0 = 0;
r3 = 2;
r4 = 0;
r5 = 2;
// loop1:
while (r4 != 6) {
r2 = r1->estadisticas[1 + r4];
// loop2:
while (1) {
if (r2 == 0) {
r4++;
break;
} else if (r2 == 1) {
switch (r4) {
case 0:
r0 += 1;
break;
case 1:
r0 += 2;
break;
case 2:
r0 += 4;
break;
case 3:
r0 += 8;
break;
case 4:
r0 += 0x10;
break;
case 5:
r0 += 0x20;
break;
}
r4++;
break;
}
r2 -= 2;
}
}
// cont:
var_8004 = r0 * 0xf / 0x3f;
r0 = 0;
}
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
u16 var_8004;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador,i;
u16 valor_estadistica;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador = 0;
i = 0;
// loop1:
while (i != 6) {
valor_estadistica = puntero_a_pokemon->estadisticas[1 + i];
// loop2:
while (1) {
if (valor_estadistica == 0) {
i++;
break;
} else if (valor_estadistica == 1) {
switch (i) {
case 0:
acumulador += 1;
break;
case 1:
acumulador += 2;
break;
case 2:
acumulador += 4;
break;
case 3:
acumulador += 8;
break;
case 4:
acumulador += 0x10;
break;
case 5:
acumulador += 0x20;
break;
}
i++;
break;
}
valor_estadistica -= 2;
}
}
// cont:
var_8004 = acumulador * 0xf / 0x3f;
acumulador = 0;
}
Código:
ldr r0, .var_8000
ldrb r0, [r0]
ldr r4, .pkmn_party
mov r2, #0x64
mul r2, r0
add r4, r4, r2
mov r2, #0x58
add r4, r4, r2
mov r5, #0x0
ldrh r0, [r4]
mov r1, #0x4
swi 0x6
cmp r3, #0x1
bls jump1
mov r0, #0x1
add r5, r5, r0
Por lo que podemos traducir esto como:
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
u16 var_8004;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador,i;
u16 valor_estadistica;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador = 0;
i = 0;
// loop1:
while (i != 6) {
valor_estadistica = puntero_a_pokemon->estadisticas[1 + i];
// loop2:
while (1) {
if (valor_estadistica == 0) {
i++;
break;
} else if (valor_estadistica == 1) {
switch (i) {
case 0:
acumulador += 1;
break;
case 1:
acumulador += 2;
break;
case 2:
acumulador += 4;
break;
case 3:
acumulador += 8;
break;
case 4:
acumulador += 0x10;
break;
case 5:
acumulador += 0x20;
break;
}
i++;
break;
}
valor_estadistica -= 2;
}
}
// cont:
var_8004 = acumulador * 0xf / 0x3f;
acumulador = 0;
if (puntero_a_pokemon->estadisticas[1] / 4 != 0){
acumulador += 1;
}
}
A partir de este punto la función se vuelve muy repetitiva. Es el mismo if del anterior spoiler pero cambiando de constante a sumar y de estadística.
El código se resumiría en:
Finalmente, lo unico que hace son unas operaciones matemáticas (más específicamente "acumulador * 0x28 / 0x3F + 0x1E") y guardar el resultado en la variable 0x8005.
Bueno como podrán suponer debemos agregar el offset de la variable 0x8005 a ROM.ld y declararla en nuestro código fuente. Así que agregamos a ROM.ld:
Y luego en el código bastaría con poner:
Código:
jump1:
ldrh r0, [r4,#0x2]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump2
mov r0, #0x2
add r5, r5, r0
jump2:
ldrh r0, [r4,#0x4]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump3
mov r0, #0x4
add r5, r5, r0
jump3:
ldrh r0, [r4,#0x6]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump4
mov r0, #0x8
add r5, r5, r0
jump4:
ldrh r0, [r4,#0x8]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump5
mov r0, #0x10
add r5, r5, r0
jump5:
ldrh r0, [r4,#0xA]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump6
mov r0, #0x20
add r5, r5, r0
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
u16 var_8004;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador,i;
u16 valor_estadistica;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador = 0;
i = 0;
// loop1:
while (i != 6) {
valor_estadistica = puntero_a_pokemon->estadisticas[1 + i];
// loop2:
while (1) {
if (valor_estadistica == 0) {
i++;
break;
} else if (valor_estadistica == 1) {
switch (i) {
case 0:
acumulador += 1;
break;
case 1:
acumulador += 2;
break;
case 2:
acumulador += 4;
break;
case 3:
acumulador += 8;
break;
case 4:
acumulador += 0x10;
break;
case 5:
acumulador += 0x20;
break;
}
i++;
break;
}
valor_estadistica -= 2;
}
}
// cont:
var_8004 = acumulador * 0xf / 0x3f;
acumulador = 0;
if (puntero_a_pokemon->estadisticas[1] / 4 != 0){
acumulador += 1;
}
if (puntero_a_pokemon->estadisticas[2] / 4 != 0){
acumulador += 2;
}
if (puntero_a_pokemon->estadisticas[3] / 4 != 0){
acumulador += 4;
}
if (puntero_a_pokemon->estadisticas[4] / 4 != 0){
acumulador += 8;
}
if (puntero_a_pokemon->estadisticas[5] / 4 != 0){
acumulador += 16;
}
if (puntero_a_pokemon->estadisticas[6] / 4 != 0){
acumulador += 32;
}
}
Código:
jump6:
mov r6, #0x28
mov r1, #0x3F
mov r0, #0x0
add r0, r0, r5
mul r0,r6
swi 0x6
mov r6, #0x1E
add r0, r0, r6
ldr r1, .var_8000
strh r0, [r1,#0xA]
pop {r0-r6,pc}
Bueno como podrán suponer debemos agregar el offset de la variable 0x8005 a ROM.ld y declararla en nuestro código fuente. Así que agregamos a ROM.ld:
Código:
var_8005 = 0x020370B8 + 0xA;
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
u16 var_8004;
u16 var_8005;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador,i;
u16 valor_estadistica;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador = 0;
i = 0;
// loop1:
while (i != 6) {
valor_estadistica = puntero_a_pokemon->estadisticas[1 + i];
// loop2:
while (1) {
if (valor_estadistica == 0) {
i++;
break;
} else if (valor_estadistica == 1) {
switch (i) {
case 0:
acumulador += 1;
break;
case 1:
acumulador += 2;
break;
case 2:
acumulador += 4;
break;
case 3:
acumulador += 8;
break;
case 4:
acumulador += 0x10;
break;
case 5:
acumulador += 0x20;
break;
}
i++;
break;
}
valor_estadistica -= 2;
}
}
// cont:
var_8004 = acumulador * 0xf / 0x3f;
acumulador = 0;
if (puntero_a_pokemon->estadisticas[1] / 4 != 0){
acumulador += 1;
}
if (puntero_a_pokemon->estadisticas[2] / 4 != 0){
acumulador += 2;
}
if (puntero_a_pokemon->estadisticas[3] / 4 != 0){
acumulador += 4;
}
if (puntero_a_pokemon->estadisticas[4] / 4 != 0){
acumulador += 8;
}
if (puntero_a_pokemon->estadisticas[5] / 4 != 0){
acumulador += 16;
}
if (puntero_a_pokemon->estadisticas[6] / 4 != 0){
acumulador += 32;
}
var_8005 = acumulador * 0x28 / 0x3F + 0x1E;
}
Bueno, entonces cual piensan que es más comprensible:
A mi parecer la version de C es mucho mejor, más facil de entender. Pero aún así no deja de ser un poco fea.
¿Cuál es la causa de esto? Dos cosas:
- Javi no se dio cuenta que pudo haber optimizado el calculo de las constantes a acumular segun el numero de pasada del bucle, lo que lo llevo a hacer unos cuantos compare que podrían haber sido obviados.
- La razón principal y probablemente causa de la anterior, porque programar en asm te quema la cabeza. Javi tuvo que estar haciendo malabares con los registros que podía usar y los que no, cosa que lo llevo a hacer 2 bucles en lugar de uno, ya que el dividir números le ocupama muchos registros.
Esta es la función de javi con unas cuantas optimizaciones y aprovechando el hecho de que C te deja usar variables temporales de forma sencilla:
Código:
.align 2
.thumb
push {r0-r6,lr}
ldr r0, .var_8000
ldrb r0, [r0]
ldr r1, .pkmn_party
mov r2, #0x64
mul r2, r0
add r1, r1, r2
mov r2, #0x56
add r1, r1, r2
mov r0, #0x0
mov r3, #0x2
mov r4, #0x0
mov r5, #0x2
loop1:
cmp r4, #0x6
beq cont
add r1, r1, r3
ldrh r2, [r1]
loop2:
cmp r2, #0x1
beq impar
cmp r2, #0x0
beq par
sub r2, r2, r5
b loop2
par:
add r4, #0x1
b loop1
impar:
cmp r4, #0x1
beq corr2
cmp r4, #0x2
beq corr3
cmp r4, #0x3
beq corr4
cmp r4, #0x4
beq corr5
cmp r4, #0x5
beq corr6
mov r6, #0x1
b add
corr2:
mov r6, #0x2
b add
corr3:
mov r6, #0x4
b add
corr4:
mov r6, #0x8
b add
corr5:
mov r6, #0x10
b add
corr6:
mov r6, #0x20
add:
add r0, r0, r6
add r4, #0x1
b loop1
cont:
mov r1, #0x3F
mov r2, #0xF
mul r0, r2
swi 0x6
ldr r1, .var_8000
strh r0, [r1,#0x8]
mov r0, #0x0
ldr r0, .var_8000
ldrb r0, [r0]
ldr r4, .pkmn_party
mov r2, #0x64
mul r2, r0
add r4, r4, r2
mov r2, #0x58
add r4, r4, r2
mov r5, #0x0
ldrh r0, [r4]
mov r1, #0x4
swi 0x6
cmp r3, #0x1
bls jump1
mov r0, #0x1
add r5, r5, r0
jump1:
ldrh r0, [r4,#0x2]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump2
mov r0, #0x2
add r5, r5, r0
jump2:
ldrh r0, [r4,#0x4]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump3
mov r0, #0x4
add r5, r5, r0
jump3:
ldrh r0, [r4,#0x6]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump4
mov r0, #0x8
add r5, r5, r0
jump4:
ldrh r0, [r4,#0x8]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump5
mov r0, #0x10
add r5, r5, r0
jump5:
ldrh r0, [r4,#0xA]
mov r1, #0x4
swi 0x6
cmp r1, #0x1
bls jump6
mov r0, #0x20
add r5, r5, r0
jump6:
mov r6, #0x28
mov r1, #0x3F
mov r0, #0x0
add r0, r0, r5
mul r0,r6
swi 0x6
mov r6, #0x1E
add r0, r0, r6
ldr r1, .var_8000
strh r0, [r1,#0xA]
pop {r0-r6,pc}
.var_8000: .word 0x020370B8
.pkmn_party: .word 0x02024284
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
u16 var_8004;
u16 var_8005;
struct Pokemon pkmn_party[6];
void rutina_javi() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador,i;
u16 valor_estadistica;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador = 0;
i = 0;
// loop1:
while (i != 6) {
valor_estadistica = puntero_a_pokemon->estadisticas[1 + i];
// loop2:
while (1) {
if (valor_estadistica == 0) {
i++;
break;
} else if (valor_estadistica == 1) {
switch (i) {
case 0:
acumulador += 1;
break;
case 1:
acumulador += 2;
break;
case 2:
acumulador += 4;
break;
case 3:
acumulador += 8;
break;
case 4:
acumulador += 0x10;
break;
case 5:
acumulador += 0x20;
break;
}
i++;
break;
}
valor_estadistica -= 2;
}
}
// cont:
var_8004 = acumulador * 0xf / 0x3f;
acumulador = 0;
if (puntero_a_pokemon->estadisticas[1] / 4 != 0){
acumulador += 1;
}
if (puntero_a_pokemon->estadisticas[2] / 4 != 0){
acumulador += 2;
}
if (puntero_a_pokemon->estadisticas[3] / 4 != 0){
acumulador += 4;
}
if (puntero_a_pokemon->estadisticas[4] / 4 != 0){
acumulador += 8;
}
if (puntero_a_pokemon->estadisticas[5] / 4 != 0){
acumulador += 16;
}
if (puntero_a_pokemon->estadisticas[6] / 4 != 0){
acumulador += 32;
}
var_8005 = acumulador * 0x28 / 0x3F + 0x1E;
}
A mi parecer la version de C es mucho mejor, más facil de entender. Pero aún así no deja de ser un poco fea.
¿Cuál es la causa de esto? Dos cosas:
- Javi no se dio cuenta que pudo haber optimizado el calculo de las constantes a acumular segun el numero de pasada del bucle, lo que lo llevo a hacer unos cuantos compare que podrían haber sido obviados.
- La razón principal y probablemente causa de la anterior, porque programar en asm te quema la cabeza. Javi tuvo que estar haciendo malabares con los registros que podía usar y los que no, cosa que lo llevo a hacer 2 bucles en lugar de uno, ya que el dividir números le ocupama muchos registros.
Esta es la función de javi con unas cuantas optimizaciones y aprovechando el hecho de que C te deja usar variables temporales de forma sencilla:
Código:
#include "headers/types.h"
struct Pokemon {
u8 bytes_que_no_vamos_a_usar[0x56];
u16 estadisticas[7]; // Desde HP actual hasta Ataque especial
};
u16 var_8000;
u16 var_8004;
u16 var_8005;
struct Pokemon pkmn_party[6];
void rutina_javi_optimizada() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador_tipo, acumulador_poder,i;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador_tipo = 0;
acumulador_poder = 0;
for (i = 0; i < 6; i++) {
if (puntero_a_pokemon->estadisticas[1 + i] % 2) {
acumulador_tipo += 1 << i;
}
if (puntero_a_pokemon->estadisticas[1 + i] / 4) {
acumulador_poder += 1 << i;
}
}
var_8004 = acumulador_tipo * 0xf / 0x3f;
var_8005 = acumulador_poder * 0x28 / 0x3F + 0x1E;
}
Corrigiendo el error y aprendiendo a llamar funciones
Bueno, como dije antes, mientras estaba escribiendo este tuto me di cuenta qu la rutina de Javi tenía un pequeño error y es que usaba las estadísticas incorrectas para hacer el cálculo.Ahora lo que vamos a hacer es corregir este error. Básicamente lo único que tenemos que cambiar son estas dos líneas:
Código:
...
if (puntero_a_pokemon->estadisticas[1 + i] % 2) {
...
if (puntero_a_pokemon->estadisticas[1 + i] / 4) {
...
Los que tengan un poco de idea del tema sabrán que leer los iv's no es tan sencillo como leer los totales, ya que los mismos estan encriptados.
Pero el rom, tiene ya programadas unas funciones que sirven para obtener esos datos, así que lo que vamos a hacer es usar esas.
Y se preguntarán de donde es que vamos a sacar esas funciones...
Bueno, en este foro no nos enteramos de nada pero por si no lo sabían, la gente de pokecommunity tiene las roms bastante investigadas... Y no es que precisamente oculten los datos, todo es bastante público... Si vemos este repositorio podemos ver que hay muchas funciones con sus cabeceras declaradas.
A nosotros la que nos interesa es "pokemon_getattr" la cual se encuentra en este archivo
Ahora, si compilamos este repositorio nos va a aparecer un archivo ld, con los offset de todas estas funciones en fr. Pueden ver una version ya compilada aquí
El offset de la funcion que necesitamos es:
Código:
pokemon_getattr = 0x0803fbe8|1;
Así que no se pueden quejar de que tenemos poca información...
Ahora sí, modificando el código
Lo primero que tenemos que hacer es agregar el offset de la funcion (yo le voy a poner el nombre de pokemeerald) a ROM.ld:Que debería de estarse viendo algo así:
Código:
__aeabi_idiv = 0x081E3B68|1;
__aeabi_idivmod = 0x081E3B68|1;
__aeabi_uidiv = 0x081E3B68|1;
__aeabi_uidivmod = 0x081E3B68|1;
var_8000 = 0x020370B8;
var_8004 = 0x020370B8 + 8;
var_8005 = 0x020370B8 + 0xA;
pkmn_party = 0x02024284;
GetMonData = 0x0803fbe8|1;
Código:
#include "headers/types.h"
[B]#include "headers/pokemon.h"
u32 GetMonData(struct Pokemon *mon, s32 field, u8* data);[/B]
u16 var_8000;
u16 var_8004;
u16 var_8005;
struct Pokemon pkmn_party[6];
void rutina_javi_optimizada() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador_tipo, acumulador_poder,i;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador_tipo = 0;
acumulador_poder = 0;
for (i = 0; i < 6; i++) {
if (puntero_a_pokemon->estadisticas[1 + i] % 2) {
acumulador_tipo += 1 << i;
}
if (puntero_a_pokemon->estadisticas[1 + i] / 4) {
acumulador_poder += 1 << i;
}
}
var_8004 = acumulador_tipo * 0xf / 0x3f;
var_8005 = acumulador_poder * 0x28 / 0x3F + 0x1E;
}
Pero bueno el código terminaría tal que así:
Código:
#include "headers/types.h"
#include "headers/pokemon.h"
u32 GetMonData(struct Pokemon *mon, s32 field, u8* data);
u16 var_8000;
u16 var_8004;
u16 var_8005;
struct Pokemon pkmn_party[6];
void rutina_javi_optimizada() {
struct Pokemon* puntero_a_pokemon;
u32 acumulador_tipo, acumulador_poder,i;
puntero_a_pokemon = &pkmn_party[var_8000];
acumulador_tipo = 0;
acumulador_poder = 0;
for (i = 0; i < 6; i++) {
if (GetMonData(puntero_a_pokemon, MON_DATA_MAX_HP + i, 0) % 2) {
acumulador_tipo += 1 << i;
}
if (GetMonData(puntero_a_pokemon, MON_DATA_MAX_HP + i, 0) / 4) {
acumulador_poder += 1 << i;
}
}
var_8004 = acumulador_tipo * 0xf / 0x3f;
var_8005 = acumulador_poder * 0x28 / 0x3F + 0x1E;
}
Y con eso estaría todo.
¿Y emerald qué?
Bueno para que funcione en emerald lo único que hay que hacer es cambiar los offsets del archivo ROM.ld. Si compilan pokemerald, les aparecerá un archivo "pokemerald.map" el cual tiene las direcciones de todo en el rom. Lo único que hay que hacer es ver cual es la dirección de las funciones que usamos y las direcciones de la ram.Los equivalentes de los nombres en emerald son:
Por lo que cambiando dejando ROM.ld de la siguiente forma, la función va a funcionar perfectamente en emerald.__aeabi_idiv -> Div
__aeabi_idivmod -> Div
__aeabi_uidiv -> Div
__aeabi_uidivmod -> Div
var_8000 -> gSpecialVar_0x8000
var_8004 -> gSpecialVar_0x8004
var_8005 -> gSpecialVar_0x8005
pkmn_party -> gPlayerParty
Código:
__aeabi_idiv = 0x82E7088|1;
__aeabi_idivmod = 0x82E7088|1;
__aeabi_uidiv = 0x82E7088|1;
__aeabi_uidivmod = 0x82E7088|1;
var_8000 = 0x020375d8;
var_8004 = 0x020375e0;
var_8005 = 0x020375e2;
pkmn_party = 0x20244ec;
GetMonData = 0x0806a518|1;
Cualquier duda, pregunten. Si hay partes poco claras avisen.
Saludos.