Illegal Ancilla Dossier

Illegal ancillae are those with IDs greater $43. Through various means, these values can be placed into the ancilla array; however, there is no vector corresponding to these values. As such, they begin executing mostly garbage code.

The following section headers will name each entry as IAx##, where IA abbreviates Illegal Ancilla; x indicates a hexadecimal value; ## is the hexadecimal ID of the ancilla.

The results are fully unpredictable.

IAx44

IAx44_entry
BRK #$00
brk_vector
BRL $0702
BRK #$00
brk_vector
BRL $0702

This entry points to a location in WRAM. The address is seemingly unused, so it will most likely have $0000, which will execute the break vector. This vector jumps to $00FFFF, which has a BRL that wraps around and reads $0000 as the argument to the opcode, which is a relative distance to branch from the byte after the argument. $0000 is expected to have the original illegal pointer, so execution resumes at $000704, more unused space. This begins a long loop of repeated break vectors. Eventually, the stack will overwrite $0000, resulting in unpredictable effects.

IAx45

IAx45_entry
BRK ??
brk_vector
BRL $0106
????

This entry points to a location in WRAM. The low byte of the address is unused, so it will contain the value $00. The next byte is not predictable, but the argument to BRK is generally ignorable. The break vector leads to a jump to $000108. From here, the results are not predictable.

IAx46

IAx46_entry
BRK #$00
brk_vector
BRL $0701
BRK #$00
brk_vector
BRL $0701

This entry points to a location in WRAM. The high byte of the address is technically used, but always has a value of $00. This leads to a similar BRK loop as IAx44.

IAx47

IAx47_entry
????

This entry points to a location in WRAM. The results are fully unpredictable.

IAx48

IAx48_entry
????

This entry points to a location in WRAM. The low byte (which will determine the first opcode used) is technically predictable, as it is determined by Link's coordinates; however, with 7 consecutive $00 bytes following, there is not much useful that can be done besides returning from the routine safely with an RTS ($60).

IAx49

IAx49_entry
????

This entry points to a location in WRAM. The results are fully unpredictable.

IAx4A

IAx4A_entry
????
IAx4A_entry
BRK #$F0
brk_vector
BRL $0904
BRK #$F0
brk_vector
BRL $0904

This entry points to a location in WRAM, specifically the OAM buffer. The results are fully unpredictable.

IAx4B

IAx4B_entry
PHD
Doctor to stack
JSR ($FC0E, X)
X<10

This entry points to a location in ROM. Specifically, it is executing data that is part of the quake draw routine's tables. Depending on the slot, different code from another table will be executed. Regardless, the game will crash.

IAx4C

IAx4C_entry
BRK ??
IAx4C_entry
ORA (??,X)

This entry points to a location in WRAM. The address $0302 only takes on values of $00 or $01. In the case of $00, the BRK vector will lead to unpredictable results. When ORA (dp,X) is executed, the inevitable crash is merely delayed.

IAx4D

IAx4D_entry
????

This entry points to a location in WRAM. The results are fully unpredictable.

IAx4E

IAx4E_entry
????

This entry points to a location in WRAM. The results are fully unpredictable.

IAx4F

IAx4F_entry
BRK ??
brk_vector
BRL $0707
????

This entry points to a location in WRAM. The addresses containing the opcode is unused, resulting in a BRK, but it is followed by overworld screen properters, resulting in unpredictable code.

IAx50

IAx50_entry
BRK ??
brk_vector
BRL $0001
????

This entry points to a location in WRAM. The high byte of the address will be the opcode (BRK); after the break vector, the results are unpredictable.

IAx51

IAx51_entry
????

This entry points to a location in WRAM, specifically the OAM buffer. The results are fully unpredictable.

IAx52

IAx52_entry
BRK ??
brk_vector
BRL $04FF
????

This entry points to a location in WRAM, specifically the last slot in the torch timer array. This array has 16 slots, but in practice, this slot is never used. A BRK will occur, but it will then jump to $0501 for execution. The results are fully unpredictable.

IAx53

IAx53_entry
BRK ??
brk_vector
BRL $0409
BRK ??
brk_vector
BRL $0409

This entry points to a location in WRAM, specifically the high byte of the quadrant visits flag for the current underworld tile. This byte is unused, so it will result in a BRK. The address branched to after the BRK is also unused, resulting in a BRK loop.

IAx54

IAx55

