Each CHR file takes up 8 KB (8192 bytes) of data. In an NROM cartridge (the original Famicom/NES cartridges used in early black box games) you can only store 1 of these. So all your game's graphics had to fit in just 8 KB. Below is a picture of the latter half of Super Mario Bros.'s CHR file. Typically, the first 4 KB are used for sprites and the second four KB are used for background tiles. You decide which is which by setting bit 4 of CPU address $2000, aka PPU Control. On an NROM cartridge, there is no bank switching, so these graphics cannot be rearranged except by editing the CHR file which obviously cannot be done during gameplay.
The Konami VRC6 was used in the Japanese release of Castlevania 3. It's mostly famous for its enhanced music capabilities, which sadly do not work on an American or PAL NES (For its US and PAL release the game was ported to a cartridge with a different chip altogether.) Other than sound, one of its features is CHR-ROM bank switching. In the attached image I drew red lines between each 4-tile-tall section. By writing to certain memory addresses in the ROM you can rearrange these sections of the CHR-files. Below is a picture of the edited CHR file I used in my test ROM. It's very wasteful to do it like this, but I was making this just as a tech demo so I'll work out how to conserve space later.
I'll go ahead and give a quick rundown of how you do this in the ROM.
First, you need your registers set up. The CHR-ROM banking registers are memory mapped just like the PPU ports. You can write to them but you can't read from them so it's best to create "soft" zero page versions of these that you write to, and then transfer them to the real thing during NMI.
$D000 controls the first 1 KB of CHR-ROM
$D001 controls the second 1 KB of CHR-ROM
$D002
$D003
$E000
$E001
$E002
$E003
Although you will most likely have executable code in these locations, it has no effect on your CHR-ROM banks or vice versa. As part of the startup routine for the rom, I wrote 0 to $D000, 1 to $D001, 2 to $D002, and so on. This sets up the CHR-ROM how you would expect. Another cool feature is that you can have multiple CHR-ROM files and swap out to those as well if you write numbers higher than 7, but I didn't do that for this. (My test ROM has two CHR-ROM files loaded but the routine I'm about to share only uses the first one.)
Hopefully this isn't too confusing (it took me quite some time to understand how this works, the NESdev wiki doesn't explain it well.) If you're still reading along I'll go ahead and explain the trick.
In the code I have a controller input handling section set up to scroll right when you press right and scroll left when you press left. I also wrote the same screen to both nametables so that it loops around. To make the bank swapping happen, I used a temp variable to store one of the banks, and wrote each into the one before it, then put that temp variable back at the end. The code looks like this:
Code: Select all
ScrollRight:
inc scroll
lda softE001
sta tempBank
lda softE002
sta softE001
lda softE003
sta softE002
lda tempBank
sta softE003
The source code for this test rom is pretty long, if you're interested I'll reply to this post and paste it. If you like you can see the rom in action here, but I don't think this video does it justice:
https://vimeo.com/550048533