Basic nametable reading question (.incbin addressing)

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Basic nametable reading question (.incbin addressing)
by on (#201379)
Sorry for the newbie question, I've been searching for practical examples on this but so far not much luck...

So, when developing a nes game with the cc65 compiler and using Nes Screen Tool, if I include a full screen nametable (the whole screen tiles) binary like so:

Code:
nametable:
.incbin "level_01_bg.nam"


Is it possible to read all of its bytes to send data to the PPU if the included file is longer than $FF bytes? I can only access its first 256 bytes by using "LDA nametable,x", but the rest I have no idea how to access.

I got it to work (the full nametable loaded and displayed) by copy/paste the nametable as asm code from Nes Screen Tool and then setting labels dividing it in 4 areas, but workflow would be streamlined if I could export a file from Nes Screen Tool and compile the game as is, especially because of the nametable splitting and having to rename ".db" to ".byte" (is there a way to make NSS more cc65 compatible?).

minor edit: more descriptive title
Re: Basic nametable reading question
by on (#201380)
Do you know what a pointer is?

You'll need to make a pointer in two bytes of zero page and use the (dd),Y addressing mode to step through all four 256-byte pages of nametable data.

EDIT: spelling
Re: Basic nametable reading question
by on (#201381)
You can use the Indirect Indexed mode, with Y, to load from a large table.
Assuming a ZP two-byte variable "addr_ptr":

Code:

; Put nametable source address in addr_ptr
lda #<nametable
sta addr_ptr
lda #>nametable
sta addr_ptr+1

; Copy one page of data ($100 bytes)
@copypage:
ldy #0
@copyloop:
lda (addr_ptr), y
sta PPUDATA
iny
bne @copyloop

; Move on to the next page, or leave if finished
lda nametable+1
clc
adc #1
sta nametable+1
cmp #(>nametable + 4)
bne @copypage


This code is really rough, so please forgive minor errors. The jist of it is a copy loop using Y from $00-$FF, and then incrementing the high byte of your pointer after that and doing it again until you are done. In this case, it is four iterations to cover a whole nametable.

This code assumes you have already loaded PPUDATA.
Re: Basic nametable reading question
by on (#201382)
tepples wrote:
Do you know what a pointer is?

So you mean I can use that to retrieve the label's address at runtime and find the rest of the data relative to its position? I'll look further into zero page pointers and see if that's the case, thanks.

Pre-posting edit: It seems that's the case (sans my probably imprecise way of describing it), thanks mikejmoffitt!

edit: it works! The only typo in your code is that instead of reading and writing to/from "addr_ptr+1" you wrote "nametable+1" but that was an easy fix. Thanks! I'll study those operators (<, >, +1 etc), I kind of see what they do but I had never seen them.

Updated code for posterity:
Code:
; Put nametable source address in addr_ptr
lda #<nametable
sta addr_ptr
lda #>nametable
sta addr_ptr+1

; Copy one page of data ($100 bytes)
@copypage:
ldy #0
@copyloop:
lda (addr_ptr), y
sta PPUDATA
iny
bne @copyloop

; Move on to the next page, or leave if finished
lda addr_ptr+1
clc
adc #1
sta addr_ptr+1
cmp #(>nametable + 4)
bne @copypage
Re: Basic nametable reading question
by on (#201413)
nesrocks wrote:
Code:
nametable:
.incbin "level_01_bg.nam"

Indirect addressing with (dd),y is the way to go (smaller code size, not that much slower), but I'll just note that you could've also done this to avoid splitting the data manually to 4 parts:
Code:
nametable_part0:
nametable_part1 := nametable_part0+$100
nametable_part2 := nametable_part1+$100
nametable_part3 := nametable_part2+$100
.incbin "level_01_bg.nam"
Re: Basic nametable reading question
by on (#201415)
thefox wrote:
Code:
nametable_part0:
nametable_part1 := nametable_part0+$100
nametable_part2 := nametable_part1+$100
nametable_part3 := nametable_part2+$100
.incbin "level_01_bg.nam"

I sometimes need to do this (usually to label the individual arrays/fields of structures of arrays), so I created a ca65 macro for this (IIRC, you need to supply the names of all the labels and the macro calculates how far apart they are based on the length of the data and the number of labels - so it won't work if the arrays aren't all the same length). I can post it if anyone is interested, I just don't have it with me right now.

For something constant-sized like a name table I probably wouldn't bother creating separate labels though, I'd just adjust the base address directly in the 4 loops that copy the data:

Code:
lda nametable+$000, x
;(...)
lda nametable+$100, x
;(...)
lda nametable+$200, x
;(...)
lda nametable+$300, x
Re: Basic nametable reading question
by on (#201421)
For completeness, here's one more option (ca65 syntax):
Code:
nametable_part0:
.incbin "level_01_bg.nam", 0*256, 256
nametable_part1:
.incbin "level_01_bg.nam", 1*256, 256
nametable_part2:
.incbin "level_01_bg.nam", 2*256, 256
nametable_part3:
.incbin "level_01_bg.nam", 3*256, 256
Re: Basic nametable reading question
by on (#201422)
!!! How did I not know that ca65 has way to trim an .incbin like that???
Thanks for making me aware of this.
Re: Basic nametable reading question
by on (#201423)
nesrocks wrote:
tepples wrote:
Do you know what a pointer is?

So you mean I can use that to retrieve the label's address at runtime and find the rest of the data relative to its position? I'll look further into zero page pointers and see if that's the case, thanks.

Pre-posting edit: It seems that's the case (sans my probably imprecise way of describing it), thanks mikejmoffitt!

edit: it works! The only typo in your code is that instead of reading and writing to/from "addr_ptr+1" you wrote "nametable+1" but that was an easy fix. Thanks! I'll study those operators (<, >, +1 etc), I kind of see what they do but I had never seen them.


Glad it mostly worked! The < and > operators retrieve the low and high addresses of the provided symbol, respectively. <nametable gets the lower byte, and >nametable gets the higher byte of nametable. Putting those into a pointer in order gets you a pointer to the nametable.

addr_ptr+1 simply means "the high byte of addr_ptr", which is one byte after the symbol. That's because addr_ptr uses two bytes to represent a memory address. We're checking the high byte (in this little-endian system) because it effectively indexes the page, which we've already iterated through.

rainwarrior wrote:
!!! How did I not know that ca65 has way to trim an .incbin like that???
Thanks for making me aware of this.

Agreed, that's fantastic. This makes a yy-chr palette file directly usable as well.
Re: Basic nametable reading question
by on (#201424)
rainwarrior wrote:
!!! How did I not know that ca65 has way to trim an .incbin like that???

I didn't know either! That may come in handy sometime!
Re: Basic nametable reading question
by on (#201425)
Incbin management like this is super useful for a beginner like me, too. Tip of the month at the very least!
Re: Basic nametable reading question (.incbin addressing)
by on (#201439)
Amazing stuff that this information could be spread! And here I was wondering if writing "+1" was correct... :lol: Now I know a decimal number doesn't need a preceding character. Coming from a hacking background where I mostly worked with hex numbers typing byte code directly into the hex it looked weird to see in asm code.

And yeah, being able to .incbin only a section of a file seems very powerful for the workflow, one could include parts of other ROMs without splitting it into different files or clogging your own ROM with useless bytes. The uses are endless.