Professional Documents
Culture Documents
SMB3 All Forts Wrong Warp Detailed Explanation
SMB3 All Forts Wrong Warp Detailed Explanation
If we jump to $9057, we end up clearing the ram through $06FF -> $0000,
excluding the stack ($01xx)
We then increment the world number and end up in world 8, but this time
because the ram has been cleared properly, the world map appears
normal, not glitched, and we can’t bypass the autoscroller #1.
So we at least need to jump to $905C, which leaves the ram as it was, and
this is what ends up corrupting the world map and the reason passing
over the autoscroller doesn’t pull you in.
So the game wants to store the score that you had at the end of the level
the “in level” score, to the “out of level” score Ram address. However,
depending on where we jump to in this section of code, the indexing into
this part of the code so it knows whether to either store mario or luigi’s
score, and where in the ram it should store it, can go very wrong to the
point the inventory gets overwritten with incorrect data.
Normally, if player is Mario, the X register gets set to 0, and Y gets set to
$#1F. Then at $9080, we load the “in level” score to the Accumulator.
This is done by Loading A indexed with X, at address $0715, then at
$9083 store it to the “out of level” ram at address #$7D9F. This is
achieved via storing to address $7D80 indexed with Y, with Y being #$1F
if player is mario, this yields address $7D9F. There is then a loop to copy
the next two bytes as well, as the score is 3 bytes next to each other.
Note that in RAM, mario’s Inventory items, Score cards, and Score, are all
stored next to each other in Ram
$7D80 - $7D9B = Mario’s inventory
$7D9C - $7D9E = Mario’s Cards
$7D9F - 7DA1 = Mario’s score
Code at $907D:
TAY ; A ->Y, Y now holds offset to score
LDX #$00 ; X = 0
Code at $9080
LDA $0715,X ; Load players score (“in level”) into A
STA $7D80, Y ; Store A into memory address $7D80, indexed with
#$1F = $7D9F (Mario’s score “out of level”)
INY ; Increment Y (Next byte of Mario’s score)
INX ; Increment X (Next byte of players score)
CPX #$03
BNE $9080 ; While X <> 3, loop back to $9080 (This means it
copies across the three bytes of the player score(in level) into Mario’s
score (out of level)
LDA #$80
STA $04F4 ; Stop Music
INC $0727 ; Increment World_Num, i.e go to next world!
JMP $84A0 ; Jump to $84A0 i.e Initialise the world map.
Note that these last two lines of the code is where the skip to world 8
comes from.
One other thing: The reason we end up back as hammer mario (or what
ever powerup we entered 7-1 in) is because we have skipped the normal
level ending routine. The powerup status in the “out of level” ram is still
set to hammer suit (because we had the hammer suit when we entered 7-
1). Although we end up small mario when we execute the glitch, because
we skipped the part of the end level routine which sets the "in level"
powerup status to the “out of level” power status, as far as the game
engine is concerned, we never lost the hammer suit! So on the overworld
map when it loads the “out of level” powerup status, it is still set to
hammer suit. Yay for glitches!
EDIT:
Made a twitch video demonstrating using $9071 as our point to jump to.
We keep our inventory we had before we entered 7-1, keep our hammer
suit, and don’t get any glitched out items, cards, or scores.
After a bit more work and research, I have decided to change the jump
address to $908D instead of $9071. The main reason is so the top waling
Koopa can be killed against the left ? block from the left side, setting its
value to #$8D.
So the values we want are now 20 8D 90. Note that from the code:
Code at $908C
LDA #$80 ; Music value to stop music playing
STA $04F4 ; Store “stop music” value to $04F4 (Sound_QMusic1)
INC $0727 ; Increment World_Num, i.e go to next world!
JMP $84A0 ; Jump to $84A0 i.e Initialise the world map.
If we jump to $908D, then execution starts with the byte $80, which is an
unofficial opcode i.e we are ‘byte misaligned’.
$80 is a two byte opcode, and has the effect of a two byte NOP
instruction.
So the next instruction to execute is at $908F, which is $F4, again, an
unofficial opcode. Thankfully, again, this is a two byte opcode, and has the
effect of a two byte NOP instruction.
This means our next byte to execute is $9091, which is our INC $0727
instruction as in the above code. Execution then continues normally, next
code to execute being the JMP $84A0, which is a jump to the “initialise the
world map” code. From here the code executes normally, however, due to
the ram between $06FF and $0000 excluding the stack ($01xx) not being
set to zero which should have occurred at $9057:
Code at $9057
LDY #$06 ; Load Y with #$06
JSR Clear_Ram_Through_Zero_Page ; Jump to subroutine that clears
all RAM between $YYFF and $0000, and skips the stack i.e clear $06FF -
$0200, and $00FF - $0000
Because we skipped this section of code, this is what results in the world
map being all corrupted when we jump there. It is the sole reason that the
autoscroller can be skipped.
1.36
256 IGT
1.39.27easysetup
1.35.21easysetiup
253IGT
I have made a shell throw demonstation setup that saves around 15-17
seconds compared to my last highlight, but the last shell is frame perfect.
thankfully it's not double frame perfect like Kirua's any% world record, in
other worlds, once you enter the pipe holding the shell, you can just
release B and then it comes down to hitting the A button on the correct
frame so the shell that flies through the air passes x position #$90 just as
we hit A (actually i think its the frame before this from memory, cause the
controller is read at the end of the frame)
Realistically I doubt anyone would go for this in a run 40 minutes in, but
hey, may as well demonstrate the concept. Even though my execution of
this was not perfect, after practising for a while I calculated a time save of
around 15 - 17 seconds compared to the consistent setup.