BizHawk/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs

311 lines
7.9 KiB
C#
Raw Normal View History

using System;
using System.Diagnostics;
namespace BizHawk.Emulation.Consoles.Nintendo.Boards
{
//AKA MMC1
//http://wiki.nesdev.com/w/index.php/SxROM
//consult nestopia as well.
//the initial conditions for MMC1 games are known to be different. this may have to do with which MMC1 rev it is.
//but how do we know which revision a game is? i don't know which revision is on which board
//check UNIF for more information.. it may specify board and MMC1 rev independently because boards may have any MMC1 rev
//in that case, we need to capture MMC1 rev in the game database (maybe add a new `chip` parameter)
//this board is a little more convoluted than it might otherwise be because i switched to a more chip-centered way of modeling it partway through
//perhaps i will make other boards work that way, and perhaps not
class MMC1
{
public MMC1()
{
//collect data about whether this is required here:
//megaman 2 requires it
StandardReset();
}
public enum Rev
{
A, B1, B2, B3
}
//shift register
int shift_count, shift_val;
//register 0:
public int chr_mode;
public int prg_mode;
public int prg_slot; //complicated
public NES.EMirrorType mirror;
static NES.EMirrorType[] _mirrorTypes = new NES.EMirrorType[] { NES.EMirrorType.OneScreenA, NES.EMirrorType.OneScreenB, NES.EMirrorType.Vertical, NES.EMirrorType.Horizontal };
//register 1,2:
int chr_0, chr_1;
//register 3:
int wram_disable;
int prg;
void StandardReset()
{
prg_mode = 1;
prg_slot = 1;
}
public void Write(int addr, byte value)
{
int data = value & 1;
int reset = (value >> 7) & 1;
if (reset == 1)
{
shift_count = 0;
shift_val = 0;
StandardReset();
}
else
{
shift_val >>= 1;
shift_val |= (data<<4);
shift_count++;
if (shift_count == 5)
{
WriteRegister(addr >> 13, shift_val);
shift_count = 0;
shift_val = 0;
}
}
}
void WriteRegister(int addr, int value)
{
switch (addr)
{
case 0: //8000-9FFF
mirror = _mirrorTypes[value & 3];
prg_slot = ((value >> 2) & 1);
prg_mode = ((value >> 3) & 1);
chr_mode = ((value >> 4) & 1);
break;
case 1: //A000-BFFF
chr_0 = value & 0x1F;
break;
case 2: //C000-DFFF
chr_1 = value & 0x1F;
break;
case 3: //E000-FFFF
prg = value & 0xF;
wram_disable = (value >> 4) & 1;
break;
}
//Console.WriteLine("mapping.. chr_mode={0}, chr={1},{2}", chr_mode, chr_0, chr_1);
//Console.WriteLine("mapping.. prg_mode={0}, prg_slot{1}, prg={2}", prg_mode, prg_slot, prg);
}
public int Get_PRGBank(int addr)
{
int PRG_A14 = (addr >> 14) & 1;
if (prg_mode == 0)
if (PRG_A14 == 0)
return prg;
else
{
Debug.Assert(false, "not tested yet, and had to guess");
return (prg + 1) & 0xF;
}
else if (prg_slot == 0)
if (PRG_A14 == 0)
return 0;
else return prg;
else
if (PRG_A14 == 0)
return prg;
else return 0xF;
}
public int Get_CHRBank_4K(int addr)
{
int CHR_A12 = (addr >> 12) & 1;
if (chr_mode == 0)
return chr_0;
else if (CHR_A12 == 0)
return chr_0;
else return chr_1;
}
}
public class SxROM : NES.NESBoardBase
{
string type;
MMC1 mmc1;
int prg_mask, chr_mask;
byte[] cram, pram;
int cram_mask, pram_mask;
public SxROM(string type)
{
this.type = type;
mmc1 = new MMC1();
}
public override void Initialize(NES.RomInfo romInfo, NES nes)
{
base.Initialize(romInfo, nes);
Debug.Assert(RomInfo.PRG_Size == 8 || RomInfo.PRG_Size == 16);
prg_mask = RomInfo.PRG_Size - 1;
Debug.Assert(RomInfo.CRAM_Size == -1, "don't specify in gamedb, it is redundant");
Debug.Assert(RomInfo.PRAM_Size == -1, "don't specify in gamedb, it is redundant");
//analyze board type
switch (type)
{
case "SGROM":
Debug.Assert(RomInfo.CHR_Size == -1, "don't specify in gamedb, it is redundant");
romInfo.CHR_Size = 0; RomInfo.CRAM_Size = 8; romInfo.PRAM_Size = 0;
break;
case "SNROM":
Debug.Assert(RomInfo.CHR_Size == -1, "don't specify in gamedb, it is redundant");
romInfo.CHR_Size = 0; RomInfo.CRAM_Size = 8; RomInfo.PRAM_Size = 8;
break;
case "SL2ROM":
RomInfo.CRAM_Size = 0;
RomInfo.PRAM_Size = 0;
break;
default: throw new InvalidOperationException();
}
Debug.Assert(RomInfo.CRAM_Size == 0 || RomInfo.CRAM_Size == 8);
if (RomInfo.CRAM_Size != 0)
{
cram = new byte[RomInfo.CRAM_Size * 1024];
cram_mask = cram.Length - 1;
}
Debug.Assert(RomInfo.PRAM_Size == 0 || RomInfo.PRAM_Size == 8);
if (RomInfo.PRAM_Size != 0)
{
pram = new byte[RomInfo.CRAM_Size * 1024];
pram_mask = pram.Length - 1;
}
if (RomInfo.CHR_Size != 0)
{
Debug.Assert(RomInfo.CHR_Size == 16);
chr_mask = (RomInfo.CHR_Size*2) - 1;
}
}
public override void WritePRG(int addr, byte value)
{
mmc1.Write(addr, value);
SetMirrorType(mmc1.mirror); //often redundant, but gets the job done
}
public override byte ReadPRG(int addr)
{
int bank = mmc1.Get_PRGBank(addr) & prg_mask;
addr = (bank << 14) | (addr & 0x3FFF);
return RomInfo.ROM[addr];
}
int Gen_CHR_Address(int addr)
{
int bank = mmc1.Get_CHRBank_4K(addr);
addr = ((bank & chr_mask) << 12) | (addr & 0x0FFF);
return addr;
}
public override byte ReadPPU(int addr)
{
if (addr < 0x2000)
{
if (RomInfo.CRAM_Size != 0)
return cram[addr & cram_mask];
else return RomInfo.VROM[Gen_CHR_Address(addr)];
}
else return base.ReadPPU(addr);
}
public override void WritePPU(int addr, byte value)
{
if (addr < 0x2000)
{
if (RomInfo.CRAM_Size != 0)
cram[addr & cram_mask] = value;
}
else base.WritePPU(addr, value);
}
public override byte ReadPRAM(int addr)
{
if (RomInfo.PRAM_Size != 0)
return pram[addr & pram_mask];
else return 0xFF;
}
public override void WritePRAM(int addr, byte value)
{
if (RomInfo.PRAM_Size != 0)
pram[addr & pram_mask] = value;
}
public override byte[] SaveRam
{
get
{
if (!RomInfo.Battery) return null;
return pram;
//some boards have a pram that is backed-up or not backed-up. need to handle that somehow
//(nestopia splits it into NVWRAM and WRAM but i didnt like that at first.. but it may player better with this architecture)
}
}
}
}
//http://wiki.nesdev.com/w/index.php/Cartridge_connector
//http://benheck.com/Downloads/NES_Famicom_Pinouts.pdf
//http://kevtris.org/mappers/mmc1/index.html
//one day, i will redo mappers at the pin level and it will look something like this:
//public void Strobe(int PRG_A, int PRG_D, int PRG_READ, int CHR_A)
//{
// if (PRG_READ == 1)
// {
// int PRG_A14 = (PRG_A >> 14) & 1;
// int tmp_prg_A17_A14;
// if (prg_mode == 0)
// if (PRG_A14 == 0)
// tmp_prg_A17_A14 = prg;
// else
// tmp_prg_A17_A14 = ((prg + 1) & 0xF);
// else if (prg_slot == 0)
// if (PRG_A14 == 0) tmp_prg_A17_A14 = 0;
// else tmp_prg_A17_A14 = prg;
// else if (PRG_A14 == 0)
// tmp_prg_A17_A14 = prg;
// else tmp_prg_A17_A14 = 0xF;
// out_PRG_A = PRG_A;
// out_PRG_A &= ~0x4000;
// out_PRG_A |= (tmp_prg_A17_A14 << 14);
// }
//}
//public int Read_CHR_A(int addr)
//{
// int CHR_A10 = (addr >> 10) & 1;
// int CHR_A11 = (addr >> 10) & 1;
// int out_CIRAM_A10;
// switch (mirror)
// {
// case 0: out_CIRAM_A10 = 0; break;
// case 1: out_CIRAM_A10 = 1; break;
// case 2: out_CIRAM_A10 = CHR_A10; break;
// case 3: out_CIRAM_A10 = CHR_A11; break;
// }
// addr
//}