It's pretty simple: "simple" assemblers like asm6 do not know anything about "banks" (PRG, CHR, anything else), nor does it know anything about mappers.
All you, as the programmer, need to know is the NES file format to understand how to organise your resulting
.nes file, and keep track (yourself) of PRG bank numbers yourself.
First, the file format: the Wiki describes the file format in a way that I've maintained doesn't do a good job visualising the file itself, which makes it hard for people to understand the format, but it's there:
https://wiki.nesdev.com/w/index.php/INE ... ile_formatI do not care about the NES 2.0 header so I'm not going to describe it -- all it will do is confuse you right now.
The 16-byte NES header comes first, followed by the first 16KB PRG bank, followed by the next 16KB PRG bank, etc., followed by the first 8KB CHR-ROM bank, followed by the next 8KB CHR bank, etc.. until the end of the file. The number of PRG and CHR-ROM banks should be base-2-compliant, e.g. 0, 1, 2, 4, 8, etc.. not values like 3 or 7. If you're using CHR-RAM, you use a value of 0 for CHR bank count.
The file format header
always assumes 16KByte PRG banks and 8KByte CHR-ROM banks.
Always. It doesn't matter what mapper number you put in the header.
So as an example: if you were making an MMC1 game (the mapper which uses 16KByte PRG banks itself), which consisted of 64KBytes total PRG-ROM (4 banks), and 64KBytes total CHR-ROM (8 banks), the header and file would look like this:
Code:
NES mapper: Nintendo MMC1 = 1 = %00000001
NES header:
Bytes 0-3: NES ROM identifier string = $4E $45 $53 $1A
Byte 4: PRG-ROM bank count = 4 (4*16KB banks = 64KB)
Byte 5: CHR-ROM bank count = 8 (8*8KB banks = 64KB)
Byte 6 bits 7-4: lower nibble of NES mapper number = %0001
Byte 6 bits 3-0: depends on what you want; see Wiki
Byte 7 bits 7-4: upper nibble of NES mapper number = %0000
Byte 7 bits 3-0: depends on what you want; see Wiki
Bytes 8-15 = 0 (refer to the Wiki if you want to know what they do)
Resulting NES ROM file itself:
First 16 bytes: NES header
Next 16384 bytes: PRG bank #0
Next 16384 bytes: PRG bank #1
Next 16384 bytes: PRG bank #2
Next 16384 bytes: PRG bank #3 -- this is also your fixed bank at $C000-FFFF
Next 8192 bytes: CHR bank #0
Next 8192 bytes: CHR bank #1
Next 8192 bytes: CHR bank #2
Next 8192 bytes: CHR bank #3
Next 8192 bytes: CHR bank #4
Next 8192 bytes: CHR bank #5
Next 8192 bytes: CHR bank #6
Next 8192 bytes: CHR bank #7
This should clarify how and why
copy /b header.bin+prg00.bin+prg01.bin+...+chr00.bin+chr01.bin+... game.nes can be used to create a NES ROM.
If you're using a mapper that uses 8KByte PRG banks, the file format remains the same as I described -- you simply have to multiply by total number of 8KB PRG banks by 2 for the NES header.
For example, let's say you were using mapper 24 (VRC6a) which consisted of 64KBytes total PRG-ROM (8 banks of 8KBytes) and CHR-RAM (i.e. zero CHR-ROM), you'd have this:
Code:
NES mapper: Konami VRC6a = 24 = %00011000
NES header:
Bytes 0-3: NES ROM identifier string = $4E $45 $53 $1A
Byte 4: PRG-ROM bank count = 4 (8*8KB banks / 2 due to NES header counting PRG-ROM in 16KB banks)
Byte 5: CHR-ROM bank count = 0 (using CHR-RAM)
Byte 6 bits 7-4: lower nibble of NES mapper number = %1000
Byte 6 bits 3-0: depends on what you want; see Wiki
Byte 7 bits 7-4: upper nibble of NES mapper number = %0001
Byte 7 bits 3-0: depends on what you want; see Wiki
Bytes 8-15 = 0 (refer to the Wiki if you want to know what they do)
Resulting NES ROM file itself:
First 16 bytes: NES header
Next 8192 bytes: PRG bank #0
Next 8192 bytes: PRG bank #1
Next 8192 bytes: PRG bank #2
Next 8192 bytes: PRG bank #3
Next 8192 bytes: PRG bank #4
Next 8192 bytes: PRG bank #5
Next 8192 bytes: PRG bank #6
Next 8192 bytes: PRG bank #7 -- this is also your fixed bank at $E000-FFFF
The first thing you're going to say is "wait, is the above true? VRC6a/b support having 16KB PRG-ROM at $8000-BFFF! How does that fit into this?"
Short answer: with regards to the file format, it doesn't. Whether the mapper "swaps in/out" two 8KB PRG-ROM pages (for 16KB at $8000-BFFF) or a single 8KB PRG-ROM page (for $C000-DFFF) doesn't matter to the NES file format. It matters more to the mapper. For example, say with VRC6a you told it to swap in 16KB PRG bank #1 at $8000-BFFF. What's going to end up in $8000-9FFF is 8KB PRG bank #1, and $A000-BFFF will hold 8KB PRG bank #2.
Now you understand the file format.
As for how to get all of this to work in asm6: it's not that hard, but the important piece of the puzzle is the
.bank directive. Please refer to the documentation to understand what this does; it's sort of like
.org (meaning it sets what the assembler considers the PC (program counter)), except unlike
.org you can re-use addresses with it. It's designed for this purpose.
There's already a thread on how to use asm6 with banked mappers:
viewtopic.php?t=6027I'm in the process of writing up an example VRC6a template which you could use with asm6,
except I seem to be having problems with doing a 16KB PRG bank swap using $8000 (for swapping $8000-BFFF) -- Mesen is picking a completely different bank number than what my code has, so it's likely some misunderstanding on my part, a documentation mistake, or something. Everything else works as expected.
Edit: simple mistake on my part: for 16KB swaps, you need to divide the bank number by 2, since internally the VRC6 considers all PRG banks 8KB in size. This means for 16KB bank selection, you need to ensure the bank number is evenly divisible by 2 (i.e. 0, 2, 4, etc.).
Here's the code:
https://pastebin.com/Zqd2STsiHilariously, the captcha I got on pastebin when submitting that was
DA65. *chuckle*
The code sits in an infinite loop, just running some
lda/ldx/ldy statements over and over. The important part is to understand that after the VRC6a init routine runs, you end up with the following:
$8000-9FFF = PRG bank 4
$A000-BFFF = PRG bank 5
$C000-DFFF = PRG bank 3
$E000-FFFF = PRG bank 7 (fixed)
How do I know this for certain? Because, conveniently, Mesen actually visually shows you what bank's mapped where (both PRG and CHR). :-)
I may have gotten some part of the VRC6 PPU mirroring setup wrong, but that really isn't what the code was intended to demonstrate here. While I sort of understand what the Wiki documents, it does take some focus to comprehend.
Hopefully this is a bit more complete, and a bit easier to understand than tokumaru's example. I don't tend to "teach" things like this using macros, as I feel all it does is confuse people trying to understand something already complex enough.
As for "getting bank numbers" in your code, rather than hard-coding them: lots of ways to do this. I will bow out of such a conversation because it's entirely subjective. Do it in whatever way you want that makes the most sense to you. I tend to use equates or hard-code things simply because I don't like complicating matters ("cleaning things up" to be more fancy can be done at the end of whatever project I'm working on).