Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
A Comprehensive Super Mario Bros. Disassembly
;by doppelganger (
;This file is provided for your own use as-is. It will require the character rom data
;and an iNES file header to get it to work.
;There are so many people I have to thank for this, that taking all the credit for
;myself would be an unforgivable act of arrogance. Without their help this would
;probably not be possible. So I thank all the peeps in the nesdev scene whose insight into
;the 6502 and the NES helped me learn how it works (you guys know who you are, there's no
;way I could have done this without your help), as well as the authors of x816 and SMB
;Utility, and the reverse-engineers who did the original Super Mario Bros. Hacking Project,
;which I compared notes with but did not copy from. Last but certainly not least, I thank
;Nintendo for creating this game and the NES, without which this disassembly would
;only be theory.
;Assembles with x816.
;NES specific hardware defines
PPU_CTRL_REG1 = $2000
PPU_CTRL_REG2 = $2001
PPU_STATUS = $2002
PPU_SPR_ADDR = $2003
PPU_SPR_DATA = $2004
PPU_DATA = $2007
SPR_DMA = $4014
JOYPAD_PORT1 = $4016
JOYPAD_PORT2 = $4017
ObjectOffset = $08
FrameCounter = $09
SavedJoypadBits = $06fc
SavedJoypad1Bits = $06fc
SavedJoypad2Bits = $06fd
JoypadBitMask = $074a
JoypadOverride = $0758
A_B_Buttons = $0a
PreviousA_B_Buttons = $0d
Up_Down_Buttons = $0b
Left_Right_Buttons = $0c
GameEngineSubroutine = $0e
Mirror_PPU_CTRL_REG1 = $0778
Mirror_PPU_CTRL_REG2 = $0779
OperMode = $0770
OperMode_Task = $0772
ScreenRoutineTask = $073c
GamePauseStatus = $0776
GamePauseTimer = $0777
DemoAction = $0717
DemoActionTimer = $0718
TimerControl = $0747
IntervalTimerControl = $077f
Timers = $0780
SelectTimer = $0780
PlayerAnimTimer = $0781
JumpSwimTimer = $0782
RunningTimer = $0783
BlockBounceTimer = $0784
SideCollisionTimer = $0785
JumpspringTimer = $0786
GameTimerCtrlTimer = $0787
ClimbSideTimer = $0789
EnemyFrameTimer = $078a
FrenzyEnemyTimer = $078f
BowserFireBreathTimer = $0790
StompTimer = $0791
AirBubbleTimer = $0792
ScrollIntervalTimer = $0795
EnemyIntervalTimer = $0796
BrickCoinTimer = $079d
InjuryTimer = $079e
StarInvincibleTimer = $079f
ScreenTimer = $07a0
WorldEndTimer = $07a1
DemoTimer = $07a2
Sprite_Data = $0200
Sprite_Y_Position = $0200
Sprite_Tilenumber = $0201
Sprite_Attributes = $0202
Sprite_X_Position = $0203
ScreenEdge_PageLoc = $071a
ScreenEdge_X_Pos = $071c
ScreenLeft_PageLoc = $071a
ScreenRight_PageLoc = $071b
ScreenLeft_X_Pos = $071c
ScreenRight_X_Pos = $071d
PlayerFacingDir = $33
DestinationPageLoc = $34
VictoryWalkControl = $35
ScrollFractional = $0768
PrimaryMsgCounter = $0719
SecondaryMsgCounter = $0749
HorizontalScroll = $073f
VerticalScroll = $0740
ScrollLock = $0723
ScrollThirtyTwo = $073d
Player_X_Scroll = $06ff
Player_Pos_ForScroll = $0755
ScrollAmount = $0775
AreaData = $e7
AreaDataLow = $e7
AreaDataHigh = $e8
EnemyData = $e9
EnemyDataLow = $e9
EnemyDataHigh = $ea
AreaParserTaskNum = $071f
ColumnSets = $071e
CurrentPageLoc = $0725
CurrentColumnPos = $0726
BackloadingFlag = $0728
BehindAreaParserFlag = $0729
AreaObjectPageLoc = $072a
AreaObjectPageSel = $072b
AreaDataOffset = $072c
AreaObjOffsetBuffer = $072d
AreaObjectLength = $0730
StaircaseControl = $0734
AreaObjectHeight = $0735
MushroomLedgeHalfLen = $0736
EnemyDataOffset = $0739
EnemyObjectPageLoc = $073a
EnemyObjectPageSel = $073b
MetatileBuffer = $06a1
BlockBufferColumnPos = $06a0
CurrentNTAddr_Low = $0721
CurrentNTAddr_High = $0720
AttributeBuffer = $03f9
LoopCommand = $0745
DisplayDigits = $07d7
TopScoreDisplay = $07d7
ScoreAndCoinDisplay = $07dd
PlayerScoreDisplay = $07dd
GameTimerDisplay = $07f8
DigitModifier = $0134
VerticalFlipFlag = $0109
FloateyNum_Control = $0110
ShellChainCounter = $0125
FloateyNum_Timer = $012c
FloateyNum_X_Pos = $0117
FloateyNum_Y_Pos = $011e
FlagpoleFNum_Y_Pos = $010d
FlagpoleFNum_YMFDummy = $010e
FlagpoleScore = $010f
FlagpoleCollisionYPos = $070f
StompChainCounter = $0484
VRAM_Buffer1_Offset = $0300
VRAM_Buffer1 = $0301
VRAM_Buffer2_Offset = $0340
VRAM_Buffer2 = $0341
VRAM_Buffer_AddrCtrl = $0773
Sprite0HitDetectFlag = $0722
DisableScreenFlag = $0774
DisableIntermediate = $0769
ColorRotateOffset = $06d4
TerrainControl = $0727
AreaStyle = $0733
ForegroundScenery = $0741
BackgroundScenery = $0742
CloudTypeOverride = $0743
BackgroundColorCtrl = $0744
AreaType = $074e
AreaAddrsLOffset = $074f
AreaPointer = $0750
PlayerEntranceCtrl = $0710
GameTimerSetting = $0715
AltEntranceControl = $0752
EntrancePage = $0751
NumberOfPlayers = $077a
WarpZoneControl = $06d6
ChangeAreaTimer = $06de
MultiLoopCorrectCntr = $06d9
MultiLoopPassCntr = $06da
FetchNewGameTimerFlag = $0757
GameTimerExpiredFlag = $0759
PrimaryHardMode = $076a
SecondaryHardMode = $06cc
WorldSelectNumber = $076b
WorldSelectEnableFlag = $07fc
ContinueWorld = $07fd
CurrentPlayer = $0753
PlayerSize = $0754
PlayerStatus = $0756
OnscreenPlayerInfo = $075a
NumberofLives = $075a ;used by current player
HalfwayPage = $075b
LevelNumber = $075c ;the actual dash number
Hidden1UpFlag = $075d
CoinTally = $075e
WorldNumber = $075f
AreaNumber = $0760 ;internal number used to find areas
CoinTallyFor1Ups = $0748
OffscreenPlayerInfo = $0761
OffScr_NumberofLives = $0761 ;used by offscreen player
OffScr_HalfwayPage = $0762
OffScr_LevelNumber = $0763
OffScr_Hidden1UpFlag = $0764
OffScr_CoinTally = $0765
OffScr_WorldNumber = $0766
OffScr_AreaNumber = $0767
BalPlatformAlignment = $03a0
Platform_X_Scroll = $03a1
PlatformCollisionFlag = $03a2
YPlatformTopYPos = $0401
YPlatformCenterYPos = $58
BrickCoinTimerFlag = $06bc
StarFlagTaskControl = $0746
PseudoRandomBitReg = $07a7
WarmBootValidation = $07ff
SprShuffleAmtOffset = $06e0
SprShuffleAmt = $06e1
SprDataOffset = $06e4
Player_SprDataOffset = $06e4
Enemy_SprDataOffset = $06e5
Block_SprDataOffset = $06ec
Alt_SprDataOffset = $06ec
Bubble_SprDataOffset = $06ee
FBall_SprDataOffset = $06f1
Misc_SprDataOffset = $06f3
SprDataOffset_Ctrl = $03ee
Player_State = $1d
Enemy_State = $1e
Fireball_State = $24
Block_State = $26
Misc_State = $2a
Player_MovingDir = $45
Enemy_MovingDir = $46
SprObject_X_Speed = $57
Player_X_Speed = $57
Enemy_X_Speed = $58
Fireball_X_Speed = $5e
Block_X_Speed = $60
Misc_X_Speed = $64
Jumpspring_FixedYPos = $58
JumpspringAnimCtrl = $070e
JumpspringForce = $06db
SprObject_PageLoc = $6d
Player_PageLoc = $6d
Enemy_PageLoc = $6e
Fireball_PageLoc = $74
Block_PageLoc = $76
Misc_PageLoc = $7a
Bubble_PageLoc = $83
SprObject_X_Position = $86
Player_X_Position = $86
Enemy_X_Position = $87
Fireball_X_Position = $8d
Block_X_Position = $8f
Misc_X_Position = $93
Bubble_X_Position = $9c
SprObject_Y_Speed = $9f
Player_Y_Speed = $9f
Enemy_Y_Speed = $a0
Fireball_Y_Speed = $a6
Block_Y_Speed = $a8
Misc_Y_Speed = $ac
SprObject_Y_HighPos = $b5
Player_Y_HighPos = $b5
Enemy_Y_HighPos = $b6
Fireball_Y_HighPos = $bc
Block_Y_HighPos = $be
Misc_Y_HighPos = $c2
Bubble_Y_HighPos = $cb
SprObject_Y_Position = $ce
Player_Y_Position = $ce
Enemy_Y_Position = $cf
Fireball_Y_Position = $d5
Block_Y_Position = $d7
Misc_Y_Position = $db
Bubble_Y_Position = $e4
SprObject_Rel_XPos = $03ad
Player_Rel_XPos = $03ad
Enemy_Rel_XPos = $03ae
Fireball_Rel_XPos = $03af
Bubble_Rel_XPos = $03b0
Block_Rel_XPos = $03b1
Misc_Rel_XPos = $03b3
SprObject_Rel_YPos = $03b8
Player_Rel_YPos = $03b8
Enemy_Rel_YPos = $03b9
Fireball_Rel_YPos = $03ba
Bubble_Rel_YPos = $03bb
Block_Rel_YPos = $03bc
Misc_Rel_YPos = $03be
SprObject_SprAttrib = $03c4
Player_SprAttrib = $03c4
Enemy_SprAttrib = $03c5
SprObject_X_MoveForce = $0400
Enemy_X_MoveForce = $0401
SprObject_YMF_Dummy = $0416
Player_YMF_Dummy = $0416
Enemy_YMF_Dummy = $0417
Bubble_YMF_Dummy = $042c
SprObject_Y_MoveForce = $0433
Player_Y_MoveForce = $0433
Enemy_Y_MoveForce = $0434
Block_Y_MoveForce = $043c
DisableCollisionDet = $0716
Player_CollisionBits = $0490
Enemy_CollisionBits = $0491
SprObj_BoundBoxCtrl = $0499
Player_BoundBoxCtrl = $0499
Enemy_BoundBoxCtrl = $049a
Fireball_BoundBoxCtrl = $04a0
Misc_BoundBoxCtrl = $04a2
EnemyFrenzyBuffer = $06cb
EnemyFrenzyQueue = $06cd
Enemy_Flag = $0f
Enemy_ID = $16
PlayerGfxOffset = $06d5
Player_XSpeedAbsolute = $0700
FrictionAdderHigh = $0701
FrictionAdderLow = $0702
RunningSpeed = $0703
SwimmingFlag = $0704
Player_X_MoveForce = $0705
DiffToHaltJump = $0706
JumpOrigin_Y_HighPos = $0707
JumpOrigin_Y_Position = $0708
VerticalForce = $0709
VerticalForceDown = $070a
PlayerChangeSizeFlag = $070b
PlayerAnimTimerSet = $070c
PlayerAnimCtrl = $070d
DeathMusicLoaded = $0712
FlagpoleSoundQueue = $0713
CrouchingFlag = $0714
MaximumLeftSpeed = $0450
MaximumRightSpeed = $0456
SprObject_OffscrBits = $03d0
Player_OffscreenBits = $03d0
Enemy_OffscreenBits = $03d1
FBall_OffscreenBits = $03d2
Bubble_OffscreenBits = $03d3
Block_OffscreenBits = $03d4
Misc_OffscreenBits = $03d6
EnemyOffscrBitsMasked = $03d8
Cannon_Offset = $046a
Cannon_PageLoc = $046b
Cannon_X_Position = $0471
Cannon_Y_Position = $0477
Cannon_Timer = $047d
Whirlpool_Offset = $046a
Whirlpool_PageLoc = $046b
Whirlpool_LeftExtent = $0471
Whirlpool_Length = $0477
Whirlpool_Flag = $047d
VineFlagOffset = $0398
VineHeight = $0399
VineObjOffset = $039a
VineStart_Y_Position = $039d
Block_Orig_YPos = $03e4
Block_BBuf_Low = $03e6
Block_Metatile = $03e8
Block_PageLoc2 = $03ea
Block_RepFlag = $03ec
Block_ResidualCounter = $03f0
Block_Orig_XPos = $03f1
BoundingBox_UL_XPos = $04ac
BoundingBox_UL_YPos = $04ad
BoundingBox_DR_XPos = $04ae
BoundingBox_DR_YPos = $04af
BoundingBox_UL_Corner = $04ac
BoundingBox_LR_Corner = $04ae
EnemyBoundingBoxCoord = $04b0
PowerUpType = $39
FireballBouncingFlag = $3a
FireballCounter = $06ce
FireballThrowingTimer = $0711
HammerEnemyOffset = $06ae
JumpCoinMiscOffset = $06b7
Block_Buffer_1 = $0500
Block_Buffer_2 = $05d0
HammerThrowingTimer = $03a2
HammerBroJumpTimer = $3c
Misc_Collision_Flag = $06be
RedPTroopaOrigXPos = $0401
RedPTroopaCenterYPos = $58
XMovePrimaryCounter = $a0
XMoveSecondaryCounter = $58
CheepCheepMoveMFlag = $58
CheepCheepOrigYPos = $0434
BitMFilter = $06dd
LakituReappearTimer = $06d1
LakituMoveSpeed = $58
LakituMoveDirection = $a0
FirebarSpinState_Low = $58
FirebarSpinState_High = $a0
FirebarSpinSpeed = $0388
FirebarSpinDirection = $34
DuplicateObj_Offset = $06cf
NumberofGroupEnemies = $06d3
BlooperMoveCounter = $a0
BlooperMoveSpeed = $58
BowserBodyControls = $0363
BowserFeetCounter = $0364
BowserMovementSpeed = $0365
BowserOrigXPos = $0366
BowserFlameTimerCtrl = $0367
BowserFront_Offset = $0368
BridgeCollapseOffset = $0369
BowserGfxFlag = $036a
BowserHitPoints = $0483
MaxRangeFromOrigin = $06dc
BowserFlamePRandomOfs = $0417
PiranhaPlantUpYPos = $0417
PiranhaPlantDownYPos = $0434
PiranhaPlant_Y_Speed = $58
PiranhaPlant_MoveFlag = $a0
FireworksCounter = $06d7
ExplosionGfxCounter = $58
ExplosionTimerCounter = $a0
;sound related defines
Squ2_NoteLenBuffer = $07b3
Squ2_NoteLenCounter = $07b4
Squ2_EnvelopeDataCtrl = $07b5
Squ1_NoteLenCounter = $07b6
Squ1_EnvelopeDataCtrl = $07b7
Tri_NoteLenBuffer = $07b8
Tri_NoteLenCounter = $07b9
Noise_BeatLenCounter = $07ba
Squ1_SfxLenCounter = $07bb
Squ2_SfxLenCounter = $07bd
Sfx_SecondaryCounter = $07be
Noise_SfxLenCounter = $07bf
PauseSoundQueue = $fa
Square1SoundQueue = $ff
Square2SoundQueue = $fe
NoiseSoundQueue = $fd
AreaMusicQueue = $fb
EventMusicQueue = $fc
Square1SoundBuffer = $f1
Square2SoundBuffer = $f2
NoiseSoundBuffer = $f3
AreaMusicBuffer = $f4
EventMusicBuffer = $07b1
PauseSoundBuffer = $07b2
MusicData = $f5
MusicDataLow = $f5
MusicDataHigh = $f6
MusicOffset_Square2 = $f7
MusicOffset_Square1 = $f8
MusicOffset_Triangle = $f9
MusicOffset_Noise = $07b0
NoteLenLookupTblOfs = $f0
DAC_Counter = $07c0
NoiseDataLoopbackOfs = $07c1
NoteLengthTblAdder = $07c4
AreaMusicBuffer_Alt = $07c5
PauseModeFlag = $07c6
GroundMusicHeaderOfs = $07c7
AltRegContentFlag = $07ca
;sound effects constants
Sfx_SmallJump = %10000000
Sfx_Flagpole = %01000000
Sfx_Fireball = %00100000
Sfx_PipeDown_Injury = %00010000
Sfx_EnemySmack = %00001000
Sfx_EnemyStomp = %00000100
Sfx_Bump = %00000010
Sfx_BigJump = %00000001
Sfx_BowserFall = %10000000
Sfx_ExtraLife = %01000000
Sfx_PowerUpGrab = %00100000
Sfx_TimerTick = %00010000
Sfx_Blast = %00001000
Sfx_GrowVine = %00000100
Sfx_GrowPowerUp = %00000010
Sfx_CoinGrab = %00000001
Sfx_BowserFlame = %00000010
Sfx_BrickShatter = %00000001
;music constants
Silence = %10000000
StarPowerMusic = %01000000
PipeIntroMusic = %00100000
CloudMusic = %00010000
CastleMusic = %00001000
UndergroundMusic = %00000100
WaterMusic = %00000010
GroundMusic = %00000001
TimeRunningOutMusic = %01000000
EndOfLevelMusic = %00100000
AltGameOverMusic = %00010000
EndOfCastleMusic = %00001000
VictoryMusic = %00000100
GameOverMusic = %00000010
DeathMusic = %00000001
;enemy object constants
GreenKoopa = $00
BuzzyBeetle = $02
RedKoopa = $03
HammerBro = $05
Goomba = $06
Bloober = $07
BulletBill_FrenzyVar = $08
GreyCheepCheep = $0a
RedCheepCheep = $0b
Podoboo = $0c
PiranhaPlant = $0d
GreenParatroopaJump = $0e
RedParatroopa = $0f
GreenParatroopaFly = $10
Lakitu = $11
Spiny = $12
FlyCheepCheepFrenzy = $14
FlyingCheepCheep = $14
BowserFlame = $15
Fireworks = $16
BBill_CCheep_Frenzy = $17
Stop_Frenzy = $18
Bowser = $2d
PowerUpObject = $2e
VineObject = $2f
FlagpoleFlagObject = $30
StarFlagObject = $31
JumpspringObject = $32
BulletBill_CannonVar = $33
RetainerObject = $35
TallEnemy = $09
;other constants
World1 = 0
World2 = 1
World3 = 2
World4 = 3
World5 = 4
World6 = 5
World7 = 6
World8 = 7
Level1 = 0
Level2 = 1
Level3 = 2
Level4 = 3
WarmBootOffset = <$07d6
ColdBootOffset = <$07fe
TitleScreenDataOffset = $1ec0
SoundMemory = $07b0
SwimTileRepOffset = PlayerGraphicsTable + $9e
MusicHeaderOffsetData = MusicHeaderData - 1
MHD = MusicHeaderData
A_Button = %10000000
B_Button = %01000000
Select_Button = %00100000
Start_Button = %00010000
Up_Dir = %00001000
Down_Dir = %00000100
Left_Dir = %00000010
Right_Dir = %00000001
TitleScreenModeValue = 0
GameModeValue = 1
VictoryModeValue = 2
GameOverModeValue = 3
.index 8
.mem 8
.org $8000
sei ;pretty standard 6502 type init here
lda #%00010000 ;init PPU control register 1
ldx #$ff ;reset stack pointer
VBlank1: lda PPU_STATUS ;wait two frames
bpl VBlank1
VBlank2: lda PPU_STATUS
bpl VBlank2
ldy #ColdBootOffset ;load default cold boot pointer
ldx #$05 ;this is where we check for a warm boot
WBootCheck: lda TopScoreDisplay,x ;check each score digit in the top score
cmp #10 ;to see if we have a valid digit
bcs ColdBoot ;if not, give up and proceed with cold boot
bpl WBootCheck
lda WarmBootValidation ;second checkpoint, check to see if
cmp #$a5 ;another location has a specific value
bne ColdBoot
ldy #WarmBootOffset ;if passed both, load warm boot pointer
ColdBoot: jsr InitializeMemory ;clear memory using pointer in Y
sta SND_DELTA_REG+1 ;reset delta counter load register
sta OperMode ;reset primary mode of operation
lda #$a5 ;set warm boot flag
sta WarmBootValidation
sta PseudoRandomBitReg ;set seed for pseudorandom register
lda #%00001111
sta SND_MASTERCTRL_REG ;enable all sound channels except dmc
lda #%00000110
sta PPU_CTRL_REG2 ;turn off clipping for OAM and background
jsr MoveAllSpritesOffscreen
jsr InitializeNameTables ;initialize both name tables
inc DisableScreenFlag ;set flag to disable screen output
lda Mirror_PPU_CTRL_REG1
ora #%10000000 ;enable NMIs
jsr WritePPUReg1
EndlessLoop: jmp EndlessLoop ;endless loop, need I say more?
;$00 - vram buffer address table low, also used for pseudorandom bit
;$01 - vram buffer address table high
.db <VRAM_Buffer1, <WaterPaletteData, <GroundPaletteData
.db <UndergroundPaletteData, <CastlePaletteData, <VRAM_Buffer1_Offset
.db <VRAM_Buffer2, <VRAM_Buffer2, <BowserPaletteData
.db <DaySnowPaletteData, <NightSnowPaletteData, <MushroomPaletteData
.db <MarioThanksMessage, <LuigiThanksMessage, <MushroomRetainerSaved
.db <PrincessSaved1, <PrincessSaved2, <WorldSelectMessage1
.db <WorldSelectMessage2
.db >VRAM_Buffer1, >WaterPaletteData, >GroundPaletteData
.db >UndergroundPaletteData, >CastlePaletteData, >VRAM_Buffer1_Offset
.db >VRAM_Buffer2, >VRAM_Buffer2, >BowserPaletteData
.db >DaySnowPaletteData, >NightSnowPaletteData, >MushroomPaletteData
.db >MarioThanksMessage, >LuigiThanksMessage, >MushroomRetainerSaved
.db >PrincessSaved1, >PrincessSaved2, >WorldSelectMessage1
.db >WorldSelectMessage2
.db <VRAM_Buffer1_Offset, <VRAM_Buffer2_Offset
lda Mirror_PPU_CTRL_REG1 ;disable NMIs in mirror reg
and #%01111111 ;save all other bits
sta Mirror_PPU_CTRL_REG1
and #%01111110 ;alter name table address to be $2800
sta PPU_CTRL_REG1 ;(essentially $2000) but save other bits
lda Mirror_PPU_CTRL_REG2 ;disable OAM and background display by default
and #%11100110
ldy DisableScreenFlag ;get screen disable flag
bne ScreenOff ;if set, used bits as-is
lda Mirror_PPU_CTRL_REG2 ;otherwise reenable bits and save them
ora #%00011110
ScreenOff: sta Mirror_PPU_CTRL_REG2 ;save bits for later but not in register at the moment
and #%11100111 ;disable screen for now
ldx PPU_STATUS ;reset flip-flop and reset scroll registers to zero
lda #$00
jsr InitScroll
sta PPU_SPR_ADDR ;reset spr-ram address register
lda #$02 ;perform spr-ram DMA access on $0200-$02ff
ldx VRAM_Buffer_AddrCtrl ;load control for pointer to buffer contents
lda VRAM_AddrTable_Low,x ;set indirect at $00 to pointer
sta $00
lda VRAM_AddrTable_High,x
sta $01
jsr UpdateScreen ;update screen with buffer contents
ldy #$00
ldx VRAM_Buffer_AddrCtrl ;check for usage of $0341
cpx #$06
bne InitBuffer
iny ;get offset based on usage
InitBuffer: ldx VRAM_Buffer_Offset,y
lda #$00 ;clear buffer header at last location
sta VRAM_Buffer1_Offset,x
sta VRAM_Buffer1,x
sta VRAM_Buffer_AddrCtrl ;reinit address control to $0301
lda Mirror_PPU_CTRL_REG2 ;copy mirror of $2001 to register
jsr SoundEngine ;play sound
jsr ReadJoypads ;read joypads
jsr PauseRoutine ;handle pause
jsr UpdateTopScore
lda GamePauseStatus ;check for pause status
bcs PauseSkip
lda TimerControl ;if master timer control not set, decrement
beq DecTimers ;all frame and interval timers
dec TimerControl
bne NoDecTimers
DecTimers: ldx #$14 ;load end offset for end of frame timers
dec IntervalTimerControl ;decrement interval timer control,
bpl DecTimersLoop ;if not expired, only frame timers will decrement
lda #$14
sta IntervalTimerControl ;if control for interval timers expired,
ldx #$23 ;interval timers will decrement along with frame timers
DecTimersLoop: lda Timers,x ;check current timer
beq SkipExpTimer ;if current timer expired, branch to skip,
dec Timers,x ;otherwise decrement the current timer
SkipExpTimer: dex ;move onto next timer
bpl DecTimersLoop ;do this until all timers are dealt with
NoDecTimers: inc FrameCounter ;increment frame counter
PauseSkip: ldx #$00
ldy #$07
lda PseudoRandomBitReg ;get first memory location of LSFR bytes
and #%00000010 ;mask out all but d1
sta $00 ;save here
lda PseudoRandomBitReg+1 ;get second memory location
and #%00000010 ;mask out all but d1
eor $00 ;perform exclusive-OR on d1 from first and second bytes
clc ;if neither or both are set, carry will be clear
beq RotPRandomBit
sec ;if one or the other is set, carry will be set
RotPRandomBit: ror PseudoRandomBitReg,x ;rotate carry into d7, and rotate last bit into carry
inx ;increment to next byte
dey ;decrement for loop
bne RotPRandomBit
lda Sprite0HitDetectFlag ;check for flag here
beq SkipSprite0
Sprite0Clr: lda PPU_STATUS ;wait for sprite 0 flag to clear, which will
and #%01000000 ;not happen until vblank has ended
bne Sprite0Clr
lda GamePauseStatus ;if in pause mode, do not bother with sprites at all
bcs Sprite0Hit
jsr MoveSpritesOffscreen
jsr SpriteShuffler
Sprite0Hit: lda PPU_STATUS ;do sprite #0 hit detection
and #%01000000
beq Sprite0Hit
ldy #$14 ;small delay, to wait until we hit horizontal blank time
HBlankDelay: dey
bne HBlankDelay
SkipSprite0: lda HorizontalScroll ;set scroll registers from variables
lda VerticalScroll
lda Mirror_PPU_CTRL_REG1 ;load saved mirror of $2000
lda GamePauseStatus ;if in pause mode, do not perform operation mode stuff
bcs SkipMainOper
jsr OperModeExecutionTree ;otherwise do one of many, many possible subroutines
SkipMainOper: lda PPU_STATUS ;reset flip-flop
ora #%10000000 ;reactivate NMIs
rti ;we are done until the next frame!
lda OperMode ;are we in victory mode?
cmp #VictoryModeValue ;if so, go ahead
beq ChkPauseTimer
cmp #GameModeValue ;are we in game mode?
bne ExitPause ;if not, leave
lda OperMode_Task ;if we are in game mode, are we running game engine?
cmp #$03
bne ExitPause ;if not, leave
ChkPauseTimer: lda GamePauseTimer ;check if pause timer is still counting down
beq ChkStart
dec GamePauseTimer ;if so, decrement and leave
ChkStart: lda SavedJoypad1Bits ;check to see if start is pressed
and #Start_Button ;on controller 1
beq ClrPauseTimer
lda GamePauseStatus ;check to see if timer flag is set
and #%10000000 ;and if so, do not reset timer (residual,
bne ExitPause ;joypad reading routine makes this unnecessary)
lda #$2b ;set pause timer
sta GamePauseTimer
lda GamePauseStatus
iny ;set pause sfx queue for next pause mode
sty PauseSoundQueue
eor #%00000001 ;invert d0 and set d7
ora #%10000000
bne SetPause ;unconditional branch
ClrPauseTimer: lda GamePauseStatus ;clear timer flag if timer is at zero and start button
and #%01111111 ;is not pressed
SetPause: sta GamePauseStatus
ExitPause: rts
;$00 - used for preset value
ldy AreaType ;load level type, likely residual code
lda #$28 ;load preset value which will put it at
sta $00 ;sprite #10
ldx #$0e ;start at the end of OAM data offsets
ShuffleLoop: lda SprDataOffset,x ;check for offset value against
cmp $00 ;the preset value
bcc NextSprOffset ;if less, skip this part
ldy SprShuffleAmtOffset ;get current offset to preset value we want to add
adc SprShuffleAmt,y ;get shuffle amount, add to current sprite offset
bcc StrSprOffset ;if not exceeded $ff, skip second add
adc $00 ;otherwise add preset value $28 to offset
StrSprOffset: sta SprDataOffset,x ;store new offset here or old one if branched to here
NextSprOffset: dex ;move backwards to next one
bpl ShuffleLoop
ldx SprShuffleAmtOffset ;load offset
cpx #$03 ;check if offset + 1 goes to 3
bne SetAmtOffset ;if offset + 1 not 3, store
ldx #$00 ;otherwise, init to 0
SetAmtOffset: stx SprShuffleAmtOffset
ldx #$08 ;load offsets for values and storage
ldy #$02
SetMiscOffset: lda SprDataOffset+5,y ;load one of three OAM data offsets
sta Misc_SprDataOffset-2,x ;store first one unmodified, but
clc ;add eight to the second and eight
adc #$08 ;more to the third one
sta Misc_SprDataOffset-1,x ;note that due to the way X is set up,
clc ;this code loads into the misc sprite offsets
adc #$08
sta Misc_SprDataOffset,x
bpl SetMiscOffset ;do this until all misc spr offsets are loaded
lda OperMode ;this is the heart of the entire program,
jsr JumpEngine ;most of what goes on starts here
.dw TitleScreenMode
.dw GameMode
.dw VictoryMode
.dw GameOverMode
ldy #$00 ;this routine moves all sprites off the screen
.db $2c ;BIT instruction opcode
ldy #$04 ;this routine moves all but sprite 0
lda #$f8 ;off the screen
SprInitLoop: sta Sprite_Y_Position,y ;write 248 into OAM data's Y coordinate
iny ;which will move it off the screen
bne SprInitLoop
lda OperMode_Task
jsr JumpEngine
.dw InitializeGame
.dw ScreenRoutines
.dw PrimaryGameSetup
.dw GameMenuRoutine
.db $04, $20, $73, $01, $00, $00
ldy #$00
lda SavedJoypad1Bits ;check to see if either player pressed
ora SavedJoypad2Bits ;only the start button (either joypad)
cmp #Start_Button
beq StartGame
cmp #A_Button+Start_Button ;check to see if A + start was pressed
bne ChkSelect ;if not, branch to check select button
StartGame: jmp ChkContinue ;if either start or A + start, execute here
ChkSelect: cmp #Select_Button ;check to see if the select button was pressed
beq SelectBLogic ;if so, branch reset demo timer
ldx DemoTimer ;otherwise check demo timer
bne ChkWorldSel ;if demo timer not expired, branch to check world selection
sta SelectTimer ;set controller bits here if running demo
jsr DemoEngine ;run through the demo actions
bcs ResetTitle ;if carry flag set, demo over, thus branch
jmp RunDemo ;otherwise, run game engine for demo
ChkWorldSel: ldx WorldSelectEnableFlag ;check to see if world selection has been enabled
beq NullJoypad
cmp #B_Button ;if so, check to see if the B button was pressed
bne NullJoypad
iny ;if so, increment Y and execute same code as select
SelectBLogic: lda DemoTimer ;if select or B pressed, check demo timer one last time
beq ResetTitle ;if demo timer expired, branch to reset title screen mode
lda #$18 ;otherwise reset demo timer
sta DemoTimer
lda SelectTimer ;check select/B button timer
bne NullJoypad ;if not expired, branch
lda #$10 ;otherwise reset select button timer
sta SelectTimer
cpy #$01 ;was the B button pressed earlier? if so, branch
beq IncWorldSel ;note this will not be run if world selection is disabled
lda NumberOfPlayers ;if no, must have been the select button, therefore
eor #%00000001 ;change number of players and draw icon accordingly
sta NumberOfPlayers
jsr DrawMushroomIcon
jmp NullJoypad
IncWorldSel: ldx WorldSelectNumber ;increment world select number
and #%00000111 ;mask out higher bits
sta WorldSelectNumber ;store as current world select number
jsr GoContinue
UpdateShroom: lda WSelectBufferTemplate,x ;write template for world select in vram buffer
sta VRAM_Buffer1-1,x ;do this until all bytes are written
cpx #$06
bmi UpdateShroom
ldy WorldNumber ;get world number from variable and increment for
iny ;proper display, and put in blank byte before
sty VRAM_Buffer1+3 ;null terminator
NullJoypad: lda #$00 ;clear joypad bits for player 1
sta SavedJoypad1Bits
RunDemo: jsr GameCoreRoutine ;run game engine
lda GameEngineSubroutine ;check to see if we're running lose life routine
cmp #$06
bne ExitMenu ;if not, do not do all the resetting below
ResetTitle: lda #$00 ;reset game modes, disable
sta OperMode ;sprite 0 check and disable
sta OperMode_Task ;screen output
sta Sprite0HitDetectFlag
inc DisableScreenFlag
ChkContinue: ldy DemoTimer ;if timer for demo has expired, reset modes
beq ResetTitle
asl ;check to see if A button was also pushed
bcc StartWorld1 ;if not, don't load continue function's world number
lda ContinueWorld ;load previously saved world number for secret
jsr GoContinue ;continue function when pressing A + start
StartWorld1: jsr LoadAreaPointer
inc Hidden1UpFlag ;set 1-up box flag for both players
inc OffScr_Hidden1UpFlag
inc FetchNewGameTimerFlag ;set fetch new game timer flag
inc OperMode ;set next game mode
lda WorldSelectEnableFlag ;if world select flag is on, then primary
sta PrimaryHardMode ;hard mode must be on as well
lda #$00
sta OperMode_Task ;set game mode here, and clear demo timer
sta DemoTimer
ldx #$17
lda #$00
InitScores: sta ScoreAndCoinDisplay,x ;clear player scores and coin displays
bpl InitScores
ExitMenu: rts
GoContinue: sta WorldNumber ;start both players at the first area
sta OffScr_WorldNumber ;of the previously saved world number
ldx #$00 ;note that on power-up using this function
stx AreaNumber ;will make no difference
stx OffScr_AreaNumber
.db $07, $22, $49, $83, $ce, $24, $24, $00
ldy #$07 ;read eight bytes to be read by transfer routine
IconDataRead: lda MushroomIconData,y ;note that the default position is set for a
sta VRAM_Buffer1-1,y ;1-player game
bpl IconDataRead
lda NumberOfPlayers ;check number of players
beq ExitIcon ;if set to 1-player game, we're done
lda #$24 ;otherwise, load blank tile in 1-player position
sta VRAM_Buffer1+3
lda #$ce ;then load shroom icon tile in 2-player position
sta VRAM_Buffer1+5
ExitIcon: rts
.db $01, $80, $02, $81, $41, $80, $01
.db $42, $c2, $02, $80, $41, $c1, $41, $c1
.db $01, $c1, $01, $02, $80, $00
.db $9b, $10, $18, $05, $2c, $20, $24
.db $15, $5a, $10, $20, $28, $30, $20, $10
.db $80, $20, $30, $30, $01, $ff, $00
ldx DemoAction ;load current demo action
lda DemoActionTimer ;load current action timer
bne DoAction ;if timer still counting down, skip
inc DemoAction ;if expired, increment action, X, and
sec ;set carry by default for demo over
lda DemoTimingData-1,x ;get next timer
sta DemoActionTimer ;store as current timer
beq DemoOver ;if timer already at zero, skip
DoAction: lda DemoActionData-1,x ;get and perform action (current or next)
sta SavedJoypad1Bits
dec DemoActionTimer ;decrement action timer
clc ;clear carry if demo still going
DemoOver: rts
jsr VictoryModeSubroutines ;run victory mode subroutines
lda OperMode_Task ;get current task of victory mode
beq AutoPlayer ;if on bridge collapse, skip enemy processing
ldx #$00
stx ObjectOffset ;otherwise reset enemy object offset
jsr EnemiesAndLoopsCore ;and run enemy code
AutoPlayer: jsr RelativePlayerPosition ;get player's relative coordinates
jmp PlayerGfxHandler ;draw the player, then leave
lda OperMode_Task
jsr JumpEngine
.dw BridgeCollapse
.dw SetupVictoryMode
.dw PlayerVictoryWalk
.dw PrintVictoryMessages
.dw PlayerEndWorld
ldx ScreenRight_PageLoc ;get page location of right side of screen
inx ;increment to next page
stx DestinationPageLoc ;store here
lda #EndOfCastleMusic
sta EventMusicQueue ;play win castle music
jmp IncModeTask_B ;jump to set next major task in victory mode
ldy #$00 ;set value here to not walk player by default
sty VictoryWalkControl
lda Player_PageLoc ;get player's page location
cmp DestinationPageLoc ;compare with destination page location
bne PerformWalk ;if page locations don't match, branch
lda Player_X_Position ;otherwise get player's horizontal position
cmp #$60 ;compare with preset horizontal position
bcs DontWalk ;if still on other page, branch ahead
PerformWalk: inc VictoryWalkControl ;otherwise increment value and Y
iny ;note Y will be used to walk the player
DontWalk: tya ;put contents of Y in A and
jsr AutoControlPlayer ;use A to move player to the right or not
lda ScreenLeft_PageLoc ;check page location of left side of screen
cmp DestinationPageLoc ;against set value here
beq ExitVWalk ;branch if equal to change modes if necessary
lda ScrollFractional
clc ;do fixed point math on fractional part of scroll
adc #$80
sta ScrollFractional ;save fractional movement amount
lda #$01 ;set 1 pixel per frame
adc #$00 ;add carry from previous addition
tay ;use as scroll amount
jsr ScrollScreen ;do sub to scroll the screen
jsr UpdScrollVar ;do another sub to update screen and scroll variables
inc VictoryWalkControl ;increment value to stay in this routine
ExitVWalk: lda VictoryWalkControl ;load value set here
beq IncModeTask_A ;if zero, branch to change modes
rts ;otherwise leave
lda SecondaryMsgCounter ;load secondary message counter
bne IncMsgCounter ;if set, branch to increment message counters
lda PrimaryMsgCounter ;otherwise load primary message counter
beq ThankPlayer ;if set to zero, branch to print first message
cmp #$09 ;if at 9 or above, branch elsewhere (this comparison
bcs IncMsgCounter ;is residual code, counter never reaches 9)
ldy WorldNumber ;check world number
cpy #World8
bne MRetainerMsg ;if not at world 8, skip to next part
cmp #$03 ;check primary message counter again
bcc IncMsgCounter ;if not at 3 yet (world 8 only), branch to increment
sbc #$01 ;otherwise subtract one
jmp ThankPlayer ;and skip to next part
MRetainerMsg: cmp #$02 ;check primary message counter
bcc IncMsgCounter ;if not at 2 yet (world 1-7 only), branch
ThankPlayer: tay ;put primary message counter into Y
bne SecondPartMsg ;if counter nonzero, skip this part, do not print first message
lda CurrentPlayer ;otherwise get player currently on the screen
beq EvalForMusic ;if mario, branch
iny ;otherwise increment Y once for luigi and
bne EvalForMusic ;do an unconditional branch to the same place
SecondPartMsg: iny ;increment Y to do world 8's message
lda WorldNumber
cmp #World8 ;check world number
beq EvalForMusic ;if at world 8, branch to next part
dey ;otherwise decrement Y for world 1-7's message
cpy #$04 ;if counter at 4 (world 1-7 only)
bcs SetEndTimer ;branch to set victory end timer
cpy #$03 ;if counter at 3 (world 1-7 only)
bcs IncMsgCounter ;branch to keep counting
EvalForMusic: cpy #$03 ;if counter not yet at 3 (world 8 only), branch
bne PrintMsg ;to print message only (note world 1-7 will only
lda #VictoryMusic ;reach this code if counter = 0, and will always branch)
sta EventMusicQueue ;otherwise load victory music first (world 8 only)
PrintMsg: tya ;put primary message counter in A
clc ;add $0c or 12 to counter thus giving an appropriate value,
adc #$0c ;($0c-$0d = first), ($0e = world 1-7's), ($0f-$12 = world 8's)
sta VRAM_Buffer_AddrCtrl ;write message counter to vram address controller
IncMsgCounter: lda SecondaryMsgCounter
adc #$04 ;add four to secondary message counter
sta SecondaryMsgCounter
lda PrimaryMsgCounter
adc #$00 ;add carry to primary message counter
sta PrimaryMsgCounter
cmp #$07 ;check primary counter one more time
SetEndTimer: bcc ExitMsgs ;if not reached value yet, branch to leave
lda #$06
sta WorldEndTimer ;otherwise set world end timer
IncModeTask_A: inc OperMode_Task ;move onto next task in mode
ExitMsgs: rts ;leave
lda WorldEndTimer ;check to see if world end timer expired
bne EndExitOne ;branch to leave if not
ldy WorldNumber ;check world number
cpy #World8 ;if on world 8, player is done with game,
bcs EndChkBButton ;thus branch to read controller
lda #$00
sta AreaNumber ;otherwise initialize area number used as offset
sta LevelNumber ;and level number control to start at area 1
sta OperMode_Task ;initialize secondary mode of operation
inc WorldNumber ;increment world number to move onto the next world
jsr LoadAreaPointer ;get area address offset for the next area
inc FetchNewGameTimerFlag ;set flag to load game timer from header
lda #GameModeValue
sta OperMode ;set mode of operation to game mode
EndExitOne: rts ;and leave
EndChkBButton: lda SavedJoypad1Bits
ora SavedJoypad2Bits ;check to see if B button was pressed on
and #B_Button ;either controller
beq EndExitTwo ;branch to leave if not
lda #$01 ;otherwise set world selection flag
sta WorldSelectEnableFlag
lda #$ff ;remove onscreen player's lives
sta NumberofLives
jsr TerminateGame ;do sub to continue other player or end game
EndExitTwo: rts ;leave
;data is used as tiles for numbers
;that appear when you defeat enemies
.db $ff, $ff ;dummy
.db $f6, $fb ; "100"
.db $f7, $fb ; "200"
.db $f8, $fb ; "400"
.db $f9, $fb ; "500"
.db $fa, $fb ; "800"
.db $f6, $50 ; "1000"
.db $f7, $50 ; "2000"
.db $f8, $50 ; "4000"
.db $f9, $50 ; "5000"
.db $fa, $50 ; "8000"
.db $fd, $fe ; "1-UP"
;high nybble is digit number, low nybble is number to
;add to the digit of the player's score
.db $ff ;dummy
.db $41, $42, $44, $45, $48
.db $31, $32, $34, $35, $38, $00
lda FloateyNum_Control,x ;load control for floatey number
beq EndExitOne ;if zero, branch to leave
cmp #$0b ;if less than $0b, branch
bcc ChkNumTimer
lda #$0b ;otherwise set to $0b, thus keeping
sta FloateyNum_Control,x ;it in range
ChkNumTimer: tay ;use as Y
lda FloateyNum_Timer,x ;check value here
bne DecNumTimer ;if nonzero, branch ahead
sta FloateyNum_Control,x ;initialize floatey number control and leave
DecNumTimer: dec FloateyNum_Timer,x ;decrement value here
cmp #$2b ;if not reached a certain point, branch
bne ChkTallEnemy
cpy #$0b ;check offset for $0b
bne LoadNumTiles ;branch ahead if not found
inc NumberofLives ;give player one extra life (1-up)
lda #Sfx_ExtraLife
sta Square2SoundQueue ;and play the 1-up sound
LoadNumTiles: lda ScoreUpdateData,y ;load point value here
lsr ;move high nybble to low
tax ;use as X offset, essentially the digit
lda ScoreUpdateData,y ;load again and this time
and #%00001111 ;mask out the high nybble
sta DigitModifier,x ;store as amount to add to the digit
jsr AddToScore ;update the score accordingly
ChkTallEnemy: ldy Enemy_SprDataOffset,x ;get OAM data offset for enemy object
lda Enemy_ID,x ;get enemy object identifier
cmp #Spiny
beq FloateyPart ;branch if spiny
cmp #PiranhaPlant
beq FloateyPart ;branch if piranha plant
cmp #HammerBro
beq GetAltOffset ;branch elsewhere if hammer bro
cmp #GreyCheepCheep
beq FloateyPart ;branch if cheep-cheep of either color
cmp #RedCheepCheep
beq FloateyPart
cmp #TallEnemy
bcs GetAltOffset ;branch elsewhere if enemy object => $09
lda Enemy_State,x
cmp #$02 ;if enemy state defeated or otherwise
bcs FloateyPart ;$02 or greater, branch beyond this part
GetAltOffset: ldx SprDataOffset_Ctrl ;load some kind of control bit
ldy Alt_SprDataOffset,x ;get alternate OAM data offset
ldx ObjectOffset ;get enemy object offset again
FloateyPart: lda FloateyNum_Y_Pos,x ;get vertical coordinate for
cmp #$18 ;floatey number, if coordinate in the
bcc SetupNumSpr ;status bar, branch
sbc #$01
sta FloateyNum_Y_Pos,x ;otherwise subtract one and store as new
SetupNumSpr: lda FloateyNum_Y_Pos,x ;get vertical coordinate
sbc #$08 ;subtract eight and dump into the
jsr DumpTwoSpr ;left and right sprite's Y coordinates
lda FloateyNum_X_Pos,x ;get horizontal coordinate
sta Sprite_X_Position,y ;store into X coordinate of left sprite
adc #$08 ;add eight pixels and store into X
sta Sprite_X_Position+4,y ;coordinate of right sprite
lda #$02
sta Sprite_Attributes,y ;set palette control in attribute bytes
sta Sprite_Attributes+4,y ;of left and right sprites
lda FloateyNum_Control,x
asl ;multiply our floatey number control by 2
tax ;and use as offset for look-up table
lda FloateyNumTileData,x
sta Sprite_Tilenumber,y ;display first half of number of points
lda FloateyNumTileData+1,x
sta Sprite_Tilenumber+4,y ;display the second half
ldx ObjectOffset ;get enemy object offset and leave
lda ScreenRoutineTask ;run one of the following subroutines
jsr JumpEngine
.dw InitScreen
.dw SetupIntermediate
.dw WriteTopStatusLine
.dw WriteBottomStatusLine
.dw DisplayTimeUp
.dw ResetSpritesAndScreenTimer
.dw DisplayIntermediate
.dw ResetSpritesAndScreenTimer
.dw AreaParserTaskControl
.dw GetAreaPalette
.dw GetBackgroundColor
.dw GetAlternatePalette1
.dw DrawTitleScreen
.dw ClearBuffersDrawIcon
.dw WriteTopScore
jsr MoveAllSpritesOffscreen ;initialize all sprites including sprite #0
jsr InitializeNameTables ;and erase both name and attribute tables
lda OperMode
beq NextSubtask ;if mode still 0, do not load
ldx #$03 ;into buffer pointer
jmp SetVRAMAddr_A
lda BackgroundColorCtrl ;save current background color control
pha ;and player status to stack
lda PlayerStatus
lda #$00 ;set background color to black
sta PlayerStatus ;and player status to not fiery
lda #$02 ;this is the ONLY time background color control
sta BackgroundColorCtrl ;is set to less than 4
jsr GetPlayerColors
pla ;we only execute this routine for
sta PlayerStatus ;the intermediate lives display
pla ;and once we're done, we return bg
sta BackgroundColorCtrl ;color ctrl and player status from stack
jmp IncSubtask ;then move onto the next task
.db $01, $02, $03, $04
ldy AreaType ;select appropriate palette to load
ldx AreaPalette,y ;based on area type
SetVRAMAddr_A: stx VRAM_Buffer_AddrCtrl ;store offset into buffer control
NextSubtask: jmp IncSubtask ;move onto next task
;$00 - used as temp counter in GetPlayerColors
.db $00, $09, $0a, $04
.db $22, $22, $0f, $0f ;used by area type if bg color ctrl not set
.db $0f, $22, $0f, $0f ;used by background color control if set
.db $22, $16, $27, $18 ;mario's colors
.db $22, $30, $27, $19 ;luigi's colors
.db $22, $37, $27, $16 ;fiery (used by both)
ldy BackgroundColorCtrl ;check background color control
beq NoBGColor ;if not set, increment task and fetch palette
lda BGColorCtrl_Addr-4,y ;put appropriate palette into vram
sta VRAM_Buffer_AddrCtrl ;note that if set to 5-7, $0301 will not be read
NoBGColor: inc ScreenRoutineTask ;increment to next subtask and plod on through
ldx VRAM_Buffer1_Offset ;get current buffer offset
ldy #$00
lda CurrentPlayer ;check which player is on the screen
beq ChkFiery
ldy #$04 ;load offset for luigi
ChkFiery: lda PlayerStatus ;check player status
cmp #$02
bne StartClrGet ;if fiery, load alternate offset for fiery player
ldy #$08
StartClrGet: lda #$03 ;do four colors
sta $00
ClrGetLoop: lda PlayerColors,y ;fetch player colors and store them
sta VRAM_Buffer1+3,x ;in the buffer
dec $00
bpl ClrGetLoop
ldx VRAM_Buffer1_Offset ;load original offset from before
ldy BackgroundColorCtrl ;if this value is four or greater, it will be set
bne SetBGColor ;therefore use it as offset to background color
ldy AreaType ;otherwise use area type bits from area offset as offset
SetBGColor: lda BackgroundColors,y ;to background color instead
sta VRAM_Buffer1+3,x
lda #$3f ;set for sprite palette address
sta VRAM_Buffer1,x ;save to buffer
lda #$10
sta VRAM_Buffer1+1,x
lda #$04 ;write length byte to buffer
sta VRAM_Buffer1+2,x
lda #$00 ;now the null terminator
sta VRAM_Buffer1+7,x
txa ;move the buffer pointer ahead 7 bytes
clc ;in case we want to write anything else later
adc #$07
SetVRAMOffset: sta VRAM_Buffer1_Offset ;store as new vram buffer offset
lda AreaStyle ;check for mushroom level style
cmp #$01
bne NoAltPal
lda #$0b ;if found, load appropriate palette
SetVRAMAddr_B: sta VRAM_Buffer_AddrCtrl
NoAltPal: jmp IncSubtask ;now onto the next task
lda #$00 ;select main status bar
jsr WriteGameText ;output it
jmp IncSubtask ;onto the next task
jsr GetSBNybbles ;write player's score and coin tally to screen
ldx VRAM_Buffer1_Offset
lda #$20 ;write address for world-area number on screen
sta VRAM_Buffer1,x
lda #$73
sta VRAM_Buffer1+1,x
lda #$03 ;write length for it
sta VRAM_Buffer1+2,x
ldy WorldNumber ;first the world number
sta VRAM_Buffer1+3,x
lda #$28 ;next the dash
sta VRAM_Buffer1+4,x
ldy LevelNumber ;next the level number
iny ;increment for proper number display
sta VRAM_Buffer1+5,x
lda #$00 ;put null terminator on
sta VRAM_Buffer1+6,x
txa ;move the buffer offset up by 6 bytes
adc #$06
sta VRAM_Buffer1_Offset
jmp IncSubtask
lda GameTimerExpiredFlag ;if game timer not expired, increment task
beq NoTimeUp ;control 2 tasks forward, otherwise, stay here
lda #$00
sta GameTimerExpiredFlag ;reset timer expiration flag
lda #$02 ;output time-up screen to buffer
jmp OutputInter
NoTimeUp: inc ScreenRoutineTask ;increment control task 2 tasks forward
jmp IncSubtask
lda OperMode ;check primary mode of operation
beq NoInter ;if in title screen mode, skip this
cmp #GameOverModeValue ;are we in game over mode?
beq GameOverInter ;if so, proceed to display game over screen
lda AltEntranceControl ;otherwise check for mode of alternate entry
bne NoInter ;and branch if found
ldy AreaType ;check if we are on castle level
cpy #$03 ;and if so, branch (possibly residual)
beq PlayerInter
lda DisableIntermediate ;if this flag is set, skip intermediate lives display
bne NoInter ;and jump to specific task, otherwise
PlayerInter: jsr DrawPlayer_Intermediate ;put player in appropriate place for
lda #$01 ;lives display, then output lives display to buffer
OutputInter: jsr WriteGameText
jsr ResetScreenTimer
lda #$00
sta DisableScreenFlag ;reenable screen output
GameOverInter: lda #$12 ;set screen timer
sta ScreenTimer
lda #$03 ;output game over screen to buffer
jsr WriteGameText
jmp IncModeTask_B
NoInter: lda #$08 ;set for specific task and leave
sta ScreenRoutineTask
inc DisableScreenFlag ;turn off screen
TaskLoop: jsr AreaParserTaskHandler ;render column set of current area
lda AreaParserTaskNum ;check number of tasks
bne TaskLoop ;if tasks still not all done, do another one
dec ColumnSets ;do we need to render more column sets?
bpl OutputCol
inc ScreenRoutineTask ;if not, move on to the next task
OutputCol: lda #$06 ;set vram buffer to output rendered column set
sta VRAM_Buffer_AddrCtrl ;on next NMI
;$00 - vram buffer address table low
;$01 - vram buffer address table high
lda OperMode ;are we in title screen mode?
bne IncModeTask_B ;if not, exit
lda #>TitleScreenDataOffset ;load address $1ec0 into
sta PPU_ADDRESS ;the vram address register
lda #<TitleScreenDataOffset
lda #$03 ;put address $0300 into
sta $01 ;the indirect at $00
ldy #$00
sty $00
lda PPU_DATA ;do one garbage read
OutputTScr: lda PPU_DATA ;get title screen from chr-rom
sta ($00),y ;store 256 bytes into buffer
bne ChkHiByte ;if not past 256 bytes, do not increment
inc $01 ;otherwise increment high byte of indirect
ChkHiByte: lda $01 ;check high byte?
cmp #$04 ;at $0400?
bne OutputTScr ;if not, loop back and do another
cpy #$3a ;check if offset points past end of data
bcc OutputTScr ;if not, loop back and do another
lda #$05 ;set buffer transfer control to $0300,
jmp SetVRAMAddr_B ;increment task and exit
lda OperMode ;check game mode
bne IncModeTask_B ;if not title screen mode, leave
ldx #$00 ;otherwise, clear buffer space
TScrClear: sta VRAM_Buffer1-1,x
sta VRAM_Buffer1-1+$100,x
bne TScrClear
jsr DrawMushroomIcon ;draw player select icon
IncSubtask: inc ScreenRoutineTask ;move onto next task
lda #$fa ;run display routine to display top score on title
jsr UpdateNumber
IncModeTask_B: inc OperMode_Task ;move onto next mode
.db $20, $43, $05, $16, $0a, $1b, $12, $18 ; "MARIO"
.db $20, $52, $0b, $20, $18, $1b, $15, $0d ; "WORLD TIME"
.db $24, $24, $1d, $12, $16, $0e
.db $20, $68, $05, $00, $24, $24, $2e, $29 ; score trailing digit and coin display
.db $23, $c0, $7f, $aa ; attribute table data, clears name table 0 to palette 2
.db $23, $c2, $01, $ea ; attribute table data, used for coin icon in status bar
.db $ff ; end of data block
.db $21, $cd, $07, $24, $24 ; cross with spaces used on
.db $29, $24, $24, $24, $24 ; lives display
.db $21, $4b, $09, $20, $18 ; "WORLD - " used on lives display
.db $1b, $15, $0d, $24, $24, $28, $24
.db $22, $0c, $47, $24 ; possibly used to clear time up
.db $23, $dc, $01, $ba ; attribute table data for crown if more than 9 lives
.db $ff
.db $21, $cd, $05, $16, $0a, $1b, $12, $18 ; "MARIO"
.db $22, $0c, $07, $1d, $12, $16, $0e, $24, $1e, $19 ; "TIME UP"
.db $ff
.db $21, $cd, $05, $16, $0a, $1b, $12, $18 ; "MARIO"
.db $22, $0b, $09, $10, $0a, $16, $0e, $24 ; "GAME OVER"
.db $18, $1f, $0e, $1b
.db $ff
.db $25, $84, $15, $20, $0e, $15, $0c, $18, $16 ; "WELCOME TO WARP ZONE!"
.db $0e, $24, $1d, $18, $24, $20, $0a, $1b, $19
.db $24, $23, $18, $17, $0e, $2b
.db $26, $25, $01, $24 ; placeholder for left pipe
.db $26, $2d, $01, $24 ; placeholder for middle pipe
.db $26, $35, $01, $24 ; placeholder for right pipe
.db $27, $d9, $46, $aa ; attribute data
.db $27, $e1, $45, $aa
.db $ff
.db $15, $1e, $12, $10, $12 ; "LUIGI", no address or length
.db $04, $03, $02, $00 ; warp zone numbers, note spaces on middle
.db $24, $05, $24, $00 ; zone, partly responsible for
.db $08, $07, $06, $00 ; the minus world
.db TopStatusBarLine-GameText, TopStatusBarLine-GameText
.db WorldLivesDisplay-GameText, WorldLivesDisplay-GameText
.db TwoPlayerTimeUp-GameText, OnePlayerTimeUp-GameText
.db TwoPlayerGameOver-GameText, OnePlayerGameOver-GameText
.db WarpZoneWelcome-GameText, WarpZoneWelcome-GameText
pha ;save text number to stack
tay ;multiply by 2 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 there are two, 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
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
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
sty VRAM_Buffer1+21 ;we're done here
lda NumberOfPlayers ;check number of players
beq ExitChkName ;if only 1 player, leave
lda CurrentPlayer ;load current player
dex ;check to see if current message number is for time up
bne ChkLuigi
ldy OperMode ;check for game over mode
cpy #GameOverModeValue
beq ChkLuigi
eor #%00000001 ;if not, must be time up, invert d0 to do other player
ChkLuigi: lsr
bcc ExitChkName ;if mario is current player, do not change the name
ldy #$04
NameLoop: lda LuigiName,y ;otherwise, replace "MARIO" with "LUIGI"
sta VRAM_Buffer1+3,y
bpl NameLoop ;do this until each letter is replaced
ExitChkName: rts
sbc #$04 ;subtract 4 and then shift to the left
asl ;twice to get proper warp zone number
asl ;offset
ldy #$00
WarpNumLoop: lda WarpZoneNumbers,x ;print warp zone numbers into the
sta VRAM_Buffer1+27,y ;placeholders from earlier
iny ;put a number in every fourth space
cpy #$0c
bcc WarpNumLoop
lda #$2c ;load new buffer pointer at end of message
jmp SetVRAMOffset
lda ScreenTimer ;check if screen timer has expired
bne NoReset ;if not, branch to leave
jsr MoveAllSpritesOffscreen ;otherwise reset sprites now
lda #$07 ;reset timer again
sta ScreenTimer
inc ScreenRoutineTask ;move onto next task
NoReset: rts
;$00 - temp vram buffer offset
;$01 - temp metatile buffer offset
;$02 - temp metatile graphics table offset
;$03 - used to store attribute bits
;$04 - used to determine attribute table row
;$05 - used to determine attribute table column
;$06 - metatile graphics table address low
;$07 - metatile graphics table address high
lda CurrentColumnPos ;store LSB of where we're at
and #$01
sta $05
ldy VRAM_Buffer2_Offset ;store vram buffer offset
sty $00
lda CurrentNTAddr_Low ;get current name table address we're supposed to render
sta VRAM_Buffer2+1,y
lda CurrentNTAddr_High
sta VRAM_Buffer2,y
lda #$9a ;store length byte of 26 here with d7 set
sta VRAM_Buffer2+2,y ;to increment by 32 (in columns)
lda #$00 ;init attribute row
sta $04
DrawMTLoop: stx $01 ;store init value of 0 or incremented offset for buffer
lda MetatileBuffer,x ;get first metatile number, and mask out all but 2 MSB
and #%11000000
sta $03 ;store attribute table bits here
asl ;note that metatile format is:
rol ;%xx000000 - attribute table bits,
rol ;%00xxxxxx - metatile number
tay ;rotate bits to d1-d0 and use as offset here
lda MetatileGraphics_Low,y ;get address to graphics table from here
sta $06
lda MetatileGraphics_High,y
sta $07
lda MetatileBuffer,x ;get metatile number again
asl ;multiply by 4 and use as tile offset
sta $02
lda AreaParserTaskNum ;get current task number for level processing and
and #%00000001 ;mask out all but LSB, then invert LSB, multiply by 2
eor #%00000001 ;to get the correct column position in the metatile,
asl ;then add to the tile offset so we can draw either side
adc $02 ;of the metatiles
ldx $00 ;use vram buffer offset from before as X
lda ($06),y
sta VRAM_Buffer2+3,x ;get first tile number (top left or top right) and store
lda ($06),y ;now get the second (bottom left or bottom right) and store
sta VRAM_Buffer2+4,x
ldy $04 ;get current attribute row
lda $05 ;get LSB of current column where we're at, and
bne RightCheck ;branch if set (clear = left attrib, set = right)
lda $01 ;get current row we're rendering
lsr ;branch if LSB set (clear = top left, set = bottom left)
bcs LLeft
rol $03 ;rotate attribute bits 3 to the left
rol $03 ;thus in d1-d0, for upper left square
rol $03
jmp SetAttrib
RightCheck: lda $01 ;get LSB of current row we're rendering
lsr ;branch if set (clear = top right, set = bottom right)
bcs NextMTRow
lsr $03 ;shift attribute bits 4 to the right
lsr $03 ;thus in d3-d2, for upper right square
lsr $03
lsr $03
jmp SetAttrib
LLeft: lsr $03 ;shift attribute bits 2 to the right
lsr $03 ;thus in d5-d4 for lower left square
NextMTRow: inc $04 ;move onto next attribute row
SetAttrib: lda AttributeBuffer,y ;get previously saved bits from before
ora $03 ;if any, and put new bits, if any, onto
sta AttributeBuffer,y ;the old, and store
inc $00 ;increment vram buffer offset by 2
inc $00
ldx $01 ;get current gfx buffer row, and check for
inx ;the bottom of the screen
cpx #$0d
bcc DrawMTLoop ;if not there yet, loop back
ldy $00 ;get current vram buffer offset, increment by 3
iny ;(for name table address and length bytes)
lda #$00
sta VRAM_Buffer2,y ;put null terminator at end of data for name table
sty VRAM_Buffer2_Offset ;store new buffer offset
inc CurrentNTAddr_Low ;increment name table address low
lda CurrentNTAddr_Low ;check current low byte
and #%00011111 ;if no wraparound, just skip this part
bne ExitDrawM
lda #$80 ;if wraparound occurs, make sure low byte stays
sta CurrentNTAddr_Low ;just under the status bar
lda CurrentNTAddr_High ;and then invert d2 of the name table address high
eor #%00000100 ;to move onto the next appropriate name table
sta CurrentNTAddr_High
ExitDrawM: jmp SetVRAMCtrl ;jump to set buffer to $0341 and leave
;$00 - temp attribute table address high (big endian order this time!)
;$01 - temp attribute table address low
lda CurrentNTAddr_Low ;get low byte of next name table address
and #%00011111 ;to be written to, mask out all but 5 LSB,
sec ;subtract four
sbc #$04
and #%00011111 ;mask out bits again and store
sta $01
lda CurrentNTAddr_High ;get high byte and branch if borrow not set
bcs SetATHigh
eor #%00000100 ;otherwise invert d2
SetATHigh: and #%00000100 ;mask out all other bits
ora #$23 ;add $2300 to the high byte and store
sta $00
lda $01 ;get low byte - 4, divide by 4, add offset for
lsr ;attribute table and store
adc #$c0 ;we should now have the appropriate block of
sta $01 ;attribute table in our temp address
ldx #$00
ldy VRAM_Buffer2_Offset ;get buffer offset
AttribLoop: lda $00
sta VRAM_Buffer2,y ;store high byte of attribute table address
lda $01
clc ;get low byte, add 8 because we want to start
adc #$08 ;below the status bar, and store
sta VRAM_Buffer2+1,y
sta $01 ;also store in temp again
lda AttributeBuffer,x ;fetch current attribute table byte and store
sta VRAM_Buffer2+3,y ;in the buffer
lda #$01
sta VRAM_Buffer2+2,y ;store length of 1 in buffer
sta AttributeBuffer,x ;clear current byte in attribute buffer
iny ;increment buffer offset by 4 bytes
inx ;increment attribute offset and check to see
cpx #$07 ;if we're at the end yet
bcc AttribLoop
sta VRAM_Buffer2,y ;put null terminator at the end
sty VRAM_Buffer2_Offset ;store offset in case we want to do any more
SetVRAMCtrl: lda #$06
sta VRAM_Buffer_AddrCtrl ;set buffer to $0341 and leave
;$00 - used as temporary counter in ColorRotation
.db $27, $27, $27, $17, $07, $17
.db $3f, $0c, $04, $ff, $ff, $ff, $ff, $00
;used based on area type
.db $0f, $07, $12, $0f
.db $0f, $07, $17, $0f
.db $0f, $07, $17, $1c
.db $0f, $07, $17, $00
lda FrameCounter ;get frame counter
and #$07 ;mask out all but three LSB
bne ExitColorRot ;branch if not set to zero to do this every eighth frame
ldx VRAM_Buffer1_Offset ;check vram buffer offset
cpx #$31
bcs ExitColorRot ;if offset over 48 bytes, branch to leave
tay ;otherwise use frame counter's 3 LSB as offset here
GetBlankPal: lda BlankPalette,y ;get blank palette for palette 3
sta VRAM_Buffer1,x ;store it in the vram buffer
inx ;increment offsets
cpy #$08
bcc GetBlankPal ;do this until all bytes are copied
ldx VRAM_Buffer1_Offset ;get current vram buffer offset
lda #$03
sta $00 ;set counter here
lda AreaType ;get area type
asl ;multiply by 4 to get proper offset
tay ;save as offset here
GetAreaPal: lda Palette3Data,y ;fetch palette to be written based on area type
sta VRAM_Buffer1+3,x ;store it to overwrite blank palette in vram buffer
dec $00 ;decrement counter
bpl GetAreaPal ;do this until the palette is all copied
ldx VRAM_Buffer1_Offset ;get current vram buffer offset
ldy ColorRotateOffset ;get color cycling offset
lda ColorRotatePalette,y
sta VRAM_Buffer1+4,x ;get and store current color in second slot of palette
lda VRAM_Buffer1_Offset
clc ;add seven bytes to vram buffer offset
adc #$07
sta VRAM_Buffer1_Offset
inc ColorRotateOffset ;increment color cycling offset
lda ColorRotateOffset
cmp #$06 ;check to see if it's still in range
bcc ExitColorRot ;if so, branch to leave
lda #$00
sta ColorRotateOffset ;otherwise, init to keep it in range
ExitColorRot: rts ;leave
;$00 - temp store for offset control bit
;$01 - temp vram buffer offset
;$02 - temp store for vertical high nybble in block buffer routine
;$03 - temp adder for high byte of name table address
;$04, $05 - name table address low/high
;$06, $07 - block buffer address low/high
.db $45, $45, $47, $47
.db $47, $47, $47, $47
.db $57, $58, $59, $5a
.db $24, $24, $24, $24
.db $26, $26, $26, $26
ldy #$41 ;set low byte so offset points to $0341
lda #$03 ;load offset for default blank metatile
ldx AreaType ;check area type
bne WriteBlankMT ;if not water type, use offset
lda #$04 ;otherwise load offset for blank metatile used in water
WriteBlankMT: jsr PutBlockMetatile ;do a sub to write blank metatile to vram buffer
lda #$06
sta VRAM_Buffer_AddrCtrl ;set vram address controller to $0341 and leave
jsr WriteBlockMetatile ;write metatile to vram buffer to replace block object
inc Block_ResidualCounter ;increment unused counter (residual code)
dec Block_RepFlag,x ;decrement flag (residual code)
rts ;leave
lda #$00 ;force blank metatile if branched/jumped to this point
ldy #$03 ;load offset for blank metatile
cmp #$00 ;check contents of A for blank metatile
beq UseBOffset ;branch if found (unconditional if branched from 8a6b)
ldy #$00 ;load offset for brick metatile w/ line
cmp #$58
beq UseBOffset ;use offset if metatile is brick with coins (w/ line)
cmp #$51
beq UseBOffset ;use offset if metatile is breakable brick w/ line
iny ;increment offset for brick metatile w/o line
cmp #$5d
beq UseBOffset ;use offset if metatile is brick with coins (w/o line)
cmp #$52
beq UseBOffset ;use offset if metatile is breakable brick w/o line
iny ;if any other metatile, increment offset for empty block
UseBOffset: tya ;put Y in A
ldy VRAM_Buffer1_Offset ;get vram buffer offset
iny ;move onto next byte
jsr PutBlockMetatile ;get appropriate block data and write to vram buffer
MoveVOffset: dey ;decrement vram buffer offset
tya ;add 10 bytes to it
adc #10
jmp SetVRAMOffset ;branch to store as new vram buffer offset
stx $00 ;store control bit from SprDataOffset_Ctrl
sty $01 ;store vram buffer offset for next byte
asl ;multiply A by four and use as X
ldy #$20 ;load high byte for name table 0
lda $06 ;get low byte of block buffer pointer
cmp #$d0 ;check to see if we're on odd-page block buffer
bcc SaveHAdder ;if not, use current high byte
ldy #$24 ;otherwise load high byte for name table 1
SaveHAdder: sty $03 ;save high byte here
and #$0f ;mask out high nybble of block buffer pointer
asl ;multiply by 2 to get appropriate name table low byte
sta $04 ;and then store it here
lda #$00
sta $05 ;initialize temp high byte
lda $02 ;get vertical high nybble offset used in block buffer routine
adc #$20 ;add 32 pixels for the status bar
rol $05 ;shift and rotate d7 onto d0 and d6 into carry
rol $05 ;shift and rotate d6 onto d0 and d5 into carry
adc $04 ;add low byte of name table and carry to vertical high nybble
sta $04 ;and store here
lda $05 ;get whatever was in d7 and d6 of vertical high nybble
adc #$00 ;add carry
adc $03 ;then add high byte of name table
sta $05 ;store here
ldy $01 ;get vram buffer offset to be used
RemBridge: lda BlockGfxData,x ;write top left and top right
sta VRAM_Buffer1+2,y ;tile numbers into first spot
lda BlockGfxData+1,x
sta VRAM_Buffer1+3,y
lda BlockGfxData+2,x ;write bottom left and bottom
sta VRAM_Buffer1+7,y ;right tiles numbers into
lda BlockGfxData+3,x ;second spot
sta VRAM_Buffer1+8,y
lda $04
sta VRAM_Buffer1,y ;write low byte of name table
clc ;into first slot as read
adc #$20 ;add 32 bytes to value
sta VRAM_Buffer1+5,y ;write low byte of name table
lda $05 ;plus 32 bytes into second slot
sta VRAM_Buffer1-1,y ;write high byte of name
sta VRAM_Buffer1+4,y ;table address to both slots
lda #$02
sta VRAM_Buffer1+1,y ;put length of 2 in
sta VRAM_Buffer1+6,y ;both slots
lda #$00
sta VRAM_Buffer1+9,y ;put null terminator at end
ldx $00 ;get offset control bit here
rts ;and leave
.db <Palette0_MTiles, <Palette1_MTiles, <Palette2_MTiles, <Palette3_MTiles
.db >Palette0_MTiles, >Palette1_MTiles, >Palette2_MTiles, >Palette3_MTiles
.db $24, $24, $24, $24 ;blank
.db $27, $27, $27, $27 ;black metatile
.db $24, $24, $24, $35 ;bush left
.db $36, $25, $37, $25 ;bush middle
.db $24, $38, $24, $24 ;bush right
.db $24, $30, $30, $26 ;mountain left
.db $26, $26, $34, $26 ;mountain left bottom/middle center
.db $24, $31, $24, $32 ;mountain middle top
.db $33, $26, $24, $33 ;mountain right
.db $34, $26, $26, $26 ;mountain right bottom
.db $26, $26, $26, $26 ;mountain middle bottom
.db $24, $c0, $24, $c0 ;bridge guardrail
.db $24, $7f, $7f, $24 ;chain
.db $b8, $ba, $b9, $bb ;tall tree top, top half
.db $b8, $bc, $b9, $bd ;short tree top
.db $ba, $bc, $bb, $bd ;tall tree top, bottom half
.db $60, $64, $61, $65 ;warp pipe end left, points up
.db $62, $66, $63, $67 ;warp pipe end right, points up
.db $60, $64, $61, $65 ;decoration pipe end left, points up
.db $62, $66, $63, $67 ;decoration pipe end right, points up
.db $68, $68, $69, $69 ;pipe shaft left
.db $26, $26, $6a, $6a ;pipe shaft right
.db $4b, $4c, $4d, $4e ;tree ledge left edge
.db $4d, $4f, $4d, $4f ;tree ledge middle
.db $4d, $4e, $50, $51 ;tree ledge right edge
.db $6b, $70, $2c, $2d ;mushroom left edge
.db $6c, $71, $6d, $72 ;mushroom middle
.db $6e, $73, $6f, $74 ;mushroom right edge
.db $86, $8a, $87, $8b ;sideways pipe end top
.db $88, $8c, $88, $8c ;sideways pipe shaft top
.db $89, $8d, $69, $69 ;sideways pipe joint top
.db $8e, $91, $8f, $92 ;sideways pipe end bottom
.db $26, $93, $26, $93 ;sideways pipe shaft bottom
.db $90, $94, $69, $69 ;sideways pipe joint bottom
.db $a4, $e9, $ea, $eb ;seaplant
.db $24, $24, $24, $24 ;blank, used on bricks or blocks that are hit
.db $24, $2f, $24, $3d ;flagpole ball
.db $a2, $a2, $a3, $a3 ;flagpole shaft
.db $24, $24, $24, $24 ;blank, used in conjunction with vines
.db $a2, $a2, $a3, $a3 ;vertical rope
.db $99, $24, $99, $24 ;horizontal rope
.db $24, $a2, $3e, $3f ;left pulley
.db $5b, $5c, $24, $a3 ;right pulley
.db $24, $24, $24, $24 ;blank used for balance rope
.db $9d, $47, $9e, $47 ;castle top
.db $47, $47, $27, $27 ;castle window left
.db $47, $47, $47, $47 ;castle brick wall
.db $27, $27, $47, $47 ;castle window right
.db $a9, $47, $aa, $47 ;castle top w/ brick
.db $9b, $27, $9c, $27 ;entrance top
.db $27, $27, $27, $27 ;entrance bottom
.db $52, $52, $52, $52 ;green ledge stump
.db $80, $a0, $81, $a1 ;fence
.db $be, $be, $bf, $bf ;tree trunk
.db $75, $ba, $76, $bb ;mushroom stump top
.db $ba, $ba, $bb, $bb ;mushroom stump bottom
.db $45, $47, $45, $47 ;breakable brick w/ line
.db $47, $47, $47, $47 ;breakable brick
.db $45, $47, $45, $47 ;breakable brick (not used)
.db $b4, $b6, $b5, $b7 ;cracked rock terrain
.db $45, $47, $45, $47 ;brick with line (power-up)
.db $45, $47, $45, $47 ;brick with line (vine)
.db $45, $47, $45, $47 ;brick with line (star)
.db $45, $47, $45, $47 ;brick with line (coins)
.db $45, $47, $45, $47 ;brick with line (1-up)
.db $47, $47, $47, $47 ;brick (power-up)
.db $47, $47, $47, $47 ;brick (vine)
.db $47, $47, $47, $47 ;brick (star)
.db $47, $47, $47, $47 ;brick (coins)
.db $47, $47, $47, $47 ;brick (1-up)
.db $24, $24, $24, $24 ;hidden block (1 coin)
.db $24, $24, $24, $24 ;hidden block (1-up)
.db $ab, $ac, $ad, $ae ;solid block (3-d block)
.db $5d, $5e, $5d, $5e ;solid block (white wall)
.db $c1, $24, $c1, $24 ;bridge
.db $c6, $c8, $c7, $c9 ;bullet bill cannon barrel
.db $ca, $cc, $cb, $cd ;bullet bill cannon top
.db $2a, $2a, $40, $40 ;bullet bill cannon bottom
.db $24, $24, $24, $24 ;blank used for jumpspring
.db $24, $47, $24, $47 ;half brick used for jumpspring
.db $82, $83, $84, $85 ;solid block (water level, green rock)
.db $24, $47, $24, $47 ;half brick (???)
.db $86, $8a, $87, $8b ;water pipe top
.db $8e, $91, $8f, $92 ;water pipe bottom
.db $24, $2f, $24, $3d ;flag ball (residual object)
.db $24, $24, $24, $35 ;cloud left
.db $36, $25, $37, $25 ;cloud middle
.db $24, $38, $24, $24 ;cloud right
.db $24, $24, $39, $24 ;cloud bottom left
.db $3a, $24, $3b, $24 ;cloud bottom middle
.db $3c, $24, $24, $24 ;cloud bottom right
.db $41, $26, $41, $26 ;water/lava top
.db $26, $26, $26, $26 ;water/lava
.db $b0, $b1, $b2, $b3 ;cloud level terrain
.db $77, $79, $77, $79 ;bowser's bridge
.db $53, $55, $54, $56 ;question block (coin)
.db $53, $55, $54, $56 ;question block (power-up)
.db $a5, $a7, $a6, $a8 ;coin
.db $c2, $c4, $c3, $c5 ;underwater coin
.db $57, $59, $58, $5a ;empty block
.db $7b, $7d, $7c, $7e ;axe
.db $3f, $00, $20
.db $0f, $15, $12, $25
.db $0f, $3a, $1a, $0f
.db $0f, $30, $12, $0f
.db $0f, $27, $12, $0f
.db $22, $16, $27, $18
.db $0f, $10, $30, $27
.db $0f, $16, $30, $27
.db $0f, $0f, $30, $10
.db $00
.db $3f, $00, $20
.db $0f, $29, $1a, $0f
.db $0f, $36, $17, $0f
.db $0f, $30, $21, $0f
.db $0f, $27, $17, $0f
.db $0f, $16, $27, $18
.db $0f, $1a, $30, $27
.db $0f, $16, $30, $27
.db $0f, $0f, $36, $17
.db $00
.db $3f, $00, $20
.db $0f, $29, $1a, $09
.db $0f, $3c, $1c, $0f
.db $0f, $30, $21, $1c
.db $0f, $27, $17, $1c
.db $0f, $16, $27, $18
.db $0f, $1c, $36, $17
.db $0f, $16, $30, $27
.db $0f, $0c, $3c, $1c
.db $00
.db $3f, $00, $20
.db $0f, $30, $10, $00
.db $0f, $30, $10, $00
.db $0f, $30, $16, $00
.db $0f, $27, $17, $00
.db $0f, $16, $27, $18
.db $0f, $1c, $36, $17
.db $0f, $16, $30, $27
.db $0f, $00, $30, $10
.db $00
.db $3f, $00, $04
.db $22, $30, $00, $10
.db $00
.db $3f, $00, $04
.db $0f, $30, $00, $10
.db $00
.db $3f, $00, $04
.db $22, $27, $16, $0f
.db $00
.db $3f, $14, $04
.db $0f, $1a, $30, $27
.db $00
.db $25, $48, $10
.db $1d, $11, $0a, $17, $14, $24
.db $22, $18, $1e, $24
.db $16, $0a, $1b, $12, $18, $2b
.db $00
.db $25, $48, $10
.db $1d, $11, $0a, $17, $14, $24
.db $22, $18, $1e, $24
.db $15, $1e, $12, $10, $12, $2b
.db $00
.db $25, $c5, $16
.db $0b, $1e, $1d, $24, $18, $1e, $1b, $24
.db $19, $1b, $12, $17, $0c, $0e, $1c, $1c, $24
.db $12, $1c, $24, $12, $17
.db $26, $05, $0f
.db $0a, $17, $18, $1d, $11, $0e, $1b, $24
.db $0c, $0a, $1c, $1d, $15, $0e, $2b, $00
.db $25, $a7, $13
.db $22, $18, $1e, $1b, $24
.db $1a, $1e, $0e, $1c, $1d, $24
.db $12, $1c, $24, $18, $1f, $0e, $1b, $af
.db $00
.db $25, $e3, $1b
.db $20, $0e, $24
.db $19, $1b, $0e, $1c, $0e, $17, $1d, $24
.db $22, $18, $1e, $24, $0a, $24, $17, $0e, $20, $24
.db $1a, $1e, $0e, $1c, $1d, $af
.db $00
.db $26, $4a, $0d
.db $19, $1e, $1c, $11, $24
.db $0b, $1e, $1d, $1d, $18, $17, $24, $0b
.db $00
.db $26, $88, $11
.db $1d, $18, $24, $1c, $0e, $15, $0e, $0c, $1d, $24
.db $0a, $24, $20, $18, $1b, $15, $0d
.db $00
;$04 - address low to jump address
;$05 - address high to jump address
;$06 - jump address low
;$07 - jump address high
asl ;shift bit from contents of A
pla ;pull saved return address from stack
sta $04 ;save to indirect
sta $05
lda ($04),y ;load pointer from indirect
sta $06 ;note that if an RTS is performed in next routine
iny ;it will return to the execution before the sub
lda ($04),y ;that called this routine
sta $07
jmp ($06) ;jump to the address we loaded
lda PPU_STATUS ;reset flip-flop
lda Mirror_PPU_CTRL_REG1 ;load mirror of ppu reg $2000
ora #%00010000 ;set sprites for first 4k and background for second 4k
and #%11110000 ;clear rest of lower nybble, leave higher alone
jsr WritePPUReg1
lda #$24 ;set vram address to start of name table 1
jsr WriteNTAddr
lda #$20 ;and then set it to name table 0
WriteNTAddr: sta PPU_ADDRESS
lda #$00
ldx #$04 ;clear name table with blank tile #24
ldy #$c0
lda #$24
InitNTLoop: sta PPU_DATA ;count out exactly 768 tiles
bne InitNTLoop
bne InitNTLoop
ldy #64 ;now to clear the attribute table (with zero this time)
sta VRAM_Buffer1_Offset ;init vram buffer 1 offset
sta VRAM_Buffer1 ;init vram buffer 1
InitATLoop: sta PPU_DATA
bne InitATLoop
sta HorizontalScroll ;reset scroll variables
sta VerticalScroll
jmp InitScroll ;initialize scroll registers to zero
;$00 - temp joypad bit
lda #$01 ;reset and clear strobe of joypad ports
tax ;start with joypad 1's port
jsr ReadPortBits
inx ;increment for joypad 2's port
ReadPortBits: ldy #$08
PortLoop: pha ;push previous bit onto stack
lda JOYPAD_PORT,x ;read current bit on joypad port
sta $00 ;check d1 and d0 of port output
lsr ;this is necessary on the old
ora $00 ;famicom systems in japan
pla ;read bits from stack
rol ;rotate bit from carry flag
bne PortLoop ;count down bits left
sta SavedJoypadBits,x ;save controller status here always
and #%00110000 ;check for select or start
and JoypadBitMask,x ;if neither saved state nor current state
beq Save8Bits ;have any of these two set, branch
and #%11001111 ;otherwise store without select
sta SavedJoypadBits,x ;or start bits and leave
Save8Bits: pla
sta JoypadBitMask,x ;save with all bits in another place and leave
;$00 - vram buffer address table low
;$01 - vram buffer address table high
sta PPU_ADDRESS ;store high byte of vram address
lda ($00),y ;load next byte (second)
sta PPU_ADDRESS ;store low byte of vram address
lda ($00),y ;load next byte (third)
asl ;shift to left and save in stack
lda Mirror_PPU_CTRL_REG1 ;load mirror of $2000,
ora #%00000100 ;set ppu to increment by 32 by default
bcs SetupWrites ;if d7 of third byte was clear, ppu will
and #%11111011 ;only increment by 1
SetupWrites: jsr WritePPUReg1 ;write to register
pla ;pull from stack and shift to left again
bcc GetLength ;if d6 of third byte was clear, do not repeat byte
ora #%00000010 ;otherwise set d1 and increment Y
GetLength: lsr ;shift back to the right to get proper length
lsr ;note that d1 will now be in carry
OutputToVRAM: bcs RepeatByte ;if carry set, repeat loading the same byte
iny ;otherwise increment Y to load next byte
RepeatByte: lda ($00),y ;load more data from buffer and write to vram
dex ;done writing?
bne OutputToVRAM
adc $00 ;add end length plus one to the indirect at $00
sta $00 ;to allow this routine to read another set of updates
lda #$00
adc $01
sta $01
lda #$3f ;sets vram address to $3f00
lda #$00
sta PPU_ADDRESS ;then reinitializes it for some reason
UpdateScreen: ldx PPU_STATUS ;reset flip-flop
ldy #$00 ;load first byte from indirect as a pointer
lda ($00),y
bne WriteBufferToScreen ;if byte is zero we have no further updates to make here
InitScroll: sta PPU_SCROLL_REG ;store contents of A into scroll registers
sta PPU_SCROLL_REG ;and end whatever subroutine led us here
sta PPU_CTRL_REG1 ;write contents of A to PPU register 1
sta Mirror_PPU_CTRL_REG1 ;and its mirror
;$00 - used to store status bar nybbles
;$02 - used as temp vram offset
;$03 - used to store length of status bar number
;status bar name table offset and length data
.db $f0, $06 ; top score display on title screen
.db $62, $06 ; player score
.db $62, $06
.db $6d, $02 ; coin tally
.db $6d, $02
.db $7a, $03 ; game timer
.db $06, $0c, $12, $18, $1e, $24
sta $00 ;store player-specific offset
jsr OutputNumbers ;use first nybble to print the coin display
lda $00 ;move high nybble to low
lsr ;and print to score display
clc ;add 1 to low nybble
adc #$01
and #%00001111 ;mask out high nybble
cmp #$06
bcs ExitOutputN
pha ;save incremented value to stack for now and
asl ;shift to left and use as offset
ldx VRAM_Buffer1_Offset ;get current buffer pointer
lda #$20 ;put at top of screen by default
cpy #$00 ;are we writing top score on title screen?
bne SetupNums
lda #$22 ;if so, put further down on the screen
SetupNums: sta VRAM_Buffer1,x
lda StatusBarData,y ;write low vram address and length of thing
sta VRAM_Buffer1+1,x ;we're printing to the buffer
lda StatusBarData+1,y
sta VRAM_Buffer1+2,x
sta $03 ;save length byte in counter
stx $02 ;and buffer pointer elsewhere for now
pla ;pull original incremented value from stack
lda StatusBarOffset,x ;load offset to value we want to write
sbc StatusBarData+1,y ;subtract from length byte we read before
tay ;use value as offset to display digits
ldx $02
DigitPLoop: lda DisplayDigits,y ;write digits to the buffer
sta VRAM_Buffer1+3,x
dec $03 ;do this until all the digits are written
bne DigitPLoop
lda #$00 ;put null terminator at end
sta VRAM_Buffer1+3,x
inx ;increment buffer pointer by 3
stx VRAM_Buffer1_Offset ;store it in case we want to use it again
ExitOutputN: rts
lda OperMode ;check mode of operation
cmp #TitleScreenModeValue
beq EraseDMods ;if in title screen mode, branch to lock score
ldx #$05
AddModLoop: lda DigitModifier,x ;load digit amount to increment
adc DisplayDigits,y ;add to current digit
bmi BorrowOne ;if result is a negative number, branch to subtract
cmp #10
bcs CarryOne ;if digit greater than $09, branch to add
StoreNewD: sta DisplayDigits,y ;store as new score or game timer digit
dey ;move onto next digits in score or game timer
dex ;and digit amounts to increment
bpl AddModLoop ;loop back if we're not done yet
EraseDMods: lda #$00 ;store zero here
ldx #$06 ;start with the last digit
EraseMLoop: sta DigitModifier-1,x ;initialize the digit amounts to increment
bpl EraseMLoop ;do this until they're all reset, then leave
BorrowOne: dec DigitModifier-1,x ;decrement the previous digit, then put $09 in
lda #$09 ;the game timer digit we're currently on to "borrow
bne StoreNewD ;the one", then do an unconditional branch back
CarryOne: sec ;subtract ten from our digit to make it a
sbc #10 ;proper BCD number, then increment the digit
inc DigitModifier-1,x ;preceding current digit to "carry the one" properly
jmp StoreNewD ;go back to just after we branched here
ldx #$05 ;start with mario's score
jsr TopScoreCheck
ldx #$0b ;now do luigi's score
ldy #$05 ;start with the lowest digit
GetScoreDiff: lda PlayerScoreDisplay,x ;subtract each player digit from each high score digit
sbc TopScoreDisplay,y ;from lowest to highest, if any top score digit exceeds
dex ;any player digit, borrow will be set until a subsequent
dey ;subtraction clears it (player digit is higher than top)
bpl GetScoreDiff
bcc NoTopSc ;check to see if borrow is still set, if so, no new high score
inx ;increment X and Y once to the start of the score
CopyScore: lda PlayerScoreDisplay,x ;store player's score digits into high score memory area
sta TopScoreDisplay,y
cpy #$06 ;do this until we have stored them all
bcc CopyScore
NoTopSc: rts
.db $04, $30, $48, $60, $78, $90, $a8, $c0
.db $d8, $e8, $24, $f8, $fc, $28, $2c
.db $18, $ff, $23, $58
ldy #$6f ;clear all memory as in initialization procedure,
jsr InitializeMemory ;but this time, clear only as far as $076f
ldy #$1f
ClrSndLoop: sta SoundMemory,y ;clear out memory used
dey ;by the sound engines
bpl ClrSndLoop
lda #$18 ;set demo timer
sta DemoTimer
jsr LoadAreaPointer
ldy #$4b ;clear all memory again, only as far as $074b
jsr InitializeMemory ;this is only necessary if branching from
ldx #$21
lda #$00
ClrTimersLoop: sta Timers,x ;clear out memory between
dex ;$0780 and $07a1
bpl ClrTimersLoop
lda HalfwayPage
ldy AltEntranceControl ;if AltEntranceControl not set, use halfway page, if any found
beq StartPage
lda EntrancePage ;otherwise use saved entry page number here
StartPage: sta ScreenLeft_PageLoc ;set as value here
sta CurrentPageLoc ;also set as current page
sta BackloadingFlag ;set flag here if halfway page or saved entry page number found
jsr GetScreenPosition ;get pixel coordinates for screen borders
ldy #$20 ;if on odd numbered page, use $2480 as start of rendering
and #%00000001 ;otherwise use $2080, this address used later as name table
beq SetInitNTHigh ;address for rendering of game area
ldy #$24
SetInitNTHigh: sty CurrentNTAddr_High ;store name table address
ldy #$80
sty CurrentNTAddr_Low
asl ;store LSB of page number in high nybble
asl ;of block buffer column position
sta BlockBufferColumnPos
dec AreaObjectLength ;set area object lengths for all empty
dec AreaObjectLength+1
dec AreaObjectLength+2
lda #$0b ;set value for renderer to update 12 column sets
sta ColumnSets ;12 column sets = 24 metatile columns = 1 1/2 screens
jsr GetAreaDataAddrs ;get enemy and level addresses and load header
lda PrimaryHardMode ;check to see if primary hard mode has been activated
bne SetSecHard ;if so, activate the secondary no matter where we're at
lda WorldNumber ;otherwise check world number
cmp #World5 ;if less than 5, do not activate secondary
bcc CheckHalfway
bne SetSecHard ;if not equal to, then world > 5, thus activate
lda LevelNumber ;otherwise, world 5, so check level number
cmp #Level3 ;if 1 or 2, do not set secondary hard mode flag
bcc CheckHalfway
SetSecHard: inc SecondaryHardMode ;set secondary hard mode flag for areas 5-3 and beyond
CheckHalfway: lda HalfwayPage
beq DoneInitArea
lda #$02 ;if halfway page set, overwrite start position from header
sta PlayerEntranceCtrl
DoneInitArea: lda #Silence ;silence music
sta AreaMusicQueue
lda #$01 ;disable screen output
sta DisableScreenFlag
inc OperMode_Task ;increment one of the modes
lda #$01
sta FetchNewGameTimerFlag ;set flag to load game timer from header
sta PlayerSize ;set player's size to small
lda #$02
sta NumberofLives ;give each player three lives
sta OffScr_NumberofLives
lda #$00
sta DisableScreenFlag ;enable screen output
ClearVRLoop: sta VRAM_Buffer1-1,y ;clear buffer at $0300-$03ff
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
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
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
ldy #$03 ;set up sprite #0
ISpr0Loop: lda Sprite0Data,y
sta Sprite_Data,y
bpl ISpr0Loop
jsr DoNothing2 ;these jsrs doesn't do anything useful
jsr DoNothing1
inc Sprite0HitDetectFlag ;set sprite #0 check flag
inc OperMode_Task ;increment to next task
;$06 - RAM address low
;$07 - RAM address high
ldx #$07 ;set initial high byte to $0700-$07ff
lda #$00 ;set initial low byte to start of page (at $00 of page)
sta $06
InitPageLoop: stx $07
InitByteLoop: cpx #$01 ;check to see if we're on the stack ($0100-$01ff)
bne InitByte ;if not, go ahead anyway
cpy #$60 ;otherwise, check to see if we're at $0160-$01ff
bcs SkipByte ;if so, skip write
InitByte: sta ($06),y ;otherwise, initialize byte with current low byte in Y
SkipByte: dey
cpy #$ff ;do this until all bytes in page have been erased
bne InitByteLoop
dex ;go onto the next page
bpl InitPageLoop ;do this until all pages of memory have been erased
.db WaterMusic, GroundMusic, UndergroundMusic, CastleMusic
.db CloudMusic, PipeIntroMusic
lda OperMode ;if in title screen mode, leave
beq ExitGetM
lda AltEntranceControl ;check for specific alternate mode of entry
cmp #$02 ;if found, branch without checking starting position
beq ChkAreaType ;from area object data header
ldy #$05 ;select music for pipe intro scene by default
lda PlayerEntranceCtrl ;check value from level header for certain values
cmp #$06
beq StoreMusic ;load music for pipe intro scene if header
cmp #$07 ;start position either value $06 or $07
beq StoreMusic
ChkAreaType: ldy AreaType ;load area type as offset for music bit
lda CloudTypeOverride
beq StoreMusic ;check for cloud type override
ldy #$04 ;select music for cloud type level if found