Registrarse

[Otros] FR, E | Como borrar un Pokémon

Inferno

Miembro insignia
Miembro insignia
INTRODUCCIÓN

Hola!! Hoy os vengo ha traer un tutorial inspirado en la rutina en C que aporto el usuario DarkPsychic de PokeCo, así que los créditos de esa rutina van para él.
El link a su post es el siguiente: https://www.pokecommunity.com/showpost.php?p=9817333&postcount=1315
Pero como me apetecía traeros también su versión en ASM, empece a hacerla, le dije unas dudas al crack de Franco (@Kate, AliKate) y él se la ha sacado y ha hecho una versión efectiva de esa rutina en ASM para Fire Red (créditos para Franco :lovelon:), luego yo le edité los offsets necesarios para que funcionase también en Emerald.

Es un tutorial que se puede hacer muy corto, pero yo haré una explicación completa para que todo el mundo, hasta el más novato, pueda aprovechar este recurso sin ningún tipo de problema y para aclarar conceptos.



HERRAMIENTAS



-Editor hexadecimal (Yo usaré HxD)
-Editor de scripts (Yo usaré XSE)
-Una ROM de Fire Red o Emerald




Paso 1º
Lo primero que deberemos hacer es insertar la rutina en nuestra ROM.

Aquí os voy a dejar la rutina que hizo DarkPsychic en C:
Código:
#include 

void delete_pokemon()
{
    u8 slot = var_8004; // get slot id from variable
    if (slot != 5) {
        memcpy(&party_player[slot], &party_player[slot + 1], 100 * (5 - slot));
    }
    memset(&party_player[5], 0x0, 100);
    return;
}
Y aquí tenéis el código compilado:
Código:
0C 4B 1B 78 10 B5 05 2B 0C D0 64 24 05 22 59 1C 
D2 1A 61 43 63 43 08 48 62 43 09 18 18 18 07 4B 
00 F0 12 F8 64 22 00 21 05 48 06 4B 00 F0 0C F8 
10 BD C0 46 C0 70 03 02 84 42 02 02 79 5E 1E 08 
78 44 02 02 D9 5E 1E 08 18 47 C0 46
Dato: Tiene una longitud de 4C bytes.

Aquí está la rutina que Franco hizo:
Código:
.text
.align 2
.thumb
.thumb_func

main:
 mov r2, #100 @Tamaño de la data para cada pokémon en bytes
 ldr r0, =0x020370C0 @Var $8004 Offset
 ldrh r1, [r0] @Carga en r1 el valor de la variable $8004
 ldr r0, =0x02024284 @Party_Pokemon Offset
 cmp r1, #5 @Si es el último, va a last
 beq last
 mov r3, #5
 sub r3, r1 @r3 = Cantidad de pokés a mover (los de delante)
 add r1, #1
 mul r2, r1
 add r1, r0, r2 @r1 = Offset del pokémon siguiente al que se borra
 mov r0, r1 @Copia ese offset al r0
 sub r0, #100 @r0 = Offset del pokémon a borrar
 mov r2, #(100/4) @Tamaño de la data de cada pokémon en words
 mul r2, r3 @r2 = Cantidad de words a mover hacia delante

move_loop:
 ldr r3, [r1] @Carga los datos que estamos moviendo en r3
 str r3, [r0] @Guarda los datos que estamos moviendo en el offset que hay en r0
 add r1, #4 @Avanza la posición de copiado hacia la siguiente word
 add r0, #4 @Avanza la posición de guardado hacia la siguiente word
 sub r2, #1 @Resta 1 word al contador de words a copiar
 cmp r2, #0 @Mientras el contador no llegue a 0, repite los pasos anteriores
 bne move_loop
 b last_sigue @Va directo a last_sigue salteándose last

last:
 mul r2, r1 @r2 = 100 * 5 = 500
 add r0, r2 @r0 = Offset en que comienza el equipo + 500 bytes = Offset del último pokémon

last_sigue:
 mov r2, #(100/4) @Carga en r2 la cantidad de words a borrar (del último pokémon) (25)
 mov r1, #0 @Carga el valor "0" en r1