IAx54_entry
EOR ($88)
ORA $FC8D, Y
Y=$A6 so [$08FD33]:$C4
BCC −24
Carry will be cleared
LDY $0C86, X
We've been here before

IAx54 and IAx55 share the same entry point.

This entry points to a location in ROM. Funnily enough, it is pointing straight to another pointer from the jump table we just used, specifically the pointer for the unused ancilla with an ID of $03. Quite quickly, this routine converges into actual code; in fact, it's the code just run to find out which ancilla subroutine to execute. Unfortunately, this returns to a point in the code after a stack push was meant to occur. When a value assumed to be the ancilla ID is recovered from the stack, it will rip off the low byte of our return address ($37). This results in it executing the routine for the ancilla with ID $02, the fire rod shot. When it's finished, the unbalanced stack will eventually cause the game to crash. Forever.

IAx56

IAx57

IAx56_entry
BRK #$22
BRL $80B6
ADC ($80, X)
STA $03
LDA $807D, Y
Y=$AA; so [$8127]:$8D
STA $04
LDA $8099, Y
Y=$AA; so [$8143]:$83
STA $05
JMP [$0003]

IAx56 and IAx57 share the same entry point.

This entry points to a location in ROM, specifically the routine AncillaAdd_FireRodShot; however, its entry point does not align with the code, and it treats the parameter of an STA dp as the opcode, resulting in a BRK being executed. The break vector leads to the middle of the topmost subroutine, RunModule. The entry point does not align with the code, but it recovers after one instruction. From here, the result is mostly unpredictable.

However, $80 is in the middle of some unused RAM. If this illegal ancilla is in slots 0, 1, or 2, then the ADC will look at the word in addresses $80, $81, or $82, respectively, all of which contain the value $0000. In these cases, the accumulator, which currently contains the high byte of our pointer, will have the value of $0000 added to it, which contains the low byte of our pointer. The carry is always clear, so we know that we will get $36 as our result, with the carry flag set. This results in a jump to a known address in bank83, of which bank03 is a mirror. This bank is filled entirely with overworld tile32 data, so, needless to say, the results are garbage. But we can follow along for a bit:

ORA ($00, X)
CMP ($2D, X)
LDY #$0C
AND ($00, X)
ROR $122D, X
CPY $0201
CPY $4CCC
Open bus
LSR $4422
More open bus
EOR $514D4E
MVP $FF, $44
Nice
LDX #$57
EOR $4423, Y
Speed 5: Open Bus
EOR $5D5B, Y
EOR $4444
EOR $376058, X
MVP $37, $42
TAY
RTL

The game crashes because the stack is unbalanced after this RTL.

For all other indices, the results are fully unpredictable.

IAx58

IAx59

IAx5A

IAx5B

IAx58_entry
RTS

IAx58, IAx59, IAx5A, and IAx5B all share the same entry point.

This entry points to a location in ROM, specifically one of the exit points of Ancilla_QuakeSpell. The entry point aligns perfectly with the code, an RTS that allows the ancilla to exit safely, without doing anything.

IAx5C

IAx5C_entry
PLA
AND [$37],Y
BRK #$00
BRL $68BD
PLA
AND ($32)

This entry points to a location in open bus. The results are fully unpredictable.

IAx5D

IAx5D_entry
BNE $13
Z=0 because we last loaded $D0
JSR $F6B6
Ancilla_PrepAdjustedOamCoord
Adjust_OAM
LDY $0C7C, X
LDA $F67F, Y
Y=$B8; [$08F737]:$05
STA $65
Expected legal values: $10, $20, $30
STZ $64
LDA $0BFA, X
All values of X when this code runs
STA $00
should be <10
LDA $0C0E, X
So I wouldn't expect any corruption
STA $01
LDA $0C04, X
STA $02
LDA $0C18, X
STA $03
REP #$20
LDA $00
SEC
SBC $0122
STA $00
LDA $02
SEC
SBC $011E
STA $02
STA $04
SEP #$20
RTS

This entry points to a location in ROM, specifically part of the flute ancilla's consumption code. The entry point aligns perfectly with the code. The branch is always taken, as the Z flag will always be unset from reading the high byte of the pointer.

This creates a glitchy object (often the item get sprite) on screen, but beyond that, it does not appear to have any interesting consequences in and of itself. If the object ends up being off screen, it will just delete itself from the array.

IAx5E

