322 lines
8.2 KiB
C#
322 lines
8.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
using BizHawk.Common;
|
|
using BizHawk.Emulation.Common.Components;
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.NES
|
|
{
|
|
//mapper 85
|
|
//If you change any of the IRQ logic here, be sure to change it in VRC 2/3/4/6 as well.
|
|
public sealed class VRC7 : NES.NESBoardBase
|
|
{
|
|
//configuration
|
|
int prg_bank_mask_8k, chr_bank_mask_1k;
|
|
Func<int, int> remap;
|
|
|
|
//state
|
|
YM2413 fm; //= new Sound.YM2413(Sound.YM2413.ChipType.VRC7);
|
|
|
|
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;
|
|
|
|
public override void Dispose()
|
|
{
|
|
base.Dispose();
|
|
prg_banks_8k.Dispose();
|
|
chr_banks_1k.Dispose();
|
|
}
|
|
|
|
public override void SyncState(Serializer ser)
|
|
{
|
|
base.SyncState(ser);
|
|
if (fm != null)
|
|
fm.SyncState(ser);
|
|
ser.Sync("prg_banks_8k", ref prg_banks_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);
|
|
SyncIRQ();
|
|
}
|
|
|
|
void SyncIRQ()
|
|
{
|
|
IRQSignal = (irq_pending && irq_enabled);
|
|
}
|
|
|
|
|
|
static int RemapM117(int addr)
|
|
{
|
|
//addr &= 0x7007; // i don't know all of which bits are decoded, but this breaks stuff
|
|
switch (addr)
|
|
{
|
|
//prg
|
|
case 0x0000: return 0x0000;
|
|
case 0x0001: return 0x0001;
|
|
case 0x0002: return 0x1000;
|
|
//chr
|
|
case 0x2000: return 0x2000;
|
|
case 0x2001: return 0x2001;
|
|
case 0x2002: return 0x3000;
|
|
case 0x2003: return 0x3001;
|
|
case 0x2004: return 0x4000;
|
|
case 0x2005: return 0x4001;
|
|
case 0x2006: return 0x5000;
|
|
case 0x2007: return 0x5001;
|
|
//irq
|
|
// fake addressees to activate different irq handling logic
|
|
case 0x4001: return 0x10001;
|
|
case 0x4002: return 0x10002;
|
|
case 0x4003: return 0x10003;
|
|
case 0x6000: return 0x10004;
|
|
//mir
|
|
case 0x5000: return 0x6000;
|
|
|
|
//probably nothing at all
|
|
default: return 0xffff;
|
|
}
|
|
}
|
|
|
|
public override bool Configure(NES.EDetectionOrigin origin)
|
|
{
|
|
switch (Cart.board_type)
|
|
{
|
|
case "MAPPER085":
|
|
// presumably the only reason a homebrew would use mapper085 is for the sound?
|
|
// so initialize like lagrange point
|
|
remap = (addr) => ((addr & 0xF000) | ((addr & 0x30) >> 4));
|
|
fm = new YM2413(YM2413.ChipType.VRC7);
|
|
break;
|
|
case "KONAMI-VRC-7":
|
|
AssertPrg(128, 512); AssertChr(0, 128); AssertVram(0, 8); AssertWram(0, 8);
|
|
if (Cart.pcb == "353429")
|
|
{
|
|
//tiny toons 2
|
|
remap = (addr) => ((addr & 0xF000) | ((addr & 0x8) >> 3));
|
|
// there is no resonator or crystal on the board for the fm chip
|
|
fm = null;
|
|
}
|
|
else if (Cart.pcb == "352402")
|
|
{
|
|
//lagrange point
|
|
remap = (addr) => ((addr & 0xF000) | ((addr & 0x30) >> 4));
|
|
fm = new YM2413(YM2413.ChipType.VRC7);
|
|
}
|
|
else
|
|
throw new Exception("Unknown PCB type for VRC7");
|
|
break;
|
|
case "MAPPER117":
|
|
// not sure quite what this is
|
|
// different address mapping, and somewhat different irq logic
|
|
Cart.vram_size = 0;
|
|
Cart.wram_size = 0;
|
|
remap = RemapM117;
|
|
fm = null;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
prg_bank_mask_8k = Cart.prg_size / 8 - 1;
|
|
chr_bank_mask_1k = Cart.chr_size - 1;
|
|
|
|
SetMirrorType(EMirrorType.Vertical);
|
|
|
|
prg_banks_8k[3] = (byte)(0xFF & prg_bank_mask_8k);
|
|
|
|
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];
|
|
addr = (bank_8k << 13) | ofs;
|
|
return ROM[addr];
|
|
}
|
|
|
|
int Map_PPU(int addr)
|
|
{
|
|
int bank_1k = addr >> 10;
|
|
int ofs = addr & ((1 << 10) - 1);
|
|
bank_1k = chr_banks_1k[bank_1k];
|
|
addr = (bank_1k << 10) | ofs;
|
|
return addr;
|
|
}
|
|
|
|
public override byte ReadPPU(int addr)
|
|
{
|
|
if (addr < 0x2000)
|
|
{
|
|
addr = Map_PPU(addr);
|
|
if (Cart.vram_size != 0)
|
|
return base.ReadPPU(addr);
|
|
else return VROM[addr];
|
|
}
|
|
else return base.ReadPPU(addr);
|
|
}
|
|
|
|
public override void WritePPU(int addr, byte value)
|
|
{
|
|
if (addr < 0x2000)
|
|
{
|
|
base.WritePPU(Map_PPU(addr),value);
|
|
}
|
|
else base.WritePPU(addr, value);
|
|
}
|
|
|
|
public override void ApplyCustomAudio(short[] samples)
|
|
{
|
|
if (fm != null)
|
|
{
|
|
short[] fmsamples = new short[samples.Length];
|
|
fm.GetSamples(fmsamples);
|
|
//naive mixing. need to study more
|
|
int len = samples.Length;
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
short fmsamp = fmsamples[i];
|
|
samples[i] = (short)(samples[i] + fmsamp);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void WritePRG(int addr, byte value)
|
|
{
|
|
//Console.WriteLine(" mapping {0:X4} = {1:X2}", addr, value);
|
|
addr = remap(addr);
|
|
//Console.WriteLine("- remapping {0:X4} = {1:X2}", addr, value);
|
|
switch (addr)
|
|
{
|
|
case 0x0000: prg_banks_8k[0] = (byte)(value & prg_bank_mask_8k); break;
|
|
case 0x0001: prg_banks_8k[1] = (byte)(value & prg_bank_mask_8k); break;
|
|
case 0x1000: prg_banks_8k[2] = (byte)(value & prg_bank_mask_8k); break;
|
|
|
|
case 0x1001:
|
|
//sound address port
|
|
if (fm != null)
|
|
fm.RegisterLatch = value;
|
|
break;
|
|
case 0x1003:
|
|
//sound data port
|
|
if (fm != null)
|
|
fm.Write(value);
|
|
break;
|
|
|
|
//a bit creepy to mask this for lagrange point which has no VROM, but the mask will be 0xFFFFFFFF so its OK
|
|
case 0x2000: chr_banks_1k[0] = (byte)(value & chr_bank_mask_1k); break;
|
|
case 0x2001: chr_banks_1k[1] = (byte)(value & chr_bank_mask_1k); break;
|
|
case 0x3000: chr_banks_1k[2] = (byte)(value & chr_bank_mask_1k); break;
|
|
case 0x3001: chr_banks_1k[3] = (byte)(value & chr_bank_mask_1k); break;
|
|
case 0x4000: chr_banks_1k[4] = (byte)(value & chr_bank_mask_1k); break;
|
|
case 0x4001: chr_banks_1k[5] = (byte)(value & chr_bank_mask_1k); break;
|
|
case 0x5000: chr_banks_1k[6] = (byte)(value & chr_bank_mask_1k); break;
|
|
case 0x5001: chr_banks_1k[7] = (byte)(value & chr_bank_mask_1k); break;
|
|
|
|
case 0x6000:
|
|
switch (value & 3)
|
|
{
|
|
case 0: SetMirrorType(NES.NESBoardBase.EMirrorType.Vertical); break;
|
|
case 1: SetMirrorType(NES.NESBoardBase.EMirrorType.Horizontal); break;
|
|
case 2: SetMirrorType(NES.NESBoardBase.EMirrorType.OneScreenA); break;
|
|
case 3: SetMirrorType(NES.NESBoardBase.EMirrorType.OneScreenB); break;
|
|
}
|
|
break;
|
|
|
|
case 0x6001: //(reload)
|
|
irq_reload = value;
|
|
break;
|
|
case 0x7000: //(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 0x7001: //(ack)
|
|
irq_pending = false;
|
|
irq_enabled = irq_autoen;
|
|
SyncIRQ();
|
|
break;
|
|
|
|
// special irq logic for M117
|
|
// RemapM117() sends some addresses to these "virtual addresses" for irq handling
|
|
case 0x10001:
|
|
irq_reload = (byte)(237 - value); // what
|
|
break;
|
|
case 0x10002:
|
|
irq_pending = false;
|
|
SyncIRQ();
|
|
break;
|
|
case 0x10003:
|
|
irq_counter = irq_reload;
|
|
break;
|
|
case 0x10004:
|
|
irq_enabled = value.Bit(0);
|
|
irq_pending = false;
|
|
SyncIRQ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClockIRQ()
|
|
{
|
|
if (irq_counter == 0xFF)
|
|
{
|
|
irq_pending = true;
|
|
irq_counter = irq_reload;
|
|
SyncIRQ();
|
|
}
|
|
else
|
|
irq_counter++;
|
|
}
|
|
|
|
public override void ClockCPU()
|
|
{
|
|
if (!irq_enabled) return;
|
|
|
|
if (irq_mode)
|
|
{
|
|
ClockIRQ();
|
|
//throw new InvalidOperationException("needed a test case for this; you found one!");
|
|
}
|
|
else
|
|
{
|
|
irq_prescaler -= 3;
|
|
if (irq_prescaler <= 0)
|
|
{
|
|
irq_prescaler += 341;
|
|
ClockIRQ();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
} |