last_loop:
 str r1, [r0] @Rellena los datos del último pokémon con 0
 add r0, #4 @Avanza una word en la dirección de escritura
 sub r2, #1 @Resta 1 al contador de words a escribir
 cmp r2, #0 @Mientras el contador no llegue a 0, repite lo anterior
 bne last_loop

end:
 bx lr @Termina la rutina volviendo a ejecutar aquello que la llamó

.ltorg
Y aquí tenéis el código compilado:
Código:
64 22 10 48 01 88 10 48 05 29 10 D0 05 23 5B 1A 01 31 4A 43 81 18 08 1C 64 38 19 22 5A 43 0B 68 03 60 04 31 04 30 01 3A 00 2A F8 D1 01 E0 4A 43 80 18 19 22 00 21 01 60 04 30 01 3A 00 2A FA D1 70 47 00 00 C0 70 03 02 84 42 02 02
Dato: Tiene una longitud de 4C bytes

Aquí tenéis la rutina ASM que edité con los offsets necesarios:
Código:
.text
.align 2
.thumb
.thumb_func

main:
 mov r2, #100 @Tamaño de la data para cada pokémon en bytes
 ldr r0, =0x020375E0 @Var $8004 Offset
 ldrh r1, [r0] @Carga en r1 el valor de la variable $8004
 ldr r0, =0x20244EC @Party_Pokemon Offset
 cmp r1, #5 @Si es el último, va a last
 beq last
 mov r3, #5
 sub r3, r1 @r3 = Cantidad de pokés a mover (los de delante)
 add r1, #1
 mul r2, r1
 add r1, r0, r2 @r1 = Offset del pokémon siguiente al que se borra
 mov r0, r1 @Copia ese offset al r0
 sub r0, #100 @r0 = Offset del pokémon a borrar
 mov r2, #(100/4) @Tamaño de la data de cada pokémon en words
 mul r2, r3 @r2 = Cantidad de words a mover hacia delante

move_loop:
 ldr r3, [r1] @Carga los datos que estamos moviendo en r3
 str r3, [r0] @Guarda los datos que estamos moviendo en el offset que hay en r0
 add r1, #4 @Avanza la posición de copiado hacia la siguiente word
 add r0, #4 @Avanza la posición de guardado hacia la siguiente word
 sub r2, #1 @Resta 1 word al contador de words a copiar
 cmp r2, #0 @Mientras el contador no llegue a 0, repite los pasos anteriores
 bne move_loop
 b last_sigue @Va directo a last_sigue salteándose last

last:
 mul r2, r1 @r2 = 100 * 5 = 500
 add r0, r2 @r0 = Offset en que comienza el equipo + 500 bytes = Offset del último pokémon

last_sigue:
 mov r2, #(100/4) @Carga en r2 la cantidad de words a borrar (del último pokémon) (25)
 mov r1, #0 @Carga el valor "0" en r1

last_loop:
 str r1, [r0] @Rellena los datos del último pokémon con 0
 add r0, #4 @Avanza una word en la dirección de escritura
 sub r2, #1 @Resta 1 al contador de words a escribir
 cmp r2, #0 @Mientras el contador no llegue a 0, repite lo anterior
 bne last_loop

end:
 bx lr @Termina la rutina volviendo a ejecutar aquello que la llamó

.ltorg
Y aquí el código compilado:
Código:
64 22 10 48 01 88 10 48 05 29 10 D0 05 23 5B 1A 01 31 4A 43 81 18 08 1C 64 38 19 22 5A 43 0B 68 03 60 04 31 04 30 01 3A 00 2A F8 D1 01 E0 4A 43 80 18 19 22 00 21 01 60 04 30 01 3A 00 2A FA D1 70 47 00 00 E0 75 03 02 EC 44 02 02
No tuve tiempo a pasarla a lenguaje C, pero para aquellos que queráis hacerlo deciros que no es nada difícil, es cosa de cambiar los offsets que trabajan en la rutina. Os voy a dejar un tutorial que seguramente os ayude: -Aprendiendo a romhackear en C

