SMB1 Hacking
-
Pokun
- Posts: 3441
- Joined: Tue May 28, 2013 5:49 am
- Location: Hokkaido, Japan
Re: SMB1 Hacking
I see, that might explain why I could never master turtle tipping.
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
Here's a chunk of code from SMB that bothers me:
What I am thinking here is this: if this code truly means to check for either game engine subroutines #$07 (for player entering) or #$08 (for player control), then perhaps they likely meant to make the first branch instruction a BCC (branch to do pipe entry routine if the current game engine subroutine is between #$00 and #$06), and if G.E. subroutine ID #$08 is running, then shouldn't that BNE be a BEQ instead if the comments imply we want the program to exit as long as either G.E. subroutine IDs #$07 or #$08 are running?
~Ben
Code: Select all
ChkGERtn: lda GameEngineSubroutine ;get number of game engine routine running
cmp #$07
beq ExCSM ;if running player entrance routine or
cmp #$08 ;player control routine, go ahead and branch to leave
bne ExCSM
lda #$02
sta GameEngineSubroutine ;otherwise set sideways pipe entry routine to run
rts ;and leave
Code: Select all
ChkGERtn: lda GameEngineSubroutine ;get number of game engine routine running
cmp #$07
bcc SetPERtn ;if running routines #$06 or lower, branch to do pipe entry routine below
cmp #$08 ;otherwise, if doing routines #$07 or #$08, go ahead and branch to leave
beq ExCSM
SetPERtn: lda #$02
sta GameEngineSubroutine ;otherwise set sideways pipe entry routine to run
rts ;and leave
-
segaloco
- Posts: 911
- Joined: Fri Aug 25, 2023 11:56 am
Re: SMB1 Hacking
The first chunk hits ExCSM basically any time except when the game mode is mode 8. The check against 7 is pointless, because if it is 7, the bne on 8 will branch anyway. The second does indeed do the same, but an easier way would be
This bit is at the tail end of the calculation routine for side checks, the check to see if the player should enter a pipe horizontally. In my disassembly it looks something like so:
Where the lazy branch goes out past a subroutine that is called after this. So yeah, you only do a pipe enter if it is currently the player control cycle. Maybe originally it was supposed to support either? This could have something to do with the pipe entrance levels like the start of 1-2, but I'm not certain.
I hope since this is only a tiny snippet of code and not effectively sharing the whole thing, this is fine. This is the original code from Nintendo:
So indeed even in their commentary it is acknowledged what is happening here. I took a look at the original hoping to have spotted something different, that maybe the comments were mixed up and the branch was to be avoided for both PLCMOD and PLMDPY. But this seems to indicate the code is what was intended.
Code: Select all
ChkGERtn:
lda GameEngineSubroutine
cmp #$08
bne ExCSM
lda #$02
sta GameEngineSubroutine
rts
Code: Select all
lda proc_id_player
cmp #player_procs::enter
beq :++
cmp #player_procs::ctrl
bne :++ ; if (game.proc_id == ctrl) {
lda #player_procs::pipe_h
sta proc_id_player
rts
; }
I hope since this is only a tiny snippet of code and not effectively sharing the whole thing, this is fine. This is the original code from Nintendo:
Code: Select all
CPB445 EQU $
LDA <PLCMOD
CMP #PLSMMD
BEQ CPB480 ;- IF start move mode ? ( YES ; CPB480 )
;
CMP #PLMDPY
BNE CPB480 ;- IF play mode ? ( NO ; CPB480 )
;
LDA #PLC1MD
STA <PLCMOD ; In L-chimney mode set
;
RTS
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
Matt,segaloco wrote: Mon Oct 27, 2025 10:25 pm The first chunk hits ExCSM basically any time except when the game mode is mode 8. The check against 7 is pointless, because if it is 7, the bne on 8 will branch anyway. The second does indeed do the same, but an easier way would be
This bit is at the tail end of the calculation routine for side checks, the check to see if the player should enter a pipe horizontally. In my disassembly it looks something like so:Code: Select all
ChkGERtn: lda GameEngineSubroutine cmp #$08 bne ExCSM lda #$02 sta GameEngineSubroutine rts
Where the lazy branch goes out past a subroutine that is called after this. So yeah, you only do a pipe enter if it is currently the player control cycle. Maybe originally it was supposed to support either? This could have something to do with the pipe entrance levels like the start of 1-2, but I'm not certain.Code: Select all
lda proc_id_player cmp #player_procs::enter beq :++ cmp #player_procs::ctrl bne :++ ; if (game.proc_id == ctrl) { lda #player_procs::pipe_h sta proc_id_player rts ; }
I hope since this is only a tiny snippet of code and not effectively sharing the whole thing, this is fine. This is the original code from Nintendo:
So indeed even in their commentary it is acknowledged what is happening here. I took a look at the original hoping to have spotted something different, that maybe the comments were mixed up and the branch was to be avoided for both PLCMOD and PLMDPY. But this seems to indicate the code is what was intended.Code: Select all
CPB445 EQU $ LDA <PLCMOD CMP #PLSMMD BEQ CPB480 ;- IF start move mode ? ( YES ; CPB480 ) ; CMP #PLMDPY BNE CPB480 ;- IF play mode ? ( NO ; CPB480 ) ; LDA #PLC1MD STA <PLCMOD ; In L-chimney mode set ; RTS
Thank you!
~Ben
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
https://www.youtube.com/watch?v=UQkNNcJULNs
You may be familiar with this glitch. You grab a Starman and run as fast as you can to the ending flagpole. When the invincibility wears off, the ground level music plays for the rest of the ending sequence. This can be done in either World 1-1 or 6-2.
This was never fixed in VSSMB, SMB2J or ANNSMB, but it was fixed in SMAS (as well as in SMB Deluxe for the GBC); the official fix in SMAS went like this:
This 10-byte patch was included in the "GetAreaMusic" subroutine and it involved checking if the player was currently doing either the flagpole slide (ID $04) or end-of-level (ID $05) game engine subroutines and, if so, not to play any area music again during these times.
However, there are other ways to fix this glitch. In both SMB2J and ANNSMB (but not in SMAS -- why?), there is a special flag called "FlagpoleMusicFlag" (RAM $07F6) that is first initialized during the SecondaryGameSetup subroutine and then first checked during the level end subroutine (ID $05 in the list of game engine subroutines) and the specific check was that if the end-of-level music was already playing, to not play it again (if this flag was set to 1, BNE to ChkStop).
Some people have added a second check for $07F6 within the star invincibility subroutine (if FlagpoleMusicFlag = 1, then BNE to NoChgMus), but I also tried that within the GetAreaMusic subroutine (if FlagpoleMusicFlag = 1, then BNE to ExitGetM) and it also fixes the area music vs. end-of-level music bug perfectly.
I also got the same result checking for the end-of-level music within the EventMusicBuffer flag during the star invincibility routine, and if this music is playing already, to BEQ to NoChgMus:
~Ben
You may be familiar with this glitch. You grab a Starman and run as fast as you can to the ending flagpole. When the invincibility wears off, the ground level music plays for the rest of the ending sequence. This can be done in either World 1-1 or 6-2.
This was never fixed in VSSMB, SMB2J or ANNSMB, but it was fixed in SMAS (as well as in SMB Deluxe for the GBC); the official fix in SMAS went like this:
Code: Select all
CODE_05C925:
LDA $0F ; $05:C925: A5 0F
CMP #$04 ; $05:C927: C9 04
BEQ CODE_05C944 ; $05:C929: F0 19
CMP #$05 ; $05:C92B: C9 05
BEQ CODE_05C944 ; $05:C92D: F0 15
LDA $DB ; $05:C92F: A5 DB
CMP #$1B ; $05:C931: C9 1B
BNE CODE_05C939 ; $05:C933: D0 04
LDA #$01 ; $05:C935: A9 01
BRA CODE_05C93C ; $05:C937: 80 03
However, there are other ways to fix this glitch. In both SMB2J and ANNSMB (but not in SMAS -- why?), there is a special flag called "FlagpoleMusicFlag" (RAM $07F6) that is first initialized during the SecondaryGameSetup subroutine and then first checked during the level end subroutine (ID $05 in the list of game engine subroutines) and the specific check was that if the end-of-level music was already playing, to not play it again (if this flag was set to 1, BNE to ChkStop).
Some people have added a second check for $07F6 within the star invincibility subroutine (if FlagpoleMusicFlag = 1, then BNE to NoChgMus), but I also tried that within the GetAreaMusic subroutine (if FlagpoleMusicFlag = 1, then BNE to ExitGetM) and it also fixes the area music vs. end-of-level music bug perfectly.
Code: Select all
GetAreaMusic:
lda OperMode ;if in title screen mode, leave
beq ExitGetM
lda FlagpoleMusicFlag ;PATCH check flag for end-of-level music playing
bne ExitGetM ;PATCH if so, leave
lda AltEntranceControl ;check for specific alternate mode of entry
I also got the same result checking for the end-of-level music within the EventMusicBuffer flag during the star invincibility routine, and if this music is playing already, to BEQ to NoChgMus:
Code: Select all
lda Player_Y_HighPos
cmp #$02 ;if player is below the screen, don't bother with the music
bpl NoChgMus
lda StarInvincibleTimer ;if star mario invincibility timer at zero,
beq ClrPlrPal ;skip this part
cmp #$04
bne NoChgMus ;if not yet at a certain point, continue
lda IntervalTimerControl ;if interval timer not yet expired,
bne NoChgMus ;branch ahead, don't bother with the music
lda EventMusicBuffer ;PATCH check event music buffer for end-of-level music
cmp #EndOfLevelMusic ;PATCH
beq NoChgMus ;PATCH if this music currently playing, don't bother with the music
jsr GetAreaMusic ;to re-attain appropriate level music
NoChgMus: ldy StarInvincibleTimer ;get invincibility timer
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
https://www.youtube.com/watch?v=WvojgRvNriw&t=11m55s
This video for why constantly stomping on Wigglers in Super Mario World cause many glitches to happen in your score, has led me to ask: I wonder if I can do something similar in the original SMB1?
That is, I am wondering I can add something like CMP #$08 which tells the game to stop incrementing the stomp counter if it is #$08 or more?
~Ben
This video for why constantly stomping on Wigglers in Super Mario World cause many glitches to happen in your score, has led me to ask: I wonder if I can do something similar in the original SMB1?
Code: Select all
HandleStompedShellE:
lda #$04 ;set defeated state for enemy
sta Enemy_State,x
inc StompChainCounter ;increment the stomp counter
lda StompChainCounter ;add whatever is in the stomp counter
clc ;to whatever is in the stomp timer
adc StompTimer
jsr SetupFloateyNumber ;award points accordingly
inc StompTimer ;increment stomp timer of some sort
ldy PrimaryHardMode ;check primary hard mode flag
~Ben
-
segaloco
- Posts: 911
- Joined: Fri Aug 25, 2023 11:56 am
Re: SMB1 Hacking
A bounds check should be simple enough, if below, increment and act, if at or above, do nothing related to stomp counts. Reset the counter as usual when the player lands. That should be a sensible enough approach to fix the problem at its source. Then it doesn't matter if you can manage a perma-hop on a shell, it won't keep counting.
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
If you'd like to know the exact snippet of code for your convenience, so you can try to rework it to make it work for SMB1, here it is:segaloco wrote: Thu Oct 30, 2025 6:26 pm A bounds check should be simple enough, if below, increment and act, if at or above, do nothing related to stomp counts. Reset the counter as usual when the player lands. That should be a sensible enough approach to fix the problem at its source. Then it doesn't matter if you can manage a perma-hop on a shell, it won't keep counting.
Code: Select all
IncreaseStompCounter:
phy
lda StompCounter
clc
adc ShellComboCounter,x
inc StompCounter
tay
iny
cpy #$08
bcs NoIncStomp
lda StompSFXData-1,y
sta PlaySFX
NoIncStomp:
tya
cmp #$08
bcc AwardStompPts
lda #$08
AwardStompPts:
jsl GivePoints
ply
rts
GivePoints:
phx
clc
adc #$05
jsl SpawnScoreSprite
plx
rtl
-
segaloco
- Posts: 911
- Joined: Fri Aug 25, 2023 11:56 am
Re: SMB1 Hacking
Yeah that'd have to be ported, it's full of 65816 opcodes, but could be a worthwhile test case in moving some code "back in time".
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
Matt,segaloco wrote: Thu Oct 30, 2025 8:34 pm Yeah that'd have to be ported, it's full of 65816 opcodes, but could be a worthwhile test case in moving some code "back in time".
https://www.youtube.com/watch?v=AAjh72ytS44&t=7m30s
This video by Kosmic, about new ways to score 1-UPs, is another reason why I brought up why I wish to fix the stomp counter register. He briefly talks about why the glitched 1-UP tile shows between the 5000 and 8000 points registers, the other thing I want to fix.
~Ben (SMB2J-2Q)
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
Code: Select all
HandleStompedShellE:
lda #$04 ;set defeated state for enemy
sta Enemy_State,x
lda Player_State ;BUGFIX -- check player's current state
beq SkipStompCtrInc ;BUGFIX -- if player on ground, branch to skip stomp counter increment
inc StompChainCounter ;increment the stomp counter
lda StompChainCounter ;add whatever is in the stomp counter
clc ;to whatever is in the stomp timer
adc StompTimer
jsr SetupFloateyNumber ;award points accordingly
inc StompTimer ;increment stomp timer of some sort
ldy PrimaryHardMode ;check primary hard mode flag
lda RevivalRateData,y ;load timer setting according to flag
sta EnemyIntervalTimer,x ;set as enemy timer to revive stomped enemy
lda #$00 ;BUGFIX -- clear A to avoid anomalies elsewhere in code if stomp counter was reset
SkipStompCtrInc:
sta StompChainCounter ;BUGFIX
SBnce: lda #$fc ;set player's vertical speed for bounce
sta Player_Y_Speed ;and then leave!!!
rts
UPDATE 1: However, here is an error that happens after I applied this: stomping on one Goomba the points register okay, but stomping on another won't; you'll just be bouncing on it but without being able to stomp it. (Picture here) Hence, may I please ask: where else should I put the player state check?
~Ben
You do not have the required permissions to view the files attached to this post.
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
I just discovered something in both SMB2J and ANNSMB: in both these games, under the "SecondaryGameSetup" routine the "ISpr0Loop" subroutine is no longer present, and both the FDS titles also have a new NameTableSelect flag.
I added the .ifdef, .ifndef and .else labels to mark which games have this and which don't.
I am wondering if the ISpr0Loop routine is considered residual code?
~Ben (SMB2J-2Q)
I added the .ifdef, .ifndef and .else labels to mark which games have this and which don't.
Code: Select all
SecondaryGameSetup:
lda #$00
sta DisableScreenFlag ;enable screen output
.ifdef SMB2J
sta WindFlag
.endif
.ifndef SMB1
sta FlagpoleMusicFlag
.endif
tay
ClearVRLoop: sta VRAM_Buffer1-1,y ;clear buffer at $0300-$03ff
iny
bne ClearVRLoop
sta GameTimerExpiredFlag ;clear game timer exp flag
sta DisableIntermediate ;clear skip lives display flag
sta BackloadingFlag ;clear value here
lda #$ff
sta BalPlatformAlignment ;initialize balance platform assignment flag
lda ScreenLeft_PageLoc ;get left side page location
.ifdef SMB1
lsr Mirror_PPU_CTRL_REG1 ;shift LSB of ppu register #1 mirror out
and #$01 ;mask out all but LSB of page location
ror ;rotate LSB of page location into carry then onto mirror
rol Mirror_PPU_CTRL_REG1 ;this is to set the proper PPU name table
.else
and #$01 ;mask out all but LSB of page location
sta NameTableSelect
.endif
jsr GetAreaMusic ;load proper music into queue
lda #$38 ;load sprite shuffle amounts to be used later
sta SprShuffleAmt+2
lda #$48
sta SprShuffleAmt+1
lda #$58
sta SprShuffleAmt
ldx #$0e ;load default OAM offsets into $06e4-$06f2
ShufAmtLoop: lda DefaultSprOffsets,x
sta SprDataOffset,x
dex ;do this until they're all set
bpl ShufAmtLoop
.ifdef SMB1
ldy #$03 ;set up sprite #0
ISpr0Loop: lda Sprite0Data,y
sta Sprite_Data,y
dey
bpl ISpr0Loop
jsr DoNothing2 ;these jsrs doesn't do anything useful
jsr DoNothing1
inc Sprite0HitDetectFlag ;set sprite #0 check flag
.else
jsr DoNothing ;do slightly less of nothing than in super mario bros 1
inc IRQUpdateFlag
.endif
.ifdef ANN
jmp IncModeTask
.else
inc OperMode_Task ;increment to next task
rts
.endif
~Ben (SMB2J-2Q)
-
segaloco
- Posts: 911
- Joined: Fri Aug 25, 2023 11:56 am
Re: SMB1 Hacking
Here's my variation on this, which does have the proper ifdefs and assembles back into each version correctly:
So the bit that is yanked out of the V2 engine:
All this does is put a single filler tile in the base OAM slot. Perhaps the plan was to use this for 0 sprite strikes that never then got used.
If it helps, this is the original source:
Which is commented out in the SMAS sources. The only real hint there is the comment about scroll parts, but I really am not sure what that means. The OAM entry is then simply called "Scroll oam data" in the comments. Not sure if this helps illuminate what this was supposed to do, but the fact that it's also commented out for SMAS implies that they decided it wasn't really important.
Code: Select all
.export game_init_1_do
game_init_1_do:
lda #0
sta nmi_disp_disable
.if .defined(SMB2)
sta scenery_wind_flag
.endif
.if .defined(SMBV2)
sta game_level_end_apu_on
.endif
tay
: ; for (byte of ppu_displist) {
sta ppu_displist, y
iny
bne :-
; }
sta game_time_up
sta bg_msg_disable
sta course_page_loaded
lda #$FF
sta actor_plat_align
lda pos_x_hi_screen_left
.if .defined(SMBV1)
lsr ppu_ctlr0_b
.endif
and #ppu_ctlr0::bg_parity
.if .defined(SMBV1)
ror a
rol ppu_ctlr0_b
.elseif .defined(SMBV2)
sta ppu_ctlr0_bg
.endif
jsr game_init_area_music
lda #$38
sta sprite_strobe_amount+2
lda #$48
sta sprite_strobe_amount+1
lda #$58
sta sprite_strobe_amount
ldx #(game_init_data_0_end-game_init_data_0)-1
: ; for (byte of game_init_data_0) {
lda game_init_data_0, x
sta obj_data_offset_player, x
dex
bpl :-
; }
.if .defined(SMBV1)
ldy #(game_init_oam_end-game_init_oam)-1
: ; for (byte of game_init_oam) {
lda game_init_oam, y
sta oam_buffer+(OBJ_SIZE*0), y
dey
bpl :-
; }
jsr sub_92AA_rts
.endif
jsr sub_92AA
inc nmi_sprite_overlap
.if .defined(SMB)|.defined(VS_SMB)|.defined(SMB2)
inc proc_id_game
rts
.elseif .defined(ANN)
jmp bg_title_screen_finish
.endif
Code: Select all
ldy #(game_init_oam_end-game_init_oam)-1
: ; for (byte of game_init_oam) {
lda game_init_oam, y
sta oam_buffer+(OBJ_SIZE*0), y
dey
bpl :-
; }
jsr sub_92AA_rts
If it helps, this is the original source:
Code: Select all
; LDY #03H ; OBJ set used Scroll parts move !
;;
;GMI177 EQU $
; LDA SCLOMDT,Y
; STA OAM,Y
; DEY
; BPL GMI177 ;- IF set end ? ( NO ; GMI177 )
-
SMB2J-2Q
- Posts: 267
- Joined: Thu Jul 27, 2017 5:13 pm
Re: SMB1 Hacking
I just checked that one, and as you said, SMAS doesn't have the ISpr0Loop thing, either.segaloco wrote: Thu Nov 06, 2025 8:10 pm Here's my variation on this, which does have the proper ifdefs and assembles back into each version correctly:
So the bit that is yanked out of the V2 engine:Code: Select all
.export game_init_1_do game_init_1_do: lda #0 sta nmi_disp_disable .if .defined(SMB2) sta scenery_wind_flag .endif .if .defined(SMBV2) sta game_level_end_apu_on .endif tay : ; for (byte of ppu_displist) { sta ppu_displist, y iny bne :- ; } sta game_time_up sta bg_msg_disable sta course_page_loaded lda #$FF sta actor_plat_align lda pos_x_hi_screen_left .if .defined(SMBV1) lsr ppu_ctlr0_b .endif and #ppu_ctlr0::bg_parity .if .defined(SMBV1) ror a rol ppu_ctlr0_b .elseif .defined(SMBV2) sta ppu_ctlr0_bg .endif jsr game_init_area_music lda #$38 sta sprite_strobe_amount+2 lda #$48 sta sprite_strobe_amount+1 lda #$58 sta sprite_strobe_amount ldx #(game_init_data_0_end-game_init_data_0)-1 : ; for (byte of game_init_data_0) { lda game_init_data_0, x sta obj_data_offset_player, x dex bpl :- ; } .if .defined(SMBV1) ldy #(game_init_oam_end-game_init_oam)-1 : ; for (byte of game_init_oam) { lda game_init_oam, y sta oam_buffer+(OBJ_SIZE*0), y dey bpl :- ; } jsr sub_92AA_rts .endif jsr sub_92AA inc nmi_sprite_overlap .if .defined(SMB)|.defined(VS_SMB)|.defined(SMB2) inc proc_id_game rts .elseif .defined(ANN) jmp bg_title_screen_finish .endif
All this does is put a single filler tile in the base OAM slot. Perhaps the plan was to use this for 0 sprite strikes that never then got used.Code: Select all
ldy #(game_init_oam_end-game_init_oam)-1 : ; for (byte of game_init_oam) { lda game_init_oam, y sta oam_buffer+(OBJ_SIZE*0), y dey bpl :- ; } jsr sub_92AA_rts
If it helps, this is the original source:
Which is commented out in the SMAS sources. The only real hint there is the comment about scroll parts, but I really am not sure what that means. The OAM entry is then simply called "Scroll oam data" in the comments. Not sure if this helps illuminate what this was supposed to do, but the fact that it's also commented out for SMAS implies that they decided it wasn't really important.Code: Select all
; LDY #03H ; OBJ set used Scroll parts move ! ;; ;GMI177 EQU $ ; LDA SCLOMDT,Y ; STA OAM,Y ; DEY ; BPL GMI177 ;- IF set end ? ( NO ; GMI177 )
Strange too is that in SMAS, for both SMB1 and SMB2J the JSR to GetAreaMusic is also commented out.
~Ben