I've been playing with reverse-engineering FF7 NES. It currently uses mapper 163, which is a pirate mapper allowing a mind-blowing 2MB of PRG data. When looking at ROM, it's a complete mess. Almost 512K is left unused. The code looks gigantic and massively inefficient, and the Chinese character text engine eats massive amounts of PPU memory.
It has gotten to the point that a rewrite might do the game better. Sadly, mapper 163 isn't very well documented. It appears to bank 32K at a time from $8000-$FFFF, which results in a lot of duplicated code throughout the ROM. (the vectors and reset routine for example) The bank switch registers appear to be $5000-$5004 (??? - I don't have my notes in front of me), but their function is elusive to me.
I may just switch to MMC5, as that seems to allow 1MB PRG and 1MB CHR, but that looks really complicated and there isn't much I can find on programming examples. (I'll be using the assembler and linker from the CC65 package)
And yes, I'm writing this for an emulator, the size makes it impractical to think about a hardware solution.
Any ideas on how to go about this?
There is no limit. You can always build a custom board that allows any amount of PRG. For example UxROM can be modified to allow 4MB of PRG by replacing the 4-bit register with an 8-bit one. Many emulators already support this (they use all 8-bits of the bank write). However iNES format restricts the PRG ROM size to 2MB.
Bigger is cheaper for a cart really. 128KB ROMs are legacy hardware now. Often it is much cheaper to get an SMT part in 1MB to 4MB. And the UxROM variant that TheFox suggested is dead-simple, but uses CHR-RAM.
I would caution you to think about how much room you actually need. I spent a month agonizing over which mapper to use before I wrote my first attempt at a Zeldroidvania game. Once I finally gave in and started writting it for MMC1, I found I could make a game the size of Super Metroid with a 256 KB PRG-ROM with CHR-RAM.
Also remember that MMC1 can address up to 512 KB of PRG-ROM and 32 KB of PRG-RAM in the SXROM configuration, and is much better supported on emulators than MMC5. Better yet, if you're worried about a cartridge release, RetroZone has an
SXROM-Compatible Repro Board for only $9.
UxROM has no ram
MMC1 does
halkun wrote:
UxROM has no ram
Emulators might support WRAM being mapped. Adding it to a real cartridge wouldn't take alot either.
You shouldn't be surprised if this FF7 is horribly inefficient and clunky. It's not like it was made by top notch programmers and designers. MMC1 can support alot more PRG-ROM than existing configurations support. Currently only one allows for 512K but you can easily bump that up to 2MB if needed. You can hijack more CHR register bits. The 512K steals 1, just steal 2 more and you have 2MB. Ofcourse if it has 1MB CHR this doesn't help. Do you have a document on this 163 mapper?
The disassembly of the FF7 pirate game is actually straightforward and easy to decpiher. The code is a lot cleaner than a Dragon Quest game.
I just hated how long the battles took in the opening. It took me nearly 45 minutes to get out of the first area
I thought the FF7 remake was MMC1 though? Am I missing something?
The game was disassembled? Where is this and the mapper information located?
Not fully disassembled, I was just looking at the code that loads the font, and the coding wasn't that bad for that part.
The mapper is simple, 32k PRG bankswitching, with WRAM.
There's also a register that makes it magically change the left pattern table to the right one after the scanline 128, for people to lazy to use a sprite 0 hit. It's unknown how it works, so emulators just cheat to emulate this.
Dwedit wrote:
The mapper is simple, 32k PRG bankswitching, with WRAM.
In other words, it could be easily hacked to mapper 34 (BNROM + oversize ROM + PRG RAM) as implemented by emulators, correct?
Quote:
There's also a register that makes it magically change the left pattern table to the right one after the scanline 128, for people to lazy to use a sprite 0 hit.
Would you likewise call the developers of Punch-Out!!, Fire Emblem, and Famicom Wars lazy for adding CHR bankswitches that take effect on $0FD0-$0FEF reads?
Bankswitching 4k out of the entire 8k of CHR RAM is pretty feeble, especially seeing as the NES can do it itself with a sprite 0 hit and a write to PPUCTRL. Maybe they just wanted the Reset Button not to screw up the title screen?
I'm a little foggy on the save ram. It seems to occupy the same space as standard ram on the cart. Is that true? Use that ram, and on power-up the data is still there?
halkun wrote:
I'm a little foggy on the save ram. It seems to occupy the same space as standard ram on the cart. Is that true? Use that ram, and on power-up the data is still there?
Yes, given that there's a battery on the cart.
For example, a game might divide PRG RAM into $6000-$77FF for scratch space and $7800-$7FFF for saves.
The SUROM looks pretty cool. MMC1, 512K PRG, 8K CHR RAM, 8K WRAM. So that's about as "Maxed" as I can get on an MMC1, and because it's MMC1. I can always scale back.
Can I "overload" that to 512k PRG to 1MB?
Looking at the MMC1 documentation, You can only feed $1-$F into $E000 for a bank, but that only gets you 16k*16 or 256k. I'm not quite clear how the other 256K is accessed. According to some documentation, SUROM uses the CHR A16 line to bank out the whole 256k block, (including the fixed ram at $C000-$FFFF) Looking in at the wiki, both Char Bank 0 and Char bank 1 both have a bit for flipping out a 256k bank of PRG data. does this mean I can have 2 selector bits to allow me 4*256k banks (1MB) of PRG data? Am I getting into iffy emulator implementation territory? Am I just reading the documentation wrong?
I'm not big on the whole "banking out the whole 32K" thing. I'd like the kernel to stay at the bottom of memory.
I guess a perfect set up would be a 1MB or 2MB PRG ROM, with 8k WRAM and the ability to bank out only the upper 16K
To get the 512K, you have to use one of the CHR-ROM select bits. I believe there's 3 more there, so you should be able to get to 4MB. Or like SXROM and be believe 512K with 8K CHR-RAM and 16K WRAM or swapped around.
Even though you can probably do this to a real cart without much trouble, I doubt any emulators currently support this. Not having an emulator to test your programs sure makes everything harder.
You can always modify open source emulators to support different MMC1 configurations.
figuring that I'm actually making this *for* emulators. If it can't run on an emulator at all, I won't be able to do anything...
Cool beans though, I just made a blank test ROM with 2MB PRG and fceux took it. Now to play with it a little.
If you're only looking for emulator support I and don't need selectable nametable mirroring I would recommend mapper 2 (UxROM). Many emulators support up to 2MB of PRG-ROM.
Few modern emulators give WRAM to UxROM.
There's the WRAM bit in iNES to put it there for a reason, right? Although the amount selectable would be better, 99% should just want 8KB.
Some emulators insist on not allowing hardware configurations that were not used by commercial games, even when the iNES header specifically asks for said configuration. This really sucks.
tokumaru wrote:
Some emulators insist on not allowing hardware configurations that were not used by commercial games, even when the iNES header specifically asks for said configuration.
If you've added a PRG RAM decoder circuit like that used in Family BASIC to your mapper 2-compatible board, say so in the NES 2.0 header.
Which emus support NES 2.0
If you wanted hardware wise it is trivial to adjust UxROM to have a nametable mirroring control bit, 8K of WRAM at $6000-$7FFF, and have 7 bits of PRG bank select. That should give you 2 Megabytes of PRG. It would be like MMC1 with CHR-RAM only without the drawbacks like the serial access and wonky support for over 256KB of PRG.
MottZilla wrote:
If you wanted hardware wise it is trivial to adjust UxROM to have a nametable mirroring control bit, 8K of WRAM at $6000-$7FFF, and have 7 bits of PRG bank select. That should give you 2 Megabytes of PRG. It would be like MMC1 with CHR-RAM only without the drawbacks like the serial access and wonky support for over 256KB of PRG.
That's kinda what I've been thinking all along.
I was looking at UxROM, but according to the wiki, there is no WRAM at all in that configuration. Or is that just trivial to just flip a bit in the ines header?
If you set the "battery" bit in the iNES header, emulators
should emulate 8 KiB of PRG RAM, decoded to $6000-$7FFF with a 74HC20 (dual 4-input NAND) (see
schematic). There are no existing boards with PRG RAM on mapper 2, but Family BASIC has PRG RAM on mapper 0, and in my opinion the extension to other discrete boards is obvious.
For a custom version of mapper 2 with more than 256 KiB of ROM, you'll need more than four bits of bank number: replace the 74HC161 (4-bit binary counter) with a 74HC377 (8-bit flip-flop) and add a second 74HC32 (quad 2-input OR). Because this makes the bank table much bigger, you'll probably need the other half of the 74HC20 that avoids bus conflicts.
For a custom version of mapper 2 with mirroring control, you'd want to use a 74HC153 data selector/multiplexer. The inputs are GND, Vcc, CHR A10, and CHR A11, the select line is from the '377, and the output is CIRAM A10.
The parts list so far:
- PRG ROM
- 6264 or 62256: PRG RAM
- 6264 or 62256: CHR RAM
- CIClone: stop blinking on front-loaders
- 74HC377: hold bank number
- 74HC32 * 2: make fixed bank (second optional for SUROM-style super-bank operation)
- 74HC20: decode PRG RAM and avoid bus conflicts between '377 and PRG ROM
- 74HC153: mirroring control (optional; not part of mapper 2)
You have five 7400 series ICs, and at that point Nintendo would have switched your project from discrete logic to an ASIC mapper.
Yes but for homebrew purposes there is nothing wrong with doing this for your own game. Afterall Nintendo never was going to have a NES game with 2 megabytes of ROM. I'd be interested in seeing a game that actually needed 2 megabytes of ROM.
Action 52 uses 2 MB, but that's probably inefficiently coded. Donkey Kong Country for GBC (a system comparable to an NES with MMC5) uses 4 MB, but about half of each bank is blank.
If you use more than 512 KB of PRG ROM, your program won't run on a PowerPak, and you'll need to solder together your own test cart.
I'll try to have an SNROM template for you tonight so you can get started. Once you finish recoding Midgar, I should have the SUROM template ready.
(When you're looking at the subway map, is that
Cloud computing?)
I bet if someone ported the Zelda Oracles games to the NES, they would be 2MB in size when put together.
I found some emulator code that describes how the bank switcher work for Mapper 163. Anyone care to translate? I'm just copying the bank switch code from the original rom, but I 'd like to have an idea of how it works. Here is two code snippets from two different emulators.
I know that $5000 is a security check, and 5001-5003 switch the rom, but are the arguments?
there is also a scan-line interrupt at 127> I think.
anyone want to help decipher?
Code:
//////////////////////////////////////////////////////////////////////////
// Mapper163 NanJing Games (NES Chinese RPR game) //
//////////////////////////////////////////////////////////////////////////
void Mapper163::Reset()
{
reg[1] = 0xFF;
strobe = 1;
security = trigger = reg[0] = 0x00;
SetPROM_32K_Bank(15);
SetCRAM_8K_Bank(0);
}
BYTE Mapper163::ReadLow( WORD addr )
{
if((addr>=0x5000 && addr<0x6000))
{
switch (addr & 0x7700)
{
case 0x5100:
return security;
break;
case 0x5500:
if(trigger)
return security;
else
return 0;
break;
}
return 4;
}
else if( addr>=0x6000 ) {
return CPU_MEM_BANK[addr>>13][addr&0x1FFF];
}
return 0;
}
void Mapper163::WriteLow(WORD addr, BYTE data)
{
if((addr>=0x4020 && addr<0x6000))
{
if(addr==0x5101){
if(strobe && !data){
trigger ^= 1;
}
strobe = data;
}else if(addr==0x5100 && data==6){
SetPROM_32K_Bank(3);
}
else{
switch (addr & 0x7300)
{
case 0x5000:
reg[1]=data;
SetPROM_32K_Bank( (reg[1] & 0xF) | (reg[0] << 4) );
if(!(reg[1]&0x80)&&(nes->ppu->GetScanlineNo()<128))
SetCRAM_8K_Bank(0);
break;
case 0x5200:
reg[0]=data;
SetPROM_32K_Bank( (reg[1] & 0xF) | (reg[0] << 4) );
break;
case 0x5300:
security=data;
break;
}
}
}
else if( addr>=0x6000 ) {
CPU_MEM_BANK[addr>>13][addr&0x1FFF] = data;
}
}
void Mapper163::HSync(int scanline)
{
if( (reg[1]&0x80) && nes->ppu->IsDispON() ) {
if(scanline==127){
SetCRAM_4K_Bank(0, 1);
SetCRAM_4K_Bank(4, 1);
}
if(scanline==239){
SetCRAM_4K_Bank(0, 0);
SetCRAM_4K_Bank(4, 0);
}
}
}
void Mapper163::SaveState( LPBYTE p )
{
p[0] = reg[0];
p[1] = reg[1];
}
void Mapper163::LoadState( LPBYTE p )
{
reg[0] = p[0];
reg[1] = p[1];
}
Code:
/*************************************************************
Bootleg Board by Nanjing
Games: A lot of pirate originals
iNES: mapper 163
In MESS: Unsupported.
*************************************************************/
static void nanjing_irq( device_t *device, int scanline, int vblank, int blanked )
{
nes_state *state = device->machine().driver_data<nes_state>();
if (BIT(state->m_mmc_reg[0], 7))
{
if (scanline == 127)
{
chr4_0(device->machine(), 1, CHRRAM);
chr4_4(device->machine(), 1, CHRRAM);
}
if (scanline == 239)
{
chr4_0(device->machine(), 0, CHRRAM);
chr4_4(device->machine(), 0, CHRRAM);
}
}
}
static WRITE8_HANDLER( nanjing_l_w )
{
nes_state *state = space->machine().driver_data<nes_state>();
LOG_MMC(("nanjing_l_w, offset: %04x, data: %02x\n", offset, data));
offset += 0x100;
if (offset < 0x1000)
return;
if (offset == 0x1100) // 0x5100
{
if (data == 6)
prg32(space->machine(), 3);
return;
}
if (offset == 0x1101) // 0x5101
{
UINT8 temp = state->m_mmc_count;
state->m_mmc_count = data;
if (temp & !data)
state->m_mmc_latch2 ^= 0xff;
}
switch (offset & 0x300)
{
case 0x000:
case 0x200:
state->m_mmc_reg[BIT(offset, 9)] = data;
if (!BIT(state->m_mmc_reg[0], 7) && ppu2c0x_get_current_scanline(state->m_ppu) <= 127)
chr8(space->machine(), 0, CHRRAM);
break;
case 0x300:
state->m_mmc_latch1 = data;
break;
}
prg32(space->machine(), (state->m_mmc_reg[0] & 0x0f) | ((state->m_mmc_reg[1] & 0x0f) << 4));
}
static READ8_HANDLER( nanjing_l_r )
{
nes_state *state = space->machine().driver_data<nes_state>();
UINT8 value = 0;
LOG_MMC(("nanjing_l_r, offset: %04x\n", offset));
offset += 0x100;
if (offset < 0x1000)
return 0;
switch (offset & 0x700)
{
case 0x100:
value = state->m_mmc_latch1;
break;
case 0x500:
value = state->m_mmc_latch2 & state->m_mmc_latch1;
break;
case 0x000:
case 0x200:
case 0x300:
case 0x400:
case 0x600:
case 0x700:
value = 4;
break;
}
return value;
}