Registrarse

[ASM] E | Editing moves effects, with references from decompilation project

aptitud

Usuario mítico
Editing moves effects, with references from decompilation project
Application : gen2-based rebuff of Low Kick, Synthesis-style trio of moves, and Sandstorm damage.


Tutorial language : English
Game base : Emerald
(Guidelines are applicable for other games with enough decompilation references)

References (used and to use) :
Files from the Emerald decompilation project,
to find addresses and decipher how move processing are done.


Principles :
Move effects
are stored in a table at 0x2D86A8

Each entry in this table is a pointer to an entry point in a
1) formatted scripting structure,
admitting predefined 1 byte instructions
(see decompilation project files for instruction keys)
including ones pointing to
2) specific functions and data/tables at called/jumped-to addresses.

Thus, there is two (combinable) possible levels to edit a move effect :
I) (Repoint and) edit the "level 1" formatted script for the interest effect
II) (Repoint and) edit the used/called data and asm functions




As illustrations, we will be considering the practical objectives :

A) Buff Low Kick to the best of its gen2 and gen 3 versions :

In gen2, the move has a power of 50 and a 30% chance to flinch
(with an accuracy of only 90%)
In gen3, its power depends on the target's weight, with no secondary effect
(and its accuracy is raised to 100%)

So, what we want to do is to adjust the power table so it's always 50 or up,
as well as graft a secondary effect of flinching to the move.
(the effect accuracy will be adjustable through any base "attack parameters" editor)


So, for starter :
The formatted script for the effect used by Low kick
is indexed as the 196th and is located at address 0x2dA451

raw, it is :
Código:
00 02 03 DD 01 60 8A 2D 08 00 00 28 30 8A 2D 08
which the decomp files transcribe as :
Código:
BattleScript_EffectLowKick::
00    attackcanceler
02    attackstring
03    ppreduce
DD    weightdamagecalculation
01 60 8A 2D 08 00 00     accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
28 30 8A 2D 08    goto BattleScript_HitFromCritCalc (at 0x 2d 8a 30)

Starting by upping the base power to always be 50 or more

So, the weightdamagecalculation standardised instruction use the lookout table at 0x31c428 :
Código:
64 00 14 00 FA 00 28 00 F4 01 3C 00 E8 03 50 00 D0 07 64 00 FF FF FF FF
which, reformatted and annotated reads :​
weight ceiling
(in 100g multiples)​
base power
(for the induced weight interval)​
0x 00 64 = 100 (10 kg)​
0x0014 = 20​
0x 00 FA = 250 (25 kg)​
0x0028 = 40​
0x 01 F4 = 500 (50 kg)​
0x003c = 60​
0x 03 E8 = 1000 (100 kg)​
0x0050 = 80​
0x 07 D0 = 2000 (200 kg)​
0x0064 = 100​
FF FF FF FF​
termination sequence for scanning this table

The max power, for weights equal or over the last ceiling (here 200kg)
is coded, in the weightdamagecalculation function, to be at 120
...
In our case, we do not need to alter the "weightdamagecalculation" code,
as we can raise the powers to fit our case requirements,
by only altering the first two powers of the table,
and leave the ceilings and other base powers the same.


In my case, I choose to go for a first base of 50 = 0x32
then up it slightly to 55 =0x37, for the second.
Then the growth rejoin the one in og gen3.

Thus, we go with pasting other the altered table
Código:
64 00 32 00 FA 00 37 00 F4 01 3C 00 E8 03 50 00 D0 07 64 00 FF FF FF FF
at 0x31c428


Now, how do we graft the flinch effect to our remixed low kick ?
We return to adjusting its formatted script in a way,
aligned with how other reference move scripts are defined.

So, the effect for a regular damaging move, having flinch as secondary effect,
is stored as :
Código:
2E 35 43 02 02 08 28 00 8A 2D 08
decomp-deciphered as :
Código:
BattleScript_EffectFlinchHit::
2E 35 43 02 02 08    setmoveeffect MOVE_EFFECT_FLINCH
28 00 8A 2D 08    goto BattleScript_EffectHit

