Skip to content

Boss Key Save Data Corruption

BrunoValads edited this page Feb 8, 2018 · 5 revisions

When a boss key is in your inventory due to null egg glitch, its main routine gets run every frame, just as any egg's would. Of course, this has unintended consequences in any part of the game that is not an actual post-bossfight. Under the right conditions, the game's save data can be corrupted by a particular bug that shall be explained in full detail here.

Level Conditions

In order to activate this bug, some conditions about the sublevel must be met. These are so that the $7E0D3D flag gets turned on; if this flag is on, the $08DC4D Super FX routine gets called every frame, which is where the corruption actually occurs. Only the level loading routine can turn this flag on, and this is based on the data in the $01E90A table. This table gets indexed with the BG3 tileset # from the level header ($7E013E). It contains two entries per BG3 tileset: one is a graphics file (word length) and the third byte is an index. This index is what matters for the $7E0D3D flag. It must meet the following conditions: *May not be $FF *Must be negative (sign bit on) *May not be $80 or $85 (these point to routines that do NOT turn the flag on; the rest do) This narrows it down to $81, $82, $83, $84, $86, and $87. If you inspect the $01E90A table, only a few BG3 tileset #'s meet these conditions, namely $13, $1E, $20, $22, $23, $2A, $2C. The sublevels in the game that use these BG3 tilesets are: $02, $12, $19, $20, $2D, $34, $4D, $57, $64, $76, $79, $7B, $7E, $84, $90, $97, $AB, $AE.

BG3 Camera Value Corruption

This bug is a bit of a recipe of different things affecting each other in a chain. The first part involves corruption of the BG3 camera Y value (address $7000A0), specifically making it negative. This is something that happens eventually if a boss key exists at all no matter what. In fact, it is intended to happen, but of course only in a boss key cinematic, not in a regular stage!  It only happens once a particular state & mode of the boss key is reached. The state it uses is in sprite table $701978, and the mode is in sprite table $7019D8. Specifically, when the state is $03 and mode is between [$0B, $10), the camera will be written to. Once again this is all "normal" behavior for the boss key, and it works fine when the cinematic is playing, but outside of that context it can cause corruption to occur.

This is the relevant code that runs when these state & mode conditions are met:

    LDA #$0030          ; $02DDE4   |
    STA $3000           ; $02DDE7   |
    LDA $76,x           ; $02DDEA   | camera adder
    STA $300C           ; $02DDEC   |
    LDX #$0B            ; $02DDEF   |
    LDA #$86B6          ; $02DDF1   |
    JSL $7EDE44         ; $02DDF4   | GSU multiply
    LDA #$FFD8          ; $02DDF8   |
    CLC                 ; $02DDFB   |
    ADC $3000           ; $02DDFC   |
    STA $60A0           ; $02DDFF   |

The camera adder starts at $10 and increases by $10's until $100. Since it's being multiplied by $30, this causes an increase of 3 each frame to the camera value, hence $7000A0 shall look like this (frame-by-frame):

  $FFDB
  $FFDE
  $FFE1
  $FFE4
  $FFE7
  $FFEA
  $FFED
  $FFF0
  $FFF3
  $FFF6
  $FFF9
  $FFFC
  $FFFF
  $0002
  $0005
  $0008

This means there are 13 frames of negative-value BG3 camera Y. Any of these frames may cause corruption. Once it reaches positive values, the corruption stops occurring.

SRAM Corruption

Once the boss key has corrupted BG3 camera Y to be negative and if the level conditions are met so that $7E0D3D flag is on, this code shows how $7000A0 is put into r2 of Super FX:

    REP #$20            ; $01D86D   |
    LDA $0D3D           ; $01D86F   |
    AND #$0002          ; $01D872   |
    STA $3002           ; $01D875   |
    LDA $0D43           ; $01D878   |
    STA $6000           ; $01D87B   |
    LDA $60A0           ; $01D87E   | bg3 camera y
    STA $3004           ; $01D881   |
    LDA $0D3F           ; $01D884   |
    STA $3006           ; $01D887   |
    LDA #$0001          ; $01D88A   |
    STA $3008           ; $01D88D   |
    LDA #$36BA          ; $01D890   |
    STA $3014           ; $01D893   |
    LDA $61B0           ; $01D896   |
    AND #$00FF          ; $01D899   |
    STA $3016           ; $01D89C   |
    LDX #$08            ; $01D89F   |
    LDA #$DC4D          ; $01D8A1   |
    JSL $7EDE44         ; $01D8A4   | GSU init

This Super FX routine is designed to walk through a table at $01EBFC; this table contains, among other information, different "tiers" of camera Y value. The code will loop and keep walking until the tier value in the table reaches your current camera Y. It also happens to do an unsigned comparison, meaning negative values just became super large values. This effectively means: your negative (or in this scope, super large positive) value will never be reached. The tier values in the table don't quite go that high; the very highest they contain is $0800. So even something like $1000 would cause the same corruption. But certainly a negative value such as the ones listed above (that the boss key writes) also works.

What is the effect of this then? The code blows through every tier and then starts walking outside the bounds of the table. It starts pulling in values from a table after it, one that contains pointer values. These differ based on whether it is a J or U cartridge. Here is part of an actual GSU trace taken from a J 1.0 cartridge:

  08:dcc5 1e       move r14,r3     r14=$fd8c B=0
  08:dcc6 3f       alt3            ALT1=1 ALT2=1
  08:dcc7 ef       getbs           r0=$ff9f ROM=01:FD8C ALT1=0 ALT2=0
  08:dcc8 2b       with r11        B=1
  08:dcc9 b0       moves r11,r0    r11=$ff9f OV=1 B=0
  08:dcca 0a 08    bpl $dcd4
  08:dccc d3       inc r3          r3=$fd8d
  08:dccd 11       to r1
  08:dcce 3e       alt2            ALT2=1
  08:dccf 7f       and #15         r1=$000f S=0 ALT2=0
  08:dcd0 ff 65 dc iwt r15,#$dc65  r15=$dc65
  08:dc64 01       nop
  08:dc65 a0 08    ibt r0,#$08     r0=$0008
  08:dc67 3f       alt3            ALT1=1 ALT2=1
  08:dc68 df       romb            ALT1=0 ALT2=0 ROMBR=$08
  08:dc69 f0 74 dc iwt r0,#$dc74   r0=$dc74
  08:dc6c 1e       to r14
  08:dc6d 51       add r1          r14=$dc83 CY=0 S=1 OV=0
  08:dc6e ef       getb            r0=$00b0 ROM=08:DC83
  08:dc6f de       inc r14         r14=$dc84
  08:dc70 1f       to r15
  08:dc71 3d       alt1            ALT1=1
  08:dc72 ef       getbh           r15=$0ab0 ROM=08:DC84 ALT1=0

The first line shows $01FD8C, an address that is outside the bounds of the table. The getbs instruction will pull in a value from there; it pulls in $9F. This then produces an index of $F, which should never happen (only 0 and 2 are valid indices). This index gets added onto $08DC74, which is a pointer table, producing $08DC83, which is outside the bounds of the table. This address contains $0AB0, so the Super FX jumps to $0AB0!

This address region ($0000-$7FFF) is simply a mirror of $8000-$FFFF, so this is effectively $088AB0. This happens to be in the middle of a Super FX drawing routine!

Right at this point, various Super FX registers & SRAM addresses affect various aspects of the drawing. Because we are simply waltzing from one completely unrelated Super FX routine to the middle of another, the values in the registers & memory are not as the drawing routine would expect. Instead, they are affected by game state in the following manners:

Register/Memory Drawing Purpose Value At This Point
r0 Nothing Significant $00B0 (constant)
r2 SRAM Destination Y Coordinate Start ($7000A0) : BG3 Camera Y
r3 SRAM Destination X Coordinate Start $FD8D (constant)
r4 ROM Source Column Y Step Value ($700094) << 8: Camera X * $100
r5 Rotational Angle $0001 (constant)
r6 1 / y scale $0100 (constant)
r7 1 / x scale ($701FE4) + ($7000A0) * 2
r9 Offset used for source ROM Y coordinate $0800 (constant)
r12 ROM Source Graphics Address * 2 $00D2 (constant)
r13 ROM Source Graphics Bank $DCFC (constant, ROM bank will be $FC)
($700004) ROM Source Row X Step Value (Low Byte) If sprite $50's state is $12 (burning), $05
If sprite $50's state is $10 (active), $03
Otherwise $00
($700005) ROM Source Row X Step Value (High Byte) If sprite $4C's state is $12 (burning), $05
If sprite $4C's state is $10 (active), $03
Otherwise $00
SCBR Screen Base Register for SRAM destination $16 (constant)
SCMR Screen Mode Register $3D (constant, implies 16-color, OBJ mode

Hence, as you can see from this table, various aspects of game state affect various aspects of drawing. Namely, BG3 Camera Y (although this will always be set to a certain value as governed by the boss key sprite as explained above), Camera X, and the existence/states of sprites $50 and $4C. The table makes it clear what affects what, but as an overall summary, the destination addresses in SRAM cannot be affected by player control in any way, as BG3 camera Y is set by the boss key to fixed values. What the player can affect is what source ROM addresses the drawing routine pulls from to populate the SRAM addresses. The goal is to get the right values from ROM to corrupt the save data area ($707C00-$707E08) in a way that unlocks later stages.

Here is (a small part of) a trace to show the "drawing" in action:

  080ab0 hib               S:0028 zcNv
  080ab1 lob               S:0022 Zcnv R0 :0000 R13:dcfc
  080ab2 swap              S:0022 Zcnv R0 :0000
  080ab3 to    r8          S:0022 Zcnv R0 :0000 R8 :0008
  080ab4 or    r4          S:0022 Zcnv R0 :0000 R4 :9f00 R8 :0008
  080ab5 alt2              S:0028 zcNv R0 :0000 R4 :9f00 R8 :9f00
  080ab6 sms   r8,(#$0000) S:0228 zcNv R8 :9f00
  080ab8 iwt   r0,#$ac18   S:0028 zcNv R0 :0000 R8 :9f00
  080abb to    r14         S:0028 zcNv R0 :ac18 R14:dc84
  080abc add   r5          S:0028 zcNv R0 :ac18 R5 :0001 R14:dc84
  080abd getb              S:0068 zcNv R0 :ac18 R5 :0001 R14:ac19
  080abe inc   r14         S:0028 zcNv R0 :0000 R14:ac19
  080abf alt1              S:0068 zcNv R14:ac1a
  080ac0 getbh             S:0128 zcNv R0 :0000
  080ac1 with  r0          S:0028 zcNv R0 :fa00
  080ac2 to    r14         S:1028 zcNv R0 :fa00 R14:ac1a
  080ac3 alt1              S:0068 zcNv R14:fa00
  080ac4 lmult             S:0128 zcNv R0 :fa00 R4 :9f00 R6 :0100
  080ac5 with  r4          S:0028 zcNv R0 :fffa R4 :0000 R6 :0100
  080ac6 hib               S:1028 zcNv R4 :0000
  080ac7 lob               S:0022 Zcnv R0 :fffa R4 :0000
  080ac8 swap              S:0028 zcNv R0 :00fa
  080ac9 or    r4          S:0028 zcNv R0 :fa00 R4 :0000
  080aca not               S:0028 zcNv R0 :fa00 R4 :0000
  080acb inc   r0          S:0020 zcnv R0 :05ff
  080acc with  r0          S:0020 zcnv R0 :0600
  080acd to    r5          S:1020 zcnv R0 :0600 R5 :0001
  080ace alt2              S:0020 zcnv R5 :0600
  080acf sms   r5,(#$0006) S:0220 zcnv R5 :0600
  080ad1 add   r8          S:0020 zcnv R0 :0600 R5 :0600 R8 :9f00
  080ad2 not               S:0028 zcNv R0 :a500 R8 :9f00
  080ad3 inc   r0          S:0020 zcnv R0 :5aff
  080ad4 add   r0          S:0020 zcnv R0 :5b00
  080ad5 add   r0          S:0038 zcNV R0 :b600
  080ad6 add   r0          S:0034 zCnV R0 :6c00
  080ad7 add   r0          S:0038 zcNV R0 :d800
  080ad8 add   r0          S:002c zCNv R0 :b000
  080ad9 iwt   r10,#$1000  S:0034 zCnV R0 :6000 R10:36ba
  080adc to    r11         S:0034 zCnV R10:1000 R11:ff9f
  080add add   r10         S:0034 zCnV R0 :6000 R10:1000 R11:ff9f
  080ade with  r7          S:0020 zcnv R0 :6000 R7 :0142 R10:1000 R11:7000
  080adf to    r6          S:1020 zcnv R6 :0100 R7 :0142
  080ae0 from  r14         S:0020 zcnv R6 :0142 R14:fa00
  080ae1 alt1              S:0020 zcnv R14:fa00
  080ae2 lmult             S:0120 zcnv R0 :6000 R4 :0000 R6 :0142 R14:fa00
  080ae3 with  r4          S:0028 zcNv R0 :fff8 R4 :7400 R6 :0142 R14:fa00
  080ae4 hib               S:1028 zcNv R4 :7400
  080ae5 lob               S:0020 zcnv R0 :fff8 R4 :0074
  080ae6 swap              S:0028 zcNv R0 :00f8
  080ae7 or    r4          S:0028 zcNv R0 :f800 R4 :0074
  080ae8 alt2              S:0028 zcNv R0 :f874 R4 :0074
  080ae9 sms   r0,(#$0002) S:0228 zcNv R0 :f874
  080aeb add   r9          S:0028 zcNv R0 :f874 R9 :0800
  080aec not               S:0024 zCnv R0 :0074 R9 :0800
  080aed inc   r0          S:002c zCNv R0 :ff8b
  080aee add   r0          S:002c zCNv R0 :ff8c
  080aef add   r0          S:002c zCNv R0 :ff18
  080af0 add   r0          S:002c zCNv R0 :fe30
  080af1 add   r0          S:002c zCNv R0 :fc60
  080af2 add   r0          S:002c zCNv R0 :f8c0
  080af3 to    r10         S:002c zCNv R0 :f180 R10:1000
  080af4 add   r10         S:002c zCNv R0 :f180 R10:1000
  080af5 alt1              S:0024 zCnv R0 :f180 R10:0180
  080af6 lms   r9,(#$0004) S:0124 zCnv R9 :0800
  080af8 alt1              S:0024 zCnv R9 :0000
  080af9 lms   r5,(#$0006) S:0124 zCnv R5 :0600
  080afb ibt   r1,#$01     S:0024 zCnv R1 :000f R5 :0600
  080afd with  r12         S:0024 zCnv R1 :0001 R12:00d2
  080afe to    r0          S:1024 zCnv R0 :f180 R12:00d2
  080aff lsr               S:0024 zCnv R0 :00d2
  080b00 bcc   $0b05       S:0020 zcnv R0 :0069
  080b05 or    r4          S:0020 zcnv R0 :0069 R4 :0074
  080b06 from  r1          S:0020 zcnv R0 :00d2 R1 :0001 R4 :0074
  080b07 alt1              S:0020 zcnv R1 :0001
  080b08 cmode             S:0120 zcnv R1 :0001
  080b09 with  r0          S:0020 zcnv R0 :00d2 R1 :0001
  080b0a to    r6          S:1020 zcnv R0 :00d2 R6 :0142
  080b0b from  r13         S:0020 zcnv R6 :00d2 R13:dcfc
  080b0c alt3              S:0020 zcnv R13:dcfc
  080b0d romb              S:0320 zcnv R13:dcfc
  080b0e iwt   r13,#$8b1c  S:0020 zcnv R13:dcfc
  080b11 ibt   r4,#$20     S:0020 zcnv R4 :0074 R13:8b1c
  080b13 cache             S:0020 zcnv R4 :0020
  080b14 with  r3          S:0020 zcnv R3 :fd8d
  080b15 to    r1          S:1020 zcnv R1 :0001 R3 :fd8d
  080b16 with  r10         S:0020 zcnv R1 :fd8d R10:0180
  080b17 to    r8          S:1020 zcnv R8 :9f00 R10:0180
  080b18 with  r11         S:0020 zcnv R8 :0180 R11:7000
  080b19 to    r7          S:1020 zcnv R7 :0142 R11:7000
  080b1a ibt   r12,#$40    S:0020 zcnv R7 :7000 R12:00d2
  080b1c merge             S:0020 zcnv R0 :00d2 R7 :7000 R8 :0180 R12:0040
  080b1d bcs   $0b2c       S:0036 ZCnV R0 :7001 R7 :7000 R8 :0180
  080b2c nop               S:0036 ZCnV
  080b2d with  r8          S:0036 ZCnV R8 :0180
  080b2e add   r9          S:1036 ZCnV R8 :0180 R9 :0000
  080b2f with  r7          S:0020 zcnv R7 :7000 R8 :0180 R9 :0000
  080b30 add   r5          S:1020 zcnv R5 :0600 R7 :7000
  080b31 sub   r0          S:0020 zcnv R0 :7001 R5 :0600 R7 :7600
  080b32 color             S:0026 ZCnv R0 :0000
  080b33 loop              S:0026 ZCnv R0 :0000 R12:0040 R13:8b1c
  088b1b ldw   (r0)        S:0024 zCnv R0 :0000 R12:003f R13:8b1c
  088b1c merge             S:0024 zCnv R0 :0000 R7 :7600 R8 :0180
  088b1d bcs   $8b2c       S:0036 ZCnV R0 :7601 R7 :7600 R8 :0180
  088b2c nop               S:0036 ZCnV
  088b2d with  r8          S:0036 ZCnV R8 :0180
  088b2e add   r9          S:1036 ZCnV R8 :0180 R9 :0000
  088b2f with  r7          S:0020 zcnv R7 :7600 R8 :0180 R9 :0000
  088b30 add   r5          S:1020 zcnv R5 :0600 R7 :7600
  088b31 sub   r0          S:0020 zcnv R0 :7601 R5 :0600 R7 :7c00
  088b32 color             S:0026 ZCnv R0 :0000
  088b33 loop              S:0026 ZCnv R0 :0000 R12:003f R13:8b1c
  088b1b ldw   (r0)        S:0024 zCnv R0 :0000 R12:003e R13:8b1c
  088b1c merge             S:0024 zCnv R0 :0000 R7 :7c00 R8 :0180
  088b1d bcs   $8b2c       S:0036 ZCnV R0 :7c01 R7 :7c00 R8 :0180
  088b2c nop               S:0036 ZCnV
  088b2d with  r8          S:0036 ZCnV R8 :0180
  088b2e add   r9          S:1036 ZCnV R8 :0180 R9 :0000
  088b2f with  r7          S:0020 zcnv R7 :7c00 R8 :0180 R9 :0000
  088b30 add   r5          S:1020 zcnv R5 :0600 R7 :7c00
  088b31 sub   r0          S:0038 zcNV R0 :7c01 R5 :0600 R7 :8200
  088b32 color             S:0026 ZCnv R0 :0000
  088b33 loop              S:0026 ZCnv R0 :0000 R12:003e R13:8b1c
  088b1b ldw   (r0)        S:0024 zCnv R0 :0000 R12:003d R13:8b1c
  088b1c merge             S:0024 zCnv R0 :0000 R7 :8200 R8 :0180
  088b1d bcs   $8b2c       S:003e ZCNV R0 :8201 R7 :8200 R8 :0180
  088b2c nop               S:003e ZCNV
  088b2d with  r8          S:003e ZCNV R8 :0180
  088b2e add   r9          S:103e ZCNV R8 :0180 R9 :0000
  088b2f with  r7          S:0020 zcnv R7 :8200 R8 :0180 R9 :0000
  088b30 add   r5          S:1020 zcnv R5 :0600 R7 :8200
  088b31 sub   r0          S:0028 zcNv R0 :8201 R5 :0600 R7 :8800
  088b32 color             S:0026 ZCnv R0 :0000
  088b33 loop              S:0026 ZCnv R0 :0000 R12:003d R13:8b1c
  088b1b ldw   (r0)        S:0024 zCnv R0 :0000 R12:003c R13:8b1c
  088b1c merge             S:0024 zCnv R0 :0000 R7 :8800 R8 :0180
  088b1d bcs   $8b2c       S:003e ZCNV R0 :8801 R7 :8800 R8 :0180
  088b2c nop               S:003e ZCNV
  088b2d with  r8          S:003e ZCNV R8 :0180
  088b2e add   r9          S:103e ZCNV R8 :0180 R9 :0000
  088b2f with  r7          S:0020 zcnv R7 :8800 R8 :0180 R9 :0000
  088b30 add   r5          S:1020 zcnv R5 :0600 R7 :8800
  088b31 sub   r0          S:0028 zcNv R0 :8801 R5 :0600 R7 :8e00
  088b32 color             S:0026 ZCnv R0 :0000
  088b33 loop              S:0026 ZCnv R0 :0000 R12:003c R13:8b1c
  088b1b ldw   (r0)        S:0024 zCnv R0 :0000 R12:003b R13:8b1c
  088b1c merge             S:0024 zCnv R0 :0000 R7 :8e00 R8 :0180
  088b1d bcs   $8b2c       S:003e ZCNV R0 :8e01 R7 :8e00 R8 :0180
  088b2c nop               S:003e ZCNV
  088b2d with  r8          S:003e ZCNV R8 :0180
  088b2e add   r9          S:103e ZCNV R8 :0180 R9 :0000
  088b2f with  r7          S:0020 zcnv R7 :8e00 R8 :0180 R9 :0000
  088b30 add   r5          S:1020 zcnv R5 :0600 R7 :8e00
  088b31 sub   r0          S:0028 zcNv R0 :8e01 R5 :0600 R7 :9400
  088b32 color             S:0026 ZCnv R0 :0000
  088b33 loop              S:0026 ZCnv R0 :0000 R12:003b R13:8b1c
  088b1b ldw   (r0)        S:0024 zCnv R0 :0000 R12:003a R13:8b1c
  088b1c merge             S:0024 zCnv R0 :0000 R7 :9400 R8 :0180
  088b1d bcs   $8b2c       S:003e ZCNV R0 :9401 R7 :9400 R8 :0180
  088b2c nop               S:003e ZCNV
  088b2d with  r8          S:003e ZCNV R8 :0180
  088b2e add   r9          S:103e ZCNV R8 :0180 R9 :0000
  088b2f with  r7          S:0020 zcnv R7 :9400 R8 :0180 R9 :0000
  088b30 add   r5          S:1020 zcnv R5 :0600 R7 :9400
  088b31 sub   r0          S:0028 zcNv R0 :9401 R5 :0600 R7 :9a00
  088b32 color             S:0026 ZCnv R0 :0000
  088b33 loop              S:0026 ZCnv R0 :0000 R12:003a R13:8b1c
  ...

This loops on for quite a bit, but shows how it thinks it is "drawing".

U 1.0 Cartridge

On a U cart, the addresses are a tad different, so we get different results (simply from luck of the draw). Here is part of a trace:

  08:dcc5 1e       move r14,r3     r14=$ec70 B=0
  08:dcc6 3f       alt3            ALT1=1 ALT2=1
  08:dcc7 ef       getbs           r0=$fffc ROM=01:EC70 ALT1=0 ALT2=0
  08:dcc8 2b       with r11        B=1
  08:dcc9 b0       moves r11,r0    r11=$fffc S=1 OV=1 B=0
  08:dcca 0a 08    bpl $dcd4
  08:dccc d3       inc r3          r3=$ec71
  08:dccd 11       to r1
  08:dcce 3e       alt2            ALT2=1
  08:dccf 7f       and #15         r1=$000c S=0 ALT2=0
  08:dcd0 ff 65 dc iwt r15,#$dc65  r15=$dc65
  08:dc64 01       nop
  08:dc65 a0 08    ibt r0,#$08     r0=$0008
  08:dc67 3f       alt3            ALT1=1 ALT2=1
  08:dc68 df       romb            ALT1=0 ALT2=0 ROMBR=$08
  08:dc69 f0 74 dc iwt r0,#$dc74   r0=$dc74
  08:dc6c 1e       to r14
  08:dc6d 51       add r1          r14=$dc80 CY=0 S=1 OV=0
  08:dc6e ef       getb            r0=$003f ROM=08:DC80
  08:dc6f de       inc r14         r14=$dc81
  08:dc70 1f       to r15
  08:dc71 3d       alt1            ALT1=1
  08:dc72 ef       getbh           r15=$ef3f ROM=08:DC81 ALT1=0
  08:ef3e 01       nop
  08:ef3f 1c       to r12

As you can see, the same general principle applies; this time the table is located at $01EBFC and it walks outside of it all the way into $EC70 (first line). When it pulls in this value, instead of $9F like on J, it pulls in $FC. This gives us an index of $C, which adds onto the same $08DC74 table, giving us $08DC80 instead of $08DC83 on J. This address happens to contain a different Super FX address than J, $08EF3F, so the code jumps there and we get a very very large loop:

  08:ef3f 1c       to r12
  08:ef40 68       sub r8          r12=$ff54
  08:ef41 f8 00 01 iwt r8,#$0100   r8=$0100
  08:ef44 02       cache           CBR=$ef40
  08:ef45 2f       with r15        B=1
  08:ef46 1d       move r13,r15    r13=$ef47 B=0
  08:ef47 b1       from r1
  08:ef48 32       stw (r2)        SRAM=00:FFDB
  08:ef49 d2       inc r2          r2=$ffdc
  08:ef4a d2       inc r2          r2=$ffdd
  08:ef4b b3       from r3
  08:ef4c 9e       lob             r0=$0071 S=0
  08:ef4d 4d       swap            r0=$7100
  08:ef4e 2b       with r11        B=1
  08:ef4f 60       sub r0          r11=$8efc CY=1 S=1 B=0
  08:ef50 b3       from r3
  08:ef51 c0       hib             r0=$00ec
  08:ef52 95       sex             r0=$ffec
  08:ef53 21       with r1         B=1
  08:ef54 3d       alt1            ALT1=1 B=0
  08:ef55 60       sbc r0          r1=$0020 CY=0 S=0 ALT1=0
  08:ef56 f4 e0 00 iwt r4,#$00e0   r4=$00e0
  08:ef59 23       with r3         B=1
  08:ef5a 55       add r5          r3=$ec72 S=1 B=0
  08:ef5b b3       from r3
  08:ef5c 64       sub r4          r0=$eb92 CY=1
  08:ef5d 0c 03    bcc $ef62
  08:ef5f 54       add r4          r0=$ec72 CY=0
  08:ef60 24       with r4         B=1
  08:ef61 13       move r3,r4      r3=$00e0 B=0
  08:ef62 b3       from r3
  08:ef63 68       sub r8          r0=$ffe0
  08:ef64 0b 03    bmi $ef69       r15=$ef69
  08:ef68 58       add r8          r0=$00e0 CY=1 S=0
  08:ef69 25       with r5         B=1
  08:ef6a 56       add r6          r5=$b9ec CY=0 S=1 B=0
  08:ef6b 3c       loop            r12=$ff53 r15=$ef47
  08:ef46 01       nop

This happens because r12 is subtracted by r8 which wraps around to a negative number, $FF54 (normally it wouldn't). But the loop counting will of course treat r12 as positive, so we are actually looping here 65364 times. But it actually does eventually finish the loop:

  08:ef6a 56       add r6          r5=$161d B=0
  08:ef6b 3c       loop            r12=$0000 Z=1
  08:ef6c 01       nop
  08:ef6d f8 a2 44 iwt r8,#$44a2   r8=$44a2
  08:ef70 16       to r6
  08:ef71 48       ldw (r8)        r6=$4872 SRAM=00:44A2
  ...

So it will exit back to the SNES, but somewhere along the way, $7001CA has been corrupted, leading to yet another table out of bounds, this time on the SNES side. $01D915 gets indexed with $8F which points to a BPL $10 instruction (as data this is $0310):

  $01/C1FA AE CA 61    LDX $61CA  [$01:61CA]   A:858F X:0009 Y:0000 P:eNvMXdizC
  $01/C1FD F0 03       BEQ $03    [$C202]      A:858F X:008F Y:0000 P:eNvMXdizC
  $01/C1FF FC 15 D9    JSR ($D915,x)[$01:0310] A:858F X:008F Y:0000 P:eNvMXdizC
  $01/0310 02 03       COP #$03                A:858F X:008F Y:0000 P:eNvMXdizC
  $00/814F 40          RTI                     A:858F X:008F Y:0000 P:eNvMXdIzC
  $01/0312 04 05       TSB $05    [$00:0005]   A:858F X:008F Y:0000 P:eNvMXdizC
  $01/0314 06 07       ASL $07    [$00:0007]   A:858F X:008F Y:0000 P:eNvMXdiZC
  $01/0316 08          PHP                     A:858F X:008F Y:0000 P:envMXdiZc
  $01/0317 F0 F0       BEQ $F0    [$0309]      A:858F X:008F Y:0000 P:envMXdiZc
  ...

Now we are executing RAM, it gets all the way to $7E088E before jumping into an unmapped register:

  $01/088E 20 FD 20    JSR $20FD  [$01:20FD]

This part may vary depending on what is in this region of RAM at the current moment; this is going off of one particular trace. This is jumping into an unmapped register; at this point it's open bus on the SNES which likely leads to a crash of some kind.

Clone this wiki locally