This is a method to perform ACE on each game frame - provided it uses OAM DMA. I guess all games use it, so, yeah.
It also has the bonus of being cartswap-friendly.
Need no more to be convinced ? Then here is the thing !
This works only on Pokémon Red or Blue.
You need to place the following hex data at the following locations.
Use your favorite emulator's (or BGB if you intend to do cartswap) memory viewer to do that.
Alternatively, you can use an in-game memory editor such as offgao's (see this thread
Also, make sure all data at FF80 is not executed until it is complete. It's very sensitive data executed on each frame, and you don't wanna crash.
One way to do it is to write a $C9 at $FF80 first, and write the $CD last.
These two pieces of data don't persist after saving and resetting.
At DF00 : (WRITE FIRST !!)
At FF80 : (MAKE SURE YOU HAVE WRITTEN DF00 BEFORE THIS !!)
Once both pieces of code are in place, go into any house, exit it and o surprise, Hall of Fame !
Tech details are below.
If you want to try the cartswap-based funky badass-showoff version (Pokémon Blue "cartswap wrong warp%"), then set up DF00 and FF80 as above, and then this.
At D163 :
At D31D :
At D53A :
Now, open your bag (surprise, there's 8F in there), use 8F. The game will then freeze, so use "Load ROM without reset" on BGB and load Pokémon Blue (or Red, works fine too).
Press any direction on the D-Pad, and the game will start. You can then choose to start a new game, and you will soon find out your house leads directly to the Hall of Fame ! Nice.
I attached a BGB save state to be tried on Pokémon Red that has all set up. This was hex edited, but is doable legitimately. You just have to load it in BGB, then use the above method or just close the menus and walk out of your house.
On each LCD frame, the game has to run a small routine in RAM to transfer sprites from a temporary buffer to the location the GB uses. However, we hijack this routine to run custom code.
The trick is simple : hijack usual code flow, run our code, and make sure everything else goes as intended.
At $FF80 is this code :
ld a, $C3
ldh [$FF46], a
which we overwrite with
ld [hl], a
Pokémon R/B/Y allocate $DF00 to $DFFF to be stack space. However, during usual play, I never experienced the game using more than ~100 bytes at once. This leaves us with more than 128 bytes for code storage !
ld a, $76 ; Hall of Fame map ID
ld [$D365], a ; Door mats exit
ld hl, $FF46 ; OAM DMA
ld a, $C3
This code essentially sets all door mats exits to warp Red to the Hall of Fame. Pretty neato, huh ?
This is a half gameshark emulator, because it can't set a value mid-frame. However, it is cartswap-friendly, using for example the following setup :
The data at $D163 and $D31D set up 8F properly, I'll just skip explaining them.
The code at $D53B is a bigger deal.
PartialInit:: ; $D53B
ld hl, Init
; Real init function. We copy it to RAM for patching.
ld de, $DF0B
; Routine copy location.
ld bc, $0049
; How large the target code is : 73 bytes !
; We will append a jump to end it later.
ld a, $10
ldh [$FFFF], a
; Only enable joypad interrupt.
; It should pop instantly, but it doesn't crash, phew !
; a = $20
ldh [$FF00], a
; Select Dpad
; a = $1F
; Patch WRAM clear size to not clear $DF00 onwards.
ld [$DF0B+$2B], a
; Write part of the "return" address.
ld [$DF0B+$3F], a
ld a, $8A
; Patch HRAM clear code to not clear patched DMA code.
ld [$DF0B+$38], a
; It will clear past HRAM, but no problem.
ld a, $C3
; JP instruction.
ld [$DF0B+$3D], a
ld a, $A1
ld [$DF0B+$3E], a
; Patch DMA writing with returning to ROM code.
; Jump to patched init. Cartswap is complete.