initial commit

This commit is contained in:
John Bintz 2021-07-14 21:48:06 -04:00
commit 8e20553b52
11 changed files with 892 additions and 0 deletions

19
LICENSE Normal file
View File

@ -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.

20
README.md Normal file
View File

@ -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/

BIN
calderon_with_bamboo.chr Normal file

Binary file not shown.

70
data.asm Normal file
View File

@ -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

37
galois16.asm Normal file
View File

@ -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

184
nesdefs.dasm Normal file
View File

@ -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 <address> - 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 <value> - 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

64
nesppu.dasm Normal file
View File

@ -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

265
password.asm Normal file
View File

@ -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"

BIN
password.nes Normal file

Binary file not shown.

118
processing.asm Normal file
View File

@ -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

115
view.asm Normal file
View File

@ -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