Bien, para empezar abriremos con HxD nuestra ROM:

Los siguientes pasos están hechos en Fire Red, pero es exactamente igual para Emerald.
Bien, una vez tenemos nuestra ROM abierta, lo que tendremos que hacer es copiar el código que os dejé ahí arriba (la rutina compilada). Ahora nos tenemos que ir a una dirección libre de nuestra ROM (lugar lleno de FF's), para poder pegar (insertar) nuestra rutina, recordad que el offset donde queráis insertarla debe terminar en 0, 4 , 8, o C. Esta dirección donde vayáis a insertar la rutina la debéis recordar sí o sí, la necesitaréis para luego poder llamarla mediante scripting.
Si queréis ir a una dirección en particular podéis hacer CTRL+G, escribís ahí la dirección, hacéis click en aceptar y os debería de llevar a ella.

Yo voy a insertarlo en la dirección 0x720000, por lo tanto me dirijo a ella. Si recordáis el dato que os puse sobre la extensión del código compilado, sabréis que ocupa un espacio de 4C bytes, pues bien, empezamos a seleccionar bytes, desde el byte de inicio de nuestra dirección, hasta que veamos que ponga 4C en el apartado "Longitud":


Una vez hecho esto es hora de pegar la rutina (que ya tenéis copiada, o sino hacedlo ahora), pero OJO, la debéis pegar haciendo CTRL+B (Pegar escribiendo), nunca CTRL+V (Pegar insertando), la razón es muy simple, haciendo CTRL+B estamos substituyendo la anterior información que había antes allí, lo que implica que todo seguirá en la posición que debe estar, ahora bien, si se hace CTRL+V, lo que estamos haciendo es poniendo nueva información, lo que implica que todo lo que haya escrito por debajo de ahí se desplace en ese sentido. Esto obviamente sería desastroso, pensad que la información que tenías a partir de ahí va a cambiar de offsets al moverse hacia abajo, por lo que todo sería un caos y estarías estropeando TODO, así que ya sabéis, CTRL+B Sí // CTRL+V No.
Bueno, pues llegados a este punto veréis algo así:


Guardáis y listo, ya tenemos nuestra rutina insertada, podéis cerrar el HxD.



Paso 2º

En este paso haremos un script que nos permita utilizar la rutina que acabamos de insertar.

Abrimos el editor de scripts que acostumbréis utilizar y cargamos nuestra ROM en él.
Los comandos fundamentales que vamos a utilizar son los siguientes:

-Countpokemon: Lo que este comando hace es contar la cantidad de pokémon que tenemos en nuestro equipo, el resultado lo podremos aprovechar con la variable 0x800D (LASTRESULT), si tenemos 1 Pokémon la var almacenará el valor 1, y así hasta 6 si fuese el caso. No requiere parámetros.

-Compare: Este comando nos permite comparar el valor de una variable elegida por nosotros con un valor que nosotros decidamos. Dos parámetros: 0x(variable) 0x(valor decidido por nosotros).

-If (condicionantes): Como su nombre bien indica nos sirven para comparar con algo la acción que viene antes de él, se usan después del comando compare (también los usamos después de los comandos del estilo check), tenemos los siguientes tipos de condicionantes: Menor a (0x0) Igual a (0x1) Mayor a (0x2) Menor o igual a (0x3) Mayor o igual a (0x4) Distinto de (0x5). Tiene 3 parámetros: 0x(condicionante) call/goto @(puntero a donde irá el script si el condicionante se cumple).

-Special: El special lo que hará es ejecutar una acción que ya viene programada en la ROM, por así decirlo son rutinas que para hacerles una ruta más fácil se les creó este comando, así sería más fácil llamarlas. Tiene un parámetro: 0x(número del evento a llamar). Existen listas con eses evento, pero nosotros solo necesitaremos el special que abré el menú Pokémon, es decir, el special 0x9F (Fire Red) o el special 0xA2 (Emerald). Ambos special lo que hacen es abrir el menú Pokémon y guardar el la variable 0x8004 el valor del Pokémon que seleccionemos, siendo el valor 0 el primer slot, 1 el segundo slot ... y 5 el último slot (la opción "salir" dará un valor diferente, 7 en Fire Red y 255 en emerald).

-Waitstate: Se suele usar después de ciertos special, lo que hace es asegurar de que el script no se siga ejecutando hasta que la acción del special se haya ejecutado correctamente, no es estrictamente necesario usarlo, pero tampoco es algo incorrecto. No requiere parámetros.

-Callasm: Este comando por decirlo de alguna manera es el camino lento del special, con él lo que haremos es llamar a la dirección específica de la ROM donde se encuentra la rutina, pero una cosa muy importante que debéis saber es que a esa dirección (offset), se le debe sumar +1 a su último número, será necesario por estar trabajando en thumb. Tiene un parámetro: 0x(offset de la ROM con la rutina).
Para esto debíais recordar el offset donde habéis insertado la rutina, lo debéis poner aquí sumándole +1. Ejemplos:
Yo en Fire Red lo inserté en el offset 0x720000, por lo tanto ahí pondré 0x720001, en Emerald lo inserté en el offset 0xE42000, por lo que debo poner 0xE42001.

Bien, una vez explicados así un poco los comandos procedemos a realizar el script:

Código:
#dynamic 0x800000

#org @start
lock
faceplayer
countpokemon
compare 0x800D 0x1
if 0x1 goto @ultimopkm
special 0x9F
waitstate
compare 0x8004 0x6
if 0x4 goto @Noborrar
callasm 0x720001
release
end

#org @ultimopkm
msgbox @msg1 0x6
release
end

#org @Noborrar
release
end

#org @msg1
= Ese es tu último pokémon, no te\npuedo dejar sin equipo.

Código:
#dynamic 0x800000

#org @start
lock
faceplayer
countpokemon
compare 0x800D 0x1
if 0x1 goto @ultimopkm
special 0xA2
waitstate 
compare 0x8004 0x6
if 0x4 goto @Noborrar
callasm 0xE42001
release
end

#org @ultimopkm
msgbox @msg1 0x6
release
end

#org @Noborrar
release
end

#org @msg1
= Ese es tu último pokémon, no te\npuedo dejar sin equipo.


Vale, si habéis leído con atención la explicación de los comandos que os he dado ahí arriba no os debería costar mucho entender como funciona, pero de todos modos aquí va una explicación:

La explicación que voy a dar sirve para ambos scripts.

Lo primero que hacemos es contar el número de Pokémon que tenemos en el equipo, comparamos el valor obtenido con el valor 1 (equivalente a tener solo 1 pokémon), en la siguiente línea hacemos el condicionante: si el comparativo anterior es igual (los valores a comparar son los mismos), el script nos lleva al puntero @ultimopkm, todo esto lo hacemos para que, si solo tenemos un pokémon en el equipo, este no pueda ser borrado para así no quedarnos con 0 pokémon.
Si el condicionante anterior no se cumple (los valores a comparar no son los mismos; tenemos más de 1 Pokémon), el script se seguirá ejecutando y el siguiente paso que debe hacer es abrir el menú Pokémon a traves del special 0x9F/A2, llegados a este punto deberemos seleccionar un Pokémon de nuestro equipo o la opción "salir", no hay más posibilidades, el valor obtenido de una de esas dos opciones se guarda en la variable 8004. Después hacemos otro comparativo, comparamos el valor de la var 8004 con el valor 6, si habéis leído la explicación de los comandos sabréis que, seleccionando un Pokémon, el valor oscilará entre 0 y 5, está oscilación nos lleva a establecer el siguiente condicionante: si la comparativa anterior es igual o mayor que nos lleve al puntero @Noborrar. Por eso comparamos el valor obtenido de la variable 8004 (lugar donde se guarda las opciones del special 0x9F/2A) con el valor 6, si el valor de la var es igual o mayor a 6 significará que no hemos elegido ningún Pokémon y por eso nos lleva a un puntero donde no se ejecuta la rutina de borrado.
Como último comando importante veréis el callasm, la llamada a la rutina, de este modo se ejecutará y borrará el Pokémon que hayáis seleccionado con anterioridad.

Sabiendo ahora todo esto le podéis añadir tantos comandos como queráis, como yo agregué ese msgbox (comando que ahí esta de adorno), pero obviamente debéis tener la estructura principal en mente para no interferir de algún modo en su funcionamiento.


Paso 3º


Comprobar los resultados en la ROM:



--------------------
--------------------
Pues esto sería todo, espero que hayáis entendido bien el proceso y los conceptos, aún así ya sabéis, para cualquier duda sobre el tutorial comentad, esto aplica también a posibles errores que se me puedan haber colado.

Un saludo ~ :D


EDIT: Se me olvidó comentar algo muy importante, y es que la rutina no está 100% anclada a esa estructura, me explico, si por ejemplo lo que queréis es borrar un Pokémon específico sin que la persona pueda elegirlo a través de la llamada al menú Pokémon, existe la posibilidad de establecer un slot en particular a través del comando setvar 0x(variable a recibir el valor) 0x(valor a dar). Eso ya puede dar juego, pero donde pueda estar el punto fuerte es en la combinación con estás dos rutinas de Franco también: FR|ASM| GPS(S) - Get Pokémon Species (Slot), no las he probado aún juntas, pero no creo que hubiese mucho problema, con ellas podríamos conseguir borrarle un Pokémon determinado al jugador.
 
Última edición:

Omega

For endless fight
Miembro del equipo
Moderador/a
Respuesta: [ASM][C] Como borrar un Pokémon

Excelente tutorial, he comprendido la teoría a la perfección, mañana tocara la practica que hoy tengo sueño (?
Ya llevaba rato pensando en una forma de hacer esto, recuerdo intentar con un script que creo haber visto en el foro, pero esto es mas practico y simple, ademas de ahorrar unos cuantos bytes como mínimo, muchas gracias por traerlo aquí @Inferno ;)

Saludos!!!
 

Inferno

Miembro insignia
Miembro insignia
Re: Respuesta: [ASM][C] Como borrar un Pokémon

Gracias!! Se agradecen un montón comentarios así :heart:

Ωmega ♀;415843 dijo:
recuerdo intentar con un script que creo haber visto en el foro
Esto es algo que no comenté y me gustaría aclarar, con ese "script" lo más seguro es que te estés refiriendo al "takepokemon" de Javi4315, con él también conseguirás que te quiten al Pokémon, el problema es que borrar lo que es borrar no lo borra, lo que hace es guardarlo en la guardería, puesto que ese script es el método que se utiliza para enviar los Pokémon a la guardería. Esto hace que el Pokémon, si tenéis pensado poner guardería o algo del estilo, se pueda recuperar, cosa que con esas rutinas no sucederá.
Si no te estabas refiriendo a eso la aclaración queda dada igualmente xDD
 

Bugrhak

A long time ago I used to call myself "Subzero".
Respuesta: [ASM][C] Como borrar un Pokémon

¡Ostras, pero qué pocos comentarios hay aquí! Si mal no recuerdo ya había una rutina que borraba un POKéMON del equipo, rutina la cual fue hecha por HackMew. Me parece muy bueno que hayas traído dos versiones. :D
Sin dudas le daré buen uso!

Por cierto, decir que la rutina que Franco hizo para FR, debería funcionar perfectamente en RF ya que las direcciones de la RAM que utiliza, son las mismas en ambos ROMs xD.


♠Un saludo!♠
 

Versekr Dark

Usuario mítico
Para Ruby (aunque nadie lo quiera yo si):

.text
.align 2
.thumb
.thumb_func

main:
mov r2, #100 @Tamaño de la data para cada pokémon en bytes
ldr r0, =0x0202e8cc @Var $8004 Offset
ldrh r1, [r0] @Carga en r1 el valor de la variable $8004

ldr r0, =0x03004360 @Party_Pokemon Offset
cmp r1, #5 @Si es el último, va a last
beq last
mov r3, #5
sub r3, r1 @r3 = Cantidad de pokés a mover (los de delante)
add r1, #1
mul r2, r1
add r1, r0, r2 @r1 = Offset del pokémon siguiente al que se borra
mov r0, r1 @Copia ese offset al r0
sub r0, #100 @r0 = Offset del pokémon a borrar
mov r2, #(100/4) @Tamaño de la data de cada pokémon en words
mul r2, r3 @r2 = Cantidad de words a mover hacia delante

move_loop:
ldr r3, [r1] @Carga los datos que estamos moviendo en r3
str r3, [r0] @Guarda los datos que estamos moviendo en el offset que hay en r0
add r1, #4 @Avanza la posición de copiado hacia la siguiente word
add r0, #4 @Avanza la posición de guardado hacia la siguiente word
sub r2, #1 @Resta 1 word al contador de words a copiar
cmp r2, #0 @Mientras el contador no llegue a 0, repite los pasos anteriores
bne move_loop
b last_sigue @Va directo a last_sigue salteándose last

last:
mul r2, r1 @r2 = 100 * 5 = 500
add r0, r2 @r0 = Offset en que comienza el equipo + 500 bytes = Offset del último pokémon

last_sigue:
mov r2, #(100/4) @Carga en r2 la cantidad de words a borrar (del último pokémon) (25)
mov r1, #0 @Carga el valor "0" en r1

last_loop:
str r1, [r0] @Rellena los datos del último pokémon con 0
add r0, #4 @Avanza una word en la dirección de escritura
sub r2, #1 @Resta 1 al contador de words a escribir
cmp r2, #0 @Mientras el contador no llegue a 0, repite lo anterior
bne last_loop

end:
bx lr @Termina la rutina volviendo a ejecutar aquello que la llamó

.ltorg
 

Samu

Miembro insignia
Miembro insignia
No me fije en su momento, pero bueno lo digo ahora. Con esas rutinas estáis haciendo acceso directo a memoria y lo estáis borrando a lo bruto, cosa que os puede dar lugar a varios errores. Esto os lo digo por experiencia, ya que hice una rutina similar hace años y tuve que modificarla.

La forma más correcta de borrar los Pokémon del equipo es usar la función ZeroMonData que tienen los ROM. A esta función solo hay que pasarle el puntero al Pokémon que queréis borrar y el juego se encargará de eliminarlo y correr las posiciones que sean necesarias. Os dejo una posible solución universal en ASM (no la he probado, por lo que podría tener algo mal) y un ejemplo del código en C.

Código:
.align 2
.thumb

main:
	push {r0-r1, lr}
	ldr r0,=0x8004 			@Variable ID
	bl VarGet				@Obtiene el puntero de la variable
	ldrb r0, [r0]		 [MENTION=24009]Alma[/MENTION]cena el valor de la variable
	mul r0, r0, 0x64		@Obtiene el offset del slot
	ldr r1, gPlayerParty	@Dirección del party
	add r1, r1, r0			@Obtiene la dirección del slot
	mov r0, r1
	bl ZeroMonData			@Elimina el pokemon
	pop {r0-r1, pc}
	
.gPlayerParty:
	.word 0x03004360 [MENTION=37844]Ruby[/MENTION]
	.word 0x02024284 @Fred
	.word 0x020244ec [MENTION=43531]Emerald[/MENTION]
	
.ZeroMonData:
	.word 0x0803a6d8+1 [MENTION=37844]Ruby[/MENTION]
	.word 0x0803d994+1	@Fred
	.word 0x08067a8c+1 [MENTION=43531]Emerald[/MENTION]
	
.VarGet:
	.word 0x08069254+1 [MENTION=37844]Ruby[/MENTION]
	.word 0x0806e568+1	@Fred
	.word 0x0809d694+1 [MENTION=43531]Emerald[/MENTION]
Nota: obviamente, para cada la dirección de cada variable/función debéis dejar únicamente la del ROM para el que estáis compilando la rutina.

Código:
void DeletePartyMon(u8 slot)
{
	ZeroMonData(&gPlayerParty[slot]);
}
 
Arriba