ORA $78, X
ORA $79, X
ORA $76, X
ORA $77, X
ORA $76, X
ORA $77, X
ORA $78, X
ORA $79, X
ORA $78, X
ORA $79, X
ORA $92, X
PHP
TYA
PHP
LDY $08
LDA $930C
DB is still $08
PHP
STA $A508, Y
This does nothing
PHP
LDA $AD8C
JMP $48A4
Open bus
PHA
STZ $64
BRK #$00
brk_vector
BRL $9E03

This entry points to a location in ROM, specifically part of the bomb draw routine's data table. The first instruction is a BRK. Eventually, this leads to open bus, making the results crashdictable.

IAx5F

IAx5F_entry
????

This entry points to a location in WRAM, specifically the start of the ancilla ID array. This array is easily manipulable; it can, and has, been used for arbitrary code execution.

IAx60

IAx60_entry
????

This entry points to a location in WRAM, specifically a general purpose DMA buffer. The results are fully unpredictable.

IAx61

IAx61_entry
????

This entry points to a location in WRAM, specifically an array of shutter door location values. Results will vary based on the position and order of doors, but most of the results are fully unpredictable..

IAx62

IAx62_entry
JSR $8035
Ancilla_SetSfxPan
STA $012D
RTS

This entry points to a location in ROM, specifically the Ancilla_SFX1_Pan routine. The entry point aligns with the code perfectly and results in a stable return. Because the sound effect attempted is $80, this will only cause the ambient sound effect (if any) to fade away, as negative values flag this behavior for those sound effects specifically.

IAx63

IAx63_entry
JSR $2020
Open bus
STA.b ($92)
JSR $2020
BIT.b $24
???

This entry points to a location in open bus. The results are fully unpredictable. And then some.

IAx64

IAx64_entry
LDA $0C22, X
Ancilla_Move_Y

This entry points to a location in ROM, specifically the Ancilla_Move_Y routine. The entry point is exactly the beginning of the routine, so the code runs perfectly and without fault. Thus, this ancilla will be in a constant state of vertical movement. Every array used by this routine is 10 entries long, so there is no potential for corruption.

IAx65

IAx65_entry
ROL A
ROL A
ROL A
ROL A
Let's skip ahead
SED
SED
SED
SED
Let's skip ahead again…
SED
SED
SED
Just a little more…
SED
SED
????

This entry points to a location in open bus. In general, the results are fully unpredictable.

In many cases, the same instructions are used ad nauseum, but eventually, execution lands on the controller ports, allowing for full control arbitrary code execution in a TAS. This can make this illegal ancilla predictable and usable; however, other CPU operations will clobber the value in open bus.

IAx66

IAx66_entry
RTS

This entry points to a location in ROM, specifically part of the door debris draw routine's data table. It just so happens to land on the value $60, which is the opcode RTS, causing the ancilla's routine to exit immediately.

IAx67

IAx67_entry
STA ($FA)
A=$A9
RTS

This entry points to a location in ROM, specifically the end of the jump splash draw routine. The entry point does not align perfectly with the code, but it recovers quickly. This code takes the value of the accumulator, which currently holds the high byte of the pointer ($A9), and stores it with an indirect write to the 16-bit address present in $FA. This address holds the player 1 joypad input from the previous frame: AXLR..... $FB is the same for player 2, but the code for writing it is unreachable; thus, the high byte of the indirect address is always $00.

Effectively, this means we can write $A9 to 16 different addresses using a standard SNES controller; within the address range $00$F0, for low nibbles of $0. The bottom nibble is the controller's signature, which is %0000 for a regular controller.

Controller input Address Effect
%0000      $00 Scratch space Useless
%0001    R $10 Game module Crash
%0010   L  $20 Link's Y-coordinate low byte Funny teleport
%0011   LR $30 Link's Y velocity Nothing; overwritten before used
%0100  X   $40 Temp variable for coordinates Nothing; overwritten before used
%0101  X R $50 Change direction flag Link cannot turn anymore
%0110  XL  $60 Attract mode sequence counter Nothing; zeroed every frame as collateral damage
%0111  XLR $70 Free RAM Nothing
%1000 A    $80 Free RAM Nothing
%1001 A  R $90 OAM buffer location pointer Seemingly nothing; at worst, broken graphics for 1 frame
%1010 A L  $A0 Room ID Repointed to EP big chest room or a nonexistent room
ID $01A9 corresponds to cape and mirror in SRAM
%1011 A LR $B0 Subsubmodule Game will crash during transitions
%1100 AX   $C0 Tilemap buffer pointer Nothing?
%1101 AX R $D0 Room load pointers No effect; overwritten when required by the CPU
%1110 AXL  $E0 BG1 horizontal scroll low byte Seemingly nothing; fixed every frame
%1111 AXLR $F0 Joypad 1 inputs Changes input for joypad 1 to B+Select+Up+Right

