BizHawk/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/VRC6.cs

391 lines
8.6 KiB
C#

using System;
using BizHawk.Common;
using BizHawk.Emulation.Common.Components;
namespace BizHawk.Emulation.Cores.Nintendo.NES
{
//mapper 24 + 26
//If you change any of the IRQ logic here, be sure to change it in VRC 4/7 as well.
public sealed class VRC6 : NES.NESBoardBase
{
#region CHRLUT
// what did i do in a previous life to deserve this?
// given the bottom four bits of $b003, and a 1K address region in PPU $0000:$3fff,
static byte[] Banks = new byte[16 * 16]; // which of the 8 chr regs is used to determine the bank here?
static byte[] Masks = new byte[16 * 16]; // what is the resulting 8 bit chr reg value ANDed with?
static byte[] A10s = new byte[16 * 16]; // and then what is it ORed with?
static byte[] PTables = new byte[]
{
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x80,0xc0,0x81,0xc1,0x82,0xc2,0x83,0xc3,
0x00,0x01,0x02,0x03,0x84,0xc4,0x85,0xc5,
};
static void GetBankByte(int b003, int banknum, out byte bank, out byte mask, out byte a10)
{
if (banknum < 8) // pattern tables
{
int ptidx = b003 & 3;
if (ptidx == 3) ptidx--;
byte pt = PTables[ptidx * 8 + banknum];
bank = (byte)(pt & 7);
mask = (byte)(pt.Bit(7) ? 0xfe : 0xff);
a10 = (byte)(pt.Bit(7) && pt.Bit(6) ? 1 : 0);
}
else // nametables
{
banknum &= 3;
switch (b003 & 7)
{
case 0:
case 6:
case 7: // H-mirror, 6677
bank = (byte)(banknum >> 1 | 6);
break;
case 2:
case 3:
case 4: // V-mirror, 6767
bank = (byte)(banknum | 6);
break;
case 1:
case 5: // 4 screen, 4567
default:
bank = (byte)(banknum | 4);
break;
}
switch (b003)
{
case 0:
case 7: // V-mirror
mask = 0xfe;
a10 = (byte)(banknum & 1);
break;
case 3:
case 4: // H-mirror
mask = 0xfe;
a10 = (byte)(banknum >> 1);
break;
case 8:
case 15: // 1scA
mask = 0xfe;
a10 = 0;
break;
case 11:
case 12: // 1scB
mask = 0xfe;
a10 = 1;
break;
default: // no replacement
mask = 0xff;
a10 = 0;
break;
}
}
}
static VRC6()
{
int idx = 0;
byte bank, mask, a10;
for (int b003 = 0; b003 < 16; b003++)
{
for (int banknum = 0; banknum < 16; banknum++)
{
GetBankByte(b003, banknum, out bank, out mask, out a10);
Banks[idx] = bank;
Masks[idx] = mask;
A10s[idx] = a10;
idx++;
}
}
}
#endregion
//configuration
int prg_bank_mask_8k, chr_bank_mask_1k;
int chr_byte_mask;
bool newer_variant;
VRC6Alt VRC6Sound;
//state
int prg_bank_16k, prg_bank_8k;
ByteBuffer prg_banks_8k = new ByteBuffer(4);
ByteBuffer chr_banks_1k = new ByteBuffer(8);
bool irq_mode;
bool irq_enabled, irq_pending, irq_autoen;
byte irq_reload;
byte irq_counter;
int irq_prescaler;
bool chrA10replace;
bool NTROM;
int PPUBankingMode;
public override void Dispose()
{
base.Dispose();
prg_banks_8k.Dispose();
chr_banks_1k.Dispose();
}
public override void SyncState(Serializer ser)
{
base.SyncState(ser);
VRC6Sound.SyncState(ser);
ser.Sync("prg_bank_16k", ref prg_bank_16k);
ser.Sync("prg_bank_8k", ref prg_bank_8k);
ser.Sync("chr_banks_1k", ref chr_banks_1k);
ser.Sync("irq_mode", ref irq_mode);
ser.Sync("irq_enabled", ref irq_enabled);
ser.Sync("irq_pending", ref irq_pending);
ser.Sync("irq_autoen", ref irq_autoen);
ser.Sync("irq_reload", ref irq_reload);
ser.Sync("irq_counter", ref irq_counter);
ser.Sync("irq_prescaler", ref irq_prescaler);
ser.Sync("chrA10replace", ref chrA10replace);
ser.Sync("NTROM", ref NTROM);
ser.Sync("PPUBankingMode", ref PPUBankingMode);
SyncPRG();
SyncIRQ();
}
void SyncPRG()
{
prg_banks_8k[0] = (byte)(prg_bank_16k * 2);
prg_banks_8k[1] = (byte)(prg_bank_16k * 2 + 1);
prg_banks_8k[2] = (byte)(prg_bank_8k);
prg_banks_8k[3] = 0xFF;
}
void SyncIRQ()
{
IRQSignal = (irq_pending && irq_enabled);
}
public override bool Configure(NES.EDetectionOrigin origin)
{
switch (Cart.board_type)
{
case "MAPPER024":
newer_variant = false;
break;
case "MAPPER026":
newer_variant = true;
break;
case "KONAMI-VRC-6":
if (Cart.pcb == "351951")
newer_variant = false;
else if (Cart.pcb == "351949A")
newer_variant = true;
else throw new Exception("Unknown PCB type for VRC6");
AssertPrg(256); AssertChr(128, 256);
break;
default:
return false;
}
AssertVram(0); AssertWram(0, 8);
prg_bank_mask_8k = Cart.prg_size / 8 - 1;
chr_bank_mask_1k = Cart.chr_size - 1;
chr_byte_mask = Cart.chr_size * 1024 - 1;
prg_bank_16k = 0;
prg_bank_8k = 0;
SyncPRG();
if (NES.apu != null) // don't start up sound when in configurator
VRC6Sound = new VRC6Alt((uint)NES.cpuclockrate, NES.apu.ExternalQueue);
return true;
}
public override byte ReadPRG(int addr)
{
int bank_8k = addr >> 13;
int ofs = addr & ((1 << 13) - 1);
bank_8k = prg_banks_8k[bank_8k];
bank_8k &= prg_bank_mask_8k;
addr = (bank_8k << 13) | ofs;
return ROM[addr];
}
int MapPPU(int addr)
{
int lutidx = addr >> 10 | PPUBankingMode << 4;
int bank = chr_banks_1k[Banks[lutidx]];
if (chrA10replace)
{
bank &= Masks[lutidx];
bank |= A10s[lutidx];
}
return addr & 0x3ff | bank << 10;
}
public override byte ReadPPU(int addr)
{
if (addr >= 0x2000 && !NTROM)
return NES.CIRAM[MapPPU(addr) & 0x7ff];
else
return VROM[MapPPU(addr) & chr_byte_mask];
}
public override void WritePPU(int addr, byte value)
{
if (addr >= 0x2000 && !NTROM)
NES.CIRAM[MapPPU(addr) & 0x7ff] = value;
}
public override void WritePRG(int addr, byte value)
{
if (newer_variant)
{
addr = (addr & 0xFFFC) | ((addr >> 1) & 1) | ((addr << 1) & 2);
}
switch (addr)
{
case 0x0000: //$8000
case 0x0001:
case 0x0002:
case 0x0003:
prg_bank_16k = value;
SyncPRG();
break;
case 0x1000: //$9000
VRC6Sound.Write9000(value);
break;
case 0x1001: //$9001
VRC6Sound.Write9001(value);
break;
case 0x1002: //$9002
VRC6Sound.Write9002(value);
break;
case 0x1003: //$9003
VRC6Sound.Write9003(value);
break;
case 0x2000: //$A000
VRC6Sound.WriteA000(value);
break;
case 0x2001: //$A001
VRC6Sound.WriteA001(value);
break;
case 0x2002: //$A002
VRC6Sound.WriteA002(value);
break;
case 0x3000: //$B000
VRC6Sound.WriteB000(value);
break;
case 0x3001: //$B001
VRC6Sound.WriteB001(value);
break;
case 0x3002: //$B002
VRC6Sound.WriteB002(value);
break;
case 0x3003: //$B003
PPUBankingMode = value & 15;
NTROM = value.Bit(4);
chrA10replace = value.Bit(5);
break;
case 0x4000: //$C000
case 0x4001:
case 0x4002:
case 0x4003:
prg_bank_8k = value;
SyncPRG();
break;
case 0x5000: //$D000
case 0x5001: //$D001
case 0x5002: //$D002
case 0x5003: //$D003
chr_banks_1k[addr - 0x5000] = value;
break;
case 0x6000: //$E000
case 0x6001: //$E001
case 0x6002: //$E002
case 0x6003: //$E003
chr_banks_1k[4 + addr - 0x6000] = value;
break;
case 0x7000: //$F000 (reload)
irq_reload = value;
break;
case 0x7001: //$F001 (control)
irq_mode = value.Bit(2);
irq_autoen = value.Bit(0);
if (value.Bit(1))
{
//enabled
irq_enabled = true;
irq_counter = irq_reload;
irq_prescaler = 341;
}
else
{
//disabled
irq_enabled = false;
}
//acknowledge
irq_pending = false;
SyncIRQ();
break;
case 0x7002: //$F002 (ack)
irq_pending = false;
irq_enabled = irq_autoen;
SyncIRQ();
break;
}
}
void ClockIRQ()
{
if (irq_counter == 0xFF)
{
irq_pending = true;
irq_counter = irq_reload;
SyncIRQ();
}
else
irq_counter++;
}
public override void ClockCPU()
{
VRC6Sound.Clock();
if (!irq_enabled) return;
if (irq_mode)
{
ClockIRQ();
}
else
{
irq_prescaler -= 3;
if (irq_prescaler <= 0)
{
irq_prescaler += 341;
ClockIRQ();
}
}
}
}
}