using System; using BizHawk.Common.NumberExtensions; using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components.M6502; namespace BizHawk.Emulation.Cores.Atari.Atari2600 { public partial class Atari2600 { private readonly GameInfo _game; private TIA _tia; private M6532 _m6532; private DCFilter _dcfilter; private MapperBase _mapper; private byte[] _ram; private IController _controller; 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; } // keeps track of tia cycles, 3 cycles per CPU cycle private int cyc_counter; private static MapperBase SetMultiCartMapper(int romLength, int gameTotal) { switch (romLength / gameTotal) { case 1024 * 2: // 2K return new Multicart2K(gameTotal); default: case 1024 * 4: // 4K return new Multicart4K(gameTotal); case 1024 * 8: // 8K return new Multicart8K(gameTotal); } } 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")); } } 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; MemoryCallbacks.CallReads(addr, "System Bus"); return temp; } private byte PeekMemory(ushort addr) { var temp = _mapper.PeekMemory((ushort)(addr & 0x1FFF)); return temp; } private void WriteMemory(ushort addr, byte value) { if (addr != _lastAddress) { DistinctAccessCount++; _lastAddress = addr; } _mapper.WriteMemory((ushort)(addr & 0x1FFF), value); MemoryCallbacks.CallWrites(addr, "System Bus"); } internal void PokeMemory(ushort addr, byte value) { _mapper.PokeMemory((ushort)(addr & 0x1FFF), value); } private void ExecFetch(ushort addr) { MemoryCallbacks.CallExecutes(addr, "System Bus"); } private void RebootCore() { // Regenerate mapper here to make sure its state is entirely clean switch (_game.GetOptionsDict()["m"]) { case "2IN1": _mapper = SetMultiCartMapper(Rom.Length, 2); break; case "4IN1": _mapper = SetMultiCartMapper(Rom.Length, 4); break; case "8IN1": _mapper = SetMultiCartMapper(Rom.Length, 8); break; case "16IN1": _mapper = SetMultiCartMapper(Rom.Length, 16); break; case "32IN1": _mapper = SetMultiCartMapper(Rom.Length, 32); break; case "AR": _mapper = new mAR(this); // This mapper has to set up configurations in the contructor. break; case "4K": _mapper = new m4K(); break; case "2K": _mapper = new m2K(); break; case "CM": _mapper = new mCM(); break; case "CV": _mapper = new mCV(); break; case "DPC": _mapper = new mDPC(); break; case "DPC+": _mapper = new mDPCPlus(); break; case "F8": _mapper = new mF8(); break; case "F8SC": _mapper = new mF8SC(); break; case "F6": _mapper = new mF6(); break; case "F6SC": _mapper = new mF6SC(); break; case "F4": _mapper = new mF4(); break; case "F4SC": _mapper = new mF4SC(); break; case "FE": _mapper = new mFE(); break; case "E0": _mapper = new mE0(); break; case "3F": _mapper = new m3F(); break; case "FA": _mapper = new mFA(); break; case "FA2": _mapper = new mFA2(); break; case "E7": _mapper = new mE7(); break; case "F0": _mapper = new mF0(); break; case "UA": _mapper = new mUA(); break; // Special Sega Mapper which has swapped banks case "F8_sega": _mapper = new mF8_sega(); break; // Homebrew mappers case "3E": _mapper = new m3E(); break; case "0840": _mapper = new m0840(); break; case "MC": _mapper = new mMC(); break; case "EF": _mapper = new mEF(); break; case "EFSC": _mapper = new mEFSC(); break; case "X07": _mapper = new mX07(); break; case "4A50": _mapper = new m4A50(); break; case "SB": _mapper = new mSB(); break; default: throw new InvalidOperationException("mapper not supported: " + _game.GetOptionsDict()["m"]); } _mapper.Core = this; _lagcount = 0; Cpu = new MOS6502X { ReadMemory = ReadMemory, WriteMemory = WriteMemory, PeekMemory = PeekMemory, DummyReadMemory = ReadMemory, OnExecFetch = ExecFetch }; 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(); // Show mapper class on romstatusdetails CoreComm.RomStatusDetails = $"{this._game.Name}\r\nSHA1:{Rom.HashSHA1()}\r\nMD5:{Rom.HashMD5()}\r\nMapper Impl \"{_mapper.GetType()}\""; } private bool _pal; private void HardReset() { _ram = new byte[128]; _mapper.HardReset(); Cpu = new MOS6502X { ReadMemory = ReadMemory, WriteMemory = WriteMemory, PeekMemory = PeekMemory, DummyReadMemory = ReadMemory, OnExecFetch = ExecFetch }; _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) { Tracer.Put(Cpu.TraceState()); } Cpu.ExecuteOne(); _mapper.ClockCpu(); cyc_counter = 0; } } internal byte ReadControls1(bool peek) { InputCallbacks.Call(); byte value = _controllerDeck.ReadPort1(_controller); if (!peek) { _islag = false; } return value; } 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 (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; } } }