IAx68

IAx68_entry
STA $0C0E, X
A=$D9
LDA $02
Could be anything
STA $0C04, X
LDA $03
STA $0C18, X
BRA +7
Ancilla_SpinSpark

This entry points to a location in ROM, specifically the end of a routine that transmutes ancilla $2A into ancilla $2B. The low byte of the Y-coordinate will remain at its current value, and the high byte will take the value $D9. The low and high bytes of the X-coordinate will be garbage data.

After the coordinates are set each frame, this illegal ancilla will behave like a normal spin spark. The ID rewrite of the routine is not performed, so this ancilla will remain with the illegal ID.

IAx69

This illegal ancilla shares the same pointer and effect as IAx5F.

IAx6A

IAx6A_entry
????

This entry points to a location in location in WRAM, specifically the flute cooldown timer. This results in fully unpredictable results or a BRK (followed by fully unpredictable results).

IAx6B

IAx6B_entry

This entry points to a location in WRAM, specifically the arbitrary DMA buffer. The results are fully unpredictable.

IAx6C

IAx6C_entry
ORA ($00, X)
PLX
SBC $F4FFFA, X
SBC $FA0001, X
SBC $F4FFFA, X
SBC $FA0001, X
SBC $F4FFFA, X
SBC $FA0001, X
boring…
SBC $F3FFFD, X
SBC $F8FFFF, X
SBC $0F0009, X
BRK #$06
brk_vector
BRL $B9F8
ADC ($71, X)
TAX
ADC ($EC), Y
ORA ($EC, X)
ORA ($70, X)
AND ($71), Y
AND ($72), Y
AND ($7E), Y
Not worth looking at…
ADC ($AB), Y
ADC ($AA), Y
ADC ($CF), Y
ORA $8DCF
CMP $CDCF4D
ORA $8D1F0D, X
ORA $CD1F4D, X
ORA ($0D, X)
ORA ($8D, X)
ORA ($4D, X)
ORA ($CD, X)
BNE +25

This entry points to a location in ROM, specifically part of the magic powder draw routine's table. After doing a bunch of subtraction, a BRK is hit and execution resumes in the middle of a large data table. Given the onslaught of indirect addressing, results are fully unpredictable. A number of branch instructions can potentially occur, but overall almost all outcomes will be a crash.

IAx6D

IAx6D_entry
????

This entry points to a location in WRAM, specifically an array of ancillae OAM priority values. Results are, you guessed it, fully unpredictable.

IAx6E

IAx6E_entry
????

This entry points to a location in WRAM, specifically the torch timer array. Unlike IAx52, this points to the beginning of the array, which is actually used in practice. Unfortunately, this still results in unpredictable code or a BRK.

IAx6F

IAx6F_entry
BMI $30DB
IAx6F_entry
BMI $30DF
IAx6F_entry
BMI $30E1
IAx6F_entry
BMI $30E3

This entry points to a location in open bus. It will constantly not branch on negatives until it reaches CPU registers. Afterwards, the results are fully unpredictable.

IAx70

IAx70_entry
????

This entry points to a location in WRAM, specifically the last byte in an array that counts the number spiral staircases. The results are fully unpredictable.

IAx71

IAx71_entry
????

This entry points to a location in WRAM, specifically part of the arbitrary DMA buffer. The results are fully unpredictable.

IAx72

PHX
Code before entry for reference
LDA $7F5810, X
CMP #$0D
BEQ +118
Does not align with below
IAx72_entry
ROR $0A, X
CLC
ADC $7F5810, X
CLC
ADC #$02

This entry points to a location in ROM, specifically part of the bombos spell draw routine. The entry point does not align perfectly with the code, but it recovers after one instruction. Unfortunately, the entry is after where a stack push was meant to occur, so at the end of this routine, the stack will become unbalanced, resulting in a crash. I mean... The results are fully unpredictable.

IAx73

IAx73_entry
SEP #$85
BRK #$E2
brk_vector
BRL $F00F
CMP $7E
TAY
AND #$E0
ORA $F0, S
ASL $98
SEC
SBC #$20
BRK #$A8
brk_vector
BRL $F00F

