Revisiting Super Mario Bros.' 128 Lives Flaw

Started by SMB2J-2Q, August 22, 2022, 11:09:35 PM

Previous topic - Next topic

SMB2J-2Q

To help give you a better understanding of why I want to know what to do to change the instructions in the SMB ASM to fix the buggy part of the 128 lives, I am going to paste all the relevant code from doppelganger's SMB1 disassembly containing RAM address $075A (NumberofLives).

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?
               bcs PrintWarpZoneNumbers
               dex                      ;are we printing the world/lives display?
               bne CheckPlayerName      ;if not, branch to check player's name
               lda NumberofLives        ;otherwise, check number of lives
               clc                      ;and increment by one for display
               adc #$01
               cmp #10                  ;more than 9 lives?
               bcc PutLives
               sbc #10                  ;if so, subtract 10 and put a crown tile
               ldy #$9f                 ;next to the difference...strange things happen if
               sty VRAM_Buffer1+7       ;the number of lives exceeds 19
PutLives:      sta VRAM_Buffer1+8
               ldy WorldNumber          ;write world and level numbers (incremented for display)
               iny                      ;to the buffer in the spaces surrounding the dash
               sty VRAM_Buffer1+19
               ldy LevelNumber
               iny
               sty VRAM_Buffer1+21      ;we're done here
               rts

GiveOneCoin:
      lda #$01               ;set digit modifier to add 1 coin
      sta DigitModifier+5    ;to the current player's coin tally
      ldx CurrentPlayer      ;get current player on the screen
      ldy CoinTallyOffsets,x ;get offset for player's coin tally
      jsr DigitsMathRoutine  ;update the coin tally
      inc CoinTally          ;increment onscreen player's coin amount
      lda CoinTally
      cmp #100               ;does player have 100 coins yet?
      bne CoinPoints         ;if not, skip all of this
      lda #$00
      sta CoinTally          ;otherwise, reinitialize coin amount
      inc NumberofLives      ;give the player an extra life
      lda #Sfx_ExtraLife
      sta Square2SoundQueue  ;play 1-up sound

You will see that in the NES version, except for the PrimaryGameSetup assignment that gives you your starting number of lives (three in the NES version and five in the SNES version), this RAM address is never stored (STA), hence why the game takes the numbers 1 to 128 (and beyond) from the entire character map (which occurs when you get ten or more lives). The SNES version (Super Mario All-Stars) fixes this so that the maximum number of lives is exactly 128 (by actually including an STA instruction within the same assignment along with the INC and LDA ones), not anymore than that.

What I want to do is change the life increase logic to that used in Super Mario All-Stars, which also eliminates drawing values for numbers from the entire character map for the lives count in the NES version.
CODE_048596:
INC $075A               ; $04:8596: EE 5A 07
LDA $075A               ; $04:8599: AD 5A 07
CMP #$80                ; $04:859C: C9 80
BCC CODE_0485A5         ; $04:859E: 90 05
LDA #$7F                ; $04:85A0: A9 7F
STA $075A               ; $04:85A2: 8D 5A 07

; Increase lives by 1 & make sure
; it doesn't get past 128.

CODE_0485A5:
RTL                     ; $04:85A5: 6B

What I would like to know what to do is where to place the SNES version of the code within the existing NES SMB disassembly.

~Ben

Jorpho

This signature is an illusion and is a trap devisut by Satan. Go ahead dauntlessly! Make rapid progres!

KingMike

QuoteWhat I would like to know what to do is where to place the SNES version of the code
I'm sorry but have you made effort to learn ASM in the past eight years since you first asked this question?
Many of us who would be capable to answering this question do because we have looked up tutorials on Google to understand 6502 Assembly and NES architecture.
I understand it takes some time to learn but eight years is a long time to have come and still be asking about such fairly simply programming routines.

It sounds like you are continuing to ask questions which are largely amounting to someone else doing the work.

I think if you sit there and try to read and understand yourself the code snippets you have posted, you should be capable of figuring out yourself where to put the code.

If you are having trouble assembling the modified code, that is a little more reasonable of a question than "where do I put it"?
"My watch says 30 chickens" Google, 2018

