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 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(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(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; } } }