NES Adventures - Part 1

Posted by Astryl on Aug. 16, 2014, 1:47 p.m.

I've been dreaming of assembly for the last two nights…

In his blog, Cyrus non-seriously challenged me to create a raycaster for the NES.

I'm not 100% sure whether I'll be able to pull that off or not, but I did start tinkering with some of the tools again, and decided to challenge myself incrementally. There's still a lot I have to learn about the NES hardware, and the tricks I can pull off with it.

So what I'm going to do is post blogs like this each time I complete a challenge I've set. The first one was to create a "text writer", something like the printf() function, for the NES.

I'm using pure assembly code, and a simple bitmap font. Strings are written to the nametable at runtime (A considerable amount of my time was spent last night trying to safely modify the nametables without glitching).

The result is this:

It's also scrolling, but you can't see that.

If you have an NES emulator, you can take a look at the ROM:

CH01.NES

And here's the assembly listing for those of you who can read it :P

asm
;;;;;;;;;;;;;;;;;;;;;;;
;; CH01
;; Text Writer
;;;;;;;;;;;;;;;;;;;;;;;
;; By 64Mega
;;;;;;;;;;;;;;;;;;;;;;;

; Set up iNES header
	.inesprg 	1
	.ineschr 	1
	.inesmap	0
	.inesmir 	1

;;;;;;;;;;;
;; CHR BANK
;;;;;;;;;;;
	.bank 0
	.org $C000
chr_palette:
	.db $1D, $0C, $1C, $27, $1D, $0C, $1C, $26, $1D, $1D, $1D, $1D, $1D, $1D, $1D, $1D
	.db $1D, $0C, $1C, $27, $1D, $0C, $1C, $26, $1D, $1D, $1D, $1D, $1D, $1D, $1D, $1D
	
;;;;;;;;;;;;;;;
;; VECTOR SETUP
;;;;;;;;;;;;;;;

RESET:
	sei	;; Disable interrupts
	cld

	lda #$40
	stx $4017
	ldx #$FF
	txs
	inx
	stx $2000
	stx $2001
	stx $4010

_vblank1:
	bit $2002
	bpl _vblank1

; Clear working memory
clearmem:
	lda #$00
	sta $0000, x
	sta $0100, x
	sta $0200, x
	sta $0400, x
	sta $0500, x
	sta $0600, x
	sta $0700, x
	lda #$FE
	sta $0300, x
	inx
	bne clearmem

_vblank2:
	bit $2002
    bpl _vblank2


;;;;;;;;;;;;;;;;;;
;; MACROS
;;;;;;;;;;;;;;;;;;

ppu_addr .macro
	lda \1
	sta $2006
	lda \2
	sta $2006
	.endm

write_nt_char .macro
	lda \1
	sta $2007
	.endm
	
write_nt_str .macro	;; Doesn't work
	ldx #$00
.xloop\@:
	lda \1, x
	beq .xloopend\@
	sbc #$41
	sta $2007
.xloopend\@:
	.endm

	
;;;;;;;;;;;;;;;;;
;; Initialize PPU
;;;;;;;;;;;;;;;;;
	lda #%00011111
	sta $2001

loadPalettes:
	lda $2002
	lda #$3F
	sta $2006
	lda #$10
	sta $2006

	ldx #$00

loop_paletteLoop:
	lda chr_palette, x
	sta $2007
	inx 
	cpx #$20
	bne loop_paletteLoop
	ldx #$00

finalizePPU:
	lda #%10000000
	sta $2000

setupVars:
	; YScroll	
	lda #$00
	sta $0100
	
gameLoop:
gameLoopVBlank:
	bit $2002
	bpl gameLoopVBlank ;; I should probably write a macro for this...

	bit $2002
	lda #$20
	sta $2006
	lda #$00
	sta $2006

	lda #$00
	ldx #$40
zeroWrite:
	sta $2007
	dex
	bne zeroWrite 
	ldx #$00
	
	bit $2002
	bit $2002
	ldx #$00
loopwc_begin:
	lda str_test2, x
	cmp #$00
	beq loopwc_end
	sbc #64
	sta $2007
	inx
	jmp loopwc_begin
loopwc_end:

	
	
	;; Scroll screen for kicks
	bit $2002
	ldx #00
	stx $2005
	
	ldx $0100
	dex	
	txa 
	cmp #$00
	beq reset_yscroll
	jmp or_not
reset_yscroll:
	ldx #$E0
or_not:
	stx $2005
	stx $0100

	jmp gameLoop

;; Non-Maskable Interrupt
;; Do any DMA stuff here.
NMI:
	rti

str_test2:
	.db "HELLO SIXTY FOUR DIGITS"
	.db 0
	
;;;;;;;;;;;;;;;
;; BANK 1 SETUP
;;;;;;;;;;;;;;;
	.bank 1 
	.org $FFFA

	.dw NMI
	.dw RESET
	.dw 0

	.bank 2
	.org $0000
	.incbin "./res/simplefont.chr"

There are a couple of problems with this code that I'm already aware of. Firstly, the nametable modification is inefficient. I only need to write to the nametable once, and leave it until I need to change it again.

At the moment, I'm doing it every frame.

The write_nt_str macro doesn't work, because I can't figure out how to pass a label's address to a macro with this damned assembler (I've nearly convinced myself to write my own assembler for 6502 code).

There are a bunch of inefficient comparisons/branches. I remembered afterwards that the lda/ldx/ldy ops also affect the C/Z flags, so…

asm
;; I should do this
    lda $<somelocation>
   bne a_label

;; Instead of
  lda $<somelocation>
  cmp #$00
  bne a_label

Also, the scrolling is somewhat jerky when it hits the top of the screen. I'm derping somewhere in my checks, but I'll fix that next time I use scrolling.

Next up, I'm going to try do something a bit more complicated.

Comments

Quietus 10 years, 1 month ago

i've been playing NES games exclusively lately, lol. this does indeed work in an emulator. it's white text on grey though, not orange and black.

if you managed to make something with this, that would be pretty neat.

Astryl 10 years, 1 month ago

Could be an emulator specific problem, though it could also be that I'm accidentally clobbering the palette values in certain situations.

If I get into this enough, I'll probably at least try making a shmup or platformer for the NES. :P