From Terranigma Wiki
Jump to navigation Jump to search

Recently the ACE was leveraged into a Credits-Warp, this page is intended to collect information about it.

Proof of Concept

this video shows a proof of concept execution.

Note that the route was only tested on the German version, the principles apply to all PAL versions, minor adjustments might however be needed.

So far no ACE for the Japanese version has been found, so this whole page does not apply to NTSC

Also the validity of this on original cartridges is still unclear, it should however work on all emulators, sd2snes etc. (SRAM needs to be initialized to 0xFF for this to work reliably)

To reproduce the video, :

The savegame in the first slot needs to be manipulated in the following ways:

- Saved at Indus River after entering Indus from the world map (no death warping and saving without re-entering)
- Filename of the saved game has to be "ZZQRZ"
- When saving the game, the Filename in the third slot has to be "jhKpy"
- Experience total has to be any of 9182, 9382, 9582 9782 or 9982
- Exactly the right chests and Magirocks need to be collected (check the video or lsnes movie file for exactly which)

When the savegame is prepared, the chicken glitch has to be performed, and the ACE triggered with specific buttons pressed in order to execute the payload embedded in the savegame:

- go to Shadowkeeper with 2 fire rings                                                                                                                                     
- make sure your xp is at a value where killing shadow keepers final phase will give you a level up (<90 or 170-269 before entering the fight should be the relevant ranges)
- enter the chest with 0 hp, equip the magic chest, optionally remap y to "use item" and cast a fire ring that kills Shadowkeeper to achieve a "double kill"
- the game will glitch out at when souls reenter the body. you now have to use the magic chest while pressing a specific set of buttons on both controllers: P1: Y select up right L  P2: A L R  
- to achieve that:
  - press select to enter the chest
  - if not done yet, remap y to "use item"                                                                                                                          
  - press select to leave the chest, keeping select pressed
  - during the transition out of the chest press at least L on controller 1 and keep it pressed                                                                       
  - press and keep pressed all buttons except Y. order, timing etc. does not matter here
  - finally press y to use the magic-chest and trigger the code-execution                                                                                           

If everything was done correctly the screen should turn black and the game freeze.

After resetting the console, a savegame should be seen which was saved in the glitched state (slot 2 atm but this may change with route development).

Now just load the glitched savegame, walk to Crysta, go to bed, GG!

Technical Details

When activating the magic ring menu during the chicken glitch the execution of gamecode will reach the autojoypad input registers ($4218 to $421f),
depending on input device configuration (multitap / no multitap) either $4218 (no multitap, 2 controllers) or $4219 (with multitap) will be executed as an opcode.

Withn two controllers plugged in without a multitap, when the bug is triggered with the keys pressed as outlined above, code execution will eventually reach the SRAM like this:

276000 sbc $ffffff,x [004219] A:f9f9 X:421a Y:6c00 S:01e2 D:80f3 DB:81 NvmxdizC

Here it gets a little weird, because the game never uses the first $100 bytes of SRAM, so the state of those bytes is technically undefined.
However, as far as i know all the emulators, as well as Mister FPGA and SD2SNES initialize SRAM to 0xFF - which is what the payload expects.
I did not find any information about initalization of SRAM on actual cartridges and i lack the hardware to check for myself
I expect however, that the SRAM on physical cartridges is actually initialized to random values which will render this route broken or at least inconsistent across cartridges.
Additional testing and/or research will be required to figure out if this route actually works on real, un-tampered cartridges.

The savefile manipulations outlined above were made in a way so that the following parts do the following things:

save-location and bonepin location: 
     nothing useful, just dont crash, or jump away,
     continuing execution into the following savefile names
savefile names: 
     set the direct-page register to a value that allows adressing a specific memory location with a single byte in the later stages
     jump over uncontrollable memory to total XP
total XP:
     jump over parts of the savefile to reach the chestflags safely
