Skip to content

Save space and improve performance with RST vectors

ElectroDeoxys edited this page Jul 9, 2024 · 1 revision

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.

Contents

  1. Define a vector for GetTurnDuelistVariable
  2. Turn the RST instruction into an expressive macro
  3. Replace all occurences of GetTurnDuelistVariable
  4. Extending this to SwapTurn

1. Define a vector for GetTurnDuelistVariable

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.

2. Turn the RST instruction into an expressive macro

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
+

3. Replace all occurences of GetTurnDuelistVariable

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).

4. Extending this to SwapTurn

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.)