Compare commits

..

10 Commits

7 changed files with 568 additions and 180 deletions

View File

@@ -1,13 +1,20 @@
SOURCE=nesgame.S \
include/defines.S \
include/math.S \
include/graphics.S
MAIN_SOURCE=nesgame.S
all: nesgame.nes
nesgame.nes : nesgame.S
NESASM3 $<
nesgame.nes : $(SOURCE)
ophis -o $@ $(MAIN_SOURCE)
.PHONY: test
test:
test: nesgame.nes
fceux nesgame.nes
.PHONY: clean
clean:
rm *nes
rm *nes.deb
rm *fns
tags: $(SOURCE)
find . -name "*S" | xargs etags -a

13
README
View File

@@ -2,20 +2,25 @@ Yo, it's a NES game!
Sort of. It will get better.
Compiles with Ophis (http://michaelcmartin.github.com/Ophis/), tests with fceux (http://www.fceux.com/web/home.html). Put them both in your path, type "make", "make test", have fun staring at the pretty (?) pictures.
Milestone list:
1- (DONE) Boot a ROM that does nothing
2- (DONE) Single sprite visible on the screen
3- (DONE) Complete complex (multi-part) sprite visible on the screen
3.5 - single background tile visible on the screen
4- Swap palette of sprite in response to gamepad
5- Move sprite on screen with no animation
3.5 - (DONE) single background tile visible on the screen
4- (INVALIDATED) Swap palette of sprite in response to gamepad
This milestone got invalidated because my sprite data is in ROM, not in RAM
and besides, milestone 5 covers everything this was supposed to anyway
5- (DONE) Move sprite on screen with no animation
6- Display a full tile map on the screen
7- Collide a sprite with the world map
8- Apply gravity to a sprite and have him stand on the "ground"
9- Jumping sprite in response to the gamepad
10- Scrolling tile map (moving camera)
11- Add a second actor/sprite to the screen and make it stand
11- (DONE) Add a second actor/sprite to the screen
11.5 Make all visible actors stand
12- Collide two actors with each other
13- Create actors on screen from map data (e.g. some data in RAM)
14- Change an actor's sprite based on its state

View File

@@ -1,6 +0,0 @@
playerx=$0010
playery=$0011
curSpriteAttrOffset=$0012
curSpriteTileOffset=$0013
curSpriteXOffset=$0014
curSpriteYOffset=$0015

24
include/defines.S Normal file
View File

@@ -0,0 +1,24 @@
.alias curSpriteDataRead curSpriteDataLo
.alias curSpriteDataWrite curSpriteDataHi
.alias pada %10000000
.alias padb %01000000
.alias padselect %00100000
.alias padstart %00010000
.alias padup %00001000
.alias paddown %00000100
.alias padleft %00000010
.alias padright %00000001
.macro storeStackReturn ; storeStackReturn
PLA
STA prevReturnAddrHi
PLA
STA prevReturnAddrLo
.macend
.macro restoreStackReturn
LDA prevReturnAddrLo
PHA
LDA prevReturnAddrhi
PHA
.macend

140
include/graphics.S Normal file
View File

@@ -0,0 +1,140 @@
.require "defines.S"
;; Function : paletteLoad
;;
;; Given the address of a palette in ROM or RAM, and the
;; index of which palette to load (0=3F00, 1=3F10), load a
;; palette into the PPU
;;
;; arguments are on the stack (in push order):
;; - 00 or 10, lyte byte of PPU palette address to load
;; - Low byte of palette address
;; - High byte of palette address
paletteLoad:
.invoke storeStackReturn
PLA
STA curPaletteHi
PLA
STA curPaletteLo
LDA $2002 ; The PPU Memory address at $2006 expects
; the high byte of the palette address first,
; then the low byte, but we can't know
; which one it's expecting right now, so we
; read the PPU status at $2002 to reset the
; high/low latch on $2006.
LDA #$3F ; palettes live at $3F00 and $3F10
STA $2006
PLA
STA $2006
LDX #$00
_loop:
LDA (curPaletteLo), y ; Loop over each index of the byte array at
STA $2007 ; 'palette', store each one into the accumulator
INY ; and then store the accumulator into the PPU
CPY #$20 ; .. compare X to 20 (size of 'palette'), and
BNE _loop ; loop as long as the Zero flag isn't set (NE)
.invoke restoreStackReturn
RTS
;; Function : backgroundLoad
;;
;; Given the address of a set of background tiles, and the
;; address of their attribute data, load the background into the PPU
;;
;; arguments on the stack (in push order):
;; - Length of background attribute data
;; - Hi byte of background attribute data address
;; - Lo byte of background attribute data address
;; - Length of background data
;; - Hi byte of background data address
;; - Lo byte of background data address
backgroundLoad:
.invoke storeStackReturn
PLA
STA curBackgroundLo
PLA
STA curBackgroundHi
PLA
STA curBackgroundLen
PLA
STA curBackgroundAttrLo
PLA
STA curBackgroundAttrHi
PLA
STA curBackgroundAttrLen
LDA $2002 ; reset the PPU hi/low latch
LDA #$20
STA $2006 ; PPU address data is written high then low
LDA #$00
STA $2006
LDY #$00
_loadbgloop:
LDA (curBackgroundLo), y
STA $2007
INY
CPY curBackgroundLen
BNE _loadbgloop
LDA $2002 ; reset the PPU latch again
LDA #$23
STA $2006
LDA #$C0
STA $2006
LDY #$00
_loadattrloop:
LDA (curBackgroundAttrLo), y
STA $2007
INY
CPY curBackgroundAttrLen
BNE _loadattrloop
.invoke restoreStackReturn
RTS
;; Function : oamInsertMultiSprite
;;
;; Given the address of a multisprite, and its length (number of
;; subsprites), load the multisprite into the OAM memory at
;; index 0.
;;
;; Arguments on the stack:
;; - Length of multisprite
;; - High byte of multisprite's address
;; - Low byte of multisprite's address
;;
;; FIXME: Need to keep a list of all multisprites so I can append
;; new ones to the list, and remove dead ones; right now this all
;; presumes $0200 is the root for the multisprite, which will stop
;; being true once I have more than one.
oamInsertMultiSprite:
.invoke storeStackReturn
LDX #$0
LDY #$0
PLA
STA curSpriteLen
PLA
STA curSpriteDataHi
PLA
STA curSpriteDataLo
;; ----
_looptop:
LDA playery ; set Y position
CLC
ADC (curSpriteDataLo), y
STA (curSpriteOAMIndexLo), y
INY
LDA (curSpriteDataLo), y ; set tile number
STA (curSpriteOAMIndexLo), y
INY
LDA (curSpriteDataLo), y
STA (curSpriteOAMIndexLo), y
INY
LDA playerx ; set X position
CLC
ADC (curSpriteDataLo), y
STA (curSpriteOAMIndexLo), y
INY
INX ; increment the sprite counter
CPX curSpriteLen ; any more sprites in the current multisprite?
BNE _looptop
.invoke restoreStackReturn
RTS

28
include/math.S Normal file
View File

@@ -0,0 +1,28 @@
;; Function : divide
;;
;; Divide the value in A by the value in Y
;; The dividend is returned in A
;; The modulus is returned in Y
divide:
.invoke storeStackReturn
sta tempdivident ;Stores divident
sty tempdivisor ;Stores divisor
lda #$00
sta tempdivresult ;Clear result
ldy #$10 ;The loop is for 16-bit result
_divide_loop:
asl tempdivident
rol ;Shift divisor in 1 bit
cmp tempdivisor ;Check if fractional dividend is greater than divisor
bcc _divide_subloop
sbc tempdivisor ;Substract (C is always set)
_divide_subloop:
rol tempdivresult ;Shift result (1 if substation was done, 0 otherwise)
rol tempdivmodulus
dey
bne _divide_loop
lda tempdivresult
ldy tempdivmodulus
.invoke restoreStackReturn
RTS

506
nesgame.S
View File

@@ -3,17 +3,66 @@
;; how many 8kB banks of CHR data, which mapper to use for bank swapping
;; and how to perform background mirroring
.inesprg 1 ;; 1x 16kB bank of PRG (program) code
.ineschr 1 ;; 1x 8kB bank of CHR (tile/sprite) data
.inesmap 0 ;; use mapper 0; NROM, no bank swapping
.inesmir 1 ;; background mirroring (we don't care for now)
;; iNES header block
.include "defines.S"
.byte "NES",$1A
.byte $01 ;; 1 PRG ROM page (how big is an ophis page?)
.byte $02 ;; 1 CHR (tile/sprite) ROM page (how big is a page?)
.byte $00 ; Horizontal mirroring
.byte $00 ; Mapper 0 (NROM, no bank switching)
.byte $00,$00,$00,$00 ; Reserved bytes
.byte $00,$00,$00,$00 ; Reserved bytes
;; For NESASM, we need to tell it where each bank begins.
.require "include/defines.S"
.bank 0
.org $C000 ;; PRG bank 1 has 8kB at 0x0C000
.text zp
.org $0000
;; $0000 is reserved for things we use to extend PL/PH for call/return stacks
;; FIXME: Will need to expand this stuff once I start calling 2+
;; functions at once; e.g., JSR a -> JSR b -> JSR c...
;; will need to expand it to a real stack.
.space prevReturnAddrLo 1
.space prevReturnAddrHi 1
.org $0010
;; $0010 - 001F is reserved for global pointers
.space curPaletteLo 1
.space curPaletteHi 1
.space curSpriteOAMIndexLo 1
.space curSpriteOAMIndexHi 1
.space curBackgroundLo 1
.space curBackgroundHi 1
.space curBackgroundAttrLo 1
.space curBackgroundAttrHi 1
.space curSpriteDataLo 1
.space curSpriteDataHi 1
.org $0020
;; $0020 - 00FF is space for general purpose global variable
.space curBackgroundLen 1
.space curBackgroundAttrLen 1
.space curSpriteLen 1
.space playery 1
.space playerx 1
.space pad1state 1
.space pad2state 1
.space pad3state 1
.space pad4state 1
.space tempdivident 1
.space tempdivisor 1
.space tempdivresult 1
.space tempdivmodulus 1
.org $0040
.space sprMario2DataHook1 1
.space sprMario2DataHook2 1
.space sprMario2DataHook3 1
.space sprMario2DataHook4 1
.text
.org $C000 ;; PRG bank code starts at 0xC000
.require "include/math.S"
.require "include/graphics.S"
;; START will be called by the NES whenever the system boots
;; or when the reset button is pressed (think of _start in libc )
@@ -38,7 +87,7 @@ START:
STX $4010 ;; disable APU IRQs, no audio
_START_vblankwait:
BIT $2002 ;; Bitwise AND the accumulator (LDA) with mem
;; at 0x2002, and set the Zero, Sign and Overflow
;; at 0x2002, and set the Zero, Sign & Overflow
;; flags accordingly. 0x2002 is the PPU status
;; register; when 0x2002 has bit 7 set, we are
;; in vblank, so this is how we check for it.
@@ -81,31 +130,273 @@ _START_vblankwait2:
BPL _START_vblankwait2 ;; once we've gotten 1 vblank,
;; cleared mem, and gotten another vblank,
;; the PPU is ready. Wait for it.
JMP main
MAIN:
main:
;; horray, here is main()
;; Let's hit milestone 2 and draw a sprite
_MAIN_LoadPalettes:
LDA $2002 ; The PPU Memory address at $2006 expects
; the high byte of the palette address first,
; then the low byte, but we can't know
; which one it's expecting right now, so we
; read the PPU status at $2002 to reset the
; high/low latch on $2006.
LDA #$3F ; we're populating the second palette, at $3F10
STA $2006
LDA #$00
STA $2006
LDX #$00
_MAIN_LoadPaletteLoop:
LDA palette, x ; Loop over each index of the byte array at
STA $2007 ; 'palette', store each one into the accumulator
INX ; and then store the accumulator into the PPU
CPX #$20 ; .. compare X to 20 (size of 'palette'), and
BNE _MAIN_LoadPaletteLoop ; loop as long as the Zero flag isn't set (NE)
PHA
LDA #<palette
PHA
LDA #>palette
PHA
JSR paletteLoad
;; $2000 is the PPU Control register, controlled by various bitflags.
;;
;; 7654 3210
;; |||| ||||
;; |||| ||++- Base nametable address
;; |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
;; |||| |+--- VRAM address increment per CPU read/write of PPUDATA
;; |||| | (0: increment by 1, going across;
;; |||| | 1: increment by 32, going down)
;; |||| +---- Sprite pattern table address for 8x8 sprites
;; |||| (0: $0000; 1: $1000; ignored in 8x16 mode)
;; |||+------ Background pattern table address (0: $0000; 1: $1000)
;; ||+------- Sprite size (0: 8x8; 1: 8x16)
;; |+-------- PPU master/slave select (has no effect on the NES)
;; +--------- Generate an NMI at the start of the
;; vertical blanking interval (0: off; 1: on)
;; The PPU mask is set at $2001, the 2nd PPU Control register, and it
;; sets one config option for every bit of the byte
;;
;; 76543210
;; ||||||||
;; |||||||+- Grayscale (0: normal color; 1: AND all palette entries
;; ||||||| with 0x30, effectively producing a monochrome display;
;; ||||||| note that colour emphasis STILL works when this is on!)
;; ||||||+-- Disable background clipping in leftmost 8 pixels of screen
;; |||||+--- Disable sprite clipping in leftmost 8 pixels of screen
;; ||||+---- Enable background rendering
;; |||+----- Enable sprite rendering
;; ||+------ Intensify reds (and darken other colors)
;; |+------- Intensify greens (and darken other colors)
;; +-------- Intensify blues (and darken other colors)
LDA #$80
STA playerx
STA playery
;; Put mario in as the first multisprite
LDA #<sprMarioData
PHA
LDA #>sprMarioData
PHA
LDA sprMario
PHA
;; DO IT
LDA #$00
STA curSpriteOAMIndexLo
LDA #$02
STA curSpriteOAMIndexHi
JSR oamInsertMultiSprite
;; ----------------------------------
LDA #$20
STA playerx
LDA #$20
STA playery
LDA #<sprMario2Data
PHA
LDA #>sprMario2Data
PHA
LDA sprMario2
PHA
LDA #$40
STA curSpriteOAMIndexLo
LDA #$02
STA curSpriteOAMIndexHi
JSR oamInsertMultiSprite
;; ---------------------------
_bgcall:
LDA bgattrsize
PHA
LDA #>backgroundattrs
PHA
LDA #<backgroundattrs
PHA
LDA bgsize
PHA
LDA #>background
PHA
LDA #<background
PHA
JSR backgroundLoad
;; Apparently you do NOT set sprite and tile data while these are turned on, bad things
;; will happen!
LDA #%10010000 ; enable NMI (so we get a function call every
; vblank), and draw sprites from table 0
STA $2000
LDA #%10011110 ;; turn on sprites, turn on background, don't clip on the left
STA $2001 ;; Write to PPU Control Register 2
LDA #$00 ; hey PPU, stop friggin scrolling!
STA $2005
STA $2005
LDX #$0
_MAIN_loop:
JMP _MAIN_loop ;; Loop forever
NMI:
;; We need to copy all our OAM data to put sprites on screen during
;; vblank. $2003 is the PPU OAM address, so we're going to tell it
;; to pull OAM from $0200, and do a DMA transfer.
LDA #$00
STA $2003
LDA #$02
STA $4014 ; 4014 is the OAM_DMA operation, which will
; do a DMA from the (LDA|$2003) address,
; for FF bytes (in our case $0200-$02FF),
; which takes ~513 cycles. An unrolled
; loop to do the same thing would take
; 3-4 times as long.
_latch1:
LDA #$01
STA $4016
LDA #$00
STA $4016
LDA #$00
STA pad1state
LDX #$08
CLC
_readpad1:
LDA $4016
AND #%00000001
ORA pad1state
DEX
BEQ _storepad1
CLC
ROL
STA pad1state
JMP _readpad1
_storepad1:
STA pad1state
_checkselect:
LDA #padselect
AND pad1state
BEQ _checkstart
LDA sprMario2DataHook1
CLC
ADC #$01
STA sprMario2DataHook1
_checkstart:
LDA #padstart
AND pad1state
BEQ _checka
LDA sprMario2DataHook2
CLC
ADC #$01
STA sprMario2DataHook2
_checka:
LDA #pada
AND pad1state
BEQ _checkb
LDA sprMario2DataHook3
CLC
ADC #$01
STA sprMario2DataHook3
_checkb:
LDA #padb
AND pad1state
BEQ _checkwalkright
LDA sprMario2DataHook4
CLC
ADC #$01
STA sprMario2DataHook4
_checkwalkright:
LDA #%00000001
AND pad1state
BEQ _checkwalkleft
LDA playerx
CLC
ADC #$01
STA playerx
_checkwalkleft:
LDA #padleft
AND pad1state
BEQ _checkwalkup
LDA playerx
SEC
SBC #$01
STA playerx
_checkwalkup:
LDA #padup
AND pad1state
BEQ _checkwalkdown
LDA playery
SEC
SBC #$01
STA playery
_checkwalkdown:
LDA #paddown
AND pad1state
BEQ _move_mario
LDA playery
CLC
ADC #$01
STA playery
_move_mario:
;; Put mario in as the first multisprite
LDA #<sprMarioData
PHA
LDA #>sprMarioData
PHA
LDA sprMario
PHA
;; DO IT
LDA #$00
STA curSpriteOAMIndexLo
LDA #$02
STA curSpriteOAMIndexHi
JSR oamInsertMultiSprite
_nmi_finish:
LDA #%10010000 ; enable NMI (so we get a function call every
; vblank), and draw sprites from table 0
STA $2000
LDA #%00011110 ;; turn on sprites, turn on background, don't clip on the left
STA $2001 ;; Write to PPU Control Register 2
LDA #$00 ; hey PPU, stop friggin scrolling!
STA $2005
STA $2005
RTI
palette:
.byte $22,$29,$1A,$0F,$22,$36,$17,$0F,$22,$30,$21,$0F,$22,$27,$17,$0F ;;background palette
.byte $22,$1C,$15,$14,$22,$02,$38,$3C,$22,$1C,$15,$14,$22,$02,$38,$3C ;;sprite palette
bgsize:
.byte $80
bgattrsize:
.byte $08
background:
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;row 1
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;all sky
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;row 2
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 ;;all sky
.byte $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24 ;;row 3
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24 ;;some brick tops
.byte $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24 ;;row 4
.byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 ;;brick bottoms
backgroundattrs:
.byte %00000000,%00010000,%01010000,%00010000
.byte %00000000,%00000000,%00000000,%00110000
.byte $24,$24,$24,$24
.byte $47,$47,$24,$24
.byte $47,$47,$47,$47
.byte $47,$47,$24,$24
.byte $24,$24,$24,$24
.byte $24,$24,$24,$24
.byte $24,$24,$24,$24
.byte $55,$56,$24,$24 ;;brick bottoms
;; All sprites live between 0200-02FF; there are a max of 64 sprites
;; on screen, and each one has a 4 byte struct describing it.
;; *(sprite + 0) = y position
@@ -125,141 +416,40 @@ _MAIN_LoadPaletteLoop:
;; +-------- Flip sprite vertically
;; *(sprite + 3) = x position
;; All the sprite OAM data is initialized at the bottom of bank 1
;; at .org $FF00
;; $2000 is the PPU Control register, controlled by various bitflags.
;;
;; 7654 3210
;; |||| ||||
;; |||| ||++- Base nametable address
;; |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
;; |||| |+--- VRAM address increment per CPU read/write of PPUDATA
;; |||| | (0: increment by 1, going across;
;; |||| | 1: increment by 32, going down)
;; |||| +---- Sprite pattern table address for 8x8 sprites
;; |||| (0: $0000; 1: $1000; ignored in 8x16 mode)
;; |||+------ Background pattern table address (0: $0000; 1: $1000)
;; ||+------- Sprite size (0: 8x8; 1: 8x16)
;; |+-------- PPU master/slave select (has no effect on the NES)
;; +--------- Generate an NMI at the start of the
;; vertical blanking interval (0: off; 1: on)
LDA #%10000000 ; enable NMI (so we get a function call every
; vblank), and draw sprites from table 0
STA $2000
;; The PPU mask is set at $2001, the 2nd PPU Control register, and it
;; sets one config option for every bit of the byte
;;
;; 76543210
;; ||||||||
;; |||||||+- Grayscale (0: normal color; 1: AND all palette entries
;; ||||||| with 0x30, effectively producing a monochrome display;
;; ||||||| note that colour emphasis STILL works when this is on!)
;; ||||||+-- Disable background clipping in leftmost 8 pixels of screen
;; |||||+--- Disable sprite clipping in leftmost 8 pixels of screen
;; ||||+---- Enable background rendering
;; |||+----- Enable sprite rendering
;; ||+------ Intensify reds (and darken other colors)
;; |+------- Intensify greens (and darken other colors)
;; +-------- Intensify blues (and darken other colors)
LDA #%00010000 ;; turn on sprites, no more background color
STA $2001 ;; Write to PPU Control Register 2
LDX #$0
LDA #$80
STA playerx
STA playery
LDA #$0
STA curSpriteAttrOffset
STA curSpriteTileOffset
STA curSpriteXOffset
STA curSpriteYOffset
_MAIN_loop:
JMP _MAIN_loop ;; Loop forever
NMI:
LDX #$0
_NMI_CopyMarioToOAM:
LDA playery
LDY curSpriteYOffset
CLC
ADC sprMario_off_y, x
STA $0200, y
LDA #$4
ADC curSpriteYOffset
STA curSpriteYOffset
LDY curSpriteTileOffset
LDA sprMario, x
STA $0201, y
LDA #$4
CLC
ADC curSpriteTileOffset
STA curSpriteTileOffset
LDA #$00
LDY curSpriteAttrOffset
STA $0202, y
LDA #$4
CLC
ADC curSpriteAttrOffset
STA curSpriteAttrOffset
LDA playerx
LDY curSpriteXOffset
ADC sprMario_off_x, x
STA $0203, y
LDA #$4
CLC
ADC curSpriteXOffset
STA curSpriteXOffset
INX
CPX #08
BNE _MAIN_CopyMarioToOAM
;; We need to copy all our OAM data to put sprites on screen during
;; vblank. $2003 is the PPU OAM address, so we're going to tell it
;; to pull OAM from $0200, and do a DMA transfer.
LDA #$00
STA $2003
LDA #$02
STA $4014 ; 4014 is the OAM_DMA operation, which will
; do a DMA from the (LDA|$2003) address,
; for FF bytes (in our case $0200-$02FF),
; which takes ~513 cycles. An unrolled
; loop to do the same thing would take
; 3-4 times as long.
RTI ; just return
.bank 1 ;; NESASM sees our 16kB code banks as pairs of
.org $E000 ;; 8kB code banks, so we have to declare each
;; 8kB half-bank separately, and split code
;; between them.
;; .. How to know when we have written enough
;; code? ..
palette:
.db $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F
.db $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C
sprMario:
.db $00,$01,$02,$03,$04,$05,$06,$07
sprMario_off_x:
.db $00,$08,$00,$08,$00,$08,$00,$08
sprMario_off_y:
.db $00,$00,$08,$08,$10,$10,$18,$18
.byte $08 ; Total number of subsprites in this metasprite
sprMarioData:
;; Y, Tile, Atr, X
.byte $00,$00,$00,$00
.byte $00,$01,$00,$08
.byte $08,$02,$00,$00
.byte $08,$03,$00,$08
.byte $10,$04,$00,$00
.byte $10,$05,$00,$08
.byte $18,$06,$00,$00
.byte $18,$07,$00,$08
.bank 1
.org $FFFA
.dw NMI ;; For Non-Maskable Interrupts, please jump to the location
sprMario2:
.byte $08 ; Total number of subsprites in this metasprite
sprMario2Data:
;; Y, Tile, Atr, X
.byte $00,$08,$00,$00
.byte $00,$09,$00,$08
.byte $08,$0A,$00,$00
.byte $08,$0B,$00,$08
.byte $10,$0C,$00,$00
.byte $10,$0D,$00,$08
.byte $18,$0E,$00,$00
.byte $18,$0F,$00,$08
.advance $FFFA
.word NMI ;; For Non-Maskable Interrupts, please jump to the location
;; of the NMI label
.dw START ;; For the reset button or power-on, jump to the location
.word START ;; For the reset button or power-on, jump to the location
;; of the START label
.dw 0 ;; If we used an external IRQ vector, we would put it here
.word 0 ;; If we used an external IRQ vector, we would put it here
;; --- graphics bank
.bank 2 ;; CHR bank 0 starts here for tile/sprite data
.org $0000 ;; CHR data is below PRG data in the memory
.incbin "mario.chr" ; include 8kB of graphics from SMB1