SMB2J-2Q

#3
Quote from: KingMike on September 02, 2022, 03:12:04 AMI'm sorry but have you made effort to learn ASM in the past eight years since you first asked this question?
Many of us who would be capable to answering this question do because we have looked up tutorials on Google to understand 6502 Assembly and NES architecture.
I understand it takes some time to learn but eight years is a long time to have come and still be asking about such fairly simply programming routines.

It sounds like you are continuing to ask questions which are largely amounting to someone else doing the work.

I think if you sit there and try to read and understand yourself the code snippets you have posted, you should be capable of figuring out yourself where to put the code.

If you are having trouble assembling the modified code, that is a little more reasonable of a question than "where do I put it"?
Hi Mike,

I have tried to find a spot for it where it would not conflict with any other instruction, in which the code is like this:
128Lives:
    inc NumberofLives
    lda NumberofLives
    cmp #$80
    bcc Exit128
    lda #$7f
    sta NumberofLives
Exit128:
    rts
And, each time I do so I get an error where it says "error #120 - illegal operator" that apparently conflicts with this code listed below.
UpdateNumber:
        jsr PrintStatusBarNumbers
        ldy VRAM_Buffer1_Offset
        lda VRAM_Buffer1-6,y
        bne NoZSup
        lda #$24
        sta VRAM_Buffer1-6,y
NoZSup: ldx ObjectOffset
        rts

What would I do next?

UPDATE 1: I found a spot for it by placing it where the DoNothing code was. But, since the SMAS version of this ended with an RTL (Return from Subroutine - Long, which is different from RTS in that it does everything RTS does but also pulls and loads a third byte -- the program bank register -- to jump to the long return address), as this is available exclusively for the 65816, then to duplicate it for 6502 I will need to have something else added to it to correspond with the ending RTS for it to work properly.

Thank you,



Ben

SMB2J-2Q

#4
UPDATE: I finally just got started in making this hack work by using these new lines of code after the adc #$01 directive under WriteGameText:
      cmp #128           ; 128 lives?
      bcc PutLives       ; if not there yet, skip
      lda #$7f           ; if so, end life counter here
      sta NumberofLives  ; store result in accumulator

in place of this code:
      cmp #10            ; more than 9 lives?
      bcc PutLives       ; if not there yet, skip
      sbc #10            ; if so, subtract ten and place crown tile
      ldy #$9f           ; next to the result. Strange things happen
      sty VRAM_Buffer+7  ; if the life counter exceeds 19 lives

The new instructions above finally got the life counter to behave by stopping at exactly 128 lives.

Next step: to correct the way lives are counted so only digits (0-9, 10-19, 20-29, etc. up to 99, then 100-109, 110-119 and finally 120-128) appear.

~Ben

SMB2J-2Q

#5
What do you guys think of Hamtaro126's 99 lives max-out code that he took inspired by what was found in Southbird's SMB3 disassembly?

Found it here:
https://forums.nesdev.org/viewtopic.php?t=11576&start=135

Here is that code:
WriteGameText:
               pha                       ;save text number to stack
               asl
               tay                       ;multiply by two and use as offset
               cpy #$04                  ;if set to do top status bar or world/lives display,
               bcc LdGameText            ;branch to use current offset as is
               cpy #$08                  ;if set to do time up or game over,
               bcc Chk2Players           ;branch to check players
               ldy #$08                  ;otherwise, warp zone, therefore set offset
Chk2Players:   lda NumberofPlayers       ;check for number of players
               bne LdGameText            ;if 2-player game, use current offset to also print name
               iny                       ;otherwise, increment offset by one to not print name
LdGameText:    ldx GameTextOffsets,y     ;get offset to message we want to print
               ldy #$00
GameTextLoop:  lda GameText,x            ;load message data
               cmp #$ff                  ;check for terminator
               beq EndGameText           ;branch to end text if found
               sta VRAM_Buffer1,y        ;otherwise write data to buffer
               inx                       ;and increment increment
               iny
               bne GameTextLoop          ;do this for 256 bytes if no terminator found
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?
               bcs PrintWarpZoneNumbers
               dex                       ;are we printing the world/lives display?
               bne CheckPlayerName       ;if not, branch to check player's name
               ;H126 fix (credit: Southbird) - Jump to Lives Fix
               jsr PutLives              ;otherwise, check lives
               ldy WorldNumber           ;write world and level numbers (incremented for display)
               iny                       ;to the buffer in the spaces surrounding the dash
               sty VRAM_Buffer1+19
               ldy LevelNumber
               iny
               sty VRAM_Buffer1+21       ;we're done here
               rts

;H126 Fix (special credit: Southbird)
;Original thanks to Southbird -- based on the SMB3 disassembly!
PutLives:
       ldy #$00            ;init Y to 0
       lda NumberofLives   ;lives counter
       cmp #$ff
       bne ChkLivesCap   ;branch to check lives cap

       ;if lives counter is not -1 (255), then:
       lda #$24            ;blank tile
       jmp StoreLo         ;branch

ChkLivesCap:
        cmp #100
        bmi Under100       ;if player's lives are under 100, jump
        lda #99
        sta NumberofLives  ;else, cap at 99 lives

Under100:
        ; now loop while A is more than 10!
        cmp #10
        bmi StoreLo
        sec
        sbc #10            ;A -= 10
        iny                ;Y++
        jmp Under100       ;loop!

StoreLo:
        sta VRAM_Buffer1+8 ;store into low byte
        tya                ;most significant digit is now transferred to A
        bne StoreHi        ;if anything but zero, jump
        lda #$24           ;else, use blank tile

StoreHi:
        sta VRAM_Buffer1+7 ;store into high byte
        rts                ;return

UPDATE: I tried it out last night, and it works perfectly. The life counter stops at 99 lives as this modified code intends. I also fixed up the lives display palette attributes in order for any numbers 10 and up to count properly. Your starting lives now match the number stored in LDA per the PrimaryGameSetup, so I changed that from #$02 to #$05 to make the game at start display 5 lives.

~Ben

Cyneprepou4uk

#6
Quote from: SMB2J-2Q on September 18, 2022, 12:18:37 AMWhat do you guys think of Hamtaro126's 99 lives max-out code


Supertriangle

Quote from: SMB2J-2Q on September 18, 2022, 12:18:37 AMWhat do you guys think of Hamtaro126's 99 lives max-out code that he took inspired by what was found in Southbird's SMB3 disassembly?

Found it here:
https://forums.nesdev.org/viewtopic.php?t=11576&start=135

Here is that code:
WriteGameText:
               pha                       ;save text number to stack
               asl
               tay                       ;multiply by two and use as offset
               cpy #$04                  ;if set to do top status bar or world/lives display,
               bcc LdGameText            ;branch to use current offset as is
               cpy #$08                  ;if set to do time up or game over,
               bcc Chk2Players           ;branch to check players
               ldy #$08                  ;otherwise, warp zone, therefore set offset
Chk2Players:   lda NumberofPlayers       ;check for number of players
               bne LdGameText            ;if 2-player game, use current offset to also print name
               iny                       ;otherwise, increment offset by one to not print name
LdGameText:    ldx GameTextOffsets,y     ;get offset to message we want to print
               ldy #$00
GameTextLoop:  lda GameText,x            ;load message data
               cmp #$ff                  ;check for terminator
               beq EndGameText           ;branch to end text if found
               sta VRAM_Buffer1,y        ;otherwise write data to buffer
               inx                       ;and increment increment
               iny
               bne GameTextLoop          ;do this for 256 bytes if no terminator found
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?
               bcs PrintWarpZoneNumbers
               dex                       ;are we printing the world/lives display?
               bne CheckPlayerName       ;if not, branch to check player's name
               ;H126 fix (credit: Southbird) - Jump to Lives Fix
               jsr PutLives              ;otherwise, check lives
               ldy WorldNumber           ;write world and level numbers (incremented for display)
               iny                       ;to the buffer in the spaces surrounding the dash
               sty VRAM_Buffer1+19
               ldy LevelNumber
               iny
               sty VRAM_Buffer1+21       ;we're done here
               rts

;H126 Fix (special credit: Southbird)
;Original thanks to Southbird -- based on the SMB3 disassembly!
PutLives:
       ldy #$00            ;init Y to 0
       lda NumberofLives   ;lives counter
       cmp #$ff
       bne ChkLivesCap   ;branch to check lives cap

       ;if lives counter is not -1 (255), then:
       lda #$24            ;blank tile
       jmp StoreLo         ;branch

ChkLivesCap:
        cmp #100
        bmi Under100       ;if player's lives are under 100, jump
        lda #99
        sta NumberofLives  ;else, cap at 99 lives

Under100:
        ; now loop while A is more than 10!
        cmp #10
        bmi StoreLo
        sec
        sbc #10            ;A -= 10
        iny                ;Y++
        jmp Under100       ;loop!

StoreLo:
        sta VRAM_Buffer1+8 ;store into low byte
        tya                ;most significant digit is now transferred to A
        bne StoreHi        ;if anything but zero, jump
        lda #$24           ;else, use blank tile

StoreHi:
        sta VRAM_Buffer1+7 ;store into high byte
        rts                ;return

UPDATE: I tried it out last night, and it works perfectly. The life counter stops at 99 lives as this modified code intends. I also fixed up the lives display palette attributes in order for any numbers 10 and up to count properly. Your starting lives now match the number stored in LDA per the PrimaryGameSetup, so I changed that from #$02 to #$05 to make the game at start display 5 lives.

~Ben


Is there a way to make an IPS with this so it can be applied by hackers like me who are a little more basic?

SMB2J-2Q

Quote from: Supertriangle on September 29, 2022, 02:03:32 PMIs there a way to make an IPS with this so it can be applied by hackers like me who are a little more basic?
You could do this using FCEUX.

However, when patching the IPS you made, it must be applied to a clean ROM of SMB1.

~Ben

SMB2J-2Q

#9
Has anyone else, as of late, tried Shane M.'s fix listed below?

He coded it to also have the life counter stop at 19 lives, and wrote it like this:
lda numberoflives  
cmp #$12       
bcs end              
inc numberoflives
end:rts

UPDATE: I modified the part of the code at $84e6, where we see the first occurrence of "inc NumberOfLives", and it works! But I did my version a bit differently, and also using KingMike's fixes for this...
Lives:    lda NumberOfLives      ; check number of lives
          cmp #$13               ; 19 lives yet?
          bcs EndLives           ; if yes, skip this part
          inc NumberOfLives      ; otherwise give player one extra life (1-up)
          lda #SFX_ExtraLife
          sta Square2SoundQueue  ; and play the 1-up sound
endlives: rts

Thus, I also modified the "GiveOneCoin" routine to end with a jsr to the lives command:
GiveOneCoin:
      lda #$01               ;set digit modifier to add 1 coin
      sta DigitModifier+5    ;to the current player's coin tally
      ldx CurrentPlayer      ;get current player on the screen
      ldy CoinTallyOffsets,x ;get offset for player's coin tally
      jsr DigitsMathRoutine  ;update the coin tally
      inc CoinTally          ;increment onscreen player's coin amount
      lda CoinTally
      cmp #100               ;does player have 100 coins yet?
      bne CoinPoints         ;if not, skip all of this
      lda #$00
      sta CoinTally          ;otherwise, reinitialize coin amount
      jsr Lives              ;jump to routine to give player one extra life

~Ben

SMB2J-2Q

#10
For your information, here's the lives display coding for if you get over ten and then 100 lives, as applies to Super Mario All-Stars. It starts at address 0x68E22 in the ROM:
lda NumberofLives
inc a
cmp #10
bcc PutLives
stz $64
stz $65

To100:
sec
sbc #100
bcc 101to109
inc $e4
bra To100

101to109:
clc
adc #100

Over109:
sec
sbc #10
bcc PutLives
inc $e5
bra Over109


Again, before including these, make sure you remove the "crown-9" display attributes under "WorldLivesDisplay", and to do this under "DecNumTimer":
CheckLives:
lda NumberofLives
cmp #$7e
bcs EndChkLives
inc NumberofLives
lda #SFX_ExtraLife
sta Square2SoundQueue
EndChkLives:
rts

~Ben