View Full Version : Super Mario Bros. - 128 Lives Fix
ColecoFan1981
09-27-2014, 12:46 AM
It is well known that there is a bug in Super Mario Bros. for the NES that, if you manage to score 128 or more lives, you will get Game Over the next time you die.
My question is, which code would I modify to correct this data, so that obtaining 128 lives and beyond won't mean Game Over anymore, and would also cap 255 ($ff) as the maximum, without resetting to zero? Subsequent remakes such as Super Mario All-Stars (SNES) and Super Mario Bros. Deluxe (Game Boy Color) acknowledged the bug and the resulting side effect from the original NES port, and so have fixed this issue, thus capping the extra life amount to 127.
Thank you,
Ben
Jorpho
09-27-2014, 01:45 AM
Are you sure about that? I thought the bug persisted in SMB in Super Mario All-Stars. (Of course, the number of lives will at least display correctly.)
Anyway, you might want to take your question to a more specialized board. It's probably possible, given that there's a complete annotated disassembly of the SMB ROM out there, but I doubt it's straightforward – things probably have to be rearranged substantially.
ColecoFan1981
09-27-2014, 02:36 AM
Are you sure about that? I thought the bug persisted in SMB in Super Mario All-Stars. (Of course, the number of lives will at least display correctly.)
Anyway, you might want to take your question to a more specialized board. It's probably possible, given that there's a complete annotated disassembly of the SMB ROM out there, but I doubt it's straightforward – things probably have to be rearranged substantially.
It indeed stops at 127 lives in SMAS and SMBDX.
What I did so far for the original SMB was to change the instruction at $91D9 in the PRG-ROM so that it branches on not equal (BNE) instead of equal (BEQ) (to get Game Over after losing your last life, not when it goes into negative territory at 128 and beyond). I must give due thanks to KingMike at Romhacking.net for that bit of help.
~Ben
FoxNtd
09-27-2014, 10:05 AM
Is this basically a 6502 assembly question? Ok then...
In general, architecture aside, there are a few things you could try. Change the variable to unsigned so that the limit effectively becomes 255. (No idea why the game uses a signed 8-bit integer, it's not like there is any meaningful use for negative lives. That's what causes your game over surely, because any negative is less than 0 and that represents game over.) You could modify the routine which is incrementing the lives counter and add a bounds check to insure it does not increment once the maximum is reached. Assuming that Famicom supports 16-bit variables (I really don't know lol) allocate more memory for the lives counter. An unsigned 16-bit int will get you a cap of 65,535. The game has trouble displaying numbers on the life total screen that are greater than 9 I think so I'm not sure what side-effects there may be by trying to make the variable 16-bit instead of 8-bit...
Pete Rittwage
09-27-2014, 11:26 AM
The code must just simply be checking the 6502 N flag... In which case the fix you applied may be fine, assuming there aren't any other side effects based on the number of lives.
I looked at the disassembly... here is the code.
PlayerLoseLife:
inc DisableScreenFlag ;disable screen and sprite 0 check
lda #$00
sta Sprite0HitDetectFlag
lda #Silence ;silence music
sta EventMusicQueue
dec NumberofLives ;take one life from player
bpl StillInGame ;if player still has lives, branch
lda #$00
sta OperMode_Task ;initialize mode task,
lda #GameOverModeValue ;switch to game over mode
sta OperMode ;and leave
rts
Changing "bpl StillInGame" to "bne StillInGame" should make it so the game only ends when the variable is 0, not 128-255. However, the check will be one less than normal, so test this and see if the game ends when you have one life left instead of 0.
There may another side effect to this simple patch, though. At the end of each world, the game sets the lives to 255 when you press B. It may cause some issue. It looks like it may get set back to 3 when the game restarts anyway, so maybe not.
ColecoFan1981
09-27-2014, 01:12 PM
The code must just simply be checking the 6502 N flag... In which case the fix you applied may be fine, assuming there aren't any other side effects based on the number of lives.
I looked at the disassembly... here is the code.
PlayerLoseLife:
inc DisableScreenFlag ;disable screen and sprite 0 check
lda #$00
sta Sprite0HitDetectFlag
lda #Silence ;silence music
sta EventMusicQueue
dec NumberofLives ;take one life from player
bpl StillInGame ;if player still has lives, branch
lda #$00
sta OperMode_Task ;initialize mode task,
lda #GameOverModeValue ;switch to game over mode
sta OperMode ;and leave
rts
Changing "bpl StillInGame" to "bne StillInGame" should make it so the game only ends when the variable is 0, not 128-255. However, the check will be one less than normal, so test this and see if the game ends when you have one life left instead of 0.
There may another side effect to this simple patch, though. At the end of each world, the game sets the lives to 255 when you press B. It may cause some issue. It looks like it may get set back to 3 when the game restarts anyway, so maybe not.
Still no Game Over.
Thus, I ask: I wonder how this logic works in both Super Mario All-Stars and Super Mario Bros. Deluxe? SMAS relies on the more advanced 65816 code, whereas SMBDX relies on Z80 code.
~Ben
Jorpho
09-27-2014, 03:17 PM
It was my understanding that the code used in SMAS is in fact extremely close to the NES original and is not particularly redesigned to take advantage of any advanced 65816 features.
drunk3nj3sus
10-07-2014, 11:58 PM
Seems like a pointless fix as getting that many lives is extremely rare not a bug I ever ran into or even knew about after playing the game for 20+ years, you'd be better off asking the romhacking forum as theirs people that have done all kinds of crazy things with SMB1.
Jorpho
10-08-2014, 12:37 AM
Well, it's easy enough to do accidentally if you're doing one of the extra life tricks like in 3-1.
Anyway, yes, there is now a thread at RH.net (http://www.romhacking.net/forum/index.php/topic,18693.0.html).
drunk3nj3sus
10-08-2014, 01:17 AM
Well, it's easy enough to do accidentally if you're doing one of the extra life tricks like in 3-1.
Anyway, yes, there is now a thread at RH.net (http://www.romhacking.net/forum/index.php/topic,18693.0.html).
Yeah I can see how it could be done but I don't really think anybody would need more than 20 or so lives to beat the game, if you're going to the trouble of hacking that bug out of the game you might as well just disable the lives system altogether. Only bug I've ever really disliked in the game is the one where you can accidentally get stuck in blocks.
ColecoFan1981
07-30-2023, 11:28 AM
Sorry to re-open this thread, but now I've figured it out.
In Super Mario All-Stars, this was how it got fixed:
CheckLives:
INC NumberofLives ;increment the lives counter by one
LDA NumberofLives ;then reload it to see if player has 128 lives
CMP #$80
BCC WereGood ;if not past 128 lives yet, we're good
LDA #$7F
STA NumberofLives ;otherwise, set A=128 lives
WereGood:
RTL ;end of routine
Another way to do this:
CheckLives:
INC NumberofLives ;increment the lives counter by one
LDA NumberofLives ;then reload it to see if we have more than 128 lives
BPL WereGood ;if not past 128 lives yet, we're good
DEC NumberofLives ;otherwise decrement counter by one to prevent overflow
WereGood:
RTL ;end of routine
~Ben (ColecoFan1981)
SparTonberry
07-31-2023, 12:18 PM
Why would you LDA and INC the number of lives in that second example? It's pointless.
ColecoFan1981
08-30-2023, 08:31 PM
Why would you LDA and INC the number of lives in that second example? It's pointless.
The same would apply to the first example, too... I too would like to know why not to INC right after LDA? It might be because INC is like LDA #$01+STA.
A better way to do this would be:
CheckLives:
lda NumberofLives ;load the lives counter
cmp #$7f ;check if player has 127 lives
bcs EndCheckL ;skip to end of routine if so
inc NumberofLives ;otherwise increment counter by one
EndCheckL:
rtl
Saves a lot of bytes.
Another way...
CheckLives:
lda NumberofLives ;load the lives counter
bmi EndCheckL ;skip to end if negative flag set
inc NumberofLives ;otherwise increment counter by one
EndCheckL:
rtl
~Ben
ColecoFan1981
01-07-2024, 09:24 PM
Why would you LDA and INC the number of lives in that second example? It's pointless.
OK, I fixed the examples I listed. In SMAS, the lives counter is incremented first, and then reloaded to see if the target result has been met.
~Ben