Move Expansion Explained - haven1433/HexManiacAdvance GitHub Wiki

From Scratch: Move Expansion

This document explains in detail how Move Expansion works in HexManiacAdvance. It attempts to explain all the important details of the original implementation, and then explain what changes are made.

Prerequisits

This section talks about the parts of the game associated with the original implementation, to provide proper context so that the changes make sense.

Move Limiter

There are several places in each ROM that refer to the number of moves, generally one thing if this is a valid move and another thing if it’s not. Such code can be found by searching for the three thumb instructions

mov   r0, #177
lsl   r0, r0, #1
cmp   r1, r0

which is the 6 hex bytes B1 20 40 00 81 42. FireRed locations: 0D75FC, 0D7E96, 0D7EAE Emerald locations: 0D8F0C, 0DE83A, 14E504, 14EF4A, 14EF62 (other roms’ locations can be found in src\HexManiac.Core\ViewModels\QuickEditItems\MakeMovesExpandable.cs) The next instruction is always either a bls instruction or a bhi instruction, depending on how the code branching works.

The PP Pointer

Not only are their pointers to the movedata table, but there are also pointers to the PP field within the table. Each game has 5 such pointers. Since the code is looking for the PP of a given move, the code just before the pointer is going to do a multiply by 12, which is the width of the movedata table. The code in all the games looks like this:

lsl   r2, r0, #1  @ set r2 =  r0*2
add   r2, r2, r0  @ set r2 += r0 (r2 is now 3*r0)
lsl   r2, r2, #2  @ set r2 *= 4  (r2 is now 12*r0)

Note that these three commands appear more places than just the 5 uses of the PP field pointer. But each PP field pointer has these three instructions shortly before it.

Level-Up Move Format

By default, the game stores each pokemon's level up moves as a number of level-move pairs, terminated by FFFF. Since there are less than 128 possible levels and less than 512 possible moves, each level-move pair is stored in a 16 bit number, with the top 7 bits used for the level while the bottom 9 bits are used for the move. This means you can't have more than 512 moves in the game. Likewise, there's code that exits out of the loop if no terminator is found. This limits you to 20 level-up moves per pokemon.

Move Effects

Each move has a move effect, which decides things like what status condition it deals, any special behaviors like taking 2 turns instead of one, and basically handling whatever makes that move special. Many simple moves just use the "Normal" effect, but special effects allow moves to do interesting things like transforming the user.

By default, the effect is stored as a single byte. There are already 214 effects in the game, meaning that only about 40 custom effects can be added.

Changes

Edit code to remove the move limiter

After finding matches to B1 20 40 00 81 42 to find where the limiter applies, we can change all the lsl commands to lsl r0, r0, #9, making only a single-byte change per location. So the limiter is now 90,624 moves instead of 354.

Updating the level-up code and data

By default, 2 bytes are used per level-move pair. If we double that to 4 bytes, that gives us 2 bytes for the level and bytes for the move: plenty of space. We can still load the 4 bytes into a single register, and can still do bit-shifting and masking to get the level or the move. There are 5 functions that use the level-up moves data (+3 more in Emerald) that need to be edited to use this new format. While we're at it, we can safely increase the moveset size from 20 to 25. See expand_levelup_moves_code.hma for a full listing of the new assembly routines, which are drop-in-place replacements for the original routines: the routines are the exact same size as the originals.

Updating the data is easier technically, but requires additional space. Since each level-move pair now takes 4 bytes instead of 2, all the existing moves need to be repointed. The original table that points to each individual pokemon's moves can stay in the same place, but all the level-move pairs need to be repointed. HexManiacAdvance does this repointing automatically and guarantees that it will only use free space.

Updating Move Effects from 1 byte to 2 bytes

The move stats table uses 12 bytes per move: effect, power, type, accuracy, pp, effect accuracy, target type, priority, 'info', and then 3 unused bytes. If we shift all the fields from power to info over by one, that gives us 2 bytes for the effect.

The move stats table is used many times, and changing the code to move all these fields requires a lot of changes. So HMA uses the following algorithm to find all the locations that need to be edited:

  • Start at a pointer to the move stats table.
  • Search backwards until you find the start of the routine (a push instruction)
  • Search forwards until you find code that loads the table pointer
  • Search forward until you find an add instruction that uses that register (presumable this adds an offset to get from the start of the table to the start of a specific move)
  • Search forward until you find a ldrb instruction that loads a specific offset within that move (presumably this loads a specific one-byte field of that table entry)

Those locations can then have their load locations changed, adding 1 for all fields from power through info so that there's an extra byte available for effect. A similar process can be used to find everywhere that a move effect is loaded, and to replace the ldrb instructions with ldrh.

After updating the code, the utility also goes through the move stats table and pushes all the fields over one byte, leaving only 2 bytes free at the end and allowing 2 bytes at the start for the effect value.

Update PP Pointer offset

HexManiacAdvance already stores offset-pointers for working with the PP Pointers. But since the PP field moved, these pointers need to update as well.