And, we have
Código:
BattleScript_EffectHit::
    jumpifnotmove MOVE_SURF, BattleScript_HitFromAtkCanceler
    jumpifnostatus3 BS_TARGET, STATUS3_UNDERWATER, BattleScript_HitFromAtkCanceler
    orword gHitMarker, HITMARKER_IGNORE_UNDERWATER
    setbyte sDMG_MULTIPLIER, 0x2
BattleScript_HitFromAtkCanceler::
    attackcanceler
BattleScript_HitFromAccCheck::
    accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE
BattleScript_HitFromAtkString::
    attackstring
    ppreduce
BattleScript_HitFromCritCalc::
[...]
The part before BattleScript_HitFromAtkCanceler is skipped for low kick,
as it's not surf, and, as such,
we only need to make sure we have
setmoveeffect MOVE_EFFECT_FLINCH
and the required instructions, between
BattleScript_HitFromAtkCanceler and BattleScript_HitFromCritCalc
added to our remixed instruction scripting, in acceptable order,
compared to the og gen3 low kick script

This would induce, thus, a preliminary possibility of :
Código:
2E 35 43 02 02 08    setmoveeffect MOVE_EFFECT_FLINCH
00 attackcanceler
DD weightdamagecalculation
01 60 8A 2D 08 00 00     accuracycheck BattleScript_MoveMissedPause, ACC_CURR_MOVE
02    attackstring
03    ppreduce
28 30 8A 2D 08    goto BattleScript_HitFromCritCalc (at 0x 2d 8a 30)
as, from the reference "BattleScript_HitFromAtkCanceler"
"attackstring" and "ppreduce" may be moved after the accuracy check

!!!
Now we see the part after weightdamagecalculation
define the same thread of actual operations as BattleScript_HitFromAccCheck and onward.


Thus, we can save on script size and have it fit in the og allotted space with
Código:
2E 35 43 02 02 08 setmoveeffect MOVE_EFFECT_FLINCH
00 attackcanceler
DD weightdamagecalculation
28 27 8A 2D 08 goto BattleScript_HitFromAccCheck (at 0x 2d 8a 27 )
And, thus, it's possible to complete the second part of the objective, by also pasting
Código:
2E 35 43 02 02 08 00 DD 28 27 8A 2D 08 FF FF FF
over the og low kick formatted script at address 0x 2d a4 51



B) re-buff Morning sun, Synthesis and Moonlight to their gen2 effects :
Heal 25% in averse weather, 50% in un-affecting weather and 100% in sunny weather,
with the efficiency doubled, respectively, in the morning, day, and night period.

So...
in the base emerald game, the effects for all three interest moves point to the same script,
them being, thus, virtually identical, with the core of the effect being defined in
the standard function "recoverbasedonsunlight"

Hence, it seems more fitting to alter this move-family specific
recoverbasedonsunlight function, located at address 0x542f8 (and 0xE0 bytes long) :
Código:
F0 B5 81 B0 19 49 1A 4D 28 78 08 70 19 4F 28 78 58 26 70 43 C0 19 01 8D 80 8D 81 42 60 D0 16 4C 20 88 00 28 15 D0 00 20 00 90 13 20 00 21 0D 22 00 23 EE F7 7D FA 00 06 00 0E 00 28 09 D1 00 90 13 20 00 21 4D 22 00 23 EE F7 72 FA 00 06 00 28 16 D0 0A 4B 07 4A 06 48 01 78 58 20 48 43 80 18 80 8D 40 08 18 60 19 1C 27 E0 00 00 0C 42 02 02 0B 42 02 02 84 40 02 02 CC 43 02 02 F0 41 02 02 21 88 60 20 08 40 00 28 10 D0 07 4C 28 78 70 43 C0 19 81 8D 88 00 40 18 80 00 1E 21 93 F2 D4 F8 20 60 21 1C 09 E0 00 00 F0 41 02 02 0A 49 28 78 70 43 C0 19 80 8D 80 08 08 60 0A 1C 10 68 00 28 01 D1 01 20 10 60 08 68 40 42 08 60 03 49 08 68 05 30 08 60 11 E0 00 00 F0 41 02 02 14 42 02 02
Cmd_recoverbasedonsunlight:

push {r4, r5, r6, r7, lr}
add sp, sp, #-0x4

ldr r1, .L3686
ldr r5, .L3686+0x4
ldrb r0, [r5]
strb r0, [r1]