This entry points to a location in ROM, specifically part of the Skull Woods flame routine. The entry point does not align with the code and ends up executing a BRK. Interestingly, the SEP instruction sets the interrupt-disable flag; however, this flag has no effect on a software interrupt.

The location branched to does not align perfectly with the code. It recovers after 1 instruction; however, the routine entered expects both the accumulator and index registers to be in 16-bit mode. Having entered with both in 8-bit mode, the CPU fails to stay aligned with the intended code. This is irrelevant, as the code eventually enters a BRK loop.

IAx74

IAx74_entry
BRK #$99
brk_vector
BRL $BC11
EOR ($85), Y
etc.

This entry points to a location in ROM, specifically the end of the dash tremor code. The break vector hit leads to a data table, where endless direct page instructions are executed. They're not worth documenting, and I hope the game eventually crashes.

IAx75

IAx75_entry
????

This entry points to a location in WRAM, specifically the ancilla layer array. Without using other misslots to write different values here, only $00 or $01 will be present. The results are fully unpredictable.

IAx76

This illegal ancilla shares the same pointer and effect as IAx61.

IAx77

IAx77_entry
STA $0C0E, X
PLA
STA $0BFA, X
PLA
STA $0C18, X
PLA
STA $0C04, X

This entry points to a location in ROM, specifically part of an ancilla hitbox routine. The entry point aligns perfectly with the code; however, it skips over several stack pushes that were meant to occur. This leaves the stack unbalanced, causing the game to eventually crash.

IAx78

IAx78_entry
????

This entry points to a location in WRAM, specifically a general purpose property of sprites. Predictably, the results are fully unpredictable.

IAx79

IAx79_entry
????

This entry points to a location in WRAM, specifically part of the OAM buffer. The results are fully unpredictable.

IAx7A

IAx7A_entry
TSB $9091
Write to ROM ignored
LDA $01
SEC
SBC #$04
LDX $0FAC
LDA $8F82, X
LDY #$02
RTS

This entry points to a location in ROM, specifically the repulse spark draw routine. The entry point does not align perfectly with the code, but it recovers after 1 instruction. Eventually the routine returns cleanly.

Of particular interest is the instruction at $08:905A, which loads the X register with the value of $0FAC. The repulse spark is not a slotted ancilla, so it does not have any recovery of slot after this. Thus, this instruction will determine the next slot executed −1. Normally, this address only takes on values from 0–5; however, if we found a way to modify this address, we could execute ancillae in the illegal slots from $0A$7F.

This also comes with the potential to hardlock the game in an infinite loop. For example, if IAx7A is in slot 0 and $0FAC has a value of $02, then the next ancilla executed will be slot 1, followed by slot 0 again. This will repeat ad infinitum.

Less interestingly, this illegal ancilla should also result in some broken objects on screen.

IAx7B

This illegal ancilla shares the same pointer and effect as IAx78.

IAx7C

IAx7C_entry
????

This entry points to a location in WRAM, specifically the an array that counts the number spiral staircases. The results are fully unpredictable.

IAx7D

IAx7D_entry
BRA $7FB1
STA $8D8D
BRA $7F36
BRA $7EB8

This entry points to a location in ROM, specifically an ancilla sound effects routine. The entry point does not align with code, causing it to branch to open bus. It will keep branching backwards until it reaches CPU registers or the A bus is clobbered. These results are fully unpredictable.

Under most circumstances, HDMA will indeed clobber the A register, and the CPU should eventually reach $08:8000, where it will put a broken sound effect into SFX set 1 and allow this ancilla to recover cleanly.

IAx7E

This illegal ancilla shares the same pointer and effect as IAx78.

IAx7F

IAx7F_entry
????

This entry points to a location in WRAM, specifically the current underworld room ID. The top byte located in $A1 an only ever contain $00 or $01. These results are fully unpredictable, though being in room $60 can return safely.

IAx80

IAx80_entry
????

This entry points to a location in WRAM, specifically a general purpose property of F-slot ancillae. The results are fully unpredictable.

Further illegal ancilla

Due to the left shift when calculating the index into the pointer table, illegal ancillae with an ID of $81 and higher will behave as the ancilla with an ID $80 lower. This is because the top bit is discarded after decrementing, resulting in the same index.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.

The results are fully unpredictable.