SMB1 Hacking

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

TakuikaNinja wrote: Fri Jan 27, 2023 4:25 am I've been working on my own bugfix hack with a few others using the asm6 version of the disassembly. Most of the work was done independently from this thread but I plan on implementing the changes discussed here.
https://github.com/TakuikaNinja/smb1-bugfix

The most substantial change by far is the overhaul of the NMI handler. Now the game logic is handled outside of the NMI handler and lag frames are properly accounted for. Glitched scanlines and HUD flickering is no more.
That was pretty good and I know you'd done some minor changes to it since.

Thank you,



Ben (SMB2J-2Q)
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: 8000 Points for Bowser in Worlds 6-8?

Post by SMB2J-2Q »

SMB2J-2Q wrote: Tue Oct 18, 2022 7:05 pm While I know that Bowser is worth 5000 points whenever he's destroyed with five fireballs, I believe on the harder levels where he also throws hammers at you (worlds 6-4, 7-4 and 8-4), I think for all those who've made it that far, he should be worth 8000 points because of this increased difficulty (according to the original NES game's instructions, the number of points given for tackling him is only "???").

Hence, may I ask how I would extend the points award routine for having fireballed Bowser, so that doing so gives the player 8000 points for worlds 6-8? Here's the code for the original routine in question from doppelganger's SMB1 disassembly:

Code: Select all

SetDBSte: sta Enemy_State,x     ; set defeated enemy state
          lda #SFX_BowserFall
          sta Square2SoundQueue ; load Bowser defeat sound
          ldx $01               ; get enemy offset
          lda #$09              ; award 5000 points to player for defeating Bowser
          bne EnemySmackScore   ; unconditional branch to award points
UPDATE: I got this to work correctly, by adding these lines of code within SetDBSte:

Code: Select all

SetDBSte: [...]
          lda #$09              ; ORIGINAL CODE: award 5000 points to player for defeating Bowser
          ldy WorldNumber       ; NEW CODE: check world number
          cpy #World6           ; NEW CODE: are we on world 6?
          bcc EnemySmackScore   ; NEW CODE: if not, world 1 thru 5, so branch
          lda #$0a              ; NEW CODE: if so, award 8000 points instead
          bne EnemySmackScore   ; ORIGINAL CODE: unconditional branch to award points
And here's a screenshot of the proof:
Image

~Ben
I just made an update to this code. What I did this time around was add a data table to store the two points values.

Code: Select all

BowserPoints:
    .db $09, $0a

Code: Select all

HurtBowser:
...
       ldy #$00            ;load $00 as default offset for 5000 points
       lda WorldNumber     ;check if we're on world 6 or later
       cmp #World6
       bcc GetBP           ;if not, branch
       iny                 ;otherwise increment Y to get 8000 points instead
GetBP: lda BowserPoints,y  ;get appropriate score value from data table above
       bne EnemySmackScore ;unconditional branch to award points
~Ben
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

I want to add another level to SMB1, and here's what I did so far:

Code: Select all

EnemyAddrHOffsets:
      .db $20, $06, $1d, $00

EnemyDataAddrLow:
      .db <E_CastleArea1, <E_CastleArea2, <E_CastleArea3, <E_CastleArea4, <E_CastleArea5, <E_CastleArea6
      .db <E_GroundArea1, <E_GroundArea2, <E_GroundArea3, <E_GroundArea4, <E_GroundArea5, <E_GroundArea6
      .db <E_GroundArea7, <E_GroundArea8, <E_GroundArea9, <E_GroundArea10, <E_GroundArea11, <E_GroundArea12
      .db <E_GroundArea13, <E_GroundArea14, <E_GroundArea15, <E_GroundArea16, <E_GroundArea17, <E_GroundArea18
      .db <E_GroundArea19, <E_GroundArea20, <E_GroundArea21, <E_GroundArea22, <E_GroundArea23,
      .db <E_UndergroundArea1, <E_UndergroundArea2, <E_UndergroundArea3, <E_WaterArea1, <E_WaterArea2, <E_WaterArea3

EnemyDataAddrHigh:
      .db >E_CastleArea1, >E_CastleArea2, >E_CastleArea3, >E_CastleArea4, >E_CastleArea5, >E_CastleArea6
      .db >E_GroundArea1, >E_GroundArea2, >E_GroundArea3, >E_GroundArea4, >E_GroundArea5, >E_GroundArea6
      .db >E_GroundArea7, >E_GroundArea8, >E_GroundArea9, >E_GroundArea10, >E_GroundArea11, >E_GroundArea12
      .db >E_GroundArea13, >E_GroundArea14, >E_GroundArea15, >E_GroundArea16, >E_GroundArea17, >E_GroundArea18
      .db >E_GroundArea19, >E_GroundArea20, >E_GroundArea21, >E_GroundArea22, >E_GroundArea23,
      .db >E_UndergroundArea1, >E_UndergroundArea2, >E_UndergroundArea3, >E_WaterArea1, >E_WaterArea2, >E_WaterArea3

AreaDataHOffsets:
      .db $00, $03, $1a, $1d

AreaDataAddrLow:
      .db <L_WaterArea1, <L_WaterArea2, <L_WaterArea3, <L_GroundArea1, <L_GroundArea2, <L_GroundArea3
      .db <L_GroundArea4, <L_GroundArea5, <L_GroundArea6, <L_GroundArea7, <L_GroundArea8, <L_GroundArea9
      .db <L_GroundArea10, <L_GroundArea11, <L_GroundArea12, <L_GroundArea13, <L_GroundArea14, <L_GroundArea15
      .db <L_GroundArea16, <L_GroundArea17, <L_GroundArea18, <L_GroundArea19, <L_GroundArea20, <L_GroundArea21
      .db <L_GroundArea22, <L_GroundArea23, <L_UndergroundArea1, <L_UndergroundArea2, <L_UndergroundArea3, 
      .db <L_CastleArea1, <L_CastleArea2, <L_CastleArea3, <L_CastleArea4, <L_CastleArea5, <L_CastleArea6

AreaDataAddrHigh:
      .db >L_WaterArea1, >L_WaterArea2, >L_WaterArea3, >L_GroundArea1, >L_GroundArea2, >L_GroundArea3
      .db >L_GroundArea4, >L_GroundArea5, >L_GroundArea6, >L_GroundArea7, >L_GroundArea8, >L_GroundArea9
      .db >L_GroundArea10, >L_GroundArea11, >L_GroundArea12, >L_GroundArea13, >L_GroundArea14, >L_GroundArea15
      .db >L_GroundArea16, >L_GroundArea17, >L_GroundArea18, >L_GroundArea19, >L_GroundArea20, >L_GroundArea21
      .db >L_GroundArea22, >L_GroundArea23, >L_UndergroundArea1, >L_UndergroundArea2, >L_UndergroundArea3, 
      .db >L_CastleArea1, >L_CastleArea2, >L_CastleArea3, >L_CastleArea4, >L_CastleArea5, >L_CastleArea6

Code: Select all

;goal area used in levels 2-2 and 7-2
E_GroundArea23:
      .db $ff

Code: Select all

;level 2-2/7-2
E_WaterArea2:
;$36 is new goal pipe scene
      .db $0f, $01, $2e, $36, $2b, $4e, $36, $cb, $6b, $07
      .db $97, $47, $e9, $87, $47, $c7, $7a, $07, $d6, $c7
      .db $78, $07, $38, $87, $ab, $47, $e3, $07, $9b, $87
      .db $0f, $09, $68, $47, $db, $c7, $3b, $c7
      .db $ff

Code: Select all

;goal area used in levels 2-2 and 7-2
L_GroundArea23:
      .db $90, $31
      .db $39, $f1, $5f, $38, $6d, $c1, $af, $26
      .db $fd
When I initially tried it out, the game crashed and went to a black screen, so may I ask what else I should fix in addition to what I included here?

Thank you,


~Ben (SMB2J-2Q)
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

https://www.youtube.com/watch?v=Y6HSZ1imGRQ

For those of you that want to include the pipe trio fix used in Super Mario All-Stars (see link above) into All Night Nippon Super Mario Bros., here is how it would be modified for this occasion.

First thing to do is to modify the WarpZoneObject routine to restore the INC $06D6 command:

Code: Select all

WarpZoneObject:
      lda ScrollLock         ;check for scroll lock flag
      beq ExGTimer           ;branch if not set to leave
      lda Player_Y_Position  ;check to see if player's vertical coordinate has
      and Player_Y_HighPos   ;same bits set as in vertical high byte (why?)
      bne ExGTimer           ;if so, branch to leave
      sta ScrollLock         ;otherwise nullify scroll lock flag
      inc WarpZoneControl    ;increment $06d6 to make warp pipes for warp zone 
      jmp EraseEnemyObject   ;kill this object
and then add (remember to change the first three branches directing to ExPipeE to NoJSFnd to avoid branching errors)...

Code: Select all

HandlePipeEntry:
    lda Up_Down_Buttons    ; check saved controller bits from earlier
    and #%00000100         ; for pressing down
    beq NoJSFnd            ; if not pressing down, branch to leave
    lda $00
    cmp #$11               ; check right foot metatile for warp pipe right metatile
    bne NoJSFnd            ; branch to leave if not found
    lda $01
    cmp #$10               ; check left foot metatile for warp pipe left metatile
    bne NoJSFnd            ; branch to leave if not found
    lda WarpZoneControl    ; check warp zone control
    beq ExPipeE            ; branch to leave if $06d6 not set
    bmi CalculateWPOffset  ; branch ahead if d7 set to calculate offset normally
    ldx #$80               ; use base number for game text routine as default warp zone (4-3-2)
    lda HardWorldFlag      ; if on worlds A-D, skip ahead to next part
    bne HardModeWarps      ; note d7 is set in all entries to prevent zero condition
    lda WorldNumber        ; from happening in warp zone code elsewhere
    beq RecalcWPOffsetSave ; if on world 1-2, branch to store present warp zone control value
    inx                    ; otherwise increment for second warp zone (5)
    lda AreaType           ; check area type
    sec
    sbc #$01               ; if not ground level type, branch ahead
    bne RecalcWPOffsetSave ; otherwise jump to increment X for last warp zone (8-7-6)
    jmp WarpZone3
HardModeWarps:
    ldx #$84               ; load base number for game text routine as default warp zone (C)
    lda WorldNumber        ; if in world B, branch to increment X for last warp zone (D)     
    bne WarpZone3  
    dex                    ; otherwise decrement X (if in world A-2) for first warp zone (B)
    lda LevelNumber
    sec
    sbc #$01
    beq RecalcWPOffsetSave ; if level number < 2, branch ahead
WarpZone3:
    inx
RecalcWPOffsetSave:
    txa
    sta WarpZoneControl    ; overwrite incorrect value in $06d6
CalculateWPOffset:
    and #%00000111         ; (original vanilla code resumes)      
Please review and let me know if I need to make any corrections here.

Thank you,



Ben (SMB2J-2Q)
Last edited by SMB2J-2Q on Mon Jan 15, 2024 2:51 pm, edited 2 times in total.
User avatar
segaloco
Posts: 278
Joined: Fri Aug 25, 2023 11:56 am
Contact:

Re: SMB1 Hacking

Post by segaloco »

SMB2J-2Q wrote: Sat Oct 14, 2023 6:36 pm I want to add another level to SMB1

Thank you,


~Ben (SMB2J-2Q)
I'd suggest taking a look at the SMB2 code, this has handling for the extra four courses they added as part of that title. These changes I believe are specific to SMB2 and not just the enhanced engine as I haven't also come across them in All Night Nippon. I'm still fuzzily working through it myself but I think the way it goes is there are so many places that count the course number up to course 8 and no higher that they decided to just control that you're on a second "page" of courses with a separate variable. In other words, when you get to course 9, reset the base course counter to 0 and increment the secondary counter to 1. Then you add support for this secondary counter in the places that matter. Whether this means you could then keep adding "pages" of courses like this or not, couldn't say.

Sorry that's vague but I'm actively working through a meta-disassembly of the four primary variants of this engine right now, so have been making some notes on what differences are in each. Have seen some code that smelled of this recently, so figured I'd bring it up. Best of luck!
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

segaloco wrote: Mon Jan 15, 2024 1:54 pm
SMB2J-2Q wrote: Sat Oct 14, 2023 6:36 pm I want to add another level to SMB1

Thank you,


~Ben (SMB2J-2Q)
I'd suggest taking a look at the SMB2 code, this has handling for the extra four courses they added as part of that title. These changes I believe are specific to SMB2 and not just the enhanced engine as I haven't also come across them in All Night Nippon. I'm still fuzzily working through it myself but I think the way it goes is there are so many places that count the course number up to course 8 and no higher that they decided to just control that you're on a second "page" of courses with a separate variable. In other words, when you get to course 9, reset the base course counter to 0 and increment the secondary counter to 1. Then you add support for this secondary counter in the places that matter. Whether this means you could then keep adding "pages" of courses like this or not, couldn't say.

Sorry that's vague but I'm actively working through a meta-disassembly of the four primary variants of this engine right now, so have been making some notes on what differences are in each. Have seen some code that smelled of this recently, so figured I'd bring it up. Best of luck!
I think I can decipher it through this...

Code: Select all

AreaDataOfsLoopback:
    .db $12, $36, $0e, $0e, $0e, $32, $32, $32, $0a, $26, $40
For All Night Nippon Super Mario Bros., it was mostly the same, except the last few bytes were $0c and $54, followed by $28, $08, $24 and $00 for Worlds A thru D (when hard mode unlocked after beating World 8 eight times).

Again, here is what I am trying to include here for the end of Worlds 2-2 and 7-2. It's similar to the end of World 1-1, but the scenery is of fields and trees instead of hills and mountains.

Image 1:
Screenshot from 2024-01-15 14-05-14.png
Image 2:
Screenshot from 2024-01-15 14-05-26.png
Here again is the relevant code for it (as used in All Night Nippon):

Code: Select all

;goal area used in levels 2-2 and 7-2
L_GroundArea23:
      .db $90, $31
      .db $39, $f1, $5f, $38, $6d, $c1, $af, $26
      .db $fd
~Ben
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

In Super Mario All-Stars, when playing a two-player game of Super Mario Bros., the current player is switched over not only if that player dies, but also whenever a level is completed.

Here's what I already did to change the two-player game behavior in the NES release:

Code: Select all

NextArea: inc AreaNumber            ;increment area number used for address loader
          lda #$00
          sta HalfwayPage           ;reset halfway page to 0 (beginning)
          jsr TransposePlayers      ;SMAS diff: if playing a 2-player game, do sub to switch player
Not2Players:
          jsr LoadAreaPointer       ;get new level pointer
          inc FetchNewGameTimerFlag ;set flag to load new game timer
          jsr ChgAreaMode           ;do sub to set secondary mode, disable screen and sprite 0
          lda #Silence
          sta EventMusicQueue       ;silence music and leave
ExitNA:   rts

Code: Select all

PlayerEntrance:
...
IntroEntr:  jsr EnterSidePipe         ;execute sub to move player to the right
            dec ChangeAreaTimer       ;decrement timer for change of area
            bne ExitEntr              ;branch to exit if not yet expired
            inc DisableIntermediate   ;set flag to skip world and lives display
            inc AreaNumber            ;increment to next area (SMAS diff: 2-player game fix)
            jmp Not2Players           ;jump to set modes (SMAS diff: 2-player game fix)
May I ask you folks what I should change/add in the "TransposePlayers" routine to make the original NES SMB1 behave the same way as in SMAS for two-player games? The reason I am asking is because when I first did the above two fixes, it saved the current player's size and status. Here is the code in question below. My goal is that if, for a new game, if Mario finishes as Super or Fiery Mario, when it becomes player 2's turn, Luigi will start out small as intended.

Code: Select all

TransposePlayers:
           sec                       ;set carry flag by default to end game
           lda NumberOfPlayers       ;if only a 1 player game, leave
           beq ExTrans
           lda OffScr_NumberofLives  ;does offscreen player have any lives left?
           bmi ExTrans               ;branch if not
           lda CurrentPlayer         ;invert bit to update
           eor #%00000001            ;which player is on the screen
           sta CurrentPlayer
           ldx #$06
TransLoop: lda OnscreenPlayerInfo,x    ;transpose the information
           pha                         ;of the onscreen player
           lda OffscreenPlayerInfo,x   ;with that of the offscreen player
           sta OnscreenPlayerInfo,x
           pla
           sta OffscreenPlayerInfo,x
           dex
           bpl TransLoop
           clc            ;clear carry flag to get game going
ExTrans:   rts
Thank you,



Ben (SMB2J-2Q)
User avatar
segaloco
Posts: 278
Joined: Fri Aug 25, 2023 11:56 am
Contact:

Re: SMB1 Hacking

Post by segaloco »

Looks like there are variables PlayerSize and PlayerStatus at $754 and $756 in the doppelganger disassembly. These do not seem to be arrays, rather, just a single value for tracking the properties of the current player, with the expectation that you'd only become the other player on death (i.e. player is shrunk, no powerup) so they didn't need to store the properties for the two players separately.

You'd just want to resize these two variables to encompass two bytes, then you can X index them the same as the PlayerInfo arrays there. If you're not in a place you can shift all of memory like that to get the two extra bytes, then instead you'd have to place them some distance apart and have some sort of logical check to take one or the other depending on which player is current. Just a hunch though, I haven't tried this.
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

segaloco wrote: Mon Feb 19, 2024 7:33 pm Looks like there are variables PlayerSize and PlayerStatus at $754 and $756 in the doppelganger disassembly. These do not seem to be arrays, rather, just a single value for tracking the properties of the current player, with the expectation that you'd only become the other player on death (i.e. player is shrunk, no powerup) so they didn't need to store the properties for the two players separately.

You'd just want to resize these two variables to encompass two bytes, then you can X index them the same as the PlayerInfo arrays there. If you're not in a place you can shift all of memory like that to get the two extra bytes, then instead you'd have to place them some distance apart and have some sort of logical check to take one or the other depending on which player is current. Just a hunch though, I haven't tried this.
For your curiosity, here is how it was coded in SMAS... it's quite a long one. Three new flags are used which are not present in Doppelganger's SMB1 disassembly. It also takes into account the player's present size, status and even the world select flag for after the player beat 8-4.

Code: Select all

; Swap players
; Output: Carry set = player couldn't be swapped. Carry clear = player could be swapped.

CODE_03A22B:
SEC                     ; $03:A22B: 38
LDA $077A               ; $03:A22C: AD 7A 07

; Branch if single player
BEQ CODE_03A280         ; $03:A22F: F0 4F
LDA $0761               ; $03:A231: AD 61 07

; Branch if previous player's extra lives is below 0
BMI CODE_03A280         ; $03:A234: 30 4A
LDA $0754               ; $03:A236: AD 54 07
PHA                     ; $03:A239: 48
LDA $077F               ; $03:A23A: AD 7F 07
STA $0754               ; $03:A23D: 8D 54 07
PLA                     ; $03:A240: 68

; Swap previous player's small flag with current player's
STA $077F               ; $03:A241: 8D 7F 07
LDA $0756               ; $03:A244: AD 56 07
PHA                     ; $03:A247: 48
LDA $0780               ; $03:A248: AD 80 07
STA $0756               ; $03:A24B: 8D 56 07
PLA                     ; $03:A24E: 68

; Swap previous player's powerup with current player's
STA $0780               ; $03:A24F: 8D 80 07
LDA $07FC               ; $03:A252: AD FC 07
PHA                     ; $03:A255: 48
LDA $0781               ; $03:A256: AD 81 07
STA $07FC               ; $03:A259: 8D FC 07
PLA                     ; $03:A25C: 68

; "More difficult quest" flag
;
;
; Swap "more difficult quest" flag with previous player
STA $0781               ; $03:A25D: 8D 81 07
LDA $0753               ; $03:A260: AD 53 07
EOR #$01                ; $03:A263: 49 01

; Swap current player
STA $0753               ; $03:A265: 8D 53 07

; And the current player's image
STA $0EC2               ; $03:A268: 8D C2 0E
LDX #$06                ; $03:A26B: A2 06

CODE_03A26D:
LDA $075A,x             ; $03:A26D: BD 5A 07
PHA                     ; $03:A270: 48
LDA $0761,x             ; $03:A271: BD 61 07
STA $075A,x             ; $03:A274: 9D 5A 07
PLA                     ; $03:A277: 68
STA $0761,x             ; $03:A278: 9D 61 07
DEX                     ; $03:A27B: CA

; Swap things like current player's coins, lives, levels, etc
BPL CODE_03A26D         ; $03:A27C: 10 EF
CLC                     ; $03:A27E: 18

CODE_03A27F:
RTS                     ; $03:A27F: 60

CODE_03A280:
LDA $0F03               ; $03:A280: AD 03 0F
BNE CODE_03A27F         ; $03:A283: D0 FA
CLC                     ; $03:A285: 18
RTS                     ; $03:A286: 60

; Swap player data after level end if a player didn't quit after game-over, and game is not single player.

CODE_03A287:
PHB                     ; $03:A287: 8B
PHK                     ; $03:A288: 4B
PLB                     ; $03:A289: AB
JSR CODE_03A22B         ; $03:A28A: 20 2B A2
PLB                     ; $03:A28D: AB
RTL                     ; $03:A28E: 6B
~Ben (SMB2J-2Q)
User avatar
segaloco
Posts: 278
Joined: Fri Aug 25, 2023 11:56 am
Contact:

Re: SMB1 Hacking

Post by segaloco »

Yeah, looks like they've just got a field they swap it back and forth with, rather than adjusting RAM to co-locate them. I'd still try and co-locate for efficiency but since it's a swap that isn't happening in a loop it's not as big of a deal.
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

For all you SMB1 hackers, how many of you toyed around with the idea of using a separate flag to stop the overflow of extra lives?

This is what I did:

Code: Select all

IncrementLives:
        lda MaxLivesFlag  ;check if max lives flag has already been set
        bne WereDone      ;if so, then exit
        inc NumberofLives ;increment lives counter by one
        lda NumberofLives
        cmp #$12          ;does player have 19 lives?
        bcc WereDone      ;if not, then exit
        inc MaxLivesFlag  ;otherwise set flag to prevent overflow
WereDone:
        rts
The idea behind this goes:
LDA MaxLivesFlag
Loads the MaxLivesFlag ($07f6) into A. This is first STA'd into SecondaryGameSetup to initialize.

BNE WereDone
Tells the game to branch to exit if $07f6 has already been set to 1.

INC NumberofLives
Increments the lives counter ($075a) by 1.

LDA NumberofLives
CMP #$12
BCC WereDone
Checks $075a to see if player has 19 lives yet, and if not a BCC call to exit is executed.

INC MaxLivesFlag
If player has 19 lives already, then $07f6 is INC'd to 1 to prevent further incrementing of $075a.

If I messed up on anything, and I probably did, please let me know what to fix.

~Ben
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

For those of you that play All Night Nippon Super Mario Bros., and want the Minus World bugfix to behave similarly to Super Mario All-Stars, here's what you do to patch this up...

Code: Select all

WarpZoneObject:
      lda ScrollLock         ;check for scroll lock flag
      beq ExGTimer           ;branch if not set to leave
      lda Player_Y_Position  ;check to see if player's vertical coordinate has
      and Player_Y_HighPos   ;same bits set as in vertical high byte (why?)
      bne ExGTimer           ;if so, branch to leave
      sta ScrollLock         ;otherwise nullify scroll lock flag
      inc WarpZoneControl    ;increment warp zone flag to make warp pipes for warp zone (REMOVED IN BOTH SMB2J AND ANNSMB)
      jmp EraseEnemyObject   ;kill this object
In the command "WarpZoneObject," restore the INC $06D6 before the JMP to EraseEnemyObject.

Code: Select all

HandlePipeEntry:
...
lda WarpZoneControl
beq ExPipeE
NEW CODE bmi CalculateWarpZoneOffsets ;if bit d7 set, branch to skip offset save code
NEW CODE lda HardWorldFlag
NEW CODE bne CalculateWarpZoneOffsets ;if playing Worlds A-D, also skip offset save code
NEW CODE ldx #$80                     ;load default warp zone control offset pointing to world 1-2 warp zone (pipes 4-3-2)
NEW CODE lda WorldNumber              ;then check world number
NEW CODE beq RecalculateWPOffsetSave  ;if world 1-2, branch to save current offset
NEW CODE inx                          ;otherwise world 4-2, so increment X to get offset for this level's inside warp zone (pipe 5)
NEW CODE lda AreaType                 ;check area type variable and see if player used vine to access outside warp zone 
NEW CODE sec
NEW CODE sbc #$01
NEW CODE bne RecalculateWPOffsetSave  ;if still inside World 4-2, branch to save current offset for pipe 5
NEW CODE inx                          ;otherwise increment X again to point to offset for outside warp zone (pipes 8-7-6)
NEW CODE RecalculateWPOffsetSave:
NEW CODE txa                          ;transfer current value in X ($80, $81 or $82) to A
NEW CODE sta WarpZoneControl          ;and save to $06d6 to overwrite invalid value
NEW CODE CalculateWarpZoneOffsets:
(original vanilla code continues here)
Then, in the "HandlePIpeEntry" command, under BEQ ExPipeE, add the following that starts with NEW CODE (remove these two words from each line after copying to the actual ASM) in order to make the patch work correctly.

~Ben
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

Here is my current progress so far on the lives display improvement code:

Code: Select all

;$00 -- temporary offset used to store hundreds 
;$01 -- temporary offset used to store tens
;$02 -- temporary offset used to store ones

EndGameText:   lda #$00                 ;put null terminator at end
               sta VRAM_Buffer1,y
               pla                      ;pull original text number from stack
               tax
               cmp #$04                 ;are we printing warp zone?
               bcc PrintWorldLives      ;no, skip to print world/lives display
               jmp PrintWarpZoneNumbers ;otherwise print warp zone
PrintWorldLives:
               dex                      ;are we printing the world/lives display?
               bne CheckPlayerName      ;if not, branch to check player's name
               txa
               pha
               ldx VRAM_Buffer1_Offset
               lda NumberofLives        ;otherwise, check number of lives
               clc                      ;and increment by one for display
               adc #$01
               cmp #10                  ;more than 9 lives?
               bcc PutLives
               lda #$00
               sta $00
               sta $01
Check_Hundreds:
               sec
               sbc #100
               bcc Add_Hundreds
               inc $00
               jmp Check_Hundreds
Add_Hundreds:  clc
               adc #100
Check_Tens:    sec
               sbc #10                  
               bcc Add_Tens
               inc $01
               jmp Check_Tens
Add_Tens:      clc
               adc #10
               sta $02
               ldy $00
               beq Check_Ones
               tya
               sta VRAM_Buffer1+6,x
Check_Ones:    lda $01
               sta VRAM_Buffer1+7,x
               lda $02
PutLives:      sta VRAM_Buffer1+8,x
               ldy WorldNumber          ;write world and level numbers (incremented for display)
               iny                      ;to the buffer in the spaces surrounding the dash
               tya
               sta VRAM_Buffer1+19,x
               ldy LevelNumber
               iny
               tya
               sta VRAM_Buffer1+21,x    ;we're done here
               pla
               tax
               rts
Here's the problem I am currently having: from 10 lives, it shows "00" so I know I still have to tinker with it. Please help me.
smb1ma_008.png
smb1ma_008.png (3.19 KiB) Viewed 462 times
And here's what this code looks like in FCEUX's debugger window, just in case you guys notice something in it that I should do further to make it work right.
Screenshot from 2024-03-10 01-09-30.png
From the results in this window, the scratch memory addresses I used read:
$00 (hundreds) = #$00
$01 (tens) = #$4f
$02 (ones) = #$d0

Thank you,



~Ben
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: Family BASIC on the FDS, from the 80's

Post by SMB2J-2Q »

Mod edit: This post was originally in Family BASIC on the FDS, from the 80's, but has been moved because it was not on topic for that thread.


For TakuikaNinja: I saw you can actually use this program to get the invalid (256W) worlds in SMB via cartridge swapping...
https://www.youtube.com/watch?v=ex9Fxg3GOso

Speaking of which, here is a suggestion for your SMB1 bugfix which will eliminate the invalid worlds, without the need for a continue world cold boot check... and it's actually present in the original SMB2J and ANNSMB for the FDS. I actually tried it and it does work properly by returning the game to the title screen if any invalid world number is fed into $075F, meaning no more Minus World (-1) via 1-2 or World 9 via A+Start on cold boot.

Code: Select all

CheckInvalidWorldNum:
lda WorldNumber           ;if world number somehow goes past 9 (8 in ANNSMB), just end the game
cmp #$09 (#$08 in ANNSMB)
bcc ExecutionTree
jsr TerminateGame
For SMB1, it will need to be tweaked like this:

Code: Select all

SkipSprite0:   lda HorizontalScroll      ;set scroll registers from variables
               sta PPU_SCROLL_REG
               lda VerticalScroll
               sta PPU_SCROLL_REG
               lda Mirror_PPU_CTRL_REG1  ;load saved mirror of $2000
               pha
               sta PPU_CTRL_REG1
               lda GamePauseStatus       ;if in pause mode, do not perform operation mode stuff
               lsr
               bcs SkipMainOper
               lda WorldNumber           ;INVALID WORLD FIX: if world number somehow goes past 8, just end the game
               cmp #$08
               bcc ExecutionTree
               lda #Silence
               sta EventMusicQueue
               jsr ResetModes            ;NEW LABEL under TerminateGame to safely end game in either 1-player or 2-player mode
ExecutionTree: jsr OperModeExecutionTree ;otherwise do one of many, many possible subroutines
Then go here and add the new label "ResetModes"

Code: Select all

TerminateGame:
      lda #Silence          ;silence music
      sta EventMusicQueue
      jsr TransposePlayers  ;check if other player can keep
      bcc ContinueGame      ;going, and do so if possible
      lda WorldNumber       ;otherwise put world number of current
      sta ContinueWorld     ;player into secret continue function variable
ResetModes:
      lda #$00
      asl                   ;residual ASL instruction
      sta OperMode_Task     ;reset all modes to title screen and
      sta ScreenTimer       ;leave
      sta OperMode
      rts
~Ben (SMB2J-2Q)
Last edited by Fiskbit on Tue Mar 26, 2024 5:24 pm, edited 1 time in total.
Reason: Edited to add context for what this post was originally replying to, since it has been moved.
SMB2J-2Q
Posts: 154
Joined: Thu Jul 27, 2017 5:13 pm

Re: SMB1 Hacking

Post by SMB2J-2Q »

In Super Mario All-Stars, in both Super Mario Bros. and Super Mario Bros.: The Lost Levels, if a player falls down a pit all on-screen action stops, except for the background effects in the underground (lanterns) and castle (thunder) levels. In the original respective NES and FDS releases, this on-screen action kept going until the intermediate screen appeared.

However, looking at the SMAS disassemblies relevant to these two titles, no reference to $0747 (the RAM address which controls the enemy action timer) in the "HoleDie" routine is found, which would mean that $0747 would likely be triggered somewhere else in the game's code instead if a player fell down a hole. I think the true code might be found if I use Mesen and enter the RAM $0747 and use as a breakpoint.

For those of you that are curious to know if all on-screen action stops when a player falls down a pit in Super Mario Bros. Deluxe for the Game Boy Color, no, it does not.

~Ben
Post Reply