; HEADER .include "consts.inc" .include "header.inc" .include "reset.inc" .include "utils.inc" ;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "ZEROPAGE" Buttons: .res 1 ; Button bytes XPos: .res 1 ; Char X position YPos: .res 1 ; Char Y Position Frame: .res 1 ; Reserve 1 byte to store the number of frames Clock60: .res 1 ; Store a counter that increments every second (60 frames) BgPtr: .res 2 ; Reserve 2 bytes store a ptr to bg address ; Store low then high bytes (little endian) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PRG-ROM code located at $8000 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "CODE" ; LoadPalette ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to load all 32 color palette values from ROM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc LoadPalette PPU_SETADDR $3F00 ldy #0 ; Y = 0 : lda PaletteData,y ; Lookup byte in ROM sta PPU_DATA ; Set value to send to PPU_DATA iny ; Y++ cpy #32 ; Is Y equal to 32? bne :- ; Not yet, keep looping rts ; Return from subroutine .endproc ; LoadBackground ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to load the background 255 tiles in first nametable ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc LoadBackground lda #BackgroundData sta BgPtr+1 PPU_SETADDR $2000 ; nametable 0 ldx #$00 ; high byte (0-3) ldy #$00 ; low byte (0-FF) OuterLoop: InnerLoop: lda (BgPtr),y ; fetch the value pointed by bgptr+y sta PPU_DATA ; store value in ppu data iny ; y++ cpy #0 ; if y == 0 then wrapped beq IncreaseHiByte ; then: increase hi byte jmp InnerLoop ; else: continue inner loop IncreaseHiByte: inc BgPtr+1 ; we increment the hi byte pointer to point to the next bg section inx ; X++ cpx #4 ; break condition (4 sections bg) bne OuterLoop ; if x is stil not 4, loop back to outer loop rts ; Return from subroutine .endproc ; LoadText ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to load text in the nametable until it finds a 0-terminator ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc LoadText PPU_SETADDR $21CB ldy #0 ;y=0 Loop: lda TextMessage,y ; Fetch character byte from ROM beq EndLoop ; If the character is 0, end loop cmp #32 ; Compare to ASCII 32 bne DrawLetter ; if its not, draw the letter DrawSpace: lda #$24 ; tile $24 is a space sta PPU_DATA ; Store PPU_DATA with the empty tile jmp NextChar ; go to next character DrawLetter: sec ; set carry sbc #55 ; subtract 55 from the ASCII value, to get the character tile as chars start at 10 sta PPU_DATA ; store the data and advance the PPU_ADDR NextChar: iny ; y++, next character from the string jmp Loop ; go back to top of loop EndLoop: rts ; Return from subroutine .endproc ; LoadSprites ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Subroutine to load all 16 sprites into OAM-RAM starting at $0200 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc LoadSprites ldx #0 ; LoadSprites: ; lda SpriteData,x ; fetch bytes from SpriteData lookup table sta $0200,x ; store bytes starting at OAM $0200 inx ; X++ cpx #32; bne LoadSprites ; Loop 16 times (4 hardware sprites, 4 bytes each) rts ; Return from subroutine .endproc ;ReadControllers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Reset handler (called when the NES resets or powers on) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .proc ReadControllers lda #1 ; A=1 sta Buttons ; Buttons=1 (This will carry to the bcc, not an input value) sta JOYPAD1 ; Set latch to 1 to begin input lsr ; A=0 by shifting right, quicker maybe sta JOYPAD1 ; Set latch to 0 to begin output LoopButtons: lda JOYPAD1 ; 1. reads a bit from the controller line and inverts its value ; 2. sends a signal to the clock line to shift the bits inside the controller lsr ; we shift right to place that 1bit rol Buttons ; rolls bits to the left, placing the carry inside the first bit of `Buttons` in ram bcc LoopButtons ; loop until carry is set (from that initial 1 we had inside buttons rts .endproc ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Reset handler (called when the NES resets or powers on) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reset: INIT_NES ; Macro to initialize the NES to a known state InitVariables: lda #0 sta Frame sta Clock60 ldx #0 lda SpriteData,x sta YPos inx inx inx lda SpriteData,x sta XPos Main: jsr LoadPalette ; Jump to subroutine LoadPalette jsr LoadBackground ; Jump to subroutine LoadBackground jsr LoadSprites EnablePPURendering: lda #%10010000 ; enable NMI and set bg to use second pattern table sta PPU_CTRL lda #0 ; disable ppu scrolling sta PPU_SCROLL ; latch (in X) sta PPU_SCROLL ; latch (in y) lda #%00011110 sta PPU_MASK LoopForever: jmp LoopForever ; Force an infinite execution loop ; NMI ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; NMI interrupt handler ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; NMI: inc Frame ; Frame ++ ; ------------------------------------- ; As soon as we enter the NMI handler, start OAM copy lda #$02 ; every frame we copy sprite data starting at $02** sta PPU_OAM_DMA ; The OAM DMA Copy starts when we write to PPU_OAM_DMA ; ------------------------------------- jsr ReadControllers ; Jump to the subroutine that checks the controllers CheckRightButton: lda Buttons and #BUTTON_RIGHT beq CheckLeftButton inc XPos ; X++ CheckLeftButton: lda Buttons and #BUTTON_LEFT beq CheckDownButton dec XPos ; X-- CheckDownButton: lda Buttons and #BUTTON_DOWN beq CheckUpButton inc YPos ; Y++ CheckUpButton: lda Buttons and #BUTTON_UP beq :+ dec YPos ; Y-- : UpdateSpritePosition: lda XPos sta $0203 sta $020B clc adc #8 sta $0207 sta $020F lda YPos sta $0200 sta $0204 clc adc #8 sta $0208 sta $020C ;;;; lda Frame ; increment clock60 every time the frame reaches 60 cmp #60 ; frame equal to 60? bne :+ ; branch to end if not 60, carry on! inc Clock60 lda #0 sta Frame ; Zero Frame Counter : rti ; Return from interrupt ; IRQ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; IRQ interrupt handler ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; IRQ: rti ; Return from interrupt ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Hardcoded list of color values in ROM to be loaded by the PPU ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; PaletteData: .byte $22,$29,$1A,$0f, $22,$36,$17,$0f, $22,$30,$21,$0f, $22,$27,$17,$0f ; Background CP 00 01 10 11 .byte $22,$16,$27,$18, $22,$1a,$30,$27, $22,$16,$30,$27, $22,$0f,$36,$17 ; sprite CP 00 01 10 11 ;BackgroundData ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Background data with tile numbers that must be copied to the nametable ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BackgroundData: .incbin "background.nam" ;SpriteData ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; This is the OAM sprite attribute data data we will use in our game ;; We have only one big metasprite that is composed of 4 hardware sprites ;; the OAM is organized in sets of 4 bytes per tile ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Sprite Attribute Byte: ; ---------------------- ; 76543210 ; ||| || ; ||| ++- Color Palette of Sprite: Choose which set of 4 from the 16 colors to use ; ||| ; ||+------ Priority (0: in front of BG, 1: Behind BG) ; |+------- Flip Horizontally ; +-------- Flip Vertically SpriteData: ;-------------------------------- ; Mario: ; Y tile# attributes X .byte $AE, $3A, %00000000, $98 .byte $AE, $37, %00000000, $A0 .byte $B6, $4F, %00000000, $98 .byte $B6, $4F, %01000000, $A0 ;-------------------------------- ; Goomba: ; Y tile# attributes X .byte $93, $70, %00100011, $C7 .byte $93, $71, %00100011, $CF .byte $9B, $72, %00100011, $C7 .byte $9B, $73, %00100011, $CF ;-------------------------------- ;End Sprites ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Hardcoded ASCII message stored in ROM with 0-terminator ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; TextMessage: .byte "TYREL SOUZA",$0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Here we add the CHR-ROM data, included from an external .CHR file ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "CHARS" .incbin "mario.chr" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Vectors with the addresses of the handlers that we always add at $FFFA ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .segment "VECTORS" .word NMI ; Address (2 bytes) of the NMI handler .word Reset ; Address (2 bytes) of the Reset handler .word IRQ ; Address (2 bytes) of the IRQ handler