aptitud
Usuario mítico
Idioma del tutorial : español
Base juego : emerald (US)
Créditos :
a) Archivo del "emerald decompilation project"
para comprobar como y donde están los códigos que hacen lo que nos interesan.
b) la herramienta thumb para compilar codigo asm
Introducción :
Los pokémones errantes/"roaming" (o pkmn salvajes que pueden escapar, en general)
les gustan a algunos, y los despiden a otros.
Un problema que se puede tener es la que :
El errante se puede escapar cuando esta durmiendo o congelado.
(Esto se ha casi-cambiado en un broma en el web.)
Pero, en realidad, antes de gen 3,
es decir ; cuando aparecen, en gen2, las funcionalidades de pokemones errantes,
y de pokemones salvajes que pueden tratar de escapar :
En gen 2 un pokémon salvaje no se escapa cuando esta durmiendo o congelado.
Entonces, la idea seria :
Como hacer que, en emerald, de nuevo,
un pokemon salvaje que puede tratar de escapar,
no lo hace cuando su estado esta durmiendo o congelado ?
(He buscado como hacerlo, por años...)
Y : "¡Finalmente!"
Dos cosas que modificar
De verdad, cuando le ha descubierto a lo que debe cambiar,
es "bastante" simple hacer que un pokémon errante/salvaje no escapa durmiendo/congelado.
Hay dos cosas que se necesita cambiar para que funciona bien :
A) El fácil de comprobar/cambiar :
La "Inteligencia Artificial" del errantes para elegir de escapar o no
B) el código "HandleAction_Run" que decide lo que pasa,
en general, cuando un pokémon salvaje trata de escapar
(cuando lo ha elegido hacer al principio del turno).
A) Cambiar el código (formateado) de la IA_errante/AI_Roaming
En el juego, la IA de un pokemon, en combate, funciona con una lista de flags :
Simplificando : Cada flag decide 'si' o 'no', en orden, se necesita aplicar cosa.
(Estos flags son decidido, en general, por el tipo de batalla.)
Entonces, cuando empezá una batalla contra un errante,
el tipo de batalla es registrado como "Roaming" ;
y con eso, se activa el flag para que el juego hace el script de la IA_errante/AI_Roaming.
Aquí es un extracto de código decompilado, del "emerald decompilation project" :
Entonces, se puede comprobar que el script para AI_Roaming esta a dirección 2DE309
(y el comprobá si el errante elige de huir/escapar, depende sobre estados y habilidades).
El decompilation le transcribe con :
que esta, en hexadecimal :
También, un otro extracto de código decompilado,
cuales son las instrucciones utilizadas en estos scripts de IA de combate :
Y lo que nos interesan en el script de IA_errante
seria las dos linea de instrucciones a su principio :
los parámetros de 'if_status2' (0x0B) están, entonces, en orden :
> 01 o 00 (AI_USER o AI_TARGET) (1 byte)
> mask de estado (4 bytes)
> dirección donde iría el script si la comprobá es verdadera (i.e. no cero) (4 bytes)
Entonces, aunque :
Los estados principal y secundario de un pokémon se comprobá con "mask" números.
(que se compraban con "bitwise_and" entre el estado y la referencia "mask".)
Pues, se puede combinar
y
en un equivalente
para añadir una instrucción "if_status" (0x09) :
Y, haciendo calculaciones :
estar "durmiendo o congelado" es 0x27 (binario 100111)
y "WRAPPED o ESCAPE_PREVENTION" es 0x 04 00 E0 00 (binario 100 0000 0000 1110 0000 0000 0000)
Se nota que estos se entran, como secundario parámetros de if_status/if_status2,
en orden reverso de bytes y, entonces, nuestra modificación :
se escrito, en hex (a dirección 2DE309) :
B) modificar el código asm de "HandleAction_Run"
Con la modificación de la IA_errante, el pokemon errante
no va a elegir de escapar cuando es durmiendo (o congelado) al principio del turno.
Pero, no le impide escapar si el fue adormecido (o congelado) durante el turno.
Entonces, también se necesita comprobar y modificar donde se impide estos tipos de escapa.
(i.e. pokémon ha elegido de escapar pero es impedido
por la habilidad del enemigo y/o su estado que han cambiados desde la elección.)
...
Y el código que hace esto es el código a dirección 3EE48,
nominado "HandleAction_Run" en el emerald decompilation project.
Este código es un poco complicado, pero, de verdad,
solo os necesitan cambiar su parte de termino.
Para seguridad, aquí es el hexadecimal original para borrar el cambio, si necesiten :
Lo que hice es, quitar a ".word"s que aparecían en doble en el código original,
para añadir la parte entre "-" lineas y cambiar/comprimir un poco el resto del código,
para que hace la misma cosa que antes,
solo también con la verificación añadida del estado principal.
Finalmente, el hexadecimal compilado de la modificación
a copiar a dirección 3ef2A (hasta 3efa7) es :
Si necesite clarificación, no hesita preguntar.
Base juego : emerald (US)
Créditos :
a) Archivo del "emerald decompilation project"
para comprobar como y donde están los códigos que hacen lo que nos interesan.
b) la herramienta thumb para compilar codigo asm
Introducción :
Los pokémones errantes/"roaming" (o pkmn salvajes que pueden escapar, en general)
les gustan a algunos, y los despiden a otros.
Un problema que se puede tener es la que :
El errante se puede escapar cuando esta durmiendo o congelado.
(Esto se ha casi-cambiado en un broma en el web.)
Pero, en realidad, antes de gen 3,
es decir ; cuando aparecen, en gen2, las funcionalidades de pokemones errantes,
y de pokemones salvajes que pueden tratar de escapar :
En gen 2 un pokémon salvaje no se escapa cuando esta durmiendo o congelado.
Entonces, la idea seria :
Como hacer que, en emerald, de nuevo,
un pokemon salvaje que puede tratar de escapar,
no lo hace cuando su estado esta durmiendo o congelado ?
(He buscado como hacerlo, por años...)
Y : "¡Finalmente!"
El tutorial de hoy explica un medio para hacer esta modificacion,
con añadidos de recursos sobre como funciona los sistemas implicados.
con añadidos de recursos sobre como funciona los sistemas implicados.
Dos cosas que modificar
De verdad, cuando le ha descubierto a lo que debe cambiar,
es "bastante" simple hacer que un pokémon errante/salvaje no escapa durmiendo/congelado.
Hay dos cosas que se necesita cambiar para que funciona bien :
A) El fácil de comprobar/cambiar :
La "Inteligencia Artificial" del errantes para elegir de escapar o no
B) el código "HandleAction_Run" que decide lo que pasa,
en general, cuando un pokémon salvaje trata de escapar
(cuando lo ha elegido hacer al principio del turno).
A) Cambiar el código (formateado) de la IA_errante/AI_Roaming
En el juego, la IA de un pokemon, en combate, funciona con una lista de flags :
Simplificando : Cada flag decide 'si' o 'no', en orden, se necesita aplicar cosa.
(Estos flags son decidido, en general, por el tipo de batalla.)
Entonces, cuando empezá una batalla contra un errante,
el tipo de batalla es registrado como "Roaming" ;
y con eso, se activa el flag para que el juego hace el script de la IA_errante/AI_Roaming.
Aquí es un extracto de código decompilado, del "emerald decompilation project" :
Código:
gBattleAI_ScriptsTable:: @ 82DBEF8
.4byte AI_CheckBadMove @ AI_SCRIPT_CHECK_BAD_MOVE
.4byte AI_TryToFaint @ AI_SCRIPT_TRY_TO_FAINT
.4byte AI_CheckViability @ AI_SCRIPT_CHECK_VIABILITY
.4byte AI_SetupFirstTurn @ AI_SCRIPT_SETUP_FIRST_TURN
.4byte AI_Risky @ AI_SCRIPT_RISKY
.4byte AI_PreferStrongestMove @ AI_SCRIPT_PREFER_STRONGEST_MOVE
.4byte AI_PreferBatonPass @ AI_SCRIPT_PREFER_BATON_PASS
.4byte AI_DoubleBattle @ AI_SCRIPT_DOUBLE_BATTLE
.4byte AI_HPAware @ AI_SCRIPT_HP_AWARE
.4byte AI_Unknown @ AI_SCRIPT_UNKNOWN
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Ret
.4byte AI_Roaming @ AI_SCRIPT_ROAMING
.4byte AI_Safari @ AI_SCRIPT_SAFARI
.4byte AI_FirstBattle @ AI_SCRIPT_FIRST_BATTLE
Entonces, se puede comprobar que el script para AI_Roaming esta a dirección 2DE309
(y el comprobá si el errante elige de huir/escapar, depende sobre estados y habilidades).
El decompilation le transcribe con :
Código:
AI_Roaming:
if_status2 AI_USER, STATUS2_WRAPPED, AI_Roaming_End
if_status2 AI_USER, STATUS2_ESCAPE_PREVENTION, AI_Roaming_End
get_ability AI_TARGET
if_equal ABILITY_SHADOW_TAG, AI_Roaming_End
get_ability AI_USER
if_equal ABILITY_LEVITATE, AI_Roaming_Flee
get_ability AI_TARGET
if_equal ABILITY_ARENA_TRAP, AI_Roaming_End
AI_Roaming_Flee: @ 82DE335
flee
AI_Roaming_End: @ 82DE336
end
Código:
0B 01 00 E0 00 00 36 E3 2D 08
0B 01 00 00 00 04 36 E3 2D 08
2F 00 13 17 36 E3 2D 08
2F 01 13 1A 35 E3 2D 08
2F 00 13 47 36 E3 2D 08
45
5A
cuales son las instrucciones utilizadas en estos scripts de IA de combate :
Código:
static const BattleAICmdFunc sBattleAICmdTable[] =
{
Cmd_if_random_less_than, // 0x0
Cmd_if_random_greater_than, // 0x1
Cmd_if_random_equal, // 0x2
Cmd_if_random_not_equal, // 0x3
Cmd_score, // 0x4
Cmd_if_hp_less_than, // 0x5
Cmd_if_hp_more_than, // 0x6
Cmd_if_hp_equal, // 0x7
Cmd_if_hp_not_equal, // 0x8
Cmd_if_status, // 0x9
Cmd_if_not_status, // 0xA
Cmd_if_status2, // 0xB
Cmd_if_not_status2, // 0xC
Cmd_if_status3, // 0xD
Cmd_if_not_status3, // 0xE
Cmd_if_side_affecting, // 0xF
Cmd_if_not_side_affecting, // 0x10
Cmd_if_less_than, // 0x11
Cmd_if_more_than, // 0x12
Cmd_if_equal, // 0x13
Cmd_if_not_equal, // 0x14
Cmd_if_less_than_ptr, // 0x15
Cmd_if_more_than_ptr, // 0x16
Cmd_if_equal_ptr, // 0x17
Cmd_if_not_equal_ptr, // 0x18
Cmd_if_move, // 0x19
Cmd_if_not_move, // 0x1A
Cmd_if_in_bytes, // 0x1B
Cmd_if_not_in_bytes, // 0x1C
Cmd_if_in_hwords, // 0x1D
Cmd_if_not_in_hwords, // 0x1E
Cmd_if_user_has_attacking_move, // 0x1F
Cmd_if_user_has_no_attacking_moves, // 0x20
Cmd_get_turn_count, // 0x21
Cmd_get_type, // 0x22
Cmd_get_considered_move_power, // 0x23
Cmd_get_how_powerful_move_is, // 0x24
Cmd_get_last_used_battler_move, // 0x25
Cmd_if_equal_, // 0x26
Cmd_if_not_equal_, // 0x27
Cmd_if_user_goes, // 0x28
Cmd_if_user_doesnt_go, // 0x29
Cmd_nop_2A, // 0x2A
Cmd_nop_2B, // 0x2B
Cmd_count_usable_party_mons, // 0x2C
Cmd_get_considered_move, // 0x2D
Cmd_get_considered_move_effect, // 0x2E
Cmd_get_ability, // 0x2F
Cmd_get_highest_type_effectiveness, // 0x30
Cmd_if_type_effectiveness, // 0x31
Cmd_nop_32, // 0x32
Cmd_nop_33, // 0x33
Cmd_if_status_in_party, // 0x34
Cmd_if_status_not_in_party, // 0x35
Cmd_get_weather, // 0x36
Cmd_if_effect, // 0x37
Cmd_if_not_effect, // 0x38
Cmd_if_stat_level_less_than, // 0x39
Cmd_if_stat_level_more_than, // 0x3A
Cmd_if_stat_level_equal, // 0x3B
Cmd_if_stat_level_not_equal, // 0x3C
Cmd_if_can_faint, // 0x3D
Cmd_if_cant_faint, // 0x3E
Cmd_if_has_move, // 0x3F
Cmd_if_doesnt_have_move, // 0x40
Cmd_if_has_move_with_effect, // 0x41
Cmd_if_doesnt_have_move_with_effect, // 0x42
Cmd_if_any_move_disabled_or_encored, // 0x43
Cmd_if_curr_move_disabled_or_encored, // 0x44
Cmd_flee, // 0x45
Cmd_if_random_safari_flee, // 0x46
Cmd_watch, // 0x47
Cmd_get_hold_effect, // 0x48
Cmd_get_gender, // 0x49
Cmd_is_first_turn_for, // 0x4A
Cmd_get_stockpile_count, // 0x4B
Cmd_is_double_battle, // 0x4C
Cmd_get_used_held_item, // 0x4D
Cmd_get_move_type_from_result, // 0x4E
Cmd_get_move_power_from_result, // 0x4F
Cmd_get_move_effect_from_result, // 0x50
Cmd_get_protect_count, // 0x51
Cmd_nop_52, // 0x52
Cmd_nop_53, // 0x53
Cmd_nop_54, // 0x54
Cmd_nop_55, // 0x55
Cmd_nop_56, // 0x56
Cmd_nop_57, // 0x57
Cmd_call, // 0x58
Cmd_goto, // 0x59
Cmd_end, // 0x5A
Cmd_if_level_cond, // 0x5B
Cmd_if_target_taunted, // 0x5C
Cmd_if_target_not_taunted, // 0x5D
Cmd_if_target_is_ally, // 0x5E
Cmd_is_of_type, // 0x5F
Cmd_check_ability, // 0x60
Cmd_if_flash_fired, // 0x61
Cmd_if_holds_item, // 0x62
};
Y lo que nos interesan en el script de IA_errante
seria las dos linea de instrucciones a su principio :
Código:
if_status2 AI_USER, STATUS2_WRAPPED, AI_Roaming_End
if_status2 AI_USER, STATUS2_ESCAPE_PREVENTION, AI_Roaming_End
> 01 o 00 (AI_USER o AI_TARGET) (1 byte)
> mask de estado (4 bytes)
> dirección donde iría el script si la comprobá es verdadera (i.e. no cero) (4 bytes)
Código:
// Non-volatile status conditions
// These persist remain outside of battle and after switching out
#define STATUS1_NONE 0
#define STATUS1_SLEEP (1 << 0 | 1 << 1 | 1 << 2) // First 3 bits (Number of turns to sleep)
#define STATUS1_SLEEP_TURN(num) ((num) << 0) // Just for readability (or if rearranging statuses)
#define STATUS1_POISON (1 << 3)
#define STATUS1_BURN (1 << 4)
#define STATUS1_FREEZE (1 << 5)
#define STATUS1_PARALYSIS (1 << 6)
#define STATUS1_TOXIC_POISON (1 << 7)
#define STATUS1_TOXIC_COUNTER (1 << 8 | 1 << 9 | 1 << 10 | 1 << 11)
#define STATUS1_TOXIC_TURN(num) ((num) << 8)
#define STATUS1_PSN_ANY (STATUS1_POISON | STATUS1_TOXIC_POISON)
#define STATUS1_ANY (STATUS1_SLEEP | STATUS1_POISON | STATUS1_BURN | STATUS1_FREEZE | STATUS1_PARALYSIS | STATUS1_TOXIC_POISON)
// Volatile status ailments
// These are removed after exiting the battle or switching out
#define STATUS2_CONFUSION (1 << 0 | 1 << 1 | 1 << 2)
#define STATUS2_CONFUSION_TURN(num) ((num) << 0)
#define STATUS2_FLINCHED (1 << 3)
#define STATUS2_UPROAR (1 << 4 | 1 << 5 | 1 << 6)
#define STATUS2_UPROAR_TURN(num) ((num) << 4)
#define STATUS2_UNUSED (1 << 7)
#define STATUS2_BIDE (1 << 8 | 1 << 9)
#define STATUS2_BIDE_TURN(num) (((num) << 8) & STATUS2_BIDE)
#define STATUS2_LOCK_CONFUSE (1 << 10 | 1 << 11) // e.g. Thrash
#define STATUS2_LOCK_CONFUSE_TURN(num)((num) << 10)
#define STATUS2_MULTIPLETURNS (1 << 12)
#define STATUS2_WRAPPED (1 << 13 | 1 << 14 | 1 << 15)
#define STATUS2_WRAPPED_TURN(num) ((num) << 13)
#define STATUS2_INFATUATION (1 << 16 | 1 << 17 | 1 << 18 | 1 << 19) // 4 bits, one for every battler
#define STATUS2_INFATUATED_WITH(battler) (gBitTable[battler] << 16)
#define STATUS2_FOCUS_ENERGY (1 << 20)
#define STATUS2_TRANSFORMED (1 << 21)
#define STATUS2_RECHARGE (1 << 22)
#define STATUS2_RAGE (1 << 23)
#define STATUS2_SUBSTITUTE (1 << 24)
#define STATUS2_DESTINY_BOND (1 << 25)
#define STATUS2_ESCAPE_PREVENTION (1 << 26)
#define STATUS2_NIGHTMARE (1 << 27)
#define STATUS2_CURSED (1 << 28)
#define STATUS2_FORESIGHT (1 << 29)
#define STATUS2_DEFENSE_CURL (1 << 30)
#define STATUS2_TORMENT (1 << 31)
Entonces, aunque :
Los estados principal y secundario de un pokémon se comprobá con "mask" números.
(que se compraban con "bitwise_and" entre el estado y la referencia "mask".)
Pues, se puede combinar
Código:
if_status2 AI_USER, STATUS2_WRAPPED, AI_Roaming_End
Código:
if_status2 AI_USER, STATUS2_ESCAPE_PREVENTION, AI_Roaming_End
Código:
if_status2 AI_USER, STATUS2_WRAPPED_o_ESCAPE_PREVENTION, AI_Roaming_End
Código:
if_status AI_USER, STATUS_SLEEP_o_FROZEN, AI_Roaming_End
estar "durmiendo o congelado" es 0x27 (binario 100111)
y "WRAPPED o ESCAPE_PREVENTION" es 0x 04 00 E0 00 (binario 100 0000 0000 1110 0000 0000 0000)
Se nota que estos se entran, como secundario parámetros de if_status/if_status2,
en orden reverso de bytes y, entonces, nuestra modificación :
Código:
if_status AI_USER, STATUS_SLEEP_o_FROZEN, AI_Roaming_End
if_status2 AI_USER, STATUS2_WRAPPED_o_ESCAPE_PREVENTION, AI_Roaming_End
Código:
09 01 27 00 00 00 36 E3 2D 08
0B 01 00 E0 00 04 36 E3 2D 08
B) modificar el código asm de "HandleAction_Run"
Con la modificación de la IA_errante, el pokemon errante
no va a elegir de escapar cuando es durmiendo (o congelado) al principio del turno.
Pero, no le impide escapar si el fue adormecido (o congelado) durante el turno.
Entonces, también se necesita comprobar y modificar donde se impide estos tipos de escapa.
(i.e. pokémon ha elegido de escapar pero es impedido
por la habilidad del enemigo y/o su estado que han cambiados desde la elección.)
...
Y el código que hace esto es el código a dirección 3EE48,
nominado "HandleAction_Run" en el emerald decompilation project.
Este código es un poco complicado, pero, de verdad,
solo os necesitan cambiar su parte de termino.
Entonces, vamos a cambiar el código desde 3ef2A, hasta 3efa7 (incluido).
Para seguridad, aquí es el hexadecimal original para borrar el cambio, si necesiten :
Código:
04 49 03 20 48 71 03 49 04 48 08 60 04 49 0A 20 2C E0 32 43 02 02 14 42 02 02 02 AB 2D 08 83 40 02 02 09 49 22 78 58 20 50 43 50 31 40 18 00 68 07 49 08 40 00 28 14 D0 06 49 04 20 48 71 05 49 06 48 08 60 06 49 0A 20 10 E0 84 40 02 02 00 E0 00 04 32 43 02 02 14 42 02 02 02 AB 2D 08 83 40 02 02 04 48 00 78 28 70 04 49 06 20 08 70 70 BC 01 BC 00 47 00 00 6C 40 02 02 3A 43 02 02
Código:
mov r0, #0x3 // instrucción (2 bytes) que no se debe copiar del hex compilado
//esta añadida para que los ".align 2, 0" corresponden
//(3ef28 es como 0x0 : un múltiple de 0x4, cuando 3ef2A no esta)
ldr r1, .L500
mov r0, #0x3
strb r0, [r1, #0x5]
.LM235:
ldr r1, .L501
ldr r0, .L502
str r0, [r1]
.LM236:
.L700:
ldr r1, .L503
mov r0, #0xa
.LM237:
b .L217
.L211:
.LM238:
-------------------------------------
ldr r1, .L224 // carga la dirección de la RAM dedicada por los pokémones en batalle
ldrb r2, [r4] // r2 es el indice del pokémon
mov r0, #0x58 // tamaño de ram utilizado por cada pkmn
mul r0, r0, r2 // bytes que añadir para estar al principio de la RAM por el pokémon
add r1, r1, #0x4c // offset para el principio del información de estado principal
add r0, r0, r1 // r0 devenía la dirección del estado principal del pokémon
ldr r0, [r0] // carga el estado actual en r0
mov r1, #0x27 // define "durmiendo o congelado" en r1
and r0, r0, r1 // comprobá el estado contra "durmiendo o congelado" y poné el en r0
cmp r0, #0
bne .LM239 @cond_branch // si durmiendo o congelado, va a la parte que impide escapar
-------------------------------------
ldr r1, .L224
mov r0, #0x58
mul r0, r0, r2
add r1, r1, #0x50
add r0, r0, r1
ldr r0, [r0]
ldr r1, .L224+0x4
.L600:
and r0, r0, r1
cmp r0, #0
beq .L214 @cond_branch
.LM239: // parte que impide escapar y define el mensaje que mostrar
ldr r1, .L224+0x8
mov r0, #0x4
strb r0, [r1, #0x5]
ldr r1, .L501
ldr r0, .L502
str r0, [r1]
b .L700
.L225:
.align 2, 0
.L224:
.word 0x02024084 :: gBattleMons
.word 0x0400e000 :: mask de estado_secundario WRAPPED_o_ESCAPE_PREVENTION
.L500:
.word 0x02024332 :: gBattleCommunication
.L501:
.word 0x02024214 :: gBattlescriptCurrInstr
.L502:
.word 0x082dab02 :: BattleScript_PrintFailedToRunString
.L503:
.word 0x02024083 :: gCurrentActionFuncId
.L214: // código para que el pokémon escapa
.LM243:
ldr r0, .L226
ldrb r0, [r0]
strb r0, [r5]
.LM244:
ldr r1, .L226+0x4
mov r0, #0x6
.L217:
strb r0, [r1]
.L210:
.LM245:
pop {r4, r5, r6}
pop {r0}
bx r0
.L227:
.align 2, 0
.L226:
.word 0x0202406c :: gBattlersCount
.word 0x0202633a :: gBattleOutcome
.LFE5:
Lo que hice es, quitar a ".word"s que aparecían en doble en el código original,
para añadir la parte entre "-" lineas y cambiar/comprimir un poco el resto del código,
para que hace la misma cosa que antes,
solo también con la verificación añadida del estado principal.
Finalmente, el hexadecimal compilado de la modificación
a copiar a dirección 3ef2A (hasta 3efa7) es :
Código:
14 49 03 20 48 71 13 49 14 48 08 60 14 49 0A 20 2C E0 0D 49 22 78 58 20 50 43 4C 31 40 18 00 68 27 21 08 40 00 28 09 D1 08 49 58 20 50 43 50 31 40 18 00 68 06 49 08 40 00 28 12 D0 05 49 04 20 48 71 04 49 05 48 08 60 E0 E7 84 40 02 02 00 E0 00 04 32 43 02 02 14 42 02 02 02 AB 2D 08 83 40 02 02 04 48 00 78 28 70 04 49 06 20 08 70 70 BC 01 BC 00 47 00 00 6C 40 02 02 3A 63 02 02
Utiliza a este tutorial y modificaciones como le quiere.
Si necesite clarificación, no hesita preguntar.