chest flags:
     set event flags indicating we killed dark gaia
     load constant into the accumulator
     jump over some bytes towards magirock flags
magirock flags:
     jump into subroutine that saves the game into the slot specified by the accumulator

detailed description of execution of the savegame

when SRAM is initalized to 0xFF, execution will reach the beginning of the Savegame in Slot 1 like this:

276100 sta $0103     [810103] A:df8e X:421a Y:6c00 S:01e2 D:80f3 DB:81 Nvmxdizc

the relevant details are that bits 9 and 10 in the accumulator are set, that the direct page register is set to $80f3, and Y is set to $6c00

execution proceeds through the first 16 bytes, which are determined by save-location and last overworld-map position (possibly bonepin-marker)

8d 03 01 00 VV 01 WW 01 02 
00 XX 00 YY 04 ZZ 06

VV and WW are variable depending on pixel position when saving, but the values are irrelevant to us
XX YY and ZZ are variable depending on how you enter indus river, but the values are irrelevant to us

disassembles to:

sta $0103
brk #$VV
ora ($WW,x)
ora ($02,x)
brk #$XX
brk #$YY
tsb $ZZ
asl $3a  -- operand $3a is actually the first byte of the filename, also it does not matter what the first character is

none of the executed instructions so far are relevant to the following payload

name of our save (names are always terminated with d4)

(3a) 3a 31 32 3a d4

disassembles to:

sta $0103
dec a           # does nothing useful - any 1-byte instruction should be fine here
and ($32),y     # this will AND the accumulator with #$0600 and thereby set it to #$0600
dec a           # decrement accumulator to #$05ff
pei ($00)       # the terminating d4, basically a noop

some null bytes between the savefile names

(00) 00 00 00 00 00

disassembles to:

brk #$00
brk #$00
brk #$4a        # operand here is again the first byte of the name, and can be anything

all basically noops but again costing us the first byte of the name

name of save in 3rd slot

(4a) 48 2b 50 59 d4

disassembles to:

pha             # push accumulator (#$05ff)
pld             # pull direct page register -> D: #$05ff
                # we have now set the direct page register to #$05ff, enabling following payload to adress relevant event flags with a single byte
bvc IP+#$59     # we now jump forward as fas as possible to skip a lot of stuff that we cannot properly manipulate (e.g. frame counter)

execution lands in a bunch of $00 bytes that work out to noops once again
then execution reaches experience total which we manipulated in order to jump over even more junk

82 97          

the second byte here can be 91,93,95,97 or 99 because we again will land in a bunch of $00 bytes and dint have to hit any specific byte, just the alignment is important

disassembles to:

brl IP+#$97

after a couple of $00 bytes, we hit one stray $07

disassembles to:

ora [$00]       

but this works out to be a noop for us as well

some more $00 bytes and finally we reach the main payload, the chest flags:

c6 f6 03 00 a9 01 00 c0 00 00 80 40

disassembles to:

dec $f6         # this makes use of our prepared direct page register to decrement $0006f5, underflowing from $0000 to $ffff and thereby setting our relevant event flag
ora $00,s       # just work around restrictions in how we can set chestflags, basically a 2-byte noop
lda #$0001      # load 1 in the accumulator, this will determine in which saveslot we will save, a chest could be skipped if one wants to overwrite slot 1, which i did not want for the POC
cpy #$0000      # again just working around restrictions, basically a 3-byte noop
bra IP+#$40     # one last time we jump over some junk bytes to reach the final part of the payload

after some more $00 bytes we reach the magirock flags:

08 22 d7 87 07

disassembles to:

php             # one-byte opcode for alignment
jsl $0787d7     # jump into the routine that will save the current game into the slot indicated by the value in the accumulator

the code will now continue to save our game with the manipulated event-flags, indicating that we have already beaten dark-gaia
afterwards the code will continue to execute sram until it crashes or gets locked up - however after a reset of the console the glitched savegame persists