ldr r7, .L3686+0x8
ldrb r0, [r5]
mov r6, #0x58
mul r0, r0, r6
add r0, r0, r7
ldrh r1, [r0, #0x28]
ldrh r0, [r0, #0x2c]
cmp r1, r0
beq .L3678 @cond_branch

ldr r4, .L3686+0xc
ldrh r0, [r4]
cmp r0, #0
beq .L3680 @cond_branch
mov r0, #0x0
str r0, [sp]
mov r0, #0x13
mov r1, #0x0
mov r2, #0xd
mov r3, #0x0
bl AbilityBattleEffects
lsl r0, r0, #0x18
lsr r0, r0, #0x18
cmp r0, #0
bne .L3680 @cond_branch
str r0, [sp]
mov r0, #0x13
mov r1, #0x0
mov r2, #0x4d
mov r3, #0x0
bl AbilityBattleEffects
lsl r0, r0, #0x18
cmp r0, #0
beq .L3679 @cond_branch
.L3680:

ldr r3, .L3686+0x10
ldr r2, .L3686+0x8
ldr r0, .L3686+0x4
ldrb r1, [r0]
mov r0, #0x58
mul r0, r0, r1
add r0, r0, r2
ldrh r0, [r0, #0x2c]
lsr r0, r0, #0x1
str r0, [r3]
add r1, r3, #0
b .L3681

.align 2, 0
.L3686:
.word gBattlerTarget
.word gBattlerAttacker
.word gBattleMons
.word gBattleWeather
.word gBattleMoveDamage
.L3679:

ldrh r1, [r4]
mov r0, #0x60
and r0, r0, r1
cmp r0, #0
beq .L3682 @cond_branch

ldr r4, .L3688
ldrb r0, [r5]
mul r0, r0, r6
add r0, r0, r7
ldrh r1, [r0, #0x2c]
lsl r0, r1, #0x2
add r0, r0, r1
lsl r0, r0, #0x2
mov r1, #0x1e
bl __divsi3
str r0, [r4]
add r1, r4, #0
b .L3681

.align 2, 0
.L3688:
.word gBattleMoveDamage
.L3682:

ldr r1, .L3690
ldrb r0, [r5]
mul r0, r0, r6
add r0, r0, r7
ldrh r0, [r0, #0x2c]
lsr r0, r0, #0x2
str r0, [r1]
.L3681:

add r2, r1, #0
ldr r0, [r2]
cmp r0, #0
bne .L3684 @cond_branch

mov r0, #0x1
str r0, [r2]
.L3684:

ldr r0, [r1]
neg r0, r0
str r0, [r1]

ldr r1, .L3690+0x4
ldr r0, [r1]
add r0, r0, #0x5
str r0, [r1]

b .L3685

.align 2, 0
.L3690:
.word gBattleMoveDamage
.word gBattlescriptCurrInstr
.L3678:

ldr r3, .L3692
ldr r2, [r3]
ldrb r1, [r2, #0x1]
ldrb r0, [r2, #0x2]
lsl r0, r0, #0x8
orr r1, r1, r0
ldrb r0, [r2, #0x3]
lsl r0, r0, #0x10
orr r1, r1, r0
ldrb r0, [r2, #0x4]
lsl r0, r0, #0x18
orr r1, r1, r0
str r1, [r3]
.L3685:

add sp, sp, #0x4
pop {r4, r5, r6, r7}
pop {r0}
bx r0

.align 2, 0
.L3692:
.word gBattlescriptCurrInstr


Ideally, we want the remixed code to fit the original size.
So, one part of remixing functions and script, as in case A,
is to figure points where code can be factored and/or optimised somehow.

Here, by going back to their gen2 power levels,
we recover the advantage of having all cases being about dividing by a power of 2.
(hence applying a number of lsr, or logical shift right, to the hp_to_heal value)
so, it seems fitting to dedicate a register to compute a "averse level/count"
and then, to factor all the cases into a single lsr loop,
decreasing this register up to the floor value for not applying any lsr.

...
Okay, we have our base idea.
Now, we need a way to adjust our averse_level counter,
depending of the period of the day, as well as depending on the move being used.

Fortunately, we find there is a ram address for the currently used move :
gCurrentMove :: 0xEA410202
(or ".word 0x020241EA")
(used in some other move effects, that we can borrow)

Thus, we will store the value stored at gCurrentMove in a register.


Then, we will rely on the "RtcCalcLocalTime" routine at 0x2f 588
and use a "bl RtcCalcLocalTime" instruction to get the hour of the day,
and go from here

As seen in another tutorial of mine, we have to compute the relative offset
from the end of the bl instruction, to the routine we wish to call, to properly write it.


Now, (helpful reminder) mini course :
"bl to relative_offset XYZ UVW" is compiled to (2 complementary instructions) :
YZ FX vw Ft
where uvw = UVW/2 and t=u+8

Also, as words are compiled, in reversed order :
"bl to relative_offset XYZ UVW"
will be written, here, in code to compile through thumb, as ".word 0xFtvwFXYZ"

Nota, too, that these relative offsets are signed,
and must be in range for the instructions to make sense :
(-/+)0x 400 000
with negative values being transcribed as 0x800000-[absolute_value]​


Anyway, as the reference periods used in gen 2 are :
4h <= morning < 10h <= day < 18h <= night .. <4h
and as underflow (i.e. trying to go "under 0") of a value rolls it back, down from FFFF,
I propose to compute hour-4 and then compare it to 14=0xe and to 6
and, accordingly, to set a register to the associated value of the period signature move.
Then, we set our averse count register
to 0x0 if current_move is the period_signature_one,
and to 0x1 if not.

Then, we remix/condense the og function code,
to check for the cases where
>weather is ignored
>weather is regular/neutral
>weather is a kind of sunny (either permanent or temporary)
>by induction, we get the case where the weather is averse
And, depending, we increment the averse_count register
and then jump to computing the healed hp, passing through loops of lsr,
depending on the averse_count register (being decremented as each loop is applied)

...
push {r4, r5, r6, r7, lr}
add sp, sp, #-0x4

ldr r6, .L33M
ldrh r6, [r6]

.word 0xF942F7DB
ldr r4, .L1754
ldrb r4, [r4, #0x2]
sub r4, r4, #0x4
cmp r4, #0xe
bhs .LM1122
cmp r4, #0x6
blo .LM1119
mov r4, #0xeb
b .L890

.LM1119:

mov r4, #0xea
b .L890
.LM1122:

mov r4, #0xec

.L890:

cmp r4, r6
bne .L4444
mov r4, #0x0
b .L4445

.align 2, 0
.L33M:
.word 0x020241EA
.L1754:
.word 0x03005CF8

.L4444:
mov r4, #0x1
.L4445:

mov r6, #0x2

mov r2, #0xd
.L9999:
mov r0, #0x0
str r0, [sp]
mov r0, #0x13
mov r1, #0x0
mov r3, #0x0
.word 0xFA74F7EE
cmp r0, #0x0
bne .L3680 @cond_branch
mov r2, #0x4d
sub r6, #0x1
cmp r6, #0x0
bge .L9999

ldr r3, .L3690+0x4
ldrh r1, [r3]
cmp r1, #0
beq .L3680 @cond_branch

mov r0, #0x60
and r0, r0, r1
cmp r0, #0
bne .L3681 @cond_branch

add r4, #0x1
.L3680:
add r4, #0x1
.L3681:

mov r6, #0x58
ldr r5, .L3686+0x4
ldr r7, .L3686+0x8
ldrb r0, [r5]

ldr r1, .L3686
strb r0, [r1]

mul r0, r0, r6
add r0, r0, r7
ldrh r1, [r0, #0x28]
ldrh r0, [r0, #0x2c]
cmp r1, r0
beq .L3678 @cond_branch

.L6665:
cmp r4, #1
bls .L6666
lsr r0, r0, #0x1
sub r4, #1
b .L6665
.L6666:
ldr r1, .L3690

cmp r0, #0
bne .L3684 @cond_branch

mov r0, #0x1
.L3684:

neg r0, r0
str r0, [r1]

ldr r1, .L3692
ldr r0, [r1]
add r0, r0, #0x5
str r0, [r1]

b .L3685

.align 2, 0
.L3686:
.word 0x0202420C
.word 0x0202420B
.word 0x02024084

.L3690:
.word 0x020241F0
.word 0x020243CC
.L9900:
.word 0x02024214

.L3678:

ldr r3, .L3692
ldr r2, [r3]
ldrb r1, [r2, #0x1]
ldrb r0, [r2, #0x2]
lsl r0, r0, #0x8
orr r1, r1, r0
ldrb r0, [r2, #0x3]
lsl r0, r0, #0x10
orr r1, r1, r0
ldrb r0, [r2, #0x4]
lsl r0, r0, #0x18
orr r1, r1, r0
str r1, [r3]
.L3685:

add sp, sp, #0x4
pop {r4, r5, r6, r7}
pop {r0}
bx r0

.align 2, 0
.L3692:
.word 0x02024214
>
.word 0x02024214 at .L9900:
is an extra and unused .word inserted so that the compiled code both,
fit to og size and so that the tail-end code stay the same as og, "just in case".

> Key for the .words :
.word 0x020241EA gCurrentMove
.word 0x0202420C gBattlerTarget
.word 0x0202420B gBattlerAttacker
.word 0x02024084 gBattleMons
.word 0x020243CC gBattleWeather
.word 0x020241F0 gBattleMoveDamage
.word 0x02024214 gBattlescriptCurrInstr

moved og "bl AbilityBattleEffects" ("EE F7 74 FA" in raw og)
=> .word 0xFA7DF7EE

"RtcCalcLocalTime" being at 0x2f 588
call -> .word 0xF942F7DB

...
which eventually compiles to this raw hexadecimal chunk,
to be pasted over the og code at 0x542f8.
Código:
F0 B5 81 B0 09 4E 36 88 DB F7 42 F9 08 4C A4 78 04 3C 0E 2C 05 D2 06 2C 01 D3 EB 24 02 E0 EA 24 00 E0 EC 24 B4 42 05 D1 00 24 04 E0 EA 41 02 02 F8 5C 00 03 01 24 02 26 0D 22 00 20 00 90 13 20 00 21 00 23 EE F7 74 FA 00 28 0C D1 4D 22 01 3E 00 2E F2 DA 16 4B 19 88 00 29 04 D0 60 20 08 40 00 28 01 D1 01 34 01 34 58 26 0E 4D 0E 4F 28 78 0B 49 08 70 70 43 C0 19 01 8D 80 8D 81 42 1B D0 01 2C 02 D9 40 08 01 3C FA E7 08 49 00 28 00 D1 01 20 40 42 08 60 11 49 08 68 05 30 08 60 18 E0 0C 42 02 02 0B 42 02 02 84 40 02 02 F0 41 02 02 CC 43 02 02 14 42 02 02 08 4B 1A 68 51 78 90 78 00 02 01 43 D0 78 00 04 01 43 10 79 00 06 01 43 19 60 01 B0 F0 BC 01 BC 00 47 00 00 14 42 02 02

...
And, with this, we have, unless I missed a detail :
A fully gen2-true rebuff of the trio of sunlight-powered healing moves.



C) As a little extra treat : re-buffing sandstorm damage to 1/8 of hp, at end of turn

Some parts of battle effects pass by flags and other conditions being processed,
by the main battle engine and then, the game acting accordingly.

To adjust those elements, one has to pinpoint where their scripts compile,
for instance, by compiling significative code snippets around the entry point of interest.

And, doing so, it's possible to pinpoint that the instructions,
around defining the damage to deal, due to sandstorm being active
is at 0x51bb0 :
Código:
98 8D 00 09 08 60
i.e.
Código:
    ldrh    r0, [r3, #0x2c]
    lsr    r0, r0, #0x4
    str    r0, [r1]
again, to go from 1/16 to 1/8, one has only to reduce the number of lsr by 1

Hence, the update can be made by replacing the central instruction to get :
Código:
   ldrh    r0, [r3, #0x2c]
    lsr    r0, r0, #0x3
    str    r0, [r1]
-> at 0x51bb0 :
Código:
98 8D C0 08 08 60
Also, note that the damage induced by sandstorm is decoupled from the damage by hail.
(The code for calculating hail damage is similar and a bit after this one for sandstorm)
Thus, reverting sandstorm to its gen2 power does not alter hail at all.​


And, this was a, hopefully insightful introduction on and applications to,
customising move effects to further extent,
with the help of the reference decompilation projects results.


Happy modding and see you again at a latter "unexpected drop" time, probably.
 
Arriba