SearchForCheats.de.vu » Tutorials » Advanced User Tutorial #1 - How To Create A Shiny Encounter


Tutorial avanzado nº1 - Como batallar un Pokémon brillante

This tutorial was translated by the User Serg!o. The original version can be found in the tutorials-section.
If you want to provide your support to my website by translating this tutorial to another language, please contact me. (Details can be found in the imprint)
Thanks!
Mastermind_X

¡Que bueno verte por aquí! A la zona 'secreta' de mi laboratorio solo pueden acceder los programadores más expertos ¡ehh... *era broma*
Pero, verás... ten en cuenta que no entenderás los siguientes procedimientos de hackeo si no has llegado al nivel de la Lección de Hacking 4ª o similar.
Así que... hackeemos (¿o craqueemos?) Estamos itentando programar un encuentro con un Pokémon brillante para la versión yanqui del Pokémon Rojo Fuego. Digamos que el típico Gyarados brillante. Para este tutorial necesitarás dominar estos conocimientos:

  • operaciones básicas con un editor hex (algo así como copiar y pegar :-P );
  • como repuntear eventos con Advance Map o Elite Map;
  • conversión de direcciones GBA (de puntero a dirección, y viceversa );
  • los comandos estándar de los videojuegos Pokémon GBA;
  • y... claro... saber algo de ASM, lenguaje ensamblador, te ayudaría... (no lo necesitarás si solamente vas a copiar los códigos hex pero sí para comprender como funciona el script, que te iría bien)

Necesitaremos esto :

  • un editor hex (eh... no lo esperabas ¿no?)
  • la ROM (¡no preguntes donde conseguirla!)
  • un emulador para comprobar los resultados
  • tu cerebro <= importante!!1unouno

¿Y qué haremos con todo eso ?
Bien. La idea principal sería esta:

  • localizar la rutina-brillante
  • escindirla en dos partes - la rutina convencional y la rutina editada (100%)- para ejecutarlas según una flag de la RAM
  • escribir un script de ASM que active o desactive la flag
  • escribir un script normal con dos llamadas al código ASM

Quizá tengas dudas. ¿Para qué un solución tan enrevesada? ¿Porqué no sencillamente escribir una rutina que modifique los bytes necesarios?
La respeusta es... de esta manera, pequeño saltamontes, creamos una flag accesible desde cualquier script. Además ¿no es unasolución más elegante que dejar que la ROM se sobreescriba a sí misma?

