297 lines
6.7 KiB
C#
297 lines
6.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using BizHawk.Common;
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.NES
|
|
{
|
|
/// <summary>
|
|
/// These are used by SetMirroring() to provide the base class nametable mirroring service.
|
|
/// Apparently, these are not used for internal build configuration logic
|
|
/// </summary>
|
|
internal enum EMirrorType
|
|
{
|
|
Vertical, Horizontal, OneScreenA, OneScreenB
|
|
}
|
|
|
|
[NesBoardImpl]
|
|
internal abstract class NesBoardBase : INesBoard
|
|
{
|
|
public virtual void Create(NES nes)
|
|
{
|
|
NES = nes;
|
|
}
|
|
|
|
public virtual void NesSoftReset()
|
|
{
|
|
}
|
|
|
|
public Dictionary<string, string> InitialRegisterValues { get; set; }
|
|
|
|
public abstract bool Configure(EDetectionOrigin origin);
|
|
public virtual void ClockPpu() { }
|
|
public virtual void ClockCpu() { }
|
|
public virtual void AtVsyncNmi() { }
|
|
|
|
public CartInfo Cart => NES.cart;
|
|
public NES NES { get; set; }
|
|
|
|
//this is set to true when SyncState is called, so that we know the base class SyncState was used
|
|
public bool SyncStateFlag;
|
|
|
|
public virtual void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync(nameof(_vram), ref _vram, true);
|
|
ser.Sync(nameof(_wram), ref _wram, true);
|
|
for (int i = 0; i < 4; i++) ser.Sync("mirroring" + i, ref _mirroring[i]);
|
|
ser.Sync(nameof(_irqSignal), ref _irqSignal);
|
|
SyncStateFlag = true;
|
|
}
|
|
|
|
public virtual void SyncIRQ(bool flag)
|
|
{
|
|
IrqSignal = flag;
|
|
}
|
|
|
|
private bool _irqSignal;
|
|
public bool IrqSignal
|
|
{
|
|
get => _irqSignal;
|
|
set => _irqSignal = value;
|
|
}
|
|
|
|
private readonly int[] _mirroring = new int[4];
|
|
protected void SetMirroring(int a, int b, int c, int d)
|
|
{
|
|
_mirroring[0] = a;
|
|
_mirroring[1] = b;
|
|
_mirroring[2] = c;
|
|
_mirroring[3] = d;
|
|
}
|
|
|
|
protected void ApplyMemoryMapMask(int mask, byte[] map)
|
|
{
|
|
byte byteMask = (byte)mask;
|
|
for (int i = 0; i < map.Length; i++)
|
|
{
|
|
map[i] &= byteMask;
|
|
}
|
|
}
|
|
|
|
// make sure you have bank-masked the map
|
|
protected int ApplyMemoryMap(int blockSizeBits, byte[] map, int addr)
|
|
{
|
|
int bank = addr >> blockSizeBits;
|
|
int ofs = addr & ((1 << blockSizeBits) - 1);
|
|
bank = map[bank];
|
|
addr = (bank << blockSizeBits) | ofs;
|
|
return addr;
|
|
}
|
|
|
|
public static EMirrorType CalculateMirrorType(int pad_h, int pad_v)
|
|
{
|
|
if (pad_h == 0)
|
|
{
|
|
return pad_v == 0
|
|
? EMirrorType.OneScreenA
|
|
: EMirrorType.Horizontal;
|
|
}
|
|
|
|
if (pad_v == 0)
|
|
{
|
|
return EMirrorType.Vertical;
|
|
}
|
|
|
|
return EMirrorType.OneScreenB;
|
|
}
|
|
|
|
protected void SetMirrorType(int pad_h, int pad_v)
|
|
{
|
|
SetMirrorType(CalculateMirrorType(pad_h, pad_v));
|
|
}
|
|
|
|
public void SetMirrorType(EMirrorType mirrorType)
|
|
{
|
|
switch (mirrorType)
|
|
{
|
|
case EMirrorType.Horizontal: SetMirroring(0, 0, 1, 1); break;
|
|
case EMirrorType.Vertical: SetMirroring(0, 1, 0, 1); break;
|
|
case EMirrorType.OneScreenA: SetMirroring(0, 0, 0, 0); break;
|
|
case EMirrorType.OneScreenB: SetMirroring(1, 1, 1, 1); break;
|
|
default: SetMirroring(-1, -1, -1, -1); break; //crash!
|
|
}
|
|
}
|
|
|
|
protected int ApplyMirroring(int addr)
|
|
{
|
|
int block = (addr >> 10) & 3;
|
|
block = _mirroring[block];
|
|
int ofs = addr & 0x3FF;
|
|
return (block << 10) | ofs;
|
|
}
|
|
|
|
protected byte HandleNormalPRGConflict(int addr, byte value)
|
|
{
|
|
value &= ReadPrg(addr);
|
|
|
|
//Debug.Assert(old_value == value, "Found a test case of bus conflict. please report.");
|
|
//report: pinball quest (J). also: double dare
|
|
return value;
|
|
}
|
|
|
|
public virtual byte ReadPrg(int addr) => Rom[addr];
|
|
|
|
public virtual void WritePrg(int addr, byte value)
|
|
{
|
|
}
|
|
|
|
public virtual void WriteWram(int addr, byte value)
|
|
{
|
|
if (_wram != null)
|
|
{
|
|
_wram[addr & _wramMask] = value;
|
|
}
|
|
}
|
|
|
|
private int _wramMask;
|
|
public virtual void PostConfigure()
|
|
{
|
|
_wramMask = (Cart.WramSize * 1024) - 1;
|
|
}
|
|
|
|
public virtual byte ReadWram(int addr)
|
|
{
|
|
return _wram?[addr & _wramMask] ?? NES.DB;
|
|
}
|
|
|
|
public virtual void WriteExp(int addr, byte value)
|
|
{
|
|
}
|
|
|
|
public virtual byte ReadExp(int addr)
|
|
{
|
|
return NES.DB;
|
|
}
|
|
|
|
public virtual byte ReadReg2xxx(int addr)
|
|
{
|
|
return NES.ppu.ReadReg(addr & 7);
|
|
}
|
|
|
|
public virtual byte PeekReg2xxx(int addr)
|
|
{
|
|
return NES.ppu.PeekReg(addr & 7);
|
|
}
|
|
|
|
public virtual void WriteReg2xxx(int addr, byte value)
|
|
{
|
|
NES.ppu.WriteReg(addr, value);
|
|
}
|
|
|
|
public virtual void WritePpu(int addr, byte value)
|
|
{
|
|
if (addr < 0x2000)
|
|
{
|
|
if (Vram != null)
|
|
{
|
|
Vram[addr] = value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NES.CIRAM[ApplyMirroring(addr)] = value;
|
|
}
|
|
}
|
|
|
|
public virtual void AddressPpu(int addr)
|
|
{
|
|
}
|
|
|
|
public virtual byte PeekPPU(int addr) => ReadPpu(addr);
|
|
|
|
protected virtual byte ReadPPUChr(int addr)
|
|
{
|
|
return Vrom?[addr] ?? Vram[addr];
|
|
}
|
|
|
|
public virtual byte ReadPpu(int addr)
|
|
{
|
|
if (addr < 0x2000)
|
|
{
|
|
return Vrom?[addr] ?? Vram[addr];
|
|
}
|
|
|
|
return NES.CIRAM[ApplyMirroring(addr)];
|
|
}
|
|
|
|
/// <summary>
|
|
/// derived classes should override this if they have peek-unsafe logic
|
|
/// </summary>
|
|
public virtual byte PeekCart(int addr)
|
|
{
|
|
byte ret;
|
|
if (addr >= 0x8000)
|
|
{
|
|
ret = ReadPrg(addr - 0x8000); // easy optimization, since rom reads are so common, move this up (reordering the rest of these else ifs is not easy)
|
|
}
|
|
else if (addr < 0x6000)
|
|
{
|
|
ret = ReadExp(addr - 0x4000);
|
|
}
|
|
else
|
|
{
|
|
ret = ReadWram(addr - 0x6000);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public virtual byte[] SaveRam => Cart.WramBattery ? Wram : null;
|
|
|
|
public byte[] Wram
|
|
{
|
|
get => _wram;
|
|
set => _wram = value;
|
|
}
|
|
public byte[] Vram
|
|
{
|
|
get => _vram;
|
|
set => _vram = value;
|
|
}
|
|
public byte[] Rom { get; set; }
|
|
public byte[] Vrom { get; set; }
|
|
|
|
private byte[] _wram, _vram;
|
|
|
|
protected void Assert(bool test, string comment, params object[] args)
|
|
{
|
|
if (!test) throw new Exception(string.Format(comment, args));
|
|
}
|
|
|
|
protected void Assert(bool test)
|
|
{
|
|
if (!test) throw new Exception("assertion failed in board setup!");
|
|
}
|
|
|
|
protected void AssertPrg(params int[] prg) => AssertMemType(Cart.PrgSize, "prg", prg);
|
|
protected void AssertChr(params int[] chr) => AssertMemType(Cart.ChrSize, "chr", chr);
|
|
protected void AssertWram(params int[] wram) => AssertMemType(Cart.WramSize, "wram", wram);
|
|
protected void AssertVram(params int[] vram) => AssertMemType(Cart.VramSize, "vram", vram);
|
|
|
|
protected void AssertMemType(int value, string name, int[] valid)
|
|
{
|
|
// only disable vram and wram asserts, as UNIF knows its prg and chr sizes
|
|
if (DisableConfigAsserts && (name == "wram" || name == "vram")) return;
|
|
foreach (int i in valid) if (value == i) return;
|
|
Assert(false, "unhandled {0} size of {1}", name,value);
|
|
}
|
|
|
|
protected void AssertBattery(bool hasBattery) => Assert(Cart.WramBattery == hasBattery);
|
|
|
|
public virtual void ApplyCustomAudio(short[] samples)
|
|
{
|
|
}
|
|
|
|
public bool DisableConfigAsserts { get; set; }
|
|
}
|
|
}
|