-
Notifications
You must be signed in to change notification settings - Fork 92
Save space and improve performance with RST vectors
The game uses some RST vectors for common functionality, like FarCall
and Bank1Call
, but vectors as a whole are woefully under-utilized. We can leverage this feature in the GameBoy's arsenal to save some bytes in ROM and slightly increase performance all-around.
- Define a vector for
GetTurnDuelistVariable
- Turn the RST instruction into an expressive macro
- Replace all occurences of
GetTurnDuelistVariable
- Extending this to
SwapTurn
In Pokémon Trading Card Game 2, the devs developed a vector used solely to get the turn duelist's variable, which is a function called many times throughout the code. However, instead of using an RST vector to jump to GetTurnDuelistVariable
, we can insert the code directly in the header. (For this we will use rst08
, but the choice is rather arbitrary.) Edit src/home.asm:
SECTION "rst00", ROM0
ret
ds 7
SECTION "rst08", ROM0
+; returns [[hWhoseTurn] << 8 + a] in a and in [hl]
+; i.e. duelvar a of the player whose turn it is
+GetTurnDuelistVariable::
+ ld l, a
+ ldh a, [hWhoseTurn]
+ ld h, a
+ ld a, [hl]
ret
- ds 7
+
+ ds 2
SECTION "rst10", ROM0
ret
ds 7
We can then remove this routine from src/home/duel.asm:
CountCardIDInLocation::
pop bc
ret
-; returns [[hWhoseTurn] << 8 + a] in a and in [hl]
-; i.e. duelvar a of the player whose turn it is
-GetTurnDuelistVariable::
- ld l, a
- ldh a, [hWhoseTurn]
- ld h, a
- ld a, [hl]
- ret
-
; returns [([hWhoseTurn] ^ $1) << 8 + a] in a and in [hl]
; i.e. duelvar a of the player whose turn it is not
GetNonTurnDuelistVariable::
Now, any of the turn duelist's variables can be retrieved by using rst $08
, given that the duelvar is loaded into register a
.
Right now, rst $08
or even rst GetTurnDuelistVariable
is not terribly descriptive, so we'll wrap this inside a macro, get_turn_duelist_var
. Edit src/macros/code.asm:
MACRO retbc
push bc
ret
ENDM
+
+MACRO get_turn_duelist_var
+ rst GetTurnDuelistVariable
+ENDM
+
As the final step here, we will replace all calls to GetTurnDuelistVariable
with the newly created macro. This can be done easily in your favorite IDE with the "Find All" & "Replace" functionality. Optionally, you can run the following shell command inside the repository's root directory:
find . -name "*.asm" -exec sed -i 's/call GetTurnDuelistVariable/get_turn_duelist_var/' {} \;
So after all that, if you build your ROM you'll find that it has a considerable decrease in size. For example, in the base game this change saves over 1.1 kilobytes! Notably, the free space in the Home bank goes from 116 to 272 bytes. Not only that, but since RST vectors are very efficient instructions for the processing unit, there is a 2 cycle improvement in every invocation (from 6 cycles in a call
instruction to just 4 cycles in a rst
instruction).
Another good candidate for turning a home call into an RST function is the ubiquitous SwapTurn
routine. However, the whole routine doesn't fit neatly inside the 8-byte sections assigned to the RST vectors. This isn't a problem if you're willing to sacrifice a portion of the next RST vector in ROM, as seen here:
SECTION "rst28", ROM0
jp FarCall
ds 5
SECTION "rst30", ROM0
- ret
- ds 7
+; returns [hWhoseTurn] <-- ([hWhoseTurn] ^ $1)
+; As a side effect, this also returns a duelist variable in a similar manner to
+; GetNonTurnDuelistVariable, but this function appears to be
+; only called to swap the turn value.
+SwapTurn::
+ push af
+ push hl
+ call GetNonTurnDuelistVariable
+ ld a, h
+ ldh [hWhoseTurn], a
SECTION "rst38", ROM0
+ pop hl
+ pop af
ret
- ds 7
+ ds 5
; interrupts
SECTION "vblank", ROM0
In this case, the routine fills the entirety of the rst $30
section and some part of rst $38
. This is actually fine and just means that rst $38
is no longer usable. If you do the same treatment as GetTurnDuelistVariable
, you'll find that this change saves almost 1 more kilobyte from ROM. (And of course, entails the same performance boost.)