Haz una búsqueda estándar en la RAM de la IDPokémon en formato 32-Bit. La posibilidad de que un Pokémon sea brillante o no se basa en la IDPokémon (también conocida como Rnd#). También se se basa en estos númeritos el sexo, naturaleza, forma del Unown y cosas así. Pero a nosotros solo nos interesa lo que hace que sean brillantes. El brillo se calcula de los primeros 16 bits operados con XOR a los 16 bits finales del XKey. El XKey es el resultado de operar con XOR el PID y las ID del entrenador.
@_@ Suena muy complicado sin saber lo que significa. Verás, te lo explicaré más claro con un ejemplo. Digamos que tu ID de entrenador (OTID, los datos que ves en la ficha de un Pokémon al capturarlo) es 56432. Tu ID de entrenador 'secreta' es... eh... 026025. Convertidas a hex resultan 0xDC70 para la OTID y 0x65A9 para la OTSID. Ponlas juntas así:

(OTSID << 0x10) OR (OTID)

Obtendras la 2ª palabra hexadecimal, las OTIDS en plural, 0x65A9DC70. Ahora sigamos, el primer valor es el PID/Rnd#. Se calcula aleatoriamente. Digamos que encuentras un Pokémon salvaje y su valor es 0x57ADF001.
Sabiendo estos valores, nosotros (¿o el juego?) calculamos el Xkey (Sí. Suena guay, ¿que no?). La XKey, como dije antes, es el resultado de operar XOR del PID con las OTIDS. En nuestro caso sale:

    0x57ADF001
XOR 0x65A9DC70
--------------
    0x32042C71

Los datos del brillo se almacenan en el cociente de la primera semipalabra entre la segunda. Si el resultado de la operación XOR de estos dos valores es menor que ocho, el Pokémon es brillante. Vale. Veámoslo:

(Xkey >> 0x10) XOR (XKey AND 0xFFFF)

(Xkey >> 0x10)    ==     0x3204
(XKey AND 0xFFFF) == XOR 0x2C71
                     ----------
                         0x1E75

El resultado, 0x1E75, es mucho mayor que 8. El Pokémon no brilla. :-( Pero si estamos aquí es porque vamos a cambiar eso. Mi idea es la siguiente:
(Xkey >> 0x10) y (XKey AND 0xFFFF) pueden no ser 8. Si nosotros conseguimos que (Xkey >> 0x10) sea igual a (XKey AND 0xFFFF), la operación XOR devolverá el resultado cero. Y puesto que 0 < 8, ya tenemos un brillante.
Así que, cada semipalabra, con solo repetirla se convertirá en un integral válido. Esa es la primera parte.
Pero el XKey es el resultado de otra operación XOR. ¿Cómo forzaremos para que salga el integral que queremos? El siguiente método es el más sencillo y el que más me gusta.
Tomemos 0x00000000 como nuestro XKey deseado. Es, tanto un integral válido para conseguir un Pokémon brillante, como fácil de calcular en una operación XOR. Yo creo, ya sabes, que lo conseguiremos sacar. Configurando el PID igual a las OTIDS le resultado de la operación XOR será 0x00000000 que es exactamente lo que queremos en ese momento. Para los que lo único que entendeis es una ecuación, sería:

((PID XOR OTrainerIDs) >> 0x10) XOR ((PID XOR OTrainerIDs) AND 0xFFFF) < 0x8

Ahí está. Ya que antes decidimos que las igualaremos, si ahora sustituimos la PID por las OTIDS obtendremos:

((OTrainerIDs XOR OTrainerIDs) >> 0x10) XOR ((OTrainerIDs XOR OTrainerIDs) AND 0xFFFF) < 8
(0x0 >> 0x10) XOR (0x0 AND 0xFFFF) < 0x8
0 XOR 0 < 0x8
0 < 8

Sí. Resolvimos el problema. ¿Mola eh?
Ahora buscamos en la RAM los datos de los Pokémon salvajes. Propongo comenzar con una búsqueda "igual a 0" (SÓLO ANTES DEL PRIMER ENCUENTRO DE UN POKÉMON TRAS CARGAR LA PARTIDA) y continúa con una búsqueda de 32-bits para las OTIDS. (Puedes calcularla con la formula ya dada a partir de tus ID.)
Las encontrarás alrededor de 0x02024030. Quizá ahí exactamente pero soy muy vago para comprobar la protección DMA. Vale. Abre tu ROM en tu depurador favorito (Yo uso VBA-SDL-H). Los pasos siguientes son para el depurador que yo uso, si usas otro necesitarás apañarte tu mismo.
Intentemos determinar cuando y donde las OTIDS en formato 32-bits están escritas en la RAM. Primero introduzco:

mb 02024010

en la consola y compruebo la posición de los datos. No se ha movido aún, en mi caso, la primera palabra continúa en 0x02024030. (Los datos de los Pokémon empiezan en 0x0202402C con las PID, ¿sabías?)
Ahora me quedo en un 'puntos de interrupción' (breakpoint) de 32-bits (4-byte) para escribir la posición de los datos.

bpw 02024030 4

Pulsa "c" en la venana y continúa le ejecución. ¿Cuando se alcanzará el punto: ¿Quizá sea donde se calculan las OTIDS? ¿O quizá al encontrar un Pokémon salvaje?
Probemos a caminar en el pasto. ¡Sí! ¡El juego se interrumpe y el resultado se muestra en el depurador! Veamos:

Breakpoint (on write) address 02024030 old:00 new:00
R00=02024030 R04=0202402c R08=00000001 R12=00000001
R01=00000004 R05=02024220 R09=00000000 R13=03007d64
R02=0202402c R06=00000017 R10=00000000 R14=0803d99f
R03=00000000 R07=083c8d50 R11=00000000 R15=0803d98a
CPSR=0000003f (......T Mode: 1f)
0803d988 3101 add r1, #0x1

Mierda. La operación actual sólo escribía el byte 0x00 en 0x02024030. No queremos modificar un estúpido procedimiento nop así que pulsamos "c" para continuar. El juego se interrumpe otra vez:

Breakpoint (on write) address 02024031 old:00 new:00
R00=02024031 R04=0202402c R08=00000001 R12=00000001
R01=00000005 R05=02024220 R09=00000000 R13=03007d64
R02=0202402c R06=00000017 R10=00000000 R14=0803d99f
R03=00000000 R07=083c8d50 R11=00000000 R15=0803d98a
CPSR=0000003f (......T Mode: 1f)
0803d988 3101 add r1, #0x1

Misma dirección. Sólo cambiaron dos registros (menores). Un 4 se convirtió en 5 y en la dirección r0, quizá (de hecho, seguro) el que está soportando la dirección de escritura se incrementó en una unidad. Estamos actualmente en un bucle que destrulle los datos de los Pokémon. Como esperábamos siguen un par de interrupciones más antes de lo que buscábamo. Pulsa "c" y el juego se interrumpirá otra vez. Podrás fijarte en los cambios o pasar olímpicamente de ellos pulsando "c".
Hay algunas interrupciones depués en otra dirección de la ROM pero siguen siendo funciones nop. Sigue pulsando "c".

Breakpoint (on write) address 02024030 old:00000000 new:65a9dc70
R00=bc000000 R04=03007cf8 R08=00000000 R12=00000008
R01=65a9dc70 R05=00000000 R09=00000000 R13=03007cc0
R02=03007cf8 R06=00000000 R10=00000001 R14=0803db9d
R03=0202402c R07=0202402c R11=00000000 R15=080406dc
CPSR=8000003f (N.....T Mode: 1f)
080406da e1fe b $08040ada

¡Yoma ya! ¡Esa es nuestra rutina! La palabra en @ 02024030 ha sido sobreescrita con 0x65a9dc70, mis OTIDS. Fijémonos en este procedimiento. Descopilemos en ASM, es decir, desamblémoslo.

d 080406c2

¿Porqué? Es solo una estimación de la longitud del procedimiento. Algo limitado a 080406da, según dice el depurador. Quizá los números y letras no tengan sentido para tí así que voy a explicártelos todos. La subrutina son los operandos entre los códigos que empiezan co b $. b $location quiere decir una operación de salto (=branch). Esta es la sub:

080406c4  7821 ldrb r1, [r4, #0x0]   // carga el byte @ r4 (=03007cf8) a r1
080406c6  7860 ldrb r0, [r4, #0x1]   // carga el byte @ r4+1 (=03007cf9) a r0
080406c8  0200 lsl r0, r0, #0x08   // el byte en r0 es desplazado a la izquierda por 1 byte (=8bit)
080406ca  1809 add r1, r1, r0   // r1 and r0 son añadidos. obtenemos la semipalabra r0+r1
080406cc  78a0 ldrb r0, [r4, #0x2]   // carga el byte @ r4+2 (=03007cfa) a r0
080406ce  0400 lsl r0, r0, #0x10   // r0 es desplazado a la izquierda por 2-bytes otra vez
080406d0  1809 add r1, r1, r0   // r0 es añadido a r1
080406d2  78e0 ldrb r0, [r4, #0x3]   // carga el byte @ r4+3 (=03007cfb) a r0
080406d4  0600 lsl r0, r0, #0x18   // r0 es desplazado a la izquierda por 3-bytes
080406d6  1809 add r1, r1, r0   // y añade a a r1. este es el algoritmo que desplaza juntas la ID y la SID
080406d8  6079 str r1, [r7, #0x4]   // y finalmente las OTIDS son guardadas en r7+4 (=0202402c) (la operación que causa la interrupción)
080406da  e1fe b $08040ada   // sale de la rutina y salta a otra localización

Comprobemos nuestra teoría. Si somos capaces de sustituir...

080406d6  1809 add r1, r1, r0   // add r1 a r0

...por esto...

080406d6  xxxx str r1, [r7, #0x0]   // overwrite the PID with the OTrainerIDs

...el PID será siempre igual a las OTIDS.
El operando thumb para str r1, [r7, #0x0] es 0x6039, no me preguntes como lo calculé. Te bastará con saber lo que hace. (Bueno... Si de verdad te interesa, busca en el manual de Martin Kortht en el apartado palabra memorizada (store word)).
Modifiquemos nuestra rom a vuelo, introduciendo en el depurador el siguiente comando:

eh 080406d6 6039

Entonces borra el breakpoint.

bpwc

Pulsa "c" y en el juego busquemos la batalla. Pero... ¡No es brillante! ¿Que falló en nuestro método?

El Pokémon no es brillante porque la CPU ya había pasado esa localización cuando lo modificamos.

Fácil. Encuentra otro Pokémon. Será brillante. :-P
Conociendo tanto la dirección que modificar, como el valor, podremos crear un encuentro brillante rápidamente. La sintaxis AR V3 es:

00000000 18XXXXXX
0000YYYY 00000000

uhm... XXXXXX quiere decir ((address AND 0x3FFFFF) >> 1) e YYYY es son solo los calores que parchearmos. 0x18 dice al AR que use el primer espacio patching-slot (z0C).

00000000 1802036B
00006039 00000000

Encríptalo con ARCrypt y tu propio cheat.code para pokémon siempre brillantes estará acabado:

A74320F4 175B5B22
18452A7D DDE55BCC

Fin 1ª parte
Si solo querías un código para Pokémon siempre brillantes no necesitas seguir leyendo. Ya alcanzaste tu objetivo.
Pero si buscabas integrar el parcheo de arriba en una un hack-ROM todavía te queda la parte más excitante. Ahora prepárate para añadir el Gyarados rojo. ¿listo? ¡Ya!

Empezamos (¿o continuamos?) extrayendo la... bueno... no aunque no sea una buena descripción la llamaremos 'rutina brillante'. ¿Porqué extraelo? Pues porque si le vamos a introducir una orden para que compare una flag la subrutina se alargará. Si no, todos los bytes serían desplazados y los saltos se harían inválidos. Como ves no hay elección. Extraigámoslo lo que nos interesa es lo siguiente:

080406c4  b402 push {r1} // guarda el valor en r1 llevándolo al stack
080406c6  4902 ldr r1, [$080406d0] (=$0871b701) // carga la dirección objetivo de la nueva rutina
080406c8  f000 bl $080406cc // salta con bl y guarda la dirección de vuelta en lr (necesita 2*16bit)
080406ca  f800 blh $0000 // belongs to bl
080406cc  470f bx r1 // intercambio branch. Troca los valores de pc y r1
080406ce  0000 // no pasado por la cpu
080406d0  b701 // data1 (needed in 0x080406c6)
080406d2  0871 // data2
080406d4  0000 lsl r0, r0, #0x0 // nop, espacio innecesario
080406d6  0000 lsl r0, r0, #0x0 // nop, espacio innecesario
080406d8  0000 lsl r0, r0, #0x0 // nop, espacio innecesario
080406da  e1fe b $08040ada // continua la ejecución del programa

Abre la ROM en un hex. Salta a 0x406C0. Allí encontrarás la "rutina brillante" que vamos a modificar.

000406c0h: 39 60 0A E2 21 78 60 78 00 02 09 18 A0 78 00 04 ; 9`.!x`x.... x..
000406d0h: 09 18 E0 78 00 06 09 18 79 60 FE E1 00 22 3B 1C ; ..x....y`.";.

Sobreescríbela con nuestra nueva rutina para hacer un repunteo (la rutina que acabamos de desamblar).

000406c0h: 39 60 0A E2 02 B4 02 49 00 F0 00 F8 0F 47 00 00 ; 9`...I...G.. 000406d0h: 01 B7 71 08 00 00 00 00 00 00 FE E1 00 22 3B 1C ; .q........";.

De esa manera, la CPU siempre saltará antes de un encuentro a la dirección ROM asignada. Puedes probarlo caminando por el pasto y encontrando un Pokémon , el juego se bloqueará. ;-)

Ahora isnertemos una "rutina brillante" modificada en 0871b700. ¿Qué porqué 0x00 si el puntero anterior acababa en 0x01?
Bien. El último bit de una operación branch dice si la rutina que sigue está escrita en ARM o en THUMB: 0 y 1 respectivamente. Puesto que usamos una rutina thumb, el puntero acaba en 1, aunque la dirección comience en @ 0871b700.

0871b700  bc02 pop {r1} // recupera el valor guardado
0871b702  b500 push {lr} // put the return address onto the stack
0871b704  bc02 pop {r1} // recárgalo a r1
0871b706  3108 add r1, #8 // ajusta la dirección de vuelta por 8. (para saltarnos la de los datos del objetivo )
0871b708  b402 push {r1} // guarda los datos de vuelta otra vez
0871b70a  490e ldr r1, [$0871b744] (=$02022000) // aww... y aquí se sobreescribe... aunque es bastante es inutil^^
0871b70c  6809 ldr r1, [r1, #0x0] // carga el valor @ 02022000 a r1
0871b70e  2901 cmp r1, #0x1
0871b710  d00b beq $0871b72a
0871b712  7821 ldrb r1, [r4, #0x0]   // carga el byte @ r4 (=03007cf8) a r1
0871b714  7860 ldrb r0, [r4, #0x1]   // carga el byte @ r4+1 (=03007cf9) a r0
0871b716  0200 lsl r0, r0, #0x08   // el byte en r0 es desplazado a la izquierda por 1 byte (=8bit)
0871b718  1809 add r1, r1, r0   // r1 and r0 son añadidos. we get a halfword r0+r1
0871b71a  78a0 ldrb r0, [r4, #0x2]   // carga el byte @ r4+2 (=03007cfa) a r0
0871b71c  0400 lsl r0, r0, #0x10   // r0 es desplazado a la izquierda por 2 bytes otra vez
0871b71e  1809 add r1, r1, r0   // r0 es añadido a r1
0871b720  78e0 ldrb r0, [r4, #0x3]   // carga el byte @ r4+3 (=03007cfb) a r0
0871b722  0600 lsl r0, r0, #0x18   // r0 es desplazado a la izquierda por 3 bytes
0871b724  1809 add r1, r1, r0   // y añade a a r1
0871b726  6079 str r1, [r7, #0x4]   // y finalmente las OTIDS se almacenan en r7+4 (=0202402c) 0871b728  e00b b $0871b742 // saltando las siguientes lineas. Son usadas por lso brillantes
0871b72a  7821 ldrb r1, [r4, #0x0]   // carga el byte @ r4 (=03007cf8) a r1
0871b72c  7860 ldrb r0, [r4, #0x1]   // carga el byte @ r4+1 (=03007cf9) a r0
0871b72e  0200 lsl r0, r0, #0x08   // el byte en r0 es desplazado a la izquierda por 1 byte (=8bit)
0871b730  1809 add r1, r1, r0   // r1 and r0 son añadidos. we get a halfword r0+r1
0871b732  78a0 ldrb r0, [r4, #0x2]   // carga el byte @ r4+2 (=03007cfa) a r0
0871b734  0400 lsl r0, r0, #0x10   // r0 es desplazado a la izquierda por 2 bytes otra vez
0871b736  1809 add r1, r1, r0   // r0 es añadido a r1
0871b738  78e0 ldrb r0, [r4, #0x3]   // carga el byte @ r4+3 (=03007cfb) a r0
0871b73a  0600 lsl r0, r0, #0x18   // r0 es desplazado a la izquierda por 3 bytes
0871b73c  1809 add r1, r1, r0   // y añadido a 1
0871b73e  6079 str r1, [r7, #0x4]   // y finalmente las OTIDS son guardadas en r7+4 (=0202402c)
0871b740  6039 str r1, [r7, #0x0]   // y aquí está nuestro patch. el PID se sobreescribe con las OTIDS
0871b742  bd00 pop {pc} // volver & salir sub
0871b744  0020 //data1
0871b746  0202 //data2

Pinta bonito. Quizá te fijases que no sustituí los comandos "as" anteriores al Código ar.
¿Porqué? Sencillamente porque sobreescribir el operador que hay en @0871b73c afectaría al primer byte de las OTIDS. Usando un Action Replay estaremos contentos de tener el efecto-brillante y no tendremos que preocuparnos más de las OTIDS. (Así que para los aficionados a los cheat-codes vuestros pokes brillantes hackeados serán identificados porque las OTIDS no coincidirán.
Escribirlo directamente a la ROM (que es lo que haremos) nos dará la oportunidad de arreglar ese error. No hay necesidad de destruir un operador, sino solo de añadir una linea.
¿Cómo conseguir todos los valores calculados en vez de los operadores? Hay dos soluciones. Yo, para mí, prefiero escribir la función directamente en ASM, pero tu puedes usar un compilador (como el Arm-eabi de DevKit-Pro). Ahora abrimos la ROM en el hex y saltamos a la dirección 0x71b700. Insertamos los operadores, la ROM mostrará algo como esto:

0071b700h: 02 BC 00 B5 02 BC 08 31 02 B4 0E 49 09 68 01 29 ; ....1..I.h.)
0071b710h: 0B D0 21 78 60 78 00 02 09 18 A0 78 00 04 09 18 ; .!x`x.... x....
0071b720h: E0 78 00 06 09 18 79 60 0B E0 21 78 60 78 00 02 ; x....y`.!x`x..
0071b730h: 09 18 A0 78 00 04 09 18 E0 78 00 06 09 18 79 60 ; .. x....x....y`
0071b740h: 39 60 00 BD 00 20 02 02 FF FF FF FF FF FF FF FF ; 9`.. ..

Wah! Eso era una enorme cantidad de códigos. ¡Veamos el resultado!
Carga la ROM en el VBA, abre el visor de memoria y ve a 02022000. Verás el valor cero. Ahora busca un Pokémon. La criaturita será mediocre. Ahora, cambia la 'flag' a 0x0001. Encuentra un Pokémon. Estrellitas... Color diferente... ¡Exitazo!
Bien. Ahora necesitamos la posibiliidad de alterar esa flag con un script normal. Usaremos el comando 0x23, que llama a un código ASM. Esta rutina que llamamos la activará si está desactivada y viceversa. Así que contruiremos lo siguiente:

0871b760  b507 push {r0, r1, r2, lr} // save return adress
0871b762  4803 ldr r0, [$0871b770] (=$02022000) // get the flags position
0871b764  6801 ldr r1, [r0, #0x0] // get the flag-value
0871b766  2201 mov r2, #0x1
0871b768  4051 eor r1, r2 // switch the flags value
0871b76a  6001 str r1, [r0, #0x0] // store back the new value
0871b76c  bd07 pop {r0, r1, r2, pc} // get saved values, return
0871b76e  0000 // not passed
0871b770  0020 //data1
0871b772  0202 //data2

Bien. Cada vez que llamemos esa función, activará io desactivará la "flag brillante". La insertamos en la ROM.

0071b770h: 07 B5 03 48 01 68 01 22 51 40 01 60 07 BD 00 00 ; ..H.h."Q@.`...
0071b780h: 00 20 02 02 FF FF FF FF FF FF FF FF FF FF FF FF ; . ..

Ole, ya acabamos la parte de ensamblaje, de ASM. La final será la más sencilla. Sólo tenemos que escribir un script que llame a la función que activa la "flag brillante". El script es el siguiente:

[23]        //llamada asm
[XXXXXXXX]  //puntero a thumb-sub+1
[00]        //nop
[B6]        //batalla salvaje
[XXXX]      //índice del Pokémon (INGAME)
[XX]        //nivel
[XXXX]      //ítem que lleva
[00]
[25]        //evento especial
[3801]      //encuentro batalla Pokémon!
[28]        //pausa
[0101]      //un segundo
[23]        //llamada asm
[XXXXXXXX]  //puntero a thumb-sub+1
[02]        //fin

Metiéndolo todo junto conseguiremos (para un Gyarados nv.30) el siguiente script:

0071b7a0h: 23 71 B7 71 08 00 B6 82 00 1E 00 00 00 25 38 01 ; #qq.......%8.
0071b7b0h: 28 01 01 23 71 B7 71 08 02 FF FF FF FF FF FF FF ; (..#qq..

Y eso es todo amidos. Elige un evento y repuntéalo en $71b7a0 o inserta el script the arriba en otro script, como tu queiras. ;-)

"Pokmon" ist ein eingetragenes Warenzeichen der Firma Nintendo
"Action Replay" ist ein eingetragenes Warenzeichen von Datel Interact.
© www.SearchForCheats.de.vu by Mastermind_X
© 2006 - 2008