The beauties (and crashes) of CartSwap
CartSwap (Cartridge Swapping) is a technique that allows ACE in any Game Boy game, even before the game has actually booted up! Example applications include :
- Spoofing consoles (Pokémon Crystal can run on the Gray Brick!)
- Speedrunning games quicker than a TAS
- Gaining ACE on games that are otherwise ACE-less
ACE means Arbitrary Code Execution ; basically, run arbitrary code in the context of, for example, a game.
If your curiosity has been piqued, then it's time to get down to how it's actually done. Let's-a-go!
CartSwap's major requirement is to be able to exchange cartridges while the console is turned on.
CartSwap on console:
- DMG (Original Game Boy). This one requires either very precise manipulation of the ON/OFF switch, or to cut the piece of plastic that locks carts in place.
- Pocket Game Boy (has been tested, and should work)
- Game Boy Light (untested, but it should work)
- Game Boy Color (tests point this model as the best)
- Super Game Boy (tested and recorded by Cryo)
CartSwap will NOT work on any Game Boy Advance because popping the cartridge off will revert the console in GBA mode instead of GBC mode. The first effect is to black out the screen, which isn't reverted when putting a new GBC cartridge. (For some reason music still works)
Luckily for us, it is possible to do CartSwap on emulation! This allows for proper recording and debugging of cartswap'd programs.
CartSwap on emulation Requirements :
- Windows (anyone saying "XP" or "Vista" gets punched in the face) or Wine if you ascended to Linux. Mac users can also use Wine.
- The BGB emulator. BGB has a nifty feature that allows emulation of CartSwap (and we didn't even ask for it!) : "Load ROM without reset". It exactly "removes" the current ROM, then "pops" a new one in.
The General Principle
CartSwap is performed by :
- Having the CPU run some code in RAM
- Swapping cartridges in the meantime
Step 1 is fairly easy, all you need is a Game Boy game an ACE exploit exists on. By nature, ACE exploits run code in RAM, so this should be easy. (If anyone is interested, we know a bit more than 10 different ACE exploits in the 1st generation of Pokémon games. Enjoy quality programming :D)
Step 2 is more complicated. We need the CPU to wait while we swap cartridges. Here are details about this part :
ROM Is Dead
While the cartridge is removed, accessing any part of it (whether it be ROM or SRAM) will return a $FF byte. This may be a way of checking whether the cartridge is out or not, but most importantly, execution of ROM or SRAM is prohibited while the cartridge is out!!
Tech details: Running from ROM or SRAM will have the CPU fetch an instruction byte. The read will return $FF. $FF translates into
rst $38, which essentially does
call $0038. The CPU will push the return address and jump to
0038... which is in ROM! Not only does the cycle repeat, creating an infinite loop (until a new cartridge is in place, I agree, but it won't matter). However, on each iteration of the loop, the stack will grow by 2 bytes. The loop is very fast, and can corrupt all the address space in approximately 1 second (see the "00 39" crash in Pokémon R/B/Y, it's basically that). And since your user code is in RAM... Even if you managed to not corrupt your code, when a cartridge is inserted, execution will attempt to return multiple times to
0039. Good luck on that not crashing.
Interrupts Must Die
First of all, it is required to disable interrupts, because interrupt handler entry points are in ROM.
di should be the first instruction you execute in your RAM code.
The Mystery Of Electronics
Even if you make the perfect RAM code, you can end up crashing. For some reason, when pulling off the cartridge, you may crash anyways. Here are two examples that Cryo recorded on his SGB. The latter is more interesting because the color means the SNES freaked out, not only the Game Boy inside the SGB.
It has been established that this depends on how the cartridge is pulled. There seem to be "perfect angles", but there are no precise indications for them. Enjoy trial and error.
Distracting The CPU, But How?
Now, let's discuss various ways of making the CPU "hang" while we swap cartridges.
Ask The Cart
By reading from the cartridge, it is possible to determine whether one is present or not. A good byte to read is one of the Nintendo logo bytes, since these values are known. Except for some pirate games that wanted to pwn Nintendo.
; Code entry point di ld hl, $0104 ; Somewhere in the Nintendo logo. .popCart ld a, [hl] cp $CE ; Normal logo value. jr z, .popCart ; Cartridge hasn't been removed yet. .swapCarts ld a, [hl] cp $CE jr nz, .swapCarts ; Cartridge hasn't been inserted yet. ; HACK THE PLANET
This version should work on all official cartridges, and may not on pirate games that feature their own logo.
Alternatively, you can wait a fixed amount of time. Somewhere between 5 and 9 seconds should be good.
; Entry point di ld c, ?? ; Adjust time with this value, $20 should be enough. ; The higher, the longer this code will wait. .extLoop ld de, 0 .innerLoop ; You may pad this loop with NOPs to make it last longer. dec de ld a, d or e jr nz, .innerLoop dec c jr nz, .extLoop ; Pwned. Enjoy.
The Chef's Choice: Ask The User
This one is my favorite, because it's clever AND takes a minimal amount of space. The only requirement to make it work: the user must not be pressing the D-Pad when launching this.
; Entry point ei ; (if interrupts may be disabled) ; No "di" this time, because we **need** interrupts! ld a, $10 ldh [IE], a ; Enable joypad interrupt only rla ; a = $20 ldh [JOYP], a ; Select direction keys halt ; NOP not required because interrupts are enabled. ; Done. Already? Yep.
The Pin Trouble
For some reason, sometimes there setups don't work and crash anyways. Cryo found out that waiting for a VBlank interrupt "stabilizes" the state. Append this to any "hanger" code:
ei ld a, $01 ldh [IE], a halt ; Everything should be alright,
Or, if you don't want to run the new cartridge's VBlank handler:
di xor a ldh [IF], a .stabilize ldh a, [IF] rra jr nc, .stabilize ; , now let's get rolling.
CartSwap On Other Consoles
The concept of CartSwap is much more general than that. It's possible, in theory, with any device that doesn't have a protection against removing cartridges, USB sticks, or whatever.
MrCheeze has reported that this is in theory impossible on the NES or SNES because of the CIC lockout chip. I'm wondering if the adapters that exist to run unlicensed games on these consoles would bypass this.
This is in theory impossible on the N64 because it checks if the cartridge is still here (I'm talking about the N64 Transfer Paks). N64 ACE is very young, and it looks like the console has protections that disable CartSwap. CartSwap isn't possible on most disc-based consoles either.
Nobody tried cartswapping the GBA yet, as far as I know. Since multiboot is a feature on this console, it's not very useful. But it's possible, if you want to try!
CartSwap PoC by furrtek (uses a custom cartridge)
MrCheeze - Made the video that started it all, tested some consoles, and SNES idea
TheZZAZZGlitch - Had great ideas, and made the first "cartswap%" speedrun ever
Cryo - Testing this on the SGB, making an impressive "cartswap%" speedrun
Torchickens / ChickasaurusGL - Original method for "Pokémon TCG" debug menu, recording it for me