401 lines
9.2 KiB
C#
401 lines
9.2 KiB
C#
//this file contains the MMC3 family of chips
|
|
//which includes:
|
|
//NAMCOT 109
|
|
//MMC3 (which was apparently based on NAMCOT 109 and shares enough functionality to be derived from it in this codebase)
|
|
|
|
//see http://nesdev.parodius.com/bbs/viewtopic.php?t=5426&sid=e7472c15a758ebf05c588c8330c2187f
|
|
//and http://nesdev.parodius.com/bbs/viewtopic.php?t=311
|
|
//for some info on NAMCOT 109
|
|
|
|
//mappers handled by this:
|
|
//004,095,118,119,206
|
|
|
|
//fceux contains a comment in mmc3.cpp:
|
|
//Code for emulating iNES mappers 4,12,44,45,47,49,52,74,114,115,116,118,119,165,205,214,215,245,249,250,254
|
|
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Diagnostics;
|
|
|
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
|
{
|
|
// this is the base class for the MMC3 mapper
|
|
public class Namcot109 : IDisposable
|
|
{
|
|
//state
|
|
public int chr_mode, prg_mode, reg_addr;
|
|
ByteBuffer chr_regs_1k = new ByteBuffer(8);
|
|
ByteBuffer prg_regs_8k = new ByteBuffer(8);
|
|
|
|
protected NES.NESBoardBase board;
|
|
public Namcot109(NES.NESBoardBase board)
|
|
{
|
|
this.board = board;
|
|
|
|
prg_regs_8k[0] = 0;
|
|
prg_regs_8k[1] = 1;
|
|
prg_regs_8k[2] = 0xFE; //constant
|
|
prg_regs_8k[3] = 0xFF; //constant
|
|
prg_regs_8k[4+0] = 0xFE; //constant
|
|
prg_regs_8k[4+1] = 1;
|
|
prg_regs_8k[4+2] = 0;
|
|
prg_regs_8k[4+3] = 0xFF; //constant
|
|
|
|
chr_regs_1k[0] = 0;
|
|
chr_regs_1k[1] = 1;
|
|
chr_regs_1k[2] = 2;
|
|
chr_regs_1k[3] = 3;
|
|
chr_regs_1k[4] = 4;
|
|
chr_regs_1k[5] = 5;
|
|
chr_regs_1k[6] = 6;
|
|
chr_regs_1k[7] = 7;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
chr_regs_1k.Dispose();
|
|
prg_regs_8k.Dispose();
|
|
}
|
|
|
|
public virtual void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("chr_mode", ref chr_mode);
|
|
ser.Sync("prg_mode", ref prg_mode);
|
|
ser.Sync("reg_addr", ref reg_addr);
|
|
ser.Sync("chr_regs_1k", ref chr_regs_1k);
|
|
ser.Sync("prg_regs_8k", ref prg_regs_8k);
|
|
}
|
|
|
|
public virtual void WritePRG(int addr, byte value)
|
|
{
|
|
switch (addr & 0x6001)
|
|
{
|
|
case 0x0000: //$8000
|
|
chr_mode = (value >> 7) & 1;
|
|
chr_mode <<= 2;
|
|
prg_mode = (value >> 6) & 1;
|
|
prg_mode <<= 2;
|
|
reg_addr = (value & 7);
|
|
break;
|
|
case 0x0001: //$8001
|
|
switch (reg_addr)
|
|
{
|
|
case 0: chr_regs_1k[0] = (byte)(value & ~1); chr_regs_1k[1] = (byte)(value | 1); break;
|
|
case 1: chr_regs_1k[2] = (byte)(value & ~1); chr_regs_1k[3] = (byte)(value | 1); break;
|
|
case 2: chr_regs_1k[4] = value; break;
|
|
case 3: chr_regs_1k[5] = value; break;
|
|
case 4: chr_regs_1k[6] = value; break;
|
|
case 5: chr_regs_1k[7] = value; break;
|
|
case 6: prg_regs_8k[0] = value; prg_regs_8k[4 + 2] = value; break;
|
|
case 7: prg_regs_8k[1] = value; prg_regs_8k[4 + 1] = value; break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public int Get_PRGBank_8K(int addr)
|
|
{
|
|
int bank_8k = addr >> 13;
|
|
bank_8k += prg_mode;
|
|
bank_8k = prg_regs_8k[bank_8k];
|
|
return bank_8k;
|
|
}
|
|
|
|
public int Get_CHRBank_1K(int addr)
|
|
{
|
|
int bank_1k = addr >> 10;
|
|
bank_1k ^= chr_mode;
|
|
bank_1k = chr_regs_1k[bank_1k];
|
|
return bank_1k;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public class MMC3 : Namcot109
|
|
{
|
|
//state
|
|
public byte mirror;
|
|
int a12_old;
|
|
byte irq_reload, irq_counter;
|
|
protected bool irq_pending, irq_enable;
|
|
public bool wram_enable, wram_write_protect;
|
|
|
|
//it really seems like these should be the same but i cant seem to unify them.
|
|
//theres no sense in delaying the IRQ, so its logic must be tied to the separator.
|
|
//the hint, of course, is that the countdown value is the same.
|
|
//will someone else try to unify them?
|
|
int separator_counter;
|
|
int irq_countdown;
|
|
|
|
public NES.NESBoardBase.EMirrorType MirrorType { get { return mirror == 0 ? NES.NESBoardBase.EMirrorType.Vertical : NES.NESBoardBase.EMirrorType.Horizontal; } }
|
|
|
|
public MMC3(NES.NESBoardBase board, int num_prg_banks)
|
|
: base(board)
|
|
{
|
|
}
|
|
|
|
public override void SyncState(Serializer ser)
|
|
{
|
|
base.SyncState(ser);
|
|
ser.Sync("mirror", ref mirror);
|
|
ser.Sync("mirror", ref a12_old);
|
|
ser.Sync("irq_reload", ref irq_reload);
|
|
ser.Sync("irq_counter", ref irq_counter);
|
|
ser.Sync("irq_pending", ref irq_pending);
|
|
ser.Sync("irq_enable", ref irq_enable);
|
|
ser.Sync("separator_counter", ref separator_counter);
|
|
ser.Sync("irq_countdown", ref irq_countdown);
|
|
ser.Sync("wram_enable", ref wram_enable);
|
|
ser.Sync("wram_write_protect", ref wram_write_protect);
|
|
}
|
|
|
|
protected virtual void SyncIRQ()
|
|
{
|
|
board.NES.irq_cart = irq_pending;
|
|
}
|
|
|
|
public override void WritePRG(int addr, byte value)
|
|
{
|
|
switch (addr & 0x6001)
|
|
{
|
|
case 0x0000: //$8000
|
|
case 0x0001: //$8001
|
|
base.WritePRG(addr, value);
|
|
break;
|
|
case 0x2000: //$A000
|
|
//mirroring
|
|
mirror = (byte)(value & 1);
|
|
board.SetMirrorType(MirrorType);
|
|
break;
|
|
case 0x2001: //$A001
|
|
//wram enable/protect
|
|
wram_write_protect = value.Bit(6);
|
|
wram_enable = value.Bit(7);
|
|
Console.WriteLine("wram_write_protect={0},wram_enable={1}", wram_write_protect, wram_enable);
|
|
break;
|
|
case 0x4000: //$C000 - IRQ Reload value
|
|
irq_reload = value;
|
|
break;
|
|
case 0x4001: //$C001 - IRQ Clear
|
|
irq_counter = 0;
|
|
break;
|
|
case 0x6000: //$E000 - IRQ Acknowledge / Disable
|
|
irq_enable = false;
|
|
irq_pending = false;
|
|
SyncIRQ();
|
|
break;
|
|
case 0x6001: //$E001 - IRQ Enable
|
|
//board.NES.LogLine("irq en");
|
|
irq_enable = true;
|
|
SyncIRQ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void IRQ_EQ_Pass()
|
|
{
|
|
if (irq_enable)
|
|
{
|
|
//board.NES.LogLine("mmc3 IRQ");
|
|
irq_pending = true;
|
|
}
|
|
SyncIRQ();
|
|
}
|
|
|
|
void ClockIRQ()
|
|
{
|
|
if (irq_counter == 0)
|
|
{
|
|
irq_counter = irq_reload;
|
|
|
|
//TODO - MMC3 variant behaviour??? not sure
|
|
//was needed to pass 2-details.nes
|
|
if (irq_counter == 0)
|
|
IRQ_EQ_Pass();
|
|
}
|
|
else
|
|
{
|
|
irq_counter--;
|
|
if (irq_counter == 0)
|
|
{
|
|
IRQ_EQ_Pass();
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void ClockPPU()
|
|
{
|
|
if (separator_counter > 0)
|
|
separator_counter--;
|
|
|
|
if (irq_countdown > 0)
|
|
{
|
|
irq_countdown--;
|
|
if (irq_countdown == 0)
|
|
{
|
|
//board.NES.LogLine("ClockIRQ");
|
|
ClockIRQ();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public virtual void AddressPPU(int addr)
|
|
{
|
|
int a12 = (addr >> 12) & 1;
|
|
bool rising_edge = (a12 == 1 && a12_old == 0);
|
|
if (rising_edge)
|
|
{
|
|
if (separator_counter > 0)
|
|
{
|
|
separator_counter = 15;
|
|
}
|
|
else
|
|
{
|
|
separator_counter = 15;
|
|
irq_countdown = 15;
|
|
}
|
|
}
|
|
|
|
a12_old = a12;
|
|
}
|
|
|
|
}
|
|
|
|
public abstract class MMC3_Family_Board_Base : NES.NESBoardBase
|
|
{
|
|
protected Namcot109 mapper;
|
|
|
|
//configuration
|
|
protected int prg_mask, chr_mask;
|
|
|
|
public override void Dispose()
|
|
{
|
|
mapper.Dispose();
|
|
}
|
|
|
|
public override void SyncState(Serializer ser)
|
|
{
|
|
base.SyncState(ser);
|
|
mapper.SyncState(ser);
|
|
}
|
|
|
|
protected virtual int Get_CHRBank_1K(int addr)
|
|
{
|
|
return mapper.Get_CHRBank_1K(addr);
|
|
}
|
|
|
|
int MapCHR(int addr)
|
|
{
|
|
int bank_1k = Get_CHRBank_1K(addr);
|
|
bank_1k &= chr_mask;
|
|
addr = (bank_1k << 10) | (addr & 0x3FF);
|
|
return addr;
|
|
}
|
|
|
|
public override byte ReadPPU(int addr)
|
|
{
|
|
if (addr < 0x2000)
|
|
{
|
|
addr = MapCHR(addr);
|
|
if (VROM != null)
|
|
return VROM[addr];
|
|
else return VRAM[addr];
|
|
}
|
|
else return base.ReadPPU(addr);
|
|
}
|
|
|
|
public override void WritePPU(int addr, byte value)
|
|
{
|
|
if (addr < 0x2000)
|
|
{
|
|
if (VRAM == null) return;
|
|
addr = MapCHR(addr);
|
|
VRAM[addr] = value;
|
|
}
|
|
base.WritePPU(addr, value);
|
|
}
|
|
|
|
|
|
public override void WritePRG(int addr, byte value)
|
|
{
|
|
mapper.WritePRG(addr, value);
|
|
}
|
|
|
|
protected virtual int Get_PRGBank_8K(int addr)
|
|
{
|
|
return mapper.Get_PRGBank_8K(addr);
|
|
}
|
|
|
|
public override byte ReadPRG(int addr)
|
|
{
|
|
int bank_8k = Get_PRGBank_8K(addr);
|
|
bank_8k &= prg_mask;
|
|
addr = (bank_8k << 13) | (addr & 0x1FFF);
|
|
return ROM[addr];
|
|
}
|
|
|
|
protected virtual void BaseSetup()
|
|
{
|
|
//remember to setup the PRG banks -1 and -2
|
|
int num_prg_banks = Cart.prg_size / 8;
|
|
prg_mask = num_prg_banks - 1;
|
|
|
|
int num_chr_banks = (Cart.chr_size);
|
|
chr_mask = num_chr_banks - 1;
|
|
}
|
|
|
|
//used by a couple of boards for controlling nametable wiring with the mapper
|
|
protected int RewireNametable_Mapper095_and_TLSROM(int addr, int bitsel)
|
|
{
|
|
int bank_1k = mapper.Get_CHRBank_1K(addr & 0x1FFF);
|
|
int nt = (bank_1k >> bitsel) & 1;
|
|
int ofs = addr & 0x3FF;
|
|
addr = 0x2000 + (nt << 10);
|
|
addr |= (ofs);
|
|
return addr;
|
|
}
|
|
}
|
|
|
|
public abstract class MMC3Board_Base : MMC3_Family_Board_Base
|
|
{
|
|
//configuration
|
|
protected int wram_mask;
|
|
|
|
//state
|
|
protected MMC3 mmc3;
|
|
|
|
public override void AddressPPU(int addr)
|
|
{
|
|
mmc3.AddressPPU(addr);
|
|
}
|
|
|
|
public override void ClockPPU()
|
|
{
|
|
mmc3.ClockPPU();
|
|
}
|
|
|
|
protected override void BaseSetup()
|
|
{
|
|
wram_mask = (Cart.wram_size * 1024) - 1;
|
|
|
|
int num_prg_banks = Cart.prg_size / 8;
|
|
mapper = mmc3 = new MMC3(this,num_prg_banks);
|
|
|
|
base.BaseSetup();
|
|
SetMirrorType(EMirrorType.Vertical);
|
|
}
|
|
}
|
|
|
|
public abstract class Namcot109Board_Base : MMC3_Family_Board_Base
|
|
{
|
|
protected override void BaseSetup()
|
|
{
|
|
mapper = new Namcot109(this);
|
|
base.BaseSetup();
|
|
}
|
|
}
|
|
|
|
} |