-
Notifications
You must be signed in to change notification settings - Fork 20
Boss Key Save Data Corruption
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.
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.
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.
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".
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.