Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
.segment "HEADER"
INES_MAPPER = 0 ; 0 = NROM
INES_MIRROR = 1 ; 0 = horizontal mirroring, 1 = vertical mirroring
INES_SRAM = 0 ; 1 = battery backed SRAM at $6000-7FFF
.byte 'N', 'E', 'S', $1A ; ID
.byte $02 ; 16k PRG chunk count
.byte $01 ; 8k CHR chunk count
.byte INES_MIRROR | (INES_SRAM << 1) | ((INES_MAPPER & $f) << 4)
.byte (INES_MAPPER & %11110000)
.byte $0, $0, $0, $0, $0, $0, $0, $0 ; padding
.segment "TILES"
.incbin "background.chr"
.segment "VECTORS"
.word nmi
.word reset
.word irq
.segment "CODE"
reset:
sei ; disable interrupts during game initialization
lda #0
sta $2000 ; disable NMI
sta $2001 ; disable rendering
sta $4015 ; disable APU sound
sta $4010 ; disable DMC IRQ
lda #$40
sta $4017 ; disable APU IRQ
cld ; disable decimal mode
ldx #$FF
txs ; initialize the stack
; wait for the first vblank
bit $2002
:
bit $2002
bpl :-
; reset all RAM
lda #0
ldx #0
:
sta $0000, X
sta $0100, X
sta $0200, X
sta $0300, X
sta $0400, X
sta $0500, X
sta $0600, X
sta $0700, X
inx
bne :-
; place all sprites offscreen at Y=255
lda #255
ldx #0
:
sta oam, X
inx
inx
inx
inx
bne :-
; wait for the second vblank
:
bit $2002
bpl :-
; NES is initialized, ready to begin!
; enable the NMI for graphical updates, and jump to our main program
lda #%10001000
sta $2000
jmp main
;
; nmi routine
;
.segment "ZEROPAGE"
nmi_lock: .res 1 ; prevents NMI re-entry
nmi_count: .res 1 ; is incremented every NMI
nmi_ready: .res 1 ; set to 1 to push a PPU frame update, 2 to turn rendering off next NMI
nmt_update_len: .res 1 ; number of bytes in nmt_update buffer
scroll_x: .res 1 ; x scroll position
scroll_y: .res 1 ; y scroll position
scroll_nmt: .res 1 ; nametable select (0-3 = $2000,$2400,$2800,$2C00)
temp: .res 1 ; temporary variable
background_pointer: .res 2
.segment "BSS"
nmt_update: .res 256 ; nametable update entry buffer for PPU update
palette: .res 32 ; palette buffer for PPU update
.segment "OAM"
oam: .res 256 ; sprite OAM data to be uploaded by DMA
.segment "CODE"
nmi:
; save registers
pha
txa
pha
tya
pha
; prevent NMI re-entry
lda nmi_lock
beq :+
jmp @nmi_end
:
lda #1
sta nmi_lock
; increment frame counter
inc nmi_count
;
lda nmi_ready
bne :+ ; nmi_ready == 0 not ready to update PPU
jmp @ppu_update_end
:
cmp #2 ; nmi_ready == 2 turns rendering off
bne :+
lda #%00000000
sta $2001
ldx #0
stx nmi_ready
jmp @ppu_update_end
:
; sprite OAM DMA
ldx #0
stx $2003
lda #>oam
sta $4014
; palettes
lda #%10001000
sta $2000 ; set horizontal nametable increment
lda $2002
lda #$3F
sta $2006
stx $2006 ; set PPU address to $3F00
ldx #0
:
lda palette, X
sta $2007
inx
cpx #32
bcc :-
@scroll:
lda scroll_nmt
and #%00000011 ; keep only lowest 2 bits to prevent error
ora #%10001000
sta $2000
lda scroll_x
sta $2005
lda scroll_y
sta $2005
; enable rendering
lda #%00011110
sta $2001
; flag PPU update complete
ldx #0
stx nmi_ready
@ppu_update_end:
; if this engine had music/sound, this would be a good place to play it
; unlock re-entry flag
lda #0
sta nmi_lock
@nmi_end:
; restore registers and return
pla
tay
pla
tax
pla
rti
.segment "CODE"
irq:
rti
.segment "CODE"
; ppu_update: waits until next NMI, turns rendering on (if not already), uploads OAM, palette, and nametable update to PPU
ppu_update:
lda #1
sta nmi_ready
:
lda nmi_ready
bne :-
rts
.segment "ZEROPAGE"
.segment "RODATA"
example_palette:
.byte $0F,$15,$26,$37 ; bg0 purple/pink
.byte $0F,$09,$19,$29 ; bg1 green
.byte $0F,$01,$11,$21 ; bg2 blue
.byte $0F,$00,$10,$30 ; bg3 greyscale
.byte $0F,$27,$37,$3E ; sp0 yellow
.byte $0F,$14,$24,$34 ; sp1 purple
.byte $0F,$1B,$2B,$3B ; sp2 teal
.byte $0F,$12,$22,$32 ; sp3 marine
.segment "ZEROPAGE"
.segment "CODE"
; The main program, run after the NES is initialized
main:
; Write the palette to
ldx #0
:
lda example_palette, X
sta palette, X
inx
cpx #32
bcc :-
jsr setup_background
jsr ppu_update
; The main loop
@GameLoop:
jsr ppu_update
jmp @GameLoop
; nametable data
background:
; tile data
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $01, $01, $01
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
.byte $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
; Attribute table data
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
setup_background:
lda $2002 ; reset PPU latch
lda #$20 ; Write to the first nametable at #$2000
sta $2006
lda #$00
sta $2006
; store the memory address of the background in two bytes
; at background_pointer and background_pointer + 1
lda #<background
sta background_pointer
lda #>background
sta background_pointer + 1
; Loop 1024 times to write the entire first nametable.
; y is a counter that increases with each loop iteration.
ldy #0
; since the y register can only hold 256 values, we use
; a secondary counter, x, which counts how many times
; y has gone around the 0-256 cycle. The loop will complete
; after 4 cycles of x, 1024 interations.
ldx #0
@NametableLoop:
lda (background_pointer), y ; Load the nametable data at the pointer
sta $2007 ; Write it to the PPU
iny
cpy #0
bne @NametableLoop
inc background_pointer + 1 ; increase the high bit of the pointer
inx
cpx #4
bne @NametableLoop
rts