408 lines
9.4 KiB
C#
408 lines
9.4 KiB
C#
using System;
|
|
|
|
using BizHawk.Common.NumberExtensions;
|
|
using BizHawk.Common.BufferExtensions;
|
|
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Emulation.Cores.Components.M6502;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|
{
|
|
public partial class Atari2600
|
|
{
|
|
private readonly GameInfo _game;
|
|
|
|
public TIA _tia;
|
|
public M6532 _m6532;
|
|
private DCFilter _dcfilter;
|
|
private MapperBase _mapper;
|
|
private byte[] _ram;
|
|
|
|
private IController _controller = NullController.Instance;
|
|
private int _frame;
|
|
private int _lastAddress;
|
|
|
|
private bool _leftDifficultySwitchPressed;
|
|
private bool _rightDifficultySwitchPressed;
|
|
|
|
private bool _leftDifficultySwitchHeld;
|
|
private bool _rightDifficultySwitchHeld;
|
|
|
|
internal MOS6502X<CpuLink> Cpu { get; private set; }
|
|
internal byte[] Ram => _ram;
|
|
internal byte[] Rom { get; }
|
|
internal int DistinctAccessCount { get; private set; }
|
|
|
|
public bool SP_FRAME = false;
|
|
public bool SP_RESET = false;
|
|
public bool unselect_reset;
|
|
|
|
internal struct CpuLink : IMOS6502XLink
|
|
{
|
|
private readonly Atari2600 _atari2600;
|
|
|
|
public CpuLink(Atari2600 atari2600)
|
|
{
|
|
_atari2600 = atari2600;
|
|
}
|
|
|
|
public byte DummyReadMemory(ushort address) => _atari2600.ReadMemory(address);
|
|
|
|
public void OnExecFetch(ushort address) => _atari2600.ExecFetch(address);
|
|
|
|
public byte PeekMemory(ushort address) => _atari2600.ReadMemory(address);
|
|
|
|
public byte ReadMemory(ushort address) => _atari2600.ReadMemory(address);
|
|
|
|
public void WriteMemory(ushort address, byte value) => _atari2600.WriteMemory(address, value);
|
|
}
|
|
|
|
// keeps track of tia cycles, 3 cycles per CPU cycle
|
|
private int cyc_counter;
|
|
|
|
internal byte BaseReadMemory(ushort addr)
|
|
{
|
|
addr = (ushort)(addr & 0x1FFF);
|
|
if ((addr & 0x1080) == 0)
|
|
{
|
|
return _tia.ReadMemory(addr, false);
|
|
}
|
|
|
|
if ((addr & 0x1080) == 0x0080)
|
|
{
|
|
_tia.BusState = _m6532.ReadMemory(addr, false);
|
|
return _m6532.ReadMemory(addr, false);
|
|
}
|
|
|
|
_tia.BusState = Rom[addr & 0x0FFF];
|
|
return Rom[addr & 0x0FFF];
|
|
}
|
|
|
|
internal byte BasePeekMemory(ushort addr)
|
|
{
|
|
addr = (ushort)(addr & 0x1FFF);
|
|
if ((addr & 0x1080) == 0)
|
|
{
|
|
return _tia.ReadMemory(addr, true);
|
|
}
|
|
|
|
if ((addr & 0x1080) == 0x0080)
|
|
{
|
|
return _m6532.ReadMemory(addr, true);
|
|
}
|
|
|
|
return Rom[addr & 0x0FFF];
|
|
}
|
|
|
|
internal void BaseWriteMemory(ushort addr, byte value)
|
|
{
|
|
_tia.BusState = value;
|
|
if (addr != _lastAddress)
|
|
{
|
|
DistinctAccessCount++;
|
|
_lastAddress = addr;
|
|
}
|
|
|
|
addr = (ushort)(addr & 0x1FFF);
|
|
if ((addr & 0x1080) == 0)
|
|
{
|
|
_tia.WriteMemory(addr, value, false);
|
|
}
|
|
else if ((addr & 0x1080) == 0x0080)
|
|
{
|
|
_m6532.WriteMemory(addr, value);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("ROM write(?): " + addr.ToString("x"));
|
|
}
|
|
}
|
|
|
|
internal void BasePokeMemory(ushort addr, byte value)
|
|
{
|
|
addr = (ushort)(addr & 0x1FFF);
|
|
if ((addr & 0x1080) == 0)
|
|
{
|
|
_tia.WriteMemory(addr, value, true);
|
|
}
|
|
else if ((addr & 0x1080) == 0x0080)
|
|
{
|
|
_m6532.WriteMemory(addr, value);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("ROM write(?): " + addr.ToString("x"));
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private byte ReadMemory(ushort addr)
|
|
{
|
|
if (addr != _lastAddress)
|
|
{
|
|
DistinctAccessCount++;
|
|
_lastAddress = addr;
|
|
}
|
|
|
|
_mapper.Bit13 = addr.Bit(13);
|
|
var temp = _mapper.ReadMemory((ushort)(addr & 0x1FFF));
|
|
_tia.BusState = temp;
|
|
|
|
if (MemoryCallbacks.HasReads)
|
|
{
|
|
var flags = (uint)(MemoryCallbackFlags.AccessRead);
|
|
MemoryCallbacks.CallMemoryCallbacks(addr, 0, flags, "System Bus");
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void WriteMemory(ushort addr, byte value)
|
|
{
|
|
if (addr != _lastAddress)
|
|
{
|
|
DistinctAccessCount++;
|
|
_lastAddress = addr;
|
|
}
|
|
|
|
_mapper.WriteMemory((ushort)(addr & 0x1FFF), value);
|
|
|
|
if (MemoryCallbacks.HasWrites)
|
|
{
|
|
var flags = (uint)(MemoryCallbackFlags.AccessWrite);
|
|
MemoryCallbacks.CallMemoryCallbacks(addr, value, flags, "System Bus");
|
|
}
|
|
}
|
|
|
|
internal void PokeMemory(ushort addr, byte value)
|
|
{
|
|
_mapper.PokeMemory((ushort)(addr & 0x1FFF), value);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void ExecFetch(ushort addr)
|
|
{
|
|
if (MemoryCallbacks.HasExecutes)
|
|
{
|
|
var flags = (uint)(MemoryCallbackFlags.AccessExecute);
|
|
MemoryCallbacks.CallMemoryCallbacks(addr, 0, flags, "System Bus");
|
|
}
|
|
}
|
|
|
|
private void RebootCore()
|
|
{
|
|
// Regenerate mapper here to make sure its state is entirely clean
|
|
_mapper = CreateMapper(this, _game.GetOptionsDict()["m"], Rom.Length);
|
|
_lagCount = 0;
|
|
Cpu = new MOS6502X<CpuLink>(new CpuLink(this));
|
|
|
|
if (_game["PAL"])
|
|
{
|
|
_pal = true;
|
|
}
|
|
else if (_game["NTSC"])
|
|
{
|
|
_pal = false;
|
|
}
|
|
else
|
|
{
|
|
_pal = DetectPal(_game, Rom);
|
|
}
|
|
|
|
// dcfilter coefficent is from real observed hardware behavior: a latched "1" will fully decay by ~170 or so tia sound cycles
|
|
_tia = new TIA(this, _pal, Settings.SECAMColors);
|
|
|
|
_dcfilter = new DCFilter(_tia, 256);
|
|
|
|
_m6532 = new M6532(this);
|
|
|
|
HardReset();
|
|
|
|
RomDetails = $"{_game.Name}\r\nSHA1:{Rom.HashSHA1()}\r\nMD5:{Rom.HashMD5()}\r\nMapper Impl \"{_mapper.GetType()}\"";
|
|
|
|
// Some games (ex. 3D tic tac toe), turn off the screen for extended periods, so we need to allow for this here.
|
|
if (_game.GetOptionsDict().ContainsKey("SP_FRAME"))
|
|
{
|
|
if (_game.GetOptionsDict()["SP_FRAME"] == "true")
|
|
{
|
|
SP_FRAME = true;
|
|
}
|
|
}
|
|
if (_game.GetOptionsDict().ContainsKey("SP_RESET"))
|
|
{
|
|
if (_game.GetOptionsDict()["SP_RESET"] == "true")
|
|
{
|
|
SP_RESET = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static MapperBase CreateMapper(Atari2600 core, string mapperName, int romLength)
|
|
{
|
|
static MapperBase SetMultiCartMapper(Atari2600 core, int romLength, int gameTotal)
|
|
{
|
|
return (romLength / gameTotal) switch
|
|
{
|
|
1024 * 2 => new Multicart2K(core, gameTotal),
|
|
1024 * 4 => new Multicart4K(core, gameTotal),
|
|
1024 * 8 => new Multicart8K(core, gameTotal),
|
|
_ => new Multicart4K(core, gameTotal)
|
|
};
|
|
}
|
|
|
|
return mapperName switch
|
|
{
|
|
"2IN1" => SetMultiCartMapper(core, romLength, 2),
|
|
"4IN1" => SetMultiCartMapper(core, romLength, 4),
|
|
"8IN1" => SetMultiCartMapper(core, romLength, 8),
|
|
"16IN1" => SetMultiCartMapper(core, romLength, 16),
|
|
"32IN1" => SetMultiCartMapper(core, romLength, 32),
|
|
"AR" => new mAR(core),
|
|
"4K" => new m4K(core),
|
|
"2K" => new m2K(core),
|
|
"CM" => new mCM(core),
|
|
"CV" => new mCV(core),
|
|
"DPC" => new mDPC(core),
|
|
"DPC+" => new mDPCPlus(core),
|
|
"F8" => new mF8(core),
|
|
"F8SC" => new mF8SC(core),
|
|
"F6" => new mF6(core),
|
|
"F6SC" => new mF6SC(core),
|
|
"F4" => new mF4(core),
|
|
"F4SC" => new mF4SC(core),
|
|
"FE" => new mFE(core),
|
|
"E0" => new mE0(core),
|
|
"3F" => new m3F(core),
|
|
"FA" => new mFA(core),
|
|
"FA2" => new mFA2(core),
|
|
"E7" => new mE7(core),
|
|
"F0" => new mF0(core),
|
|
"UA" => new mUA(core),
|
|
"F8_sega" => new mF8_sega(core),
|
|
|
|
// Homebrew mappers
|
|
"3E" => new m3E(core),
|
|
"0840" => new m0840(core),
|
|
"MC" => new mMC(core),
|
|
"EF" => new mEF(core),
|
|
"EFSC" => new mEFSC(core),
|
|
"X07" => new mX07(core),
|
|
"4A50" => new m4A50(core),
|
|
"SB" => new mSB(core),
|
|
_ => throw new InvalidOperationException("mapper not supported: " + mapperName)
|
|
};
|
|
}
|
|
|
|
private bool _pal;
|
|
|
|
private void HardReset()
|
|
{
|
|
_ram = new byte[128];
|
|
_mapper.HardReset();
|
|
|
|
Cpu = new MOS6502X<CpuLink>(new CpuLink(this));
|
|
|
|
_tia.Reset();
|
|
_m6532 = new M6532(this);
|
|
SetupMemoryDomains();
|
|
cyc_counter = 0;
|
|
}
|
|
|
|
private void Cycle()
|
|
{
|
|
_tia.Execute();
|
|
cyc_counter++;
|
|
if (cyc_counter == 3)
|
|
{
|
|
_m6532.Timer.Tick();
|
|
if (Tracer.Enabled && Cpu.AtStart)
|
|
{
|
|
Tracer.Put(Cpu.TraceState());
|
|
}
|
|
|
|
Cpu.ExecuteOne();
|
|
_mapper.ClockCpu();
|
|
|
|
cyc_counter = 0;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal byte ReadControls1(bool peek)
|
|
{
|
|
InputCallbacks.Call();
|
|
|
|
byte value = _controllerDeck.ReadPort1(_controller);
|
|
|
|
if (!peek)
|
|
{
|
|
_islag = false;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal byte ReadControls2(bool peek)
|
|
{
|
|
InputCallbacks.Call();
|
|
byte value = _controllerDeck.ReadPort2(_controller);
|
|
|
|
if (!peek)
|
|
{
|
|
_islag = false;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
internal int ReadPot1(int pot)
|
|
{
|
|
int value = _controllerDeck.ReadPot1(_controller, pot);
|
|
|
|
return value;
|
|
}
|
|
|
|
internal int ReadPot2(int pot)
|
|
{
|
|
int value = _controllerDeck.ReadPot2(_controller, pot);
|
|
|
|
return value;
|
|
}
|
|
|
|
internal byte ReadConsoleSwitches(bool peek)
|
|
{
|
|
byte value = 0xFF;
|
|
bool select = _controller.IsPressed("Select");
|
|
bool reset = _controller.IsPressed("Reset");
|
|
|
|
if (unselect_reset)
|
|
{
|
|
reset = false;
|
|
}
|
|
|
|
if (reset) { value &= 0xFE; }
|
|
if (select) { value &= 0xFD; }
|
|
if (SyncSettings.BW) { value &= 0xF7; }
|
|
if (_leftDifficultySwitchPressed)
|
|
{
|
|
value &= 0xBF;
|
|
}
|
|
|
|
if (_rightDifficultySwitchPressed)
|
|
{
|
|
value &= 0x7F;
|
|
}
|
|
|
|
if (!peek)
|
|
{
|
|
_islag = false;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
}
|