Serial interrupt ACE
0x1500 control code arbitrary code execution (Crystal) | Cart-swap arbitrary code execution | Generation I custom map script pointer | Generation I invalid meta-map scripts | Generation I item ("8F", "ws m", "-g m", "5かい", "てへ" etc.) | Generation I move ("-", "TM42") | Generation I Trainer escape glitch text boxes | Generation II bad clone | Generation II Burned Tower Silver | Japanese Crystal Pokémon Communication Center SRAM glitches | Coin Case glitch | Generation II glitch Pokédex sortings | Pikachu off-screen glitch ACE | OAM DMA hijacking | Serial interrupt ACE | Pikachu glitch emote | Generation III glitch Pokémon summary | Generation III glitch move animation) | Remote code execution | TM/HMs outside of the TM/HM pocket | Type 0xFF mail arbitrary code execution (Japanese Crystal) | ZZAZZ glitch Trainer FC
List of arbitrary code execution programs
Serial interrupt ACE (Serial interrupt Arbitrary Code Execution), also known as Invalid printer opcode ACE (Invalid printer opcode Arbitrary Code Execution), is a glitch in Pokémon Yellow, as well as Pokémon Gold, Silver and Crystal, that allows arbitrary code to be executed by the serial interrupt handler, which is responsible for handling communications via the Game Boy Link Cable, and also responsible for controlling communication with the Game Boy Printer peripheral.
The glitch is set up by modifying the value of the variable wPrinterOpcode (address $D49A in Pokémon Yellow, $C1D4 in Pokémon Gold and Silver, $C2D5 in Pokémon Crystal) to an invalid value, modifying wPrinterConnectionOpen ($D499 in Pokémon Yellow, $C1D3 in Pokémon Gold and Silver, $C2D4 in Pokémon Crystal) to 0x01, and then using arbitrary code execution to initiate a transfer through the serial port. The glitch can then be triggered as many times as needed by the target code by simply initiating another serial transfer before returning.
This glitch can be useful for (though not necessarily being needed for) implementing code that utilizes the Game Boy's serial communication feature, or as a substitute to OAM DMA hijacking for code that needs to run more frequently, since while the OAM DMA routine only gets called about 60 times per second, the serial interrupt in Pokémon can be invoked up to almost 950 times per second using this technique (or double that if playing in a Game Boy Color with the double CPU speed setting selected).
Mechanism behind the glitch
In Pokémon Red and Blue, the Game Boy Link Cable feature is used to allow for linking up two instances of the game together. This allows for two players to battle against each other or trade Pokémon. The Game Boy's built-in serial interface was used to implement this. In Pokémon Yellow, as well as in Pokémon Gold, Silver and Crystal, the feature was extended to allow the game to be linked up to the Game Boy Printer peripheral, which allowed the player to print out Pokédex entries, among other things.
When the serial interrupt is triggered in Pokémon Yellow or in Pokémon Gold, Silver and Crystal, if bit 0 of wPrinterConnectionOpen is set to 1, the PrinterSerial/PrinterReceive routine will be executed. This routine is responsible for implementing the data packet protocol used by the Game Boy Printer, and it uses the value in the wPrinterOpcode variable as an index into a jump table. However, there is no form of range checking implemented for this jump table, so invalid opcodes will cause the routine to jump to invalid addresses. By carefully choosing the value for the wPrinterOpcode variable, arbitrary code execution can be attained.
Once execution is transferred to the arbitrary code payload, the next serial interrupt can be scheduled at any point by initiating a new serial transfer, which completes the cycle and makes the execution repeat automatically many times per second.
As long as an invalid value is chosen for wPrinterOpcode, its value shouldn't change. Its value will only be overwritten if the player attempts to use one of the game's printer functions, like the one in the Pokédex menu. Furthermore, exclusively in Pokémon Yellow, the memory values written to wPrinterOpcode and wPrinterConnectionOpen are persistent and stored on/loaded from the save file.
Some good choices for invalid wPrinterOpcode values, along with their target locations, are:
This article is incomplete. Please feel free to add any missing information about the subject. Reason: More options for values for wPrinterOpcode for Generation II games |
wPrinterOpcode
($D49A) value |
Target
location |
Description |
---|---|---|
0x42 | $FA73 | Echo RAM of wDayCareMonDefenseExp |
0x44 | $FA5F | Echo RAM of wDayCareMonHP |
0x83 | $FAC9 | Echo RAM of wBoxMon2AttackExp |
0x9B | $FCCD | Echo RAM of wBoxMon18CatchRate |
0xFC | $FFF0 | Unused HRAM (3 bytes) |
wPrinterOpcode
($C1D4) value |
Target
location |
Description |
---|---|---|
0x24 | $DBCD | last byte of wPartyMonNickname |
0x50 | $FA43 | Echo RAM of wPartyMon1PP + 2 |
wPrinterOpcode
($C2D5) value |
Target
location |
Description |
---|---|---|
0x7A | $E001 | Echo RAM of wStackBottom + 1 |
Setup
Invalid printer opcode ACE can be bootstrapped by any other arbitrary code execution technique in Pokémon Yellow or Pokémon Gold, Silver and Crystal, but using glitch items (for Pokémon Yellow) or wrong pocket TM/HM ACE (for Pokémon Gold, Silver and Crystal) is by far the easiest way. See Arbitrary code execution in Generation I and Arbitrary code execution in Generation II for other ways of executing arbitrary code in Pokémon.
Requirements
- A means to perform arbitrary code execution
- In Pokémon Yellow, for example, a glitch item that can execute arbitrary code (e.g. ws m or 4F)
- In Pokémon Gold, Silver and Crystal, for example, a wrong pocket TM/HM ACE setup
- A payload to be executed
- To ensure the payload gets retriggered in a loop, the payload code must initiate a serial transfer by, at minimum, writing any byte to $FF01 and writing 0x81 to $FF02.
- The payload must be written to RAM starting at the target location for the chosen wPrinterOpcode value
- For example, if playing on Pokémon Yellow and the value chosen for wPrinterOpcode was 0x44, the payload must be written starting at $DA5F since the target location is at $FA5F (which is the Echo RAM location of $DA5F)
- A simple bootstrap ACE script to kickstart the exploit will also be needed
- This bootstrap needs to trigger a serial transfer; the simplest way to do this is to simply jump to the same payload which will be executed by the exploit.
Example setup
The following setup will create a mild screen distortion/instability effect. In Pokémon Yellow, the effect only works in the overworld when no menus or text boxes are open; in Pokémon Gold, Silver and Crystal the effect works almost everywhere.
For the sake of simplicity, the following values were chosen for wPrinterOpcode depending on the game version, but other applicable values can be chosen as desired:
- Pokémon Yellow: wPrinterOpcode = 0x9B; target location = $FCCD
- Pokémon Gold and Silver: wPrinterOpcode = 0x24; target location = $DBCD
- Pokémon Crystal: wPrinterOpcode = 0x7A; target location = $E001
Bootstrap script (Pokémon Yellow): C3 CD DC Bootstrap script (Pokémon Gold and Silver): C3 CD DB Bootstrap script (Pokémon Crystal): C3 01 C0 Main code: 3E 81 E0 01 E0 02 F0 43 0E 01 A9 E0 43 C9
Disassembly of the payload |
---|
ld a, $81 ; 3E 81 ldh [rSB], a ; E0 01 ldh [rSC], a ; E0 02 trigger serial transfer ldh a, [rSCX] ; F0 43 read X scroll ld c, $01 ; 0E 01 xor a, c ; A9 toggle X scroll LSB ldh [rSCX], a ; E0 43 write it back ret ; C9 |
To prepare the setup:
- Write the main code to the target location in memory
- Write the bootstrap where it'll be executed by whatever ACE method will be used to trigger it
- Write the proper value (according to which game version you're playing) to wPrinterOpcode (address $D49A in Pokémon Yellow, $C1D4 in Pokémon Gold and Silver, $C2D5 in Pokémon Crystal)
- Write 0x01 to wPrinterConnectionOpen (address $D499 in Pokémon Yellow, $C1D3 in Pokémon Gold and Silver, $C2D4 in Pokémon Crystal).
To start the code, simply run the bootstrap script via your ACE entry point of choice.
This setup has no predefined stop condition. To stop the effect, write 0xC9 to the beginning of the main code. This will cut the cycle, preventing a new serial interrupt from being scheduled and the code from being executed again.
Technical details
The serial interface
The serial interrupt in the Game Boy is fired whenever a data transfer is completed, which means 8 bits have been transferred (sent and received simultaneously) via the serial port.
Transfers can be made either using the internal clock or using an external clock. When using the internal clock, bytes are sent at a rate of 8192 bits per second (16384 if using the GBC's double speed mode). Using an external clock requires that the Game Boy is connected to another device and relies on the clock speed of that device.
When transferring using the internal clock, even if nothing is connected to the link port, data transfer will occur normally. The data written to address $FF01 will be sent out via the serial port, and if no device is connected to the link port, the data read in via the serial port will simply be comprised of all '1' bits (0xFF). As such, the serial interrupt can be abused as a fixed-length timer interrupt with a period of 1024 clock cycles (M-cycles).
CPU usage
However, this technique of abusing Pokémon's ROM-based serial routine to indirectly call an arbitrary RAM routine imposes extra overhead. In Pokémon Yellow, for example, there are 73 clock cycles (M-cycles) of added overhead from the game's serial routines before execution is transferred to the ACE payload, plus it takes 5 cycles for an interrupt to be serviced, 4 cycles for the interrupt vector to jump to the interrupt routine in ROM, and it takes at least 8 cycles to initialize a serial transfer at the beginning of the payload. This limits the maximum rate at which the serial interrupt can be called to at most 941.2 times per second.
It's also important to note that running code with such a high frequency can have a detrimental effect to the game's performance, depending on the code being run. A simple ACE routine that only retriggers the serial interrupt and does nothing else will already consume roughly 10% of CPU time due to all of the calling overhead from the ROM serial interrupt handler. More complex routines, especially ones that perform long loops, may easily draw all of the CPU time away from the game.
Good practices
For routines that need to do a lot of work, it's a good idea to not run them as often as possible. This can be accomplished by the use of a divider counter: a variable in memory is reset to a value (the divisor). Every time the subroutine is called, it decrements the value of a counter, and only when that value reaches zero it resets it again and executes the routine; otherwise it returns immediately to save CPU time. This essentially divides the rate that the routine runs at by the value that the counter is reset to.
Long routines must also monitor the LCD status registers and be careful not to interfere with the VBlank interrupt. In the Game Boy, all interrupts have equal priority. If the VBlank period begins while the payload is running, and the payload takes too long to finish, the VBlank interrupt will be delayed until the payload finishes and the serial interrupt handler returns. This can cause timing-sensitive functions inside the VBlank routine, such as the OAM DMA transfer, row/column transfer and/or background map transfer functions, to miss the VBlank period and attempt to write to the PPU during active screen time, leading to graphical glitches.
Attribution
- Invalid printer opcode ACE was originally discovered by Evie in 2021.[1]
- This article was originally written by Kagamiin, who discovered that a serial interrupt could be used to trigger invalid printer opcode ACE and create an infinite interrupt chain.
- TimoVM coined the name "Serial Interrupt ACE" for this technique.