Registrarse

[C] Aprendiendo a romhackear en C

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.

¿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
por
Código:
export ROM_CODE := ROM
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:
Código:
        .open "BPRE0.gba","build/rom.gba", 0x08000000
por:
Código:
        .open "mirom.gba","build/rom.gba", 0x08000000
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.


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
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:
Código:
var_8000 = 0x020370B8;
pkmn_party = 0x02024284;
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:
Código:
#include "headers/types.h"

void rutina_javi() {

}
Lo que estamos incluyendo, son simples definiciones de los tipos u8, u16, u32, s8, etc (sacado de pokeagb).

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
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í:
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() {

}
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:
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;
}
(Con excepcion del r1 + 0x56, el cual usaremos de otra forma).

Código:
loop1:
cmp r4, #0x6
beq cont
add r1, r1, r3
ldrh r2, [r1]
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:
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];
	}
}
Ya que como veremos proximamente r4 ira cambiando al final de cada vuelta del bucle.

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
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:
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
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:
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
Básicamente lo que hace javi es guardar en la variable 0x8004 el valor de (r0 * 0xF / 0x3F).
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;
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:
Código:
__aeabi_idiv = 0x081E3B68|1;
__aeabi_idivmod = 0x081E3B68|1;
__aeabi_uidiv = 0x081E3B68|1;
__aeabi_uidivmod = 0x081E3B68|1;
Finalmente agregamos el nuevo código (y tambien la declaración de la variable 0x8004):
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;
}
Y ya que estamos limpiemos un poco el códio de cosas innecesarias y cambiemos el nombre a las variables:
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
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:
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.
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
El código se resumiría en:
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}
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:
Código:
var_8005 = 0x020370B8 + 0xA;
Y luego en el código bastaría con poner:
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:
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) {
...
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:
Código:
pokemon_getattr = 0x0803fbe8|1;
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...

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;
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í:
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;
}
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í:
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;
}
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.

¿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:
__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
Por lo que cambiando dejando ROM.ld de la siguiente forma, la función va a funcionar perfectamente en emerald.
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;
Y bueno, eso sería todo.
Cualquier duda, pregunten. Si hay partes poco claras avisen.

Saludos.
 

Wufzheevk

Ģ̴͕̹͉̻͕̥͇̗̼̠̫͎̜̞͎͖̼͎̅͆̈́͑͆͛̎̈́̓̒̉̉́̊́̏͑̐̐͜͝l̸͐̄̅̑̾̈
Uff men, he esperado esto por tiempo....
se que en pokeco estan los tutoriales, sin embargo al estar en ingles, y con todo lo complejo del C, me confundia mucho y lo creadores del post no me respondian ;-;
ya es tarde, lo vere mas a detalle despues.. gracias!
 

kakarotto

Leyenda de WaH
Me quito el sombrero kaiser...es un pedazo de aporte... esto sera la puntilla de calidad que los hacks necesitaban... uf..ojala le de buen uso
 

Naren Jr.

Puto amo
Usuario de Platino
Excelente, me agrada mucho esta idea de inculcar C en tutoriales así.

Si hubiesen existido cuando yo Hackeaba de verdad posiblemente hubiera elevado mi nivel en ello.

Btw, hay que aclarar que C ocupa muchísimo mas espacio que el ASM, es uno de esos puntos a tener en cuenta a la hora de insertar rutinas C, que son muy hermosas y tal, pero si tienen su complicación con el espacio...

Muchísimas gracias Kaiser.
 

pikachu240

Junior C# Developer
Me gusta como queda :) la verdad es que al ser un lenguaje de alto nivel es lógico que sea más comprensible ;) la verdad es que me gustaria que la parte de las variables no se tuviera que tocar cada vez que pones una rom u otra,vamos que detecte la rom y luego escoja cual le va bien...tambien lo del nombre me gustaria que fuese uno pasado como parametro ósea arrastrando dentro del exe el gba por lo demas esta muy bien :D me gustaria saver si se puede decompilar un código osea de gba a c aun asi ya me gusta esto como está :) lo que me falta saber es las diferencias entre rubi,rojo fuego y esmeralda en c si tienen una base en común y luego cambia solo las variables :D luego seria todo mas simple para hacer rutinas universales :)

Gracias por el aporte esta muy bien :)
 

Kaiser de Emperana

Called in hand
Re: Respuesta: Aprendiendo a romhackear en C

Me gusta como queda :) la verdad es que al ser un lenguaje de alto nivel es lógico que sea más comprensible ;) la verdad es que me gustaria que la parte de las variables no se tuviera que tocar cada vez que pones una rom u otra,vamos que detecte la rom y luego escoja cual le va bien...tambien lo del nombre me gustaria que fuese uno pasado como parametro ósea arrastrando dentro del exe el gba por lo demas esta muy bien :D me gustaria saver si se puede decompilar un código osea de gba a c aun asi ya me gusta esto como está :) lo que me falta saber es las diferencias entre rubi,rojo fuego y esmeralda en c si tienen una base en común y luego cambia solo las variables :D luego seria todo mas simple para hacer rutinas universales :)

Gracias por el aporte esta muy bien :)
Lo que proponés es básicamente tomarse el tiempo de programar una herramienta. Que lea el código del rom base y que cargue o un archivo.ld o el otro. Osea, que vamos, se podría hacer fácilmente una herramienta que compile desde una interfaz gráfica, en lugar de usar make. La cosa es que al ser un terreno de gente más técnica, es algo así como una pérdida de tiempo el programar una herramienta que no va ser casi usada por el simple hecho de que la gente está acostumbrada a la línea de comandos.

Sobre lo de decompilar, no es posible, lo mejor que podés hacer es lo que hace la gente de pokeruby/pokeemerald que es analizar el código asm y a base de pureba y error escribir un código fuente que compile igual.

Y sobre la diferencia entre los distintos roms. Todos son iguales, fr es simplemente una vesión modificada de ruby y emerald es simplemente una versión modificada de fire red. Las funciones cambian un poco, pero en esencia son lo mismo.

Por ejemplo:
Código:
static void CB2_EggHatch_0(void)
{
    switch (gMain.state)
    {
    case 0:
        REG_DISPCNT = 0;
        gEggHatchData = (struct EggHatchData*)(&ewram[0x18000]);
        gEggHatchData->eggPartyID = gSpecialVar_0x8004;
        gEggHatchData->eggShardVelocityID = 0;
        ResetTasks();
        ResetSpriteData();
        FreeAllSpritePalettes();
        SetVBlankCallback(VBlankCB_EggHatch);
        gMain.state++;
        gSpecialVar_0x8005 = GetCurrentMapMusic();
        break;
    case 1:
        SetUpWindowConfig(&gWindowConfig_81E6F84);
        InitWindowFromConfig(&gEggHatchData->window, &gWindowConfig_81E6F84);
        gEggHatchData->tileDataStartOffset = SetTextWindowBaseTileNum(20);
        LoadTextWindowGraphics(&gEggHatchData->window);
        gMain.state++;
        break;
    case 2:
        LZDecompressVram(&gUnknown_08D00000, (void*)(VRAM));
        CpuSet(&gUnknown_08D00524, &ewram[0], 0x800);
        DmaCopy16(3, &ewram[0], (void*)(VRAM + 0x2800), 0x500);
        LoadCompressedPalette(&gUnknown_08D004E0, 0, 0x20);
        gMain.state++;
        break;
    case 3:
        LoadSpriteSheet(&sUnknown_0820A3B0);
        LoadSpriteSheet(&sUnknown_0820A3B8);
        LoadSpritePalette(&sUnknown_0820A3C0);
        gMain.state++;
        break;
    case 4:
        gEggHatchData->eggSpriteID = CreateSprite(&sSpriteTemplate_820A3C8, 0x78, 0x4B, 5);
        AddHatchedMonToParty(gEggHatchData->eggPartyID);
        gMain.state++;
        break;
    case 5:
        EggHatchCreateMonSprite(0, 0, gEggHatchData->eggPartyID);
        gMain.state++;
        break;
    case 6:
        gEggHatchData->pokeSpriteID = EggHatchCreateMonSprite(0, 1, gEggHatchData->eggPartyID);
        gMain.state++;
        break;
    case 7:
        {
            u32 offsetRead, offsetWrite;
            u32 offsetRead2, offsetWrite2;
            u32 size;

            REG_BG2CNT = 0x4C06;
            LoadPalette(gUnknown_0820C9F8, 0x10, 0xA0);

            offsetRead = (u32)(&gUnknown_0820CA98);
            offsetWrite = (VRAM + 0x4000);
            size = 0x1300;
            while (TRUE)
            {
                DmaCopy16(3, offsetRead, (void *) (offsetWrite), 0x1000);
                offsetRead += 0x1000;
                offsetWrite += 0x1000;
                size -= 0x1000;
                if (size <= 0x1000)
                {
                    DmaCopy16(3, offsetRead, (void *) (offsetWrite), size);
                    break;
                }
            }

            offsetRead2 = (u32)(&gUnknown_0820F798);
            offsetWrite2 = (u32)(VRAM + 0x6000);
            DmaCopy16(3, offsetRead2, (void*)(offsetWrite2), 0x1000);
            gMain.state++;
        }
        break;
    case 8:
        REG_BG1CNT = 0x501;

        REG_BG0HOFS = 0;
        REG_BG0VOFS = 0;

        REG_BG1HOFS = 0;
        REG_BG1VOFS = 0;

        REG_BG2HOFS = 0;
        REG_BG2VOFS = 0;

        SetMainCallback2(CB2_EggHatch_1);
        gEggHatchData->CB2_state = 0;
        break;
    }
}
Código:
static void CB2_EggHatch_0(void)
{
    switch (gMain.state)
    {
    case 0:
        SetGpuReg(REG_OFFSET_DISPCNT, 0);

        sEggHatchData = Alloc(sizeof(struct EggHatchData));
        AllocateMonSpritesGfx();
        sEggHatchData->eggPartyID = gSpecialVar_0x8004;
        sEggHatchData->eggShardVelocityID = 0;

        SetVBlankCallback(VBlankCB_EggHatch);
        gSpecialVar_0x8005 = GetCurrentMapMusic();

        reset_temp_tile_data_buffers();
        ResetBgsAndClearDma3BusyFlags(0);
        InitBgsFromTemplates(0, sBgTemplates_EggHatch, ARRAY_COUNT(sBgTemplates_EggHatch));

        ChangeBgX(1, 0, 0);
        ChangeBgY(1, 0, 0);
        ChangeBgX(0, 0, 0);
        ChangeBgY(0, 0, 0);

        SetBgAttribute(1, BG_CTRL_ATTR_MOSAIC, 2);
        SetBgTilemapBuffer(1, Alloc(0x1000));
        SetBgTilemapBuffer(0, Alloc(0x2000));

        DeactivateAllTextPrinters();
        ResetPaletteFade();
        FreeAllSpritePalettes();
        ResetSpriteData();
        ResetTasks();
        remove_some_task();
        m4aSoundVSyncOn();
        gMain.state++;
        break;
    case 1:
        InitWindows(sWinTemplates_EggHatch);
        sEggHatchData->windowId = 0;
        gMain.state++;
        break;
    case 2:
        copy_decompressed_tile_data_to_vram_autofree(0, gUnknown_08C00000, 0, 0, 0);
        CopyToBgTilemapBuffer(0, gUnknown_08C00524, 0, 0);
        LoadCompressedPalette(gUnknown_08C004E0, 0, 0x20);
        gMain.state++;
        break;
    case 3:
        LoadSpriteSheet(&sEggHatch_Sheet);
        LoadSpriteSheet(&sEggShards_Sheet);
        LoadSpritePalette(&sEgg_SpritePalette);
        gMain.state++;
        break;
    case 4:
        CopyBgTilemapBufferToVram(0);
        AddHatchedMonToParty(sEggHatchData->eggPartyID);
        gMain.state++;
        break;
    case 5:
        EggHatchCreateMonSprite(0, 0, sEggHatchData->eggPartyID, &sEggHatchData->species);
        gMain.state++;
        break;
    case 6:
        sEggHatchData->pokeSpriteID = EggHatchCreateMonSprite(0, 1, sEggHatchData->eggPartyID, &sEggHatchData->species);
        gMain.state++;
        break;
    case 7:
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);
        LoadPalette(gUnknown_08DD7300, 0x10, 0xA0);
        LoadBgTiles(1, gUnknown_08DD7360, 0x1420, 0);
        CopyToBgTilemapBuffer(1, gUnknown_08331F60, 0x1000, 0);
        CopyBgTilemapBufferToVram(1);
        gMain.state++;
        break;
    case 8:
        SetMainCallback2(CB2_EggHatch_1);
        sEggHatchData->CB2_state = 0;
        break;
    }
    RunTasks();
    RunTextPrinters();
    AnimateSprites();
    BuildOamBuffer();
    UpdatePaletteFade();
}
Estas son funciones de la carga de la escena de cuando un huevo se abre. Las funciones son casi iguales, pero por ejemplo, mirando las dos primeras líneas. En Ruby se cambia los valors de los registros que controlan los bg escribiendo directamente sobre la I/O, mientras que en emeral se llama a una función que lo hace. Y en la segunda línea, en ruby, la memoria se asigna de forma directa; mientras que en emerald, que tiene una librería malloc, se llama a la función de alojar memora de la misma.
 

Samu

Miembro insignia
Miembro insignia
Acabo de leerlo todo detenidamente, en fin, muchas gracias. Ojalá hubiese leído esto hace dos meses cuando estaba haciendo todas las rutinas ASM porque la verdad es que me habría ahorrado bastante tiempo.

Voy a pasar varias rutinas a C y a comparar la longitud del bytecode con el de assembly. Si bien ha de ser mayor como dice Naren, no creo que la diferencia sea tan grande o al menos tan significativa, ya que al fin y al cabo las rutinas no ocupan prácticamente nada de espacio, sobretodo si se tiene en cuenta la posibilidad de expandir el rom.

Sí es como pienso pasaré todas las rutinas a C más que nada de cara al mantenimiento del código. Realizar cambios sobre rutinas ASM que no ves desde hace 3 meses ... es... digamos.... tedioso.

Como pequeña crítica decirte que quizá debiste utilizar varias rutinas algo más pequeñas en lugar de una más grande para hacer el tutorial más accesible (Aunque entiendo que has usado esta precisamente porque podías hacer uso de varios tipos de estructuras de control y además explicar como cargar la función de división de la bios de una sola pasada).
 

Berserker1523

2-Intentando discernir qué es lo más importante...
Muy buen tutorial, muy acertado el utilizar una rutina con gran variedad de "comandos" de ASM, logré seguirlo a pesar de que no entendí la mitad de las cosas que escribiste al no saber mucho de ASM y casi nada de C. Puesto que ya hay muchos tutoriales de lo primero, recomiendo entonces unirse a este canal de discord de FBI donde podrán aprender de lo segundo: (Lo pone al final del tema) https://whackahack.com/foro/t-49491/rom-hacking-with-c también trataré de hacer una traducción para los que no saben mucho inglés.
 

Cheve

MoonLover~
Miembro de honor
*Aplausos*

Creo recordar que ya te lo dije por perfil, pero éste tutorial me parece de lo mejor.
El que tenga un poco de tiempo y ganas para ponerse a ello, puede aprender en solo poco tiempo a hacer grandes cosas. El C es más sencillo que ASM (Pa' eso se invento xD) así que espero que más gente se anime a darle y tenga un buen inicio con ésto.

Salu2!
 

Juanjo

Hacker del pasado... Compilador del presente
Miembro insignia
Hola, ¿Qué tal?

Excelente tutorial amigo mgbbgm la verdad siempre soñé con llegar a hackear en C puesto que es de los lenguajes de programación que mejor domino. Siempre me dio pereza aprender bien ASM, por lo que por lo general o intentaba hacer todo mediante scripts o aprendía a lo bestia a editar registros directamente en HEX (así he hecho gran cantidad de maravillas) para lograr las cosas (lo que no implica que nunc haya usado rutinas ASM).

Me pondré como meta poner en práctica este tutorial y los de habla inglesa para ponerme a la altura y lograr culminar muchas investigaciones comenzadas que jamás logré terminar y lograr así aportar mucho más de lo que siempre aportar a esta gran comunidad.

La verdad me siento muy feliz de haber llegado a este post tuyo

Saludos!!!

Att: Juanjo
 

Berserker1523

2-Intentando discernir qué es lo más importante...
Gracias a IDA Pro pude encontrar los offsets de las funciones de este tutorial para Rojo Fuego:
Código:
__aeabi_idiv = 0x081E32EC|1;
__aeabi_idivmod = 0x081E32EC|1;
__aeabi_uidiv = 0x081E32EC|1;
__aeabi_uidivmod = 0x081E32EC|1;
GetMonData = 0x0803FAD4|1;
Las variables y la pokémon party tienen los mismos offsets que en Fire Red, pero los dejo también por si acaso:
Código:
var_8000 = 0x020370B8;
var_8004 = 0x020370B8 + 8;
var_8005 = 0x020370B8 + 0xA;
pkmn_party = 0x02024284;
Por si se les ha olvidado, esto debe ir en el archivo ROM.ld, reemplazando los de este tutorial, o pueden hacer un BPRS.ld y en el archivo Makefile reemplazan la línea correspondiente así:

Código:
export ROM_CODE := BPRS
Para que pueda usar ese archivo .ld en lugar del otro.
 
Arriba