commit 8e20553b524d8ef0ce45bdff3af44303d61ce139 Author: John Bintz <27256+johnbintz@users.noreply.github.com> Date: Wed Jul 14 21:48:06 2021 -0400 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a7b3028 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright © 2021 John Bintz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e90fe9e --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# NES Password ROM + +An NES ROM that shows how the Space character worked in Metroid passwords. +It was also an opportunity to learn 6502 assembler and the basics of +NES development! Visit +[r1kr.blog/read/nestextpasswordrom](https://r1kr.blog/read/nestextpasswordrom) +to learn more! + +## Building + +* Install [dasm](https://dasm-assembler.github.io/) +* Run `dasm password.asm -f3 -opassword.nes` +* Run `password.nes` in your favorite emulator. + +## Credits + +* 8bitworkshop: https://8bitworkshop.com/ +* galois16: https://github.com/bbbradsmith/prng_6502/blob/master/galois16.s +* Calderon Handwriting Condensed: https://www.romhacking.net/fonts/32/ + diff --git a/calderon_with_bamboo.chr b/calderon_with_bamboo.chr new file mode 100644 index 0000000..765a694 Binary files /dev/null and b/calderon_with_bamboo.chr differ diff --git a/data.asm b/data.asm new file mode 100644 index 0000000..1040b2a --- /dev/null +++ b/data.asm @@ -0,0 +1,70 @@ +Letters + byte "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz?! " +Passwords + byte "D N E " + byte "! ! ! " + byte "T R O " + byte "H R O " + + byte "JUSTIN" + byte "BAILEY" + byte "!a!b!c" + byte "!d!e!f" + + byte "6eW3!!" + byte "!!!!00" + byte "F38W!H" + byte "C0042N" + + byte "llv!?l" + byte "z!!mU4" + byte "!!y000" + byte "00m03x" + + byte "DRAGON" + byte "BALL Z" + byte "Dragon" + byte "Ball z" + +CanAlsoMessage + byte " might also be written as " + byte 0 + +AButtonMessage + byte "A - Change password" + byte 0 + +BButtonMessage + byte "B - Randomize alternative" + byte 0 + +R1KRTitleOne: + byte "The Rabbit with" + byte 0 +R1KRTitleTwo: + byte "1000 Repos" + byte 0 + +R1KRBlogTitle: + byte "NES Text Passwords" + byte 0 + +R1KRBlogLinkOne: + byte "r1kr.blog/read/" + byte 0 + +R1KRBlogLinkTwo: + byte "nestextpasswordrom" + byte 0 + +Palette: + hex ff ;screen color + hex 0d262400 ;background 0 + hex 0d262100 ;background 1 + hex 0d262a00 ;background 2 + hex 00122200 ;background 3 + hex 19293900 ;sprite 0 + hex 1a2a3a00 ;sprite 1 + hex 1b2b3b00 ;sprite 2 + hex 1c2c3c ;sprite 3 + diff --git a/galois16.asm b/galois16.asm new file mode 100644 index 0000000..7b49fed --- /dev/null +++ b/galois16.asm @@ -0,0 +1,37 @@ +; +; 6502 LFSR PRNG - 16-bit +; Brad Smith, 2019 +; http://rainwarrior.ca +; + +; A 16-bit Galois LFSR + +; Possible feedback values that generate a full 65535 step sequence: +; $2D = %00101101 +; $39 = %00111001 +; $3F = %00111111 +; $53 = %01010011 +; $BD = %10111101 +; $D7 = %11010111 + +; $39 is chosen for its compact bit pattern + +; simplest version iterates the LFSR 8 times to generate 8 random bits +; 133-141 cycles per call +; 19 bytes + +galois16: subroutine + ldy #8 + lda Seed+0 +.one + asl ; shift the register + rol Seed+1 + bcc .two + eor #$39 ; apply XOR feedback whenever a 1 bit is shifted out +.two + dey + bne .one + sta Seed+0 + cmp #0 ; reload flags + rts + diff --git a/nesdefs.dasm b/nesdefs.dasm new file mode 100644 index 0000000..554d5a8 --- /dev/null +++ b/nesdefs.dasm @@ -0,0 +1,184 @@ + + processor 6502 + +;;;;; CONSTANTS + +PPU_CTRL = $2000 +PPU_MASK = $2001 +PPU_STATUS = $2002 +OAM_ADDR = $2003 +OAM_DATA = $2004 +PPU_SCROLL = $2005 +PPU_ADDR = $2006 +PPU_DATA = $2007 + +PPU_OAM_DMA = $4014 +DMC_FREQ = $4010 +APU_STATUS = $4015 +APU_NOISE_VOL = $400C +APU_NOISE_FREQ = $400E +APU_NOISE_TIMER = $400F +APU_DMC_CTRL = $4010 +APU_CHAN_CTRL = $4015 +APU_FRAME = $4017 + +JOYPAD1 = $4016 +JOYPAD2 = $4017 + +; NOTE: I've put this outside of the PPU & APU, because it is a feature +; of the APU that is primarily of use to the PPU. +OAM_DMA = $4014 +; OAM local RAM copy goes from $0200-$02FF: +OAM_RAM = $0200 + +; PPU_CTRL flags +CTRL_NMI = %10000000 ; Execute Non-Maskable Interrupt on VBlank +CTRL_8x8 = %00000000 ; Use 8x8 Sprites +CTRL_8x16 = %00100000 ; Use 8x16 Sprites +CTRL_BG_0000 = %00000000 ; Background Pattern Table at $0000 in VRAM +CTRL_BG_1000 = %00010000 ; Background Pattern Table at $1000 in VRAM +CTRL_SPR_0000 = %00000000 ; Sprite Pattern Table at $0000 in VRAM +CTRL_SPR_1000 = %00001000 ; Sprite Pattern Table at $1000 in VRAM +CTRL_INC_1 = %00000000 ; Increment PPU Address by 1 (Horizontal rendering) +CTRL_INC_32 = %00000100 ; Increment PPU Address by 32 (Vertical rendering) +CTRL_NT_2000 = %00000000 ; Name Table Address at $2000 +CTRL_NT_2400 = %00000001 ; Name Table Address at $2400 +CTRL_NT_2800 = %00000010 ; Name Table Address at $2800 +CTRL_NT_2C00 = %00000011 ; Name Table Address at $2C00 + +; PPU_MASK flags +MASK_TINT_RED = %00100000 ; Red Background +MASK_TINT_BLUE = %01000000 ; Blue Background +MASK_TINT_GREEN = %10000000 ; Green Background +MASK_SPR = %00010000 ; Sprites Visible +MASK_BG = %00001000 ; Backgrounds Visible +MASK_SPR_CLIP = %00000100 ; Sprites clipped on left column +MASK_BG_CLIP = %00000010 ; Background clipped on left column +MASK_COLOR = %00000000 ; Display in Color +MASK_MONO = %00000001 ; Display in Monochrome + +; read flags +F_BLANK = %10000000 ; VBlank Active +F_SPRITE0 = %01000000 ; VBlank hit Sprite 0 +F_SCAN8 = %00100000 ; More than 8 sprites on current scanline +F_WIGNORE = %00010000 ; VRAM Writes currently ignored. + + +;;;;; CARTRIDGE FILE HEADER + +NES_MIRR_HORIZ = 0 +NES_MIRR_VERT = 1 +NES_MIRR_QUAD = 8 + + MAC NES_HEADER + seg Header + org $7ff0 +.NES_MAPPER SET {1} ;mapper number +.NES_PRG_BANKS SET {2} ;number of 16K PRG banks, change to 2 for NROM256 +.NES_CHR_BANKS SET {3} ;number of 8K CHR banks (0 = RAM) +.NES_MIRRORING SET {4} ;0 horizontal, 1 vertical, 8 four screen + byte $4e,$45,$53,$1a ; header + byte .NES_PRG_BANKS + byte .NES_CHR_BANKS + byte .NES_MIRRORING|(.NES_MAPPER<<4) + byte .NES_MAPPER&$f0 + byte 0,0,0,0,0,0,0,0 ; reserved, set to zero + seg Code + org $8000 + ENDM + +;;;;; NES_INIT SETUP MACRO (place at start) + + MAC NES_INIT + sei ;disable IRQs + cld ;decimal mode not supported + ldx #$ff + txs ;set up stack pointer + inx ;increment X to 0 + stx PPU_MASK ;disable rendering + stx DMC_FREQ ;disable DMC interrupts + stx PPU_CTRL ;disable NMI interrupts + bit PPU_STATUS ;clear VBL flag + bit APU_CHAN_CTRL ;ack DMC IRQ bit 7 + lda #$40 + sta APU_FRAME ;disable APU Frame IRQ + lda #$0F + sta APU_CHAN_CTRL ;disable DMC, enable/init other channels. + ENDM + +;;;;; NES_VECTORS - CPU vectors at end of address space + + MAC NES_VECTORS + seg Vectors ; segment "Vectors" + org $fffa ; start at address $fffa + .word NMIHandler ; $fffa vblank nmi + .word Start ; $fffc reset + .word NMIHandler ; $fffe irq / brk + ENDM + +;;;;; PPU_SETADDR
- set 16-bit PPU address + + MAC PPU_SETADDR + lda #>{1} ; upper byte + sta PPU_ADDR + lda #<{1} ; lower byte + sta PPU_ADDR + ENDM + +;;;;; PPU_SETVALUE - feed 8-bit value to PPU + + MAC PPU_SETVALUE + lda #{1} + sta PPU_DATA + ENDM + +;;;;; SAVE_REGS - save A/X/Y registers + + MAC SAVE_REGS + pha + txa + pha + tya + pha + ENDM + +;;;;; RESTORE_REGS - restore Y/X/A registers + + MAC RESTORE_REGS + pla + tay + pla + tax + pla + ENDM + +;------------------------------------------------------------------------------- +; SLEEP clockcycles +; Original author: Thomas Jentzsch +; Inserts code which takes the specified number of cycles to execute. This is +; useful for code where precise timing is required. +; LEGAL OPCODE VERSION MAY AFFECT FLAGS (uses 'bit' opcode) + +NO_ILLEGAL_OPCODES = 1 + + MAC SLEEP ;usage: SLEEP n (n>1) +.CYCLES SET {1} + + IF .CYCLES < 2 + ECHO "MACRO ERROR: 'SLEEP': Duration must be > 1" + ERR + ENDIF + + IF .CYCLES & 1 + IFNCONST NO_ILLEGAL_OPCODES + nop 0 + ELSE + bit $00 + ENDIF +.CYCLES SET .CYCLES - 3 + ENDIF + + REPEAT .CYCLES / 2 + nop + REPEND + ENDM diff --git a/nesppu.dasm b/nesppu.dasm new file mode 100644 index 0000000..a570dc7 --- /dev/null +++ b/nesppu.dasm @@ -0,0 +1,64 @@ + +;;;;; SUBROUTINES + +ClearRAM: subroutine + lda #0 ; A = 0 + tax ; X = 0 +.clearRAM + sta $0,x ; clear $0-$ff + cpx #$fe ; last 2 bytes of stack? + bcs .skipStack ; don't clear it + sta $100,x ; clear $100-$1fd +.skipStack + sta $200,x ; clear $200-$2ff + sta $300,x ; clear $300-$3ff + sta $400,x ; clear $400-$4ff + sta $500,x ; clear $500-$5ff + sta $600,x ; clear $600-$6ff + sta $700,x ; clear $700-$7ff + inx ; X = X + 1 + bne .clearRAM ; loop 256 times + rts + +; wait for VSYNC to start +WaitSync: + bit PPU_STATUS + bpl WaitSync + rts + +;;;;; RANDOM NUMBERS + +NextRandom subroutine + lsr + bcc .NoEor + eor #$d4 +.NoEor: + rts +; Get previous random value +PrevRandom subroutine + asl + bcc .NoEor + eor #$a9 +.NoEor: + rts + +;;;;; CONTROLLER READING + +ReadJoypad0 subroutine + ldy #0 +ReadJoypadY + lda #$01 + sta JOYPAD1,y ; set strobe bit + lsr ; now A is 0 + sta JOYPAD1,y ; clear strobe bit + ldx #8 ; read 8 bits +.loop: + pha ; save A (result) + lda JOYPAD1,y ; load controller state + lsr ; bit 0 -> carry + pla ; restore A (result) + rol ; carry -> bit 0 of result + dex ; X = X - 1 + bne .loop ; repeat if X is 0 + rts ; controller bits returned in A + diff --git a/password.asm b/password.asm new file mode 100644 index 0000000..61b0a5f --- /dev/null +++ b/password.asm @@ -0,0 +1,265 @@ +PASSWORD_LENGTH = 24 ; length for comparing to Y +SPACE_VALUE = 64 +SPACE_LOCATION = 64 +SPACE_BECOME_VALUE = SPACE_LOCATION - 1 +PASSWORD_COUNT = 5 + + include "nesdefs.dasm" + +;;;;; VARIABLES + + seg.u ZEROPAGE + org $0 + +;; global + +Seed ds 2 +PasswordInUse ds 1 + +;; controller + +CurrentController ds 1 +BButtonDown ds 1 +AButtonDown ds 1 + +;; decoding + +CurrentLetter ds 1 +CurrentPasswordChunk ds 1 +RandomValueForPasswordLetterModification ds 1 +DecodedPasswordOffset ds 1 +CurrentLetterPositionProcess ds 1 +CurrentLetterPositionDisplay ds 1 + +;; rendering + +BambooRenderPos ds 1 +StringToWrite ds 2 + + seg.u PASSWORDS + org $400 + +;; password storage + +DecodedPassword ds 24 +AlternatePassword ds 24 + + +;;;;; NES CARTRIDGE HEADER + + NES_HEADER 0,2,1,NES_MIRR_HORIZ ; mapper 0, 2 PRGs, 1 CHR + +;;;;; START OF CODE + +Start: +; wait for PPU warmup; clear CPU RAM + NES_INIT ; set up stack pointer, turn off PPU + jsr WaitSync ; wait for VSYNC + jsr ClearRAM ; clear RAM + jsr WaitSync ; wait for VSYNC (and PPU warmup) +; set palette and nametable VRAM + jsr SetPalette ; set palette colors + lda #0 + sta PasswordInUse + +; reset PPU address and scroll registers + lda #0 + sta PPU_ADDR + sta PPU_ADDR ; PPU addr = $0000 + sta PPU_SCROLL + sta PPU_SCROLL ; PPU scroll = $0000 + +; seed rng + lda #25 + sta Seed + lda #43 + sta Seed+1 + +; disable NMI and scroll + lda #0 ; disable NMI + sta PPU_CTRL + + lda #$0 + sta PPU_SCROLL + sta PPU_SCROLL ; PPU scroll = $0000 + +; set up controller + lda #0 + sta AButtonDown + sta BButtonDown + + jsr DecodePassword + jsr ConvertDecodedPasswordToAlternatePassword + + jsr WriteUI + +.endless + jsr ReadJoypad0 + sta CurrentController + + jsr HandleBButton + jsr HandleAButton + + inc Seed + jmp .endless ; endless loop + + +;;;;; COMMON SUBROUTINES + + include "nesppu.dasm" + include "processing.asm" + include "view.asm" + include "galois16.asm" + include "data.asm" + +WriteString: subroutine + ldy #0 +.Continue: + lda (StringToWrite),y + beq .Done + sta PPU_DATA + iny + bne .Continue +.Done + rts + + MAC WRITE_STRING + lda #<{1} + sta StringToWrite + lda #>{1} + sta StringToWrite+1 + jsr WriteString + ENDM + +WriteUI: subroutine + lda #0 + sta PPU_MASK ; turn rendering off + + PPU_SETADDR $2069 + jsr WritePassword + + PPU_SETADDR $20c1 + WRITE_STRING CanAlsoMessage + + PPU_SETADDR $2109 + jsr WriteAlternateDecodedPassword + + PPU_SETADDR $21a6 + WRITE_STRING AButtonMessage + + PPU_SETADDR $21c3 + WRITE_STRING BButtonMessage + + PPU_SETADDR $2242 + jsr DrawBamboo + + PPU_SETADDR $224a + WRITE_STRING R1KRTitleOne + + PPU_SETADDR $226d + WRITE_STRING R1KRTitleTwo + + PPU_SETADDR $22a9 + WRITE_STRING R1KRBlogTitle + + PPU_SETADDR $22e9 + WRITE_STRING R1KRBlogLinkOne + + PPU_SETADDR $230b + WRITE_STRING R1KRBlogLinkTwo + +;;;;; palette stuff + + PPU_SETADDR $23e8 + + lda #%01010101 + sta PPU_DATA + sta PPU_DATA + + PPU_SETADDR $23f0 + + lda #%10101010 + sta PPU_DATA + sta PPU_DATA + + lda #MASK_BG + sta PPU_MASK ; enable rendering + lda #$0 + sta PPU_ADDR + sta PPU_ADDR ; PPU addr = $0000 + + rts + +HandleBButton: subroutine + lda #%01000000 ; lowest bit is b + bit CurrentController + beq .BButtonUp ; button is up +.BButtonDown + lda #0 + cmp BButtonDown + bne .BButtonWasDown + + jsr ConvertDecodedPasswordToAlternatePassword + jsr WriteUI + +.BButtonWasDown + lda #1 + sta BButtonDown + bne .BButtonDone +.BButtonUp + lda #0 + sta BButtonDown +.BButtonDone + rts + +HandleAButton: subroutine + lda #%10000000 ; lowest bit is b + bit CurrentController + beq .AButtonUp ; button is up +.AButtonDown + lda #0 + cmp AButtonDown + bne .AButtonWasDown + + inc PasswordInUse + lda #PASSWORD_COUNT + cmp PasswordInUse + bne .NotEqual + + lda #0 + sta PasswordInUse +.NotEqual + + jsr DecodePassword + jsr ConvertDecodedPasswordToAlternatePassword + jsr WriteUI + +.AButtonWasDown + lda #1 + sta AButtonDown + bne .AButtonDone +.AButtonUp + lda #0 + sta AButtonDown +.AButtonDone + rts + + +;;;;; INTERRUPT HANDLERS + + +NMIHandler: subroutine + SAVE_REGS + + RESTORE_REGS + rti + +;;;;; CPU VECTORS + + NES_VECTORS + + org $10000 + + + incbin "calderon_with_bamboo.chr" + incbin "calderon_with_bamboo.chr" diff --git a/password.nes b/password.nes new file mode 100644 index 0000000..738fa2e Binary files /dev/null and b/password.nes differ diff --git a/processing.asm b/processing.asm new file mode 100644 index 0000000..bb44c5c --- /dev/null +++ b/processing.asm @@ -0,0 +1,118 @@ +GetCurrentLetterPosition: subroutine + lda #0 + ldx #0 +SelectPassword: + cpx PasswordInUse + beq SelectPasswordDone + adc #24 + inx + bne SelectPassword +SelectPasswordDone: + rts + +DecodePassword: subroutine + lda #0 ; set up decoding variables + sta DecodedPasswordOffset + jsr GetCurrentLetterPosition + sta CurrentLetterPositionProcess + ldx #0 +.ReadLetterLoop + ldy CurrentLetterPositionProcess ; load the next letter of the password + lda Passwords,y + sta CurrentLetter ; ...and stick it in a spot in RAM + ldx #0 ; + ldy DecodedPasswordOffset +.FindMatchLoop + lda Letters,x ; put the next search letter into accumulator + jsr PushLetterToDecodedPassword ; always push the letter offset onto the decode list + cmp CurrentLetter ; if it's the same, we're done + beq .FindMatchExit + inx + ;cpx #SPACE_LOCATION + bne .FindMatchLoop + + lda Letters,x ; this is a space + ldx #SPACE_VALUE + jsr PushLetterToDecodedPassword +.FindMatchExit + inc DecodedPasswordOffset + iny + inc CurrentLetterPositionProcess + cpy #PASSWORD_LENGTH + bne .ReadLetterLoop + + rts + +ConvertDecodedPasswordToAlternatePassword: subroutine + ldx #0 +.NextCharacter + lda DecodedPassword,x + sta AlternatePassword,x + cpx #0 ; we can't do anything at position 0 + beq .NotSpaceValue + cmp #SPACE_VALUE ; not a candidate for modifying the prior value + beq .SpaceValue + cmp #SPACE_BECOME_VALUE ; not a candidate for modifying the prior value + beq .CanBecomeSpaceValue + bne .NotSpaceValue +.SpaceValue + lda #SPACE_BECOME_VALUE + sta AlternatePassword,x + jsr galois16 + tay + and #%00000100 + bne .UseASpace + dex + lda DecodedPassword,x + ora #%00000011 + sta AlternatePassword,x + inx + bcs .NotSpaceValue +.CanBecomeSpaceValue + ; check to make sure both low bits are set on prior + dex + lda AlternatePassword,x + and #%00000011 + bne .DoneBecomingSpace + inx + jsr galois16 + tay + and #%00000100 + bne .NotSpaceValue + lda #SPACE_VALUE + sta AlternatePassword,x + dex + lda AlternatePassword,x + cmp #SPACE_VALUE +.DoneBecomingSpace + inx + lda #0 + beq .NotSpaceValue +.UseASpace + tya + and #%00000011 + sta RandomValueForPasswordLetterModification ; y contains the lower bits to use instead + lda #SPACE_LOCATION + sta AlternatePassword,x + dex + lda DecodedPassword,x + and #%11111100 + ora RandomValueForPasswordLetterModification + sta AlternatePassword,x + inx +.NotSpaceValue + inx + cpx #PASSWORD_LENGTH + bne .NextCharacter +.done + rts + +PushLetterToDecodedPassword: subroutine + ; X contains the letters.indexOf(letter) + ; Y contains the current letter index + ; A will get preserved + pha + txa + sta DecodedPassword,y + pla + rts \ No newline at end of file diff --git a/view.asm b/view.asm new file mode 100644 index 0000000..d19b1d4 --- /dev/null +++ b/view.asm @@ -0,0 +1,115 @@ + MAC MOVE_TO_NEXT_CHUNK + lda #1 + bit CurrentPasswordChunk + bne .NextLine + lda #0 + sta PPU_DATA + beq .NextChunk +.NextLine: + txa + pha + lda #0 + ldx #19 +.NextLineLoop: + sta PPU_DATA + dex + cpx #0 + bne .NextLineLoop + pla + tax +.NextChunk: + inc CurrentPasswordChunk + ENDM + +WritePassword: subroutine + lda #0 + sta CurrentPasswordChunk + jsr GetCurrentLetterPosition + tax + ldy #0 +.WriteLetterInChunk: + tya + pha + lda Passwords,x + sta PPU_DATA + pla + tay + cpy #5 + beq .ChunkDone + inx + iny + bne .WriteLetterInChunk +.ChunkDone: + MOVE_TO_NEXT_CHUNK + lda #4 + ldy #0 + inx + cmp CurrentPasswordChunk + bne .WriteLetterInChunk + rts + +WriteAlternateDecodedPassword: subroutine + lda #0 + sta CurrentPasswordChunk + ldx #0 + ldy #0 +.WriteLetterInChunk + tya + pha + lda AlternatePassword,x + tay + lda Letters,y + sta PPU_DATA + pla + tay + cpy #5 + beq .ChunkDone + inx + iny + bne .WriteLetterInChunk +.ChunkDone + MOVE_TO_NEXT_CHUNK + lda #4 + ldy #0 + inx + cmp CurrentPasswordChunk + bne .WriteLetterInChunk + rts + +DrawBamboo: subroutine + lda #$80 + sta BambooRenderPos +.WriteColumn: + lda BambooRenderPos + sta PPU_DATA + inc BambooRenderPos + lda #%00000111 + and BambooRenderPos + bne .WriteColumn +.MaybeNextRow + lda #%11111110 + and BambooRenderPos + beq .Done + ldy #8 + lda #0 +.ToNextRow + sta PPU_DATA + dey + cpy #0 + bne .ToNextRow + bcs .WriteColumn +.Done + rts + +; set palette colors +SetPalette: subroutine +; set PPU address to palette start + PPU_SETADDR $3f00 + ldy #0 +.loop: + lda Palette,y ; lookup byte in ROM + sta PPU_DATA ; store byte to PPU data + iny ; Y = Y + 1 + cpy #32 ; is Y equal to 32? + bne .loop ; not yet, loop + rts ; return to caller \ No newline at end of file