From 427a80548c44a12d6729bd34a28b5c600ad4419b Mon Sep 17 00:00:00 2001 From: adelikat Date: Tue, 23 Oct 2012 03:33:57 +0000 Subject: [PATCH] Start an atari 7800 core based on emu7800, if you have bios files in very specific locations and filenames, you can open 2k ntsc games, and watch with no sound or controls! --- BizHawk.Emulation/BizHawk.Emulation.csproj | 66 + .../Consoles/Atari/7800/Atari7800.Core.cs | 18 + .../Consoles/Atari/7800/Atari7800.cs | 197 +++ .../Atari/7800/EMU7800/AddressSpace.cs | 154 ++ .../Consoles/Atari/7800/EMU7800/Bios7800.cs | 65 + .../Atari/7800/EMU7800/BufferElement.cs | 55 + .../Consoles/Atari/7800/EMU7800/Cart.cs | 170 +++ .../Consoles/Atari/7800/EMU7800/Cart7808.cs | 50 + .../Consoles/Atari/7800/EMU7800/Cart7816.cs | 50 + .../Consoles/Atari/7800/EMU7800/Cart7832.cs | 50 + .../Consoles/Atari/7800/EMU7800/Cart7832P.cs | 90 ++ .../Consoles/Atari/7800/EMU7800/Cart7848.cs | 50 + .../Consoles/Atari/7800/EMU7800/Cart78AB.cs | 67 + .../Consoles/Atari/7800/EMU7800/Cart78AC.cs | 86 ++ .../Consoles/Atari/7800/EMU7800/Cart78S4.cs | 89 ++ .../Consoles/Atari/7800/EMU7800/Cart78S9.cs | 70 + .../Consoles/Atari/7800/EMU7800/Cart78SG.cs | 94 ++ .../Consoles/Atari/7800/EMU7800/Cart78SGP.cs | 118 ++ .../Consoles/Atari/7800/EMU7800/CartA16K.cs | 86 ++ .../Consoles/Atari/7800/EMU7800/CartA16KR.cs | 100 ++ .../Consoles/Atari/7800/EMU7800/CartA2K.cs | 56 + .../Consoles/Atari/7800/EMU7800/CartA32K.cs | 88 ++ .../Consoles/Atari/7800/EMU7800/CartA32KR.cs | 104 ++ .../Consoles/Atari/7800/EMU7800/CartA4K.cs | 47 + .../Consoles/Atari/7800/EMU7800/CartA8K.cs | 87 ++ .../Consoles/Atari/7800/EMU7800/CartA8KR.cs | 103 ++ .../Consoles/Atari/7800/EMU7800/CartCBS12K.cs | 100 ++ .../Consoles/Atari/7800/EMU7800/CartDC8K.cs | 55 + .../Consoles/Atari/7800/EMU7800/CartDPC.cs | 341 +++++ .../Consoles/Atari/7800/EMU7800/CartMN16K.cs | 136 ++ .../Consoles/Atari/7800/EMU7800/CartPB8K.cs | 103 ++ .../Consoles/Atari/7800/EMU7800/CartTV8K.cs | 90 ++ .../Consoles/Atari/7800/EMU7800/CartType.cs | 43 + .../Atari/7800/EMU7800/ConsoleSwitch.cs | 11 + .../Consoles/Atari/7800/EMU7800/Controller.cs | 14 + .../Atari/7800/EMU7800/ControllerAction.cs | 17 + .../7800/EMU7800/DeserializationContext.cs | 245 +++ .../Atari/7800/EMU7800/Emu7800Exception.cs | 19 + .../EMU7800/Emu7800SerializationException.cs | 19 + .../Atari/7800/EMU7800/FontRenderer.cs | 170 +++ .../Atari/7800/EMU7800/FrameBuffer.cs | 79 + .../Consoles/Atari/7800/EMU7800/HSC7800.cs | 85 ++ .../Consoles/Atari/7800/EMU7800/IDevice.cs | 16 + .../Consoles/Atari/7800/EMU7800/ILogger.cs | 10 + .../Consoles/Atari/7800/EMU7800/InputState.cs | 368 +++++ .../Consoles/Atari/7800/EMU7800/M6502.cs | 1099 +++++++++++++ .../Consoles/Atari/7800/EMU7800/M6502DASM.cs | 242 +++ .../Atari/7800/EMU7800/Machine2600.cs | 112 ++ .../Atari/7800/EMU7800/Machine2600NTSC.cs | 30 + .../Atari/7800/EMU7800/Machine2600PAL.cs | 30 + .../Atari/7800/EMU7800/Machine7800.cs | 244 +++ .../Atari/7800/EMU7800/Machine7800NTSC.cs | 30 + .../Atari/7800/EMU7800/Machine7800PAL.cs | 30 + .../Atari/7800/EMU7800/MachineBase.cs | 331 ++++ .../Atari/7800/EMU7800/MachineInput.cs | 42 + .../Atari/7800/EMU7800/MachineType.cs | 19 + .../Consoles/Atari/7800/EMU7800/Maria.cs | 1156 ++++++++++++++ .../Atari/7800/EMU7800/MariaTables.cs | 180 +++ .../Consoles/Atari/7800/EMU7800/NullDevice.cs | 78 + .../Consoles/Atari/7800/EMU7800/NullLogger.cs | 21 + .../Consoles/Atari/7800/EMU7800/PIA.cs | 348 +++++ .../Consoles/Atari/7800/EMU7800/PokeySound.cs | 437 ++++++ .../Consoles/Atari/7800/EMU7800/RAM6116.cs | 65 + .../7800/EMU7800/SerializationContext.cs | 230 +++ .../Consoles/Atari/7800/EMU7800/TIA.cs | 1355 +++++++++++++++++ .../Consoles/Atari/7800/EMU7800/TIASound.cs | 361 +++++ .../Consoles/Atari/7800/EMU7800/TIATables.cs | 424 ++++++ BizHawk.Emulation/Database/Database.cs | 1 + BizHawk.MultiClient/Config.cs | 1 + BizHawk.MultiClient/MainForm.cs | 9 +- 70 files changed, 11005 insertions(+), 1 deletion(-) create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/Atari7800.Core.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/Atari7800.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/AddressSpace.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Bios7800.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/BufferElement.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7808.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7816.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832P.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7848.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AB.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AC.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S4.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S9.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SG.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SGP.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16KR.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA2K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32KR.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA4K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8KR.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartCBS12K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDC8K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDPC.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartMN16K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartPB8K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartTV8K.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartType.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ConsoleSwitch.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Controller.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ControllerAction.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/DeserializationContext.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800Exception.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800SerializationException.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FontRenderer.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FrameBuffer.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/HSC7800.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/IDevice.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ILogger.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/InputState.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502DASM.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600NTSC.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600PAL.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800NTSC.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800PAL.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineBase.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineInput.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineType.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Maria.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MariaTables.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullDevice.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullLogger.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PIA.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PokeySound.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/RAM6116.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/SerializationContext.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIA.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIASound.cs create mode 100644 BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIATables.cs diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index 229d2f8aba..6086ddf47a 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -103,6 +103,72 @@ Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BizHawk.Emulation/Consoles/Atari/7800/Atari7800.Core.cs b/BizHawk.Emulation/Consoles/Atari/7800/Atari7800.Core.cs new file mode 100644 index 0000000000..044dddd05f --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/Atari7800.Core.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using BizHawk.Emulation.CPUs.M6502; +using BizHawk.Emulation.Consoles.Atari; +using EMU7800.Core; + +namespace BizHawk +{ + partial class Atari7800 + { + public byte[] rom; + Bios7800 BIOS; + public byte[] hsbios; + Cart cart; + Machine7800NTSC theMachine; //TODO: PAL support + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/Atari7800.cs b/BizHawk.Emulation/Consoles/Atari/7800/Atari7800.cs new file mode 100644 index 0000000000..20cb129353 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/Atari7800.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; +using EMU7800.Core; + +namespace BizHawk +{ + public partial class Atari7800 : IEmulator + { + public string SystemId { get { return "A78"; } } //TODO: are we going to allow this core to do 2600 games? + public GameInfo game; + + public void FrameAdvance(bool render, bool rendersound) + { + _frame++; + _islag = true; + + //TODO + FrameBuffer fb = new FrameBuffer(0, 262); //TODO: 262 is NTSC + theMachine.ComputeNextFrame(fb); + if (_islag) + { + LagCount++; + } + + videoProvider.FillFrameBuffer(); + } + + /* TODO */ + public CoreInputComm CoreInputComm { get; set; } + public CoreOutputComm CoreOutputComm { get; private set; } + public ISyncSoundProvider SyncSoundProvider { get { return null; } } + public bool StartAsyncSound() { return true; } + public void EndAsyncSound() { } + public bool DeterministicEmulation { get; set; } + public void SaveStateText(TextWriter writer) { } + public void LoadStateText(TextReader reader) { } + public void SaveStateBinary(BinaryWriter bw) { } + public void LoadStateBinary(BinaryReader br) { } + private IList memoryDomains; + public IList MemoryDomains { get { return null; } } + public MemoryDomain MainMemory { get { return null; } } + /********************/ + + public int Frame { get { return _frame; } set { _frame = value; } } + public int LagCount { get { return _lagcount; } set { _lagcount = value; } } + public bool IsLagFrame { get { return _islag; } } + private bool _islag = true; + private int _lagcount = 0; + private int _frame = 0; + + public byte[] ReadSaveRam() { return null; } + public void StoreSaveRam(byte[] data) { } + public void ClearSaveRam() { } + public bool SaveRamModified { get; set; } + public void Dispose() { } + public IVideoProvider VideoProvider { get { return videoProvider; } } + public ISoundProvider SoundProvider { get { return soundProvider; } } + + + public void ResetFrameCounter() + { + _frame = 0; + } + + public byte[] SaveStateBinary() + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + return ms.ToArray(); + } + + public ControllerDefinition ControllerDefinition { get { return Atari7800ControllerDefinition; } } + public IController Controller { get; set; } + public static readonly ControllerDefinition Atari7800ControllerDefinition = new ControllerDefinition + { + Name = "Atari 7800 Basic Controller", //TODO + BoolButtons = + { + "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button", + "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Button", + "Reset", "Select" + } + }; + + public Atari7800(GameInfo game, byte[] rom, byte[] bios, byte[] highscoreBIOS) + { + var domains = new List(1); + domains.Add(new MemoryDomain("Main RAM", 1, Endian.Little, addr => 0xFF, null)); //TODO + memoryDomains = domains.AsReadOnly(); + CoreOutputComm = new CoreOutputComm(); + CoreInputComm = new CoreInputComm(); + this.rom = rom; + this.game = game; + this.hsbios = highscoreBIOS; + BIOS = new Bios7800(bios); + videoProvider = new MyVideoProvider(this); + soundProvider = new MySoundProvider(); //TODO + HardReset(); + } + + public void HardReset() + { + _lagcount = 0; + // show mapper class on romstatusdetails + CoreOutputComm.RomStatusDetails = + string.Format("{0}\r\nSHA1:{1}\r\nMD5:{2}\r\nMapper Impl \"{3}\"", + game.Name, + Util.BytesToHexString(System.Security.Cryptography.SHA1.Create().ComputeHash(rom)), + Util.BytesToHexString(System.Security.Cryptography.MD5.Create().ComputeHash(rom)), + "TODO"); + + cart = new CartA2K(rom); //TODO: mapper selection system + + int[] bob = new int[] { 0, 0, 0 }; + + FileStream fs = new FileStream("C:\\dummy", FileMode.Create, FileAccess.ReadWrite); //TODO: I don't see what this context is used for, see if it can be whacked or pass in a null + BinaryReader blah = new BinaryReader(fs); + DeserializationContext george = new DeserializationContext(blah); + NullLogger logger = new NullLogger(); + HSC7800 hsc7800 = new HSC7800(hsbios, new byte[4096]); //TODO: why should I have to feed it ram? how much? + theMachine = new Machine7800NTSC(cart, BIOS, hsc7800, logger); + //TODO: clean up, the hs and bios are passed in, the bios has an object AND byte array in the core, and naming is inconsistent + } + + void SyncState(Serializer ser) //TODO + { + ser.Sync("Lag", ref _lagcount); + ser.Sync("Frame", ref _frame); + ser.Sync("IsLag", ref _islag); + } + + private void SoftReset() //TOOD: hook this up + { + theMachine.Reset(); + } + + MyVideoProvider videoProvider; + + class MyVideoProvider : IVideoProvider + { + public int top = 0; //TODO: I should delete these probably + public int bottom = 262; + public int left = 0; + public int right = 320; + + Atari7800 emu; + public MyVideoProvider(Atari7800 emu) + { + this.emu = emu; + } + + int[] buffer = new int[262 * 320]; //TODO: use videobuffer values for this if there's a logical way + + public void FillFrameBuffer() //TODO: don't recalculate consantly, fill this on frame advance instead + { + FrameBuffer fb = emu.theMachine.CreateFrameBuffer(); + + + for (int i = 0; i < 262; i++) + { + for (int j = 0; j < 320; j++) + { + buffer[(i * fb.VisiblePitch) + j] = fb.VideoBuffer[i][j]; + } + } + } + + public int[] GetVideoBuffer() + { + return buffer; + } + + public int VirtualWidth { get { return BufferWidth; } } + public int BufferWidth { get { return right - left + 1; } } + public int BufferHeight { get { return bottom - top + 1; } } + public int BackgroundColor { get { return 0; } } + } + + MySoundProvider soundProvider; + + class MySoundProvider : ISoundProvider + { + public int MaxVolume { get { return 0; } set { } } + public void DiscardSamples() + { + } + + public void GetSamples(short[] samples) + { + } + } + + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/AddressSpace.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/AddressSpace.cs new file mode 100644 index 0000000000..17b8b44f00 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/AddressSpace.cs @@ -0,0 +1,154 @@ +/* + * AddressSpace.cs + * + * The class representing the memory map or address space of a machine. + * + * Copyright © 2003, 2011 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class AddressSpace + { + public MachineBase M { get; private set; } + + readonly int AddrSpaceShift; + readonly int AddrSpaceSize; + readonly int AddrSpaceMask; + + readonly int PageShift; + readonly int PageSize; + + readonly IDevice[] MemoryMap; + IDevice Snooper; + + public byte DataBusState { get; private set; } + + public override string ToString() + { + return "AddressSpace"; + } + + public byte this[ushort addr] + { + get + { + if (Snooper != null) + { + // here DataBusState is just facilitating a dummy read to the snooper device + // the read operation may have important side effects within the device + DataBusState = Snooper[addr]; + } + var pageno = (addr & AddrSpaceMask) >> PageShift; + var dev = MemoryMap[pageno]; + DataBusState = dev[addr]; + return DataBusState; + } + set + { + DataBusState = value; + if (Snooper != null) + { + Snooper[addr] = DataBusState; + } + var pageno = (addr & AddrSpaceMask) >> PageShift; + var dev = MemoryMap[pageno]; + dev[addr] = DataBusState; + } + } + + public void Map(ushort basea, ushort size, IDevice device) + { + if (device == null) + throw new ArgumentNullException("device"); + + for (int addr = basea; addr < basea + size; addr += PageSize) + { + var pageno = (addr & AddrSpaceMask) >> PageShift; + MemoryMap[pageno] = device; + } + + LogDebug("{0}: Mapped {1} to ${2:x4}:${3:x4}", this, device, basea, basea + size - 1); + } + + public void Map(ushort basea, ushort size, Cart cart) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + cart.Attach(M); + var device = (IDevice)cart; + if (cart.RequestSnooping) + { + Snooper = device; + } + Map(basea, size, device); + } + + #region Constructors + + private AddressSpace() + { + } + + public AddressSpace(MachineBase m, int addrSpaceShift, int pageShift) + { + if (m == null) + throw new ArgumentNullException("m"); + + M = m; + + AddrSpaceShift = addrSpaceShift; + AddrSpaceSize = 1 << AddrSpaceShift; + AddrSpaceMask = AddrSpaceSize - 1; + + PageShift = pageShift; + PageSize = 1 << PageShift; + + MemoryMap = new IDevice[1 << addrSpaceShift >> PageShift]; + IDevice nullDev = new NullDevice(M); + for (var pageno=0; pageno < MemoryMap.Length; pageno++) + { + MemoryMap[pageno] = nullDev; + } + } + + #endregion + + #region Serialization Members + + public AddressSpace(DeserializationContext input, MachineBase m, int addrSpaceShift, int pageShift) : this(m, addrSpaceShift, pageShift) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + DataBusState = input.ReadByte(); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(DataBusState); + } + + #endregion + + #region Helpers + + [System.Diagnostics.Conditional("DEBUG")] + void LogDebug(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Bios7800.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Bios7800.cs new file mode 100644 index 0000000000..c02b3a648f --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Bios7800.cs @@ -0,0 +1,65 @@ +/* + * BIOS7800.cs + * + * The BIOS of the Atari 7800. + * + * Copyright © 2004 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class Bios7800 : IDevice + { + readonly byte[] ROM; + readonly ushort Mask; + + public ushort Size { get { return (ushort)ROM.Length; } } + + public void Reset() { } + + public byte this[ushort addr] + { + get { return ROM[addr & Mask]; } + set { } + } + + public Bios7800(byte[] rom) + { + if (rom == null) + throw new ArgumentNullException("rom"); + if (rom.Length != 4096 && rom.Length != 16384) + throw new ArgumentException("ROM size not 4096 or 16384", "rom"); + + ROM = rom; + Mask = (ushort)ROM.Length; + Mask--; + } + + #region Serialization Members + + public Bios7800(DeserializationContext input) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + ROM = input.ReadExpectedBytes(4096, 16384); + + Mask = (ushort)ROM.Length; + Mask--; + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/BufferElement.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/BufferElement.cs new file mode 100644 index 0000000000..86a79b196b --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/BufferElement.cs @@ -0,0 +1,55 @@ +namespace EMU7800.Core +{ + /// + /// Frames are composed of s, + /// that group bytes into machine words for efficient array processing. + /// Bytes are packed in logical little endian order. + /// + public struct BufferElement + { + /// + /// The number of bytes contained within a . + /// + public const int SIZE = 4; // 2^SHIFT + + /// + /// The mask value applied against a byte array index to access the individual bytes within a . + /// + public const int MASK = 3; // SIZE - 1 + + /// + /// The left shift value to convert a byte array index to a array index. + /// + public const int SHIFT = 2; + + uint _data; + + /// + /// A convenience accessor for reading/writing individual bytes within this . + /// + /// + public byte this[int offset] + { + get + { + var i = (offset & MASK) << 3; + return (byte)(_data >> i); + } + set + { + var i = (offset & MASK) << 3; + var d = (uint)value << i; + var di = (uint)0xff << i; + _data = _data & ~di | d; + } + } + + /// + /// Zeros out all bytes of this . + /// + public void ClearAll() + { + _data = 0; + } + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart.cs new file mode 100644 index 0000000000..bd0e6a2aea --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart.cs @@ -0,0 +1,170 @@ +/* + * Cart.cs + * + * An abstraction of a game cart. Attributable to Kevin Horton's Bankswitching + * document, the Stella source code, and Eckhard Stolberg's 7800 Bankswitching Guide. + * + * Copyright © 2003, 2004, 2010, 2011 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public abstract class Cart : IDevice + { + static int _multicartBankSelector; + + protected MachineBase M { get; set; } + protected internal byte[] ROM { get; set; } + + #region IDevice Members + + public virtual void Reset() { } + + public abstract byte this[ushort addr] { get; set; } + + #endregion + + public virtual void Attach(MachineBase m) + { + if (m == null) + throw new ArgumentNullException("m"); + if (M != null && M != m) + throw new InvalidOperationException("Cart already attached to a different machine."); + M = m; + } + + public virtual void StartFrame() + { + } + + public virtual void EndFrame() + { + } + + protected internal virtual bool RequestSnooping + { + get { return false; } + } + + /// + /// Creates an instance of the specified cart. + /// + /// + /// + /// Specified CartType is unexpected. + public static Cart Create(byte[] romBytes, CartType cartType) + { + if (cartType == CartType.None) + { + switch (romBytes.Length) + { + case 2048: + cartType = CartType.A2K; + break; + case 4096: + cartType = CartType.A4K; + break; + case 8192: + cartType = CartType.A8K; + break; + case 16384: + cartType = CartType.A16K; + break; + case 32768: + cartType = CartType.A32K; + break; + } + } + + switch (cartType) + { + case CartType.A2K: return new CartA2K(romBytes); + case CartType.A4K: return new CartA4K(romBytes); + case CartType.A8K: return new CartA8K(romBytes); + case CartType.A8KR: return new CartA8KR(romBytes); + case CartType.A16K: return new CartA16K(romBytes); + case CartType.A16KR: return new CartA16KR(romBytes); + case CartType.DC8K: return new CartDC8K(romBytes); + case CartType.PB8K: return new CartPB8K(romBytes); + case CartType.TV8K: return new CartTV8K(romBytes); + case CartType.CBS12K: return new CartCBS12K(romBytes); + case CartType.A32K: return new CartA32K(romBytes); + case CartType.A32KR: return new CartA32KR(romBytes); + case CartType.MN16K: return new CartMN16K(romBytes); + case CartType.DPC: return new CartDPC(romBytes); + case CartType.M32N12K: return new CartA2K(romBytes, _multicartBankSelector++); + case CartType.A7808: return new Cart7808(romBytes); + case CartType.A7816: return new Cart7816(romBytes); + case CartType.A7832P: return new Cart7832P(romBytes); + case CartType.A7832: return new Cart7832(romBytes); + case CartType.A7848: return new Cart7848(romBytes); + case CartType.A78SGP: return new Cart78SGP(romBytes); + case CartType.A78SG: return new Cart78SG(romBytes, false); + case CartType.A78SGR: return new Cart78SG(romBytes, true); + case CartType.A78S9: return new Cart78S9(romBytes); + case CartType.A78S4: return new Cart78S4(romBytes, false); + case CartType.A78S4R: return new Cart78S4(romBytes, true); + case CartType.A78AB: return new Cart78AB(romBytes); + case CartType.A78AC: return new Cart78AC(romBytes); + default: + throw new Emu7800Exception("Unexpected CartType: " + cartType); + } + } + + protected void LoadRom(byte[] romBytes, int multicartBankSize, int multicartBankNo) + { + if (romBytes == null) + throw new ArgumentNullException("romBytes"); + + ROM = new byte[multicartBankSize]; + Buffer.BlockCopy(romBytes, multicartBankSize*multicartBankNo, ROM, 0, multicartBankSize); + } + + protected void LoadRom(byte[] romBytes, int minSize) + { + if (romBytes == null) + throw new ArgumentNullException("romBytes"); + + if (romBytes.Length >= minSize) + { + ROM = romBytes; + } + else + { + ROM = new byte[minSize]; + Buffer.BlockCopy(romBytes, 0, ROM, 0, romBytes.Length); + } + } + + protected void LoadRom(byte[] romBytes) + { + LoadRom(romBytes, romBytes.Length); + } + + protected Cart() + { + } + + #region Serialization Members + + protected Cart(DeserializationContext input) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + } + + public virtual void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7808.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7808.cs new file mode 100644 index 0000000000..7fa1c277bc --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7808.cs @@ -0,0 +1,50 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 non-bankswitched 8KB cartridge + /// + public sealed class Cart7808 : Cart + { + // + // Cart Format Mapping to ROM Address Space + // 0x0000:0x2000 0xE000:0x2000 (repeated downward to 0x4000) + // + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return ROM[addr & 0x1fff]; } + set { } + } + + #endregion + + private Cart7808() + { + } + + public Cart7808(byte[] romBytes) + { + LoadRom(romBytes, 0x2000); + } + + #region Serialization Members + + public Cart7808(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x2000), 0x2000); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7816.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7816.cs new file mode 100644 index 0000000000..fe93e39585 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7816.cs @@ -0,0 +1,50 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 non-bankswitched 16KB cartridge + /// + public sealed class Cart7816 : Cart + { + // + // Cart Format Mapping to ROM Address Space + // 0x0000:0x4000 0xC000:0x4000 (repeated downward to 0x4000) + // + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return ROM[addr & 0x3fff]; } + set { } + } + + #endregion + + private Cart7816() + { + } + + public Cart7816(byte[] romBytes) + { + LoadRom(romBytes, 0x4000); + } + + #region Serialization Members + + public Cart7816(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x4000), 0x4000); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832.cs new file mode 100644 index 0000000000..601e2f83f8 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832.cs @@ -0,0 +1,50 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 non-bankswitched 32KB cartridge + /// + public sealed class Cart7832 : Cart + { + // + // Cart Format Mapping to ROM Address Space + // 0x0000:0x8000 0x8000:0x8000 (repeated downward until 0x4000) + // + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return ROM[addr & 0x7fff]; } + set { } + } + + #endregion + + private Cart7832() + { + } + + public Cart7832(byte[] romBytes) + { + LoadRom(romBytes, 0x8000); + } + + #region Serialization Members + + public Cart7832(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x8000), 0x8000); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832P.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832P.cs new file mode 100644 index 0000000000..c3a24be76e --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7832P.cs @@ -0,0 +1,90 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 non-bankswitched 32KB cartridge w/Pokey + /// + public sealed class Cart7832P : Cart + { + // + // Cart Format Mapping to ROM Address Space + // 0x4000:0x4000 Pokey + // 0x0000:0x8000 0x8000:0x8000 + // + PokeySound _pokeySound; + + #region IDevice Members + + public override void Reset() + { + base.Reset(); + if (_pokeySound != null) + _pokeySound.Reset(); + } + + public override byte this[ushort addr] + { + get + { + return ((addr & 0xF000) == 0x4000 && _pokeySound != null) ? _pokeySound.Read(addr) : ROM[addr & 0x7fff]; + } + set + { + if ((addr & 0xF000) == 0x4000 && _pokeySound != null) + _pokeySound.Update(addr, value); + } + } + + #endregion + + public override void Attach(MachineBase m) + { + base.Attach(m); + if (_pokeySound == null) + _pokeySound = new PokeySound(M); + } + + public override void StartFrame() + { + if (_pokeySound != null) + _pokeySound.StartFrame(); + } + + public override void EndFrame() + { + if (_pokeySound != null) + _pokeySound.EndFrame(); + } + + private Cart7832P() + { + } + + public Cart7832P(byte[] romBytes) + { + LoadRom(romBytes, 0x8000); + } + + #region Serialization Members + + public Cart7832P(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x8000), 0x8000); + _pokeySound = input.ReadOptionalPokeySound(m); + } + + public override void GetObjectData(SerializationContext output) + { + if (_pokeySound == null) + throw new Emu7800SerializationException("Cart7832P must be attached before serialization."); + + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.WriteOptional(_pokeySound); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7848.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7848.cs new file mode 100644 index 0000000000..b9536da7eb --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart7848.cs @@ -0,0 +1,50 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 non-bankswitched 48KB cartridge + /// + public sealed class Cart7848 : Cart + { + // + // Cart Format Mapping to ROM Address Space + // 0x0000:0xc000 0x4000:0xc000 + // + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return ROM[addr - 0x4000]; } + set { } + } + + #endregion + + private Cart7848() + { + } + + public Cart7848(byte[] romBytes) + { + LoadRom(romBytes, 0xc000); + } + + #region Serialization Members + + public Cart7848(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0xc000), 0xc000); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AB.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AB.cs new file mode 100644 index 0000000000..6b4b6b53a7 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AB.cs @@ -0,0 +1,67 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 Absolute bankswitched cartridge + /// + public sealed class Cart78AB : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank0: 0x00000:0x4000 + // Bank1: 0x04000:0x4000 0x4000:0x4000 Bank0-1 (0 on startup) + // Bank2: 0x08000:0x4000 0x8000:0x4000 Bank2 + // Bank3: 0x0c000:0x4000 0xc000:0x4000 Bank3 + // + readonly int[] Bank = new int[4]; + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return ROM[ (Bank[addr >> 14] << 14) | (addr & 0x3fff) ]; } + set + { + if ((addr >> 14) == 2) + { + Bank[1] = (value - 1) & 1; + } + } + } + + #endregion + + private Cart78AB() + { + } + + public Cart78AB(byte[] romBytes) + { + Bank[1] = 0; + Bank[2] = 2; + Bank[3] = 3; + LoadRom(romBytes, 0x10000); + } + + #region Serialization Members + + public Cart78AB(DeserializationContext input, MachineBase m) : base(input) + { + var version = input.CheckVersion(1, 2); + LoadRom(input.ReadBytes()); + Bank = input.ReadIntegers(4); + if (version == 1) + input.ReadInt32(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(2); + output.Write(ROM); + output.Write(Bank); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AC.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AC.cs new file mode 100644 index 0000000000..fcf854c8b5 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78AC.cs @@ -0,0 +1,86 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 Activision bankswitched cartridge + /// + public sealed class Cart78AC : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank0 : 0x00000:0x2000 + // Bank1 : 0x02000:0x2000 + // Bank2 : 0x04000:0x2000 0x4000:0x2000 Bank13 + // Bank3 : 0x06000:0x2000 0x6000:0x2000 Bank12 + // Bank4 : 0x08000:0x2000 0x8000:0x2000 Bank15 + // Bank5 : 0x0a000:0x2000 0xa000:0x2000 Bank(2*n) n in [0-7], n=0 on startup + // Bank6 : 0x0c000:0x2000 0xc000:0x2000 Bank(2*n+1) + // Bank7 : 0x0e000:0x2000 0xe000:0x2000 Bank14 + // Bank8 : 0x10000:0x2000 + // Bank9 : 0x12000:0x2000 + // Bank10: 0x14000:0x2000 + // Bank11: 0x16000:0x2000 + // Bank12: 0x18000:0x2000 + // Bank13: 0x1a000:0x2000 + // Bank14: 0x1c000:0x2000 + // Bank15: 0x1e000:0x2000 + // + // Banks are actually 16KB, but handled as 8KB for implementation ease. + // + readonly int[] Bank = new int[8]; + + #region IDevice Members + + public override byte this[ushort addr] + { + get + { + return ROM[ (Bank[addr >> 13] << 13) | (addr & 0x1fff) ]; + } + set + { + if ((addr & 0xfff0) == 0xff80) + { + Bank[5] = (addr & 7) << 1; + Bank[6] = Bank[5] + 1; + } + } + } + + #endregion + + private Cart78AC() + { + } + + public Cart78AC(byte[] romBytes) + { + Bank[2] = 13; + Bank[3] = 12; + Bank[4] = 15; + Bank[5] = 0; + Bank[6] = 1; + Bank[7] = 14; + LoadRom(romBytes, 0x20000); + } + + #region Serialization Members + + public Cart78AC(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadBytes()); + Bank = input.ReadIntegers(8); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(Bank); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S4.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S4.cs new file mode 100644 index 0000000000..7920f19713 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S4.cs @@ -0,0 +1,89 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 SuperGame S4 bankswitched cartridge + /// + public sealed class Cart78S4 : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank0: 0x00000:0x4000 + // Bank1: 0x04000:0x4000 0x4000:0x4000 Bank2 + // Bank2: 0x08000:0x4000 0x8000:0x4000 Bank0 (0 on startup) + // Bank3: 0x0c000:0x4000 0xc000:0x4000 Bank3 + // + // Banks 0-3 are the same as banks 4-7 + // + readonly byte[] RAM; + readonly int[] Bank = new int[4]; + + #region IDevice Members + + public override byte this[ushort addr] + { + get + { + if (RAM != null && addr >= 0x6000 && addr <= 0x7fff) + { + return RAM[addr & 0x1fff]; + } + return ROM[(Bank[addr >> 14] << 14) | (addr & 0x3fff)]; + } + set + { + if (RAM != null && addr >= 0x6000 && addr <= 0x7fff) + { + RAM[addr & 0x1fff] = value; + } + else if ((addr >> 14) == 2) + { + Bank[2] = value & 3; + } + } + } + + #endregion + + private Cart78S4() + { + } + + public Cart78S4(byte[] romBytes, bool needRAM) + { + if (needRAM) + { + RAM = new byte[0x2000]; + } + + LoadRom(romBytes, 0xffff); + + Bank[1] = 2; + Bank[2] = 0; + Bank[3] = 3; + } + + #region Serialization Members + + public Cart78S4(DeserializationContext input, MachineBase m) : base(input) + { + var version = input.CheckVersion(1, 2); + LoadRom(input.ReadBytes()); + Bank = input.ReadIntegers(4); + if (version == 1) + input.ReadInt32(); + RAM = input.ReadOptionalBytes(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(2); + output.Write(ROM); + output.Write(Bank); + output.WriteOptional(RAM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S9.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S9.cs new file mode 100644 index 0000000000..63b7ff0394 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78S9.cs @@ -0,0 +1,70 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 SuperGame S9 bankswitched cartridge + /// + public sealed class Cart78S9 : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank0: 0x00000:0x4000 + // Bank1: 0x04000:0x4000 0x4000:0x4000 Bank0 + // Bank2: 0x08000:0x4000 0x8000:0x4000 Bank0-8 (1 on startup) + // Bank3: 0x0c000:0x4000 0xc000:0x4000 Bank8 + // Bank4: 0x10000:0x4000 + // Bank5: 0x14000:0x4000 + // Bank6: 0x18000:0x4000 + // Bank7: 0x1c000:0x4000 + // Bank8: 0x20000:0x4000 + // + readonly int[] Bank = new int[4]; + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return ROM[ (Bank[addr >> 14] << 14) | (addr & 0x3fff) ]; } + set + { + if ((addr >> 14) == 2 && value < 8) + { + Bank[2] = (value + 1); + } + } + } + + #endregion + + private Cart78S9() + { + } + + public Cart78S9(byte[] romBytes) + { + Bank[1] = 0; + Bank[2] = 1; + Bank[3] = 8; + LoadRom(romBytes, 0x24000); + } + + #region Serialization Members + + public Cart78S9(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadBytes()); + Bank = input.ReadIntegers(4); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(Bank); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SG.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SG.cs new file mode 100644 index 0000000000..71dcdcfb64 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SG.cs @@ -0,0 +1,94 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 SuperGame bankswitched cartridge + /// + public sealed class Cart78SG : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank0: 0x00000:0x4000 + // Bank1: 0x04000:0x4000 0x4000:0x4000 Bank6 + // Bank2: 0x08000:0x4000 0x8000:0x4000 Bank0-7 (0 on startup) + // Bank3: 0x0c000:0x4000 0xc000:0x4000 Bank7 + // Bank4: 0x10000:0x4000 + // Bank5: 0x14000:0x4000 + // Bank6: 0x18000:0x4000 + // Bank7: 0x1c000:0x4000 + // + readonly int[] Bank = new int[4]; + readonly byte[] RAM; + + #region IDevice Members + + public override byte this[ushort addr] + { + get + { + var bankNo = addr >> 14; + if (RAM != null && bankNo == 1) + { + return RAM[addr & 0x3fff]; + } + return ROM[ (Bank[bankNo] << 14) | (addr & 0x3fff) ]; + } + set + { + var bankNo = addr >> 14; + if (bankNo == 2) + { + Bank[2] = value & 7; + } + else if (RAM != null && bankNo == 1) + { + RAM[addr & 0x3fff] = value; + } + } + } + + #endregion + + private Cart78SG() + { + } + + public Cart78SG(byte[] romBytes, bool needRAM) + { + if (needRAM) + { + // This works for titles that use 8KB instead of 16KB + RAM = new byte[0x4000]; + } + + Bank[1] = 6; + Bank[2] = 0; + Bank[3] = 7; + + LoadRom(romBytes, 0x20000); + } + + #region Serialization Members + + public Cart78SG(DeserializationContext input, MachineBase m) : base(input) + { + var version = input.CheckVersion(1, 2); + LoadRom(input.ReadBytes()); + Bank = input.ReadIntegers(4); + if (version == 1) + input.ReadInt32(); + RAM = input.ReadOptionalBytes(0x4000); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(2); + output.Write(ROM); + output.Write(Bank); + output.WriteOptional(RAM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SGP.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SGP.cs new file mode 100644 index 0000000000..65feda2f84 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Cart78SGP.cs @@ -0,0 +1,118 @@ +namespace EMU7800.Core +{ + /// + /// Atari 7800 SuperGame bankswitched cartridge w/Pokey + /// + public sealed class Cart78SGP : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank0: 0x00000:0x4000 + // Bank1: 0x04000:0x4000 0x4000:0x4000 Pokey + // Bank2: 0x08000:0x4000 0x8000:0x4000 Bank0-7 (0 on startup) + // Bank3: 0x0c000:0x4000 0xc000:0x4000 Bank7 + // Bank4: 0x10000:0x4000 + // Bank5: 0x14000:0x4000 + // Bank6: 0x18000:0x4000 + // Bank7: 0x1c000:0x4000 + // + readonly int[] _bank = new int[4]; + PokeySound _pokeySound; + + #region IDevice Members + + public override void Reset() + { + base.Reset(); + if (_pokeySound != null) + _pokeySound.Reset(); + } + + public override byte this[ushort addr] + { + get + { + var bankNo = addr >> 14; + switch (bankNo) + { + case 1: + return (_pokeySound != null) ? _pokeySound.Read(addr) : (byte)0; + default: + return ROM[(_bank[bankNo] << 14) | (addr & 0x3fff)]; + } + } + set + { + var bankNo = addr >> 14; + switch (bankNo) + { + case 1: + if (_pokeySound != null) + _pokeySound.Update(addr, value); + break; + case 2: + _bank[2] = value & 7; + break; + } + } + } + + #endregion + + public override void Attach(MachineBase m) + { + base.Attach(m); + if (_pokeySound == null) + _pokeySound = new PokeySound(M); + } + + public override void StartFrame() + { + if (_pokeySound != null) + _pokeySound.StartFrame(); + } + + public override void EndFrame() + { + if (_pokeySound != null) + _pokeySound.EndFrame(); + } + + private Cart78SGP() + { + } + + public Cart78SGP(byte[] romBytes) + { + _bank[2] = 0; + _bank[3] = 7; + + LoadRom(romBytes, 0x20000); + } + + #region Serialization Members + + public Cart78SGP(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadBytes()); + _bank = input.ReadIntegers(4); + _pokeySound = input.ReadOptionalPokeySound(m); + } + + public override void GetObjectData(SerializationContext output) + { + if (_pokeySound == null) + throw new Emu7800SerializationException("Cart78SGP must be attached before serialization."); + + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(_bank); + output.WriteOptional(_pokeySound); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16K.cs new file mode 100644 index 0000000000..69bcc68700 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16K.cs @@ -0,0 +1,86 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 16KB bankswitched carts + /// + public sealed class CartA16K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x1ff9-0x1ff9 + // Bank2: 0x1000:0x1000 + // Bank3: 0x2000:0x1000 + // Bank4: 0x3000:0x1000 + // + ushort BankBaseAddr; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 0; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + UpdateBank(addr); + } + } + + #endregion + + private CartA16K() + { + } + + public CartA16K(byte[] romBytes) + { + LoadRom(romBytes, 0x4000); + Bank = 0; + } + + void UpdateBank(ushort addr) + { + if (addr < 0x0ff6 || addr > 0x0ff9) + {} + else + { + Bank = addr - 0x0ff6; + } + } + + #region Serialization Members + + public CartA16K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x4000), 0x4000); + BankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(BankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16KR.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16KR.cs new file mode 100644 index 0000000000..cd5f307bce --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA16KR.cs @@ -0,0 +1,100 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 16KB bankswitched carts with 128 bytes of RAM + /// + public sealed class CartA16KR : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x1ff9-0x1ff9 + // Bank2: 0x1000:0x1000 + // Bank3: 0x2000:0x1000 + // Bank4: 0x3000:0x1000 + // Shadows ROM + // 0x1000:0x0080 RAM write port + // 0x1080:0x0080 RAM read port + // + ushort BankBaseAddr; + readonly byte[] RAM; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 0; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + if (addr < 0x0100 && addr >= 0x0080) + { + return RAM[addr & 0x7f]; + } + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + if (addr < 0x0080) + { + RAM[addr & 0x7f] = value; + return; + } + UpdateBank(addr); + } + } + + #endregion + + private CartA16KR() + { + } + + public CartA16KR(byte[] romBytes) + { + LoadRom(romBytes, 0x4000); + Bank = 0; + RAM = new byte[0x80]; + } + + void UpdateBank(ushort addr) + { + if (addr < 0x0ff6 || addr > 0x0ff9) + {} + else + { + Bank = addr - 0x0ff6; + } + } + + #region Serialization Members + + public CartA16KR(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x4000), 0x4000); + BankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(BankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA2K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA2K.cs new file mode 100644 index 0000000000..14674c4138 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA2K.cs @@ -0,0 +1,56 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 2KB carts (no bankswitching) + /// + public sealed class CartA2K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // 0x0000:0x0800 0x1000:0x0800 + // 0x1800:0x0800 (1st 2k bank repeated) + // + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return ROM[addr & 0x07ff]; } + set { } + } + + #endregion + + private CartA2K() + { + } + + public CartA2K(byte[] romBytes) + { + LoadRom(romBytes, 0x0800); + } + + public CartA2K(byte[] romBytes, int multicartBankSelector) + { + LoadRom(romBytes, 0x800, multicartBankSelector & 0x1f); + } + + #region Serialization Members + + public CartA2K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x0800), 0x0800); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32K.cs new file mode 100644 index 0000000000..d3140ac915 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32K.cs @@ -0,0 +1,88 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 32KB bankswitched carts + /// + public sealed class CartA32K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x0ff4-0x0ffc + // Bank2: 0x1000:0x1000 + // Bank3: 0x2000:0x1000 + // Bank4: 0x3000:0x1000 + // Bank5: 0x4000:0x1000 + // Bank6: 0x5000:0x1000 + // Bank7: 0x6000:0x1000 + // Bank8: 0x7000:0x1000 + // + ushort BankBaseAddr; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 7; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + UpdateBank(addr); + } + } + + #endregion + + private CartA32K() + { + } + + public CartA32K(byte[] romBytes) + { + LoadRom(romBytes, 0x8000); + Bank = 7; + } + + void UpdateBank(ushort addr) + { + if (addr < 0x0ffc && addr >= 0x0ff4) + { + Bank = addr - 0x0ff4; + } + } + + #region Serialization Members + + public CartA32K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x8000), 0x8000); + BankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(BankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32KR.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32KR.cs new file mode 100644 index 0000000000..74e43461cb --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA32KR.cs @@ -0,0 +1,104 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 32KB bankswitched carts with 128 bytes of RAM + /// + public sealed class CartA32KR : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x0ff4-0x0ffc + // Bank2: 0x1000:0x1000 + // Bank3: 0x2000:0x1000 + // Bank4: 0x3000:0x1000 + // Bank5: 0x4000:0x1000 + // Bank6: 0x5000:0x1000 + // Bank7: 0x6000:0x1000 + // Bank8: 0x7000:0x1000 + // Shadows ROM + // 0x1000:0x80 RAM write port + // 0x1080:0x80 RAM read port + // + ushort BankBaseAddr; + readonly byte[] RAM; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 7; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + if (addr >= 0x0080 && addr < 0x0100) + { + return RAM[addr & 0x007f]; + } + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + if (addr < 0x0080) + { + RAM[addr & 0x007f] = value; + return; + } + UpdateBank(addr); + } + } + + #endregion + + private CartA32KR() + { + } + + public CartA32KR(byte[] romBytes) + { + LoadRom(romBytes, 0x8000); + RAM = new byte[0x80]; + Bank = 7; + } + + void UpdateBank(ushort addr) + { + if (addr < 0x0ffc && addr >= 0x0ff4 ) + { + Bank = addr - 0x0ff4; + } + } + + #region Serialization Members + + public CartA32KR(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x8000), 0x8000); + RAM = input.ReadExpectedBytes(0x80); + BankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(RAM); + output.Write(BankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA4K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA4K.cs new file mode 100644 index 0000000000..17726a3c2b --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA4K.cs @@ -0,0 +1,47 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 4KB carts (no bankswitching) + /// + public sealed class CartA4K : Cart + { + #region IDevice Members + + public override void Reset() { } + + public override byte this[ushort addr] + { + get { return ROM[addr & 0x0fff]; } + set { } + } + + #endregion + + private CartA4K() + { + } + + public CartA4K(byte[] romBytes) + { + LoadRom(romBytes, 0x1000); + } + + #region Serialization Members + + public CartA4K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x1000), 0x1000); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8K.cs new file mode 100644 index 0000000000..c8f531e5cb --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8K.cs @@ -0,0 +1,87 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 8KB bankswitched carts + /// + public sealed class CartA8K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x1ff8,0x1ff9 + // Bank2: 0x1000:0x1000 + // + ushort BankBaseAddr; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 1; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + UpdateBank(addr); + } + } + + #endregion + + private CartA8K() + { + } + + public CartA8K(byte[] romBytes) + { + LoadRom(romBytes, 0x2000); + Bank = 1; + } + + void UpdateBank(ushort addr) + { + switch(addr) + { + case 0x0ff8: + Bank = 0; + break; + case 0x0ff9: + Bank = 1; + break; + } + } + + #region Serialization Members + + public CartA8K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x2000), 0x2000); + BankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(BankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8KR.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8KR.cs new file mode 100644 index 0000000000..a0fd89e034 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartA8KR.cs @@ -0,0 +1,103 @@ +namespace EMU7800.Core +{ + /// + /// Atari standard 8KB bankswitched carts with 128 bytes of RAM + /// + public sealed class CartA8KR : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x1ff8,0x1ff9 + // Bank2: 0x1000:0x1000 + // Shadows ROM + // 0x1000:0x0080 RAM write port + // 0x1080:0x0080 RAM read port + // + ushort BankBaseAddr; + readonly byte[] RAM; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 1; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + if (addr < 0x0100 && addr >= 0x0080) + { + return RAM[addr & 0x7f]; + } + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + if (addr < 0x0080) + { + RAM[addr & 0x7f] = value; + return; + } + UpdateBank(addr); + } + } + + #endregion + + private CartA8KR() + { + } + + public CartA8KR(byte[] romBytes) + { + LoadRom(romBytes, 0x2000); + Bank = 1; + RAM = new byte[0x80]; + } + + void UpdateBank(ushort addr) + { + switch(addr) + { + case 0x0ff8: + Bank = 0; + break; + case 0x0ff9: + Bank = 1; + break; + } + } + + #region Serialization Members + + public CartA8KR(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x2000), 0x2000); + RAM = input.ReadExpectedBytes(0x80); + BankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(RAM); + output.Write(BankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartCBS12K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartCBS12K.cs new file mode 100644 index 0000000000..9b0d7bbece --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartCBS12K.cs @@ -0,0 +1,100 @@ +namespace EMU7800.Core +{ + /// + /// CBS RAM Plus 12KB bankswitched carts with 128 bytes of RAM. + /// + public sealed class CartCBS12K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 Bank1:0x1000:0x1000 Select Segment: 0ff8-0ffa + // Bank2: 0x1000:0x1000 + // Bank3: 0x2000:0x1000 + // Shadows ROM + // 0x1000:0x80 RAM write port + // 0x1080:0x80 RAM read port + // + ushort BankBaseAddr; + readonly byte[] RAM; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 2; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + if (addr < 0x0200 && addr >= 0x0100) + { + return RAM[addr & 0xff]; + } + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + if (addr < 0x0100) + { + RAM[addr & 0xff] = value; + return; + } + UpdateBank(addr); + } + } + + #endregion + + private CartCBS12K() + { + } + + public CartCBS12K(byte[] romBytes) + { + LoadRom(romBytes, 0x3000); + Bank = 2; + RAM = new byte[0x100]; + } + + void UpdateBank(ushort addr) + { + if (addr < 0x0ff8 || addr > 0x0ffa) { } + else + { + Bank = addr - 0x0ff8; + } + } + + #region Serialization Members + + public CartCBS12K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x3000), 0x3000); + RAM = input.ReadExpectedBytes(0x100); + BankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(RAM); + output.Write(BankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDC8K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDC8K.cs new file mode 100644 index 0000000000..0b3808e24c --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDC8K.cs @@ -0,0 +1,55 @@ +namespace EMU7800.Core +{ + /// + /// Activison's Robot Tank and Decathlon 8KB bankswitching cart. + /// + public sealed class CartDC8K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by A13=0/1? + // Bank2: 0x1000:0x1000 + // + // This does what the Stella code does, which is to follow A13 to determine + // the bank. Since A0-A12 are the only significant bits on the program + // counter, I am unsure how the cart/hardware could utilize this. + // + + #region IDevice Members + + public override byte this[ushort addr] + { + get { return (addr & 0x2000) == 0 ? ROM[addr & 0x0fff + 0x1000] : ROM[addr & 0x0fff]; } + set { } + } + + #endregion + + private CartDC8K() + { + } + + public CartDC8K(byte[] romBytes) + { + LoadRom(romBytes, 0x2000); + } + + #region Serialization Members + + public CartDC8K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x2000), 0x2000); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDPC.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDPC.cs new file mode 100644 index 0000000000..4695f64df7 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartDPC.cs @@ -0,0 +1,341 @@ +namespace EMU7800.Core +{ + /// + /// Pitfall II cartridge. + /// There are two 4k banks, 2k display bank, and the DPC chip. + /// For complete details on the DPC chip see David P. Crane's United States Patent Number 4,644,495. + /// + public sealed class CartDPC : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x1ff8,0x1ff9 + // Bank2: 0x1000:0x1000 + // + const ushort DisplayBaseAddr = 0x2000; + ushort BankBaseAddr; + + readonly byte[] MusicAmplitudes = new byte[] { 0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f }; + + readonly byte[] Tops = new byte[8]; + readonly byte[] Bots = new byte[8]; + readonly ushort[] Counters = new ushort[8]; + readonly byte[] Flags = new byte[8]; + readonly bool[] MusicMode = new bool[3]; + + ulong LastSystemClock; + double FractionalClocks; + + byte _ShiftRegister; + + int Bank + { + set { BankBaseAddr = (ushort)(value * 0x1000); } + } + + // + // Generate a sequence of pseudo-random numbers 255 numbers long + // by emulating an 8-bit shift register with feedback taps at + // bits 4, 3, 2, and 0. + byte ShiftRegister + { + get + { + var a = _ShiftRegister; + a &= (1 << 0); + + var x = _ShiftRegister; + x &= (1 << 2); + x >>= 2; + a ^= x; + + x = _ShiftRegister; + x &= (1 << 3); + x >>= 3; + a ^= x; + + x = _ShiftRegister; + x &= (1 << 4); + x >>= 4; + a ^= x; + + a <<= 7; + _ShiftRegister >>= 1; + _ShiftRegister |= a; + + return _ShiftRegister; + } + set { _ShiftRegister = value; } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 1; + LastSystemClock = 3*M.CPU.Clock; + FractionalClocks = 0.0; + ShiftRegister = 1; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + if (addr < 0x0040) + { + return ReadPitfall2Reg(addr); + } + UpdateBank(addr); + return ROM[BankBaseAddr + addr]; + } + set + { + addr &= 0x0fff; + if (addr >= 0x0040 && addr < 0x0080) + { + WritePitfall2Reg(addr, value); + } + else + { + UpdateBank(addr); + } + } + } + + #endregion + + private CartDPC() + { + } + + public CartDPC(byte[] romBytes) + { + LoadRom(romBytes, 0x2800); + Bank = 1; + } + + void UpdateBank(ushort addr) + { + switch(addr) + { + case 0x0ff8: + Bank = 0; + break; + case 0x0ff9: + Bank = 1; + break; + } + } + + byte ReadPitfall2Reg(ushort addr) + { + byte result; + + var i = addr & 0x07; + var fn = (addr >> 3) & 0x07; + + // Update flag register for selected data fetcher + if ((Counters[i] & 0x00ff) == Tops[i]) + { + Flags[i] = 0xff; + } + else if ((Counters[i] & 0x00ff) == Bots[i]) + { + Flags[i] = 0x00; + } + + switch (fn) + { + case 0x00: + if (i < 4) + { + // This is a random number read + result = ShiftRegister; + break; + } + // Its a music read + UpdateMusicModeDataFetchers(); + + byte j = 0; + if (MusicMode[0] && Flags[5] != 0) + { + j |= 0x01; + } + if (MusicMode[1] && Flags[6] != 0) + { + j |= 0x02; + } + if (MusicMode[2] && Flags[7] != 0) + { + j |= 0x04; + } + result = MusicAmplitudes[j]; + break; + // DFx display data read + case 0x01: + result = ROM[DisplayBaseAddr + 0x7ff - Counters[i]]; + break; + // DFx display data read AND'd w/flag + case 0x02: + result = ROM[DisplayBaseAddr + 0x7ff - Counters[i]]; + result &= Flags[i]; + break; + // DFx flag + case 0x07: + result = Flags[i]; + break; + default: + result = 0; + break; + } + + // Clock the selected data fetcher's counter if needed + if (i < 5 || i >= 5 && MusicMode[i - 5] == false) + { + Counters[i]--; + Counters[i] &= 0x07ff; + } + + return result; + } + + void UpdateMusicModeDataFetchers() + { + var sysClockDelta = 3*M.CPU.Clock - LastSystemClock; + LastSystemClock = 3*M.CPU.Clock; + + var OSCclocks = ((15750.0 * sysClockDelta) / 1193191.66666667) + FractionalClocks; + + var wholeClocks = (int)OSCclocks; + FractionalClocks = OSCclocks - wholeClocks; + if (wholeClocks <= 0) + { + return; + } + + for (var i=0; i < 3; i++) + { + var r = i + 5; + if (!MusicMode[i]) continue; + + var top = Tops[r] + 1; + var newLow = Counters[r] & 0x00ff; + + if (Tops[r] != 0) + { + newLow -= (wholeClocks % top); + if (newLow < 0) + { + newLow += top; + } + } + else + { + newLow = 0; + } + + if (newLow <= Bots[r]) + { + Flags[r] = 0x00; + } + else if (newLow <= Tops[r]) + { + Flags[r] = 0xff; + } + + Counters[r] = (ushort)((Counters[r] & 0x0700) | (ushort)newLow); + } + } + + void WritePitfall2Reg(ushort addr, byte val) + { + var i = addr & 0x07; + var fn = (addr >> 3) & 0x07; + + switch (fn) + { + // DFx top count + case 0x00: + Tops[i] = val; + Flags[i] = 0x00; + break; + // DFx bottom count + case 0x01: + Bots[i] = val; + break; + // DFx counter low + case 0x02: + Counters[i] &= 0x0700; + if (i >= 5 && MusicMode[i - 5]) + { + // Data fetcher is in music mode so its low counter value + // should be loaded from the top register not the poked value + Counters[i] |= Tops[i]; + } + else + { + // Data fetcher is either not a music mode data fetcher or it + // isn't in music mode so it's low counter value should be loaded + // with the poked value + Counters[i] |= val; + } + break; + // DFx counter high + case 0x03: + Counters[i] &= 0x00ff; + Counters[i] |= (ushort)((val & 0x07) << 8); + // Execute special code for music mode data fetchers + if (i >= 5) + { + MusicMode[i - 5] = (val & 0x10) != 0; + // NOTE: We are not handling the clock source input for + // the music mode data fetchers. We're going to assume + // they always use the OSC input. + } + break; + // Random Number Generator Reset + case 0x06: + ShiftRegister = 1; + break; + } + } + + #region Serialization Members + + public CartDPC(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x2800), 0x2800); + BankBaseAddr = input.ReadUInt16(); + Tops = input.ReadExpectedBytes(8); + Bots = input.ReadExpectedBytes(8); + Counters = input.ReadUnsignedShorts(8); + Flags = input.ReadExpectedBytes(8); + MusicMode = input.ReadBooleans(3); + LastSystemClock = input.ReadUInt64(); + FractionalClocks = input.ReadDouble(); + _ShiftRegister = input.ReadByte(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(BankBaseAddr); + output.Write(Tops); + output.Write(Bots); + output.Write(Counters); + output.Write(Flags); + output.Write(MusicMode); + output.Write(LastSystemClock); + output.Write(FractionalClocks); + output.Write(_ShiftRegister); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartMN16K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartMN16K.cs new file mode 100644 index 0000000000..fbcaf99bca --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartMN16K.cs @@ -0,0 +1,136 @@ +namespace EMU7800.Core +{ + /// + /// M-Network 16KB bankswitched carts with 2KB RAM. + /// + public sealed class CartMN16K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Segment1: 0x0000:0x0800 Bank1:0x1000:0x0800 Select Seg: 1fe0-1fe6, 1fe7=RAM Seg1 + // Segment2: 0x0800:0x0800 Bank2:0x1800:0x0800 Always Seg8 + // Segment3: 0x1000:0x0800 + // Segment4: 0x1800:0x0800 + // Segment5: 0x2000:0x0800 + // Segment6: 0x2800:0x0800 + // Segment7: 0x3000:0x0800 + // Segment8: 0x3800:0x0800 + // + // RAM RAM Segment1 when 1fe7 select is accessed + // Segment1: 0x0000:0x0400 0x1000-0x13FF write port + // Segment2: 0x0400:0x0400 0x1400-0x17FF read port + // + // RAM Segment2: 1ff8-1ffb selects 256-byte block + // 0x1800-0x18ff write port + // 0x1900-0x19ff read port + // + ushort BankBaseAddr, BankBaseRAMAddr; + bool RAMBankOn; + readonly byte[] RAM; + + int Bank + { + set + { + BankBaseAddr = (ushort)(value << 11); // multiply by 2048 + RAMBankOn = (value == 0x07); + } + } + + int BankRAM + { + set { BankBaseRAMAddr = (ushort) (value << 8); } // multiply by 256 + } + + #region IDevice Members + + public override void Reset() + { + Bank = 0; + BankRAM = 0; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + UpdateBanks(addr); + if (RAMBankOn && addr >= 0x0400 && addr < 0x0800) + { + return RAM[addr & 0x03ff]; + } + if (addr >= 0x0900 && addr < 0x0a00) + { + return RAM[0x400 + BankBaseRAMAddr + (addr & 0xff)]; + } + return addr < 0x0800 ? ROM[BankBaseAddr + (addr & 0x07ff)] : ROM[0x3800 + (addr & 0x07ff)]; + } + set + { + addr &= 0x0fff; + UpdateBanks(addr); + if (RAMBankOn && addr < 0x0400) + { + RAM[addr & 0x03ff] = value; + } + else if (addr >= 0x0800 && addr < 0x0900) + { + RAM[0x400 + BankBaseRAMAddr + (addr & 0xff)] = value; + } + } + } + + #endregion + + private CartMN16K() + { + } + + public CartMN16K(byte[] romBytes) + { + LoadRom(romBytes, 0x4000); + RAM = new byte[0x800]; + Bank = 0; + BankRAM = 0; + } + + void UpdateBanks(ushort addr) + { + if (addr >= 0x0fe0 && addr < 0x0fe8) + { + Bank = addr & 0x07; + } + else if (addr >= 0x0fe8 && addr < 0x0fec) + { + BankRAM = addr & 0x03; + } + } + + #region Serialization Members + + public CartMN16K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x4000), 0x4000); + RAM = input.ReadExpectedBytes(0x800); + BankBaseAddr = input.ReadUInt16(); + BankBaseRAMAddr = input.ReadUInt16(); + RAMBankOn = input.ReadBoolean(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(RAM); + output.Write(BankBaseAddr); + output.Write(BankBaseRAMAddr); + output.Write(RAMBankOn); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartPB8K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartPB8K.cs new file mode 100644 index 0000000000..150bca0932 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartPB8K.cs @@ -0,0 +1,103 @@ +namespace EMU7800.Core +{ + /// + /// Parker Brothers 8KB bankswitched carts. + /// + public sealed class CartPB8K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Segment1: 0x0000:0x0400 Bank1:0x1000:0x0400 Select Segment: 1fe0-1fe7 + // Segment2: 0x0400:0x0400 Bank2:0x1400:0x0400 Select Segment: 1fe8-1ff0 + // Segment3: 0x0800:0x0400 Bank3:0x1800:0x0400 Select Segment: 1ff0-1ff8 + // Segment4: 0x0c00:0x0400 Bank4:0x1c00:0x0400 Always Segment8 + // Segment5: 0x1000:0x0400 + // Segment6: 0x1400:0x0400 + // Segment7: 0x1800:0x0400 + // Segment8: 0x1c00:0x0400 + // + readonly ushort[] SegmentBase; + + #region IDevice Members + + public override void Reset() + { + SegmentBase[0] = ComputeSegmentBase(4); + SegmentBase[1] = ComputeSegmentBase(5); + SegmentBase[2] = ComputeSegmentBase(6); + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + UpdateSegmentBases(addr); + return ROM[SegmentBase[addr >> 10] + (addr & 0x03ff)]; + } + set + { + addr &= 0x0fff; + UpdateSegmentBases(addr); + } + } + + #endregion + + private CartPB8K() + { + } + + public CartPB8K(byte[] romBytes) + { + LoadRom(romBytes, 0x2000); + SegmentBase = new ushort[4]; + SegmentBase[0] = ComputeSegmentBase(4); + SegmentBase[1] = ComputeSegmentBase(5); + SegmentBase[2] = ComputeSegmentBase(6); + SegmentBase[3] = ComputeSegmentBase(7); + } + + static ushort ComputeSegmentBase(int slice) + { + return (ushort)(slice << 10); // multiply by 1024 + } + + void UpdateSegmentBases(ushort addr) + { + if (addr < 0xfe0 || addr >= 0x0ff8) { } + else if (addr >= 0x0fe0 && addr < 0x0fe8) + { + SegmentBase[0] = ComputeSegmentBase(addr & 0x07); + } + else if (addr >= 0x0fe8 && addr < 0x0ff0) + { + SegmentBase[1] = ComputeSegmentBase(addr & 0x07); + } + else if (addr >= 0x0ff0 && addr < 0x0ff8) + { + SegmentBase[2] = ComputeSegmentBase(addr & 0x07); + } + } + + #region Serialization Members + + public CartPB8K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x2000), 0x2000); + SegmentBase = input.ReadUnsignedShorts(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(SegmentBase); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartTV8K.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartTV8K.cs new file mode 100644 index 0000000000..cfeaa9d038 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartTV8K.cs @@ -0,0 +1,90 @@ +namespace EMU7800.Core +{ + /// + /// Tigervision 8KB bankswitched carts + /// + public sealed class CartTV8K : Cart + { + // + // Cart Format Mapping to ROM Address Space + // Segment1: 0x0000:0x0800 0x1000:0x0800 Selected segment via $003F + // Segment2: 0x0800:0x0800 0x1800:0x0800 Always last segment + // Segment3: 0x1000:0x0800 + // Segment4: 0x1800:0x0800 + // + ushort BankBaseAddr; + readonly ushort LastBankBaseAddr; + + byte Bank + { + set + { + BankBaseAddr = (ushort)(0x0800 * value); + BankBaseAddr %= (ushort)ROM.Length; + } + } + + protected internal override bool RequestSnooping + { + get { return true; } + } + + #region IDevice Members + + public override void Reset() + { + Bank = 0; + } + + public override byte this[ushort addr] + { + get + { + addr &= 0x0fff; + return addr < 0x0800 ? ROM[BankBaseAddr + (addr & 0x07ff)] : ROM[LastBankBaseAddr + (addr & 0x07ff)]; + } + set + { + if (addr <= 0x003f) + { + Bank = value; + } + } + } + + #endregion + + private CartTV8K() + { + } + + public CartTV8K(byte[] romBytes) + { + LoadRom(romBytes, 0x1000); + Bank = 0; + LastBankBaseAddr = (ushort)(ROM.Length - 0x0800); + } + + #region Serialization Members + + public CartTV8K(DeserializationContext input, MachineBase m) : base(input) + { + input.CheckVersion(1); + LoadRom(input.ReadExpectedBytes(0x1000), 0x1000); + BankBaseAddr = input.ReadUInt16(); + LastBankBaseAddr = input.ReadUInt16(); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(BankBaseAddr); + output.Write(LastBankBaseAddr); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartType.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartType.cs new file mode 100644 index 0000000000..a334cb5034 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/CartType.cs @@ -0,0 +1,43 @@ +/* + * CartType.cs + * + * Defines the set of all known Game Cartridges. + * + * 2010 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public enum CartType + { + None, + A2K, // Atari 2kb cart + TV8K, // Tigervision 8kb bankswitched cart + A4K, // Atari 4kb cart + PB8K, // Parker Brothers 8kb bankswitched cart + MN16K, // M-Network 16kb bankswitched cart + A16K, // Atari 16kb bankswitched cart + A16KR, // Atari 16kb bankswitched cart w/128 bytes RAM + A8K, // Atari 8KB bankswitched cart + A8KR, // Atari 8KB bankswitched cart w/128 bytes RAM + A32K, // Atari 32KB bankswitched cart + A32KR, // Atari 32KB bankswitched cart w/128 bytes RAM + CBS12K, // CBS' RAM Plus bankswitched cart w/256 bytes RAM + DC8K, // Special Activision cart (Robot Tank and Decathlon) + DPC, // Pitfall II DPC cart + M32N12K, // 32N1 Multicart: 32x2KB + A7808, // Atari7800 non-bankswitched 8KB cart + A7816, // Atari7800 non-bankswitched 16KB cart + A7832, // Atari7800 non-bankswitched 32KB cart + A7832P, // Atari7800 non-bankswitched 32KB cart w/Pokey + A7848, // Atari7800 non-bankswitched 48KB cart + A78SG, // Atari7800 SuperGame cart + A78SGP, // Atari7800 SuperGame cart w/Pokey + A78SGR, // Atari7800 SuperGame cart w/RAM + A78S9, // Atari7800 SuperGame cart, nine banks + A78S4, // Atari7800 SuperGame cart, four banks + A78S4R, // Atari7800 SuperGame cart, four banks, w/RAM + A78AB, // F18 Hornet cart (Absolute) + A78AC, // Double dragon cart (Activision) + }; +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ConsoleSwitch.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ConsoleSwitch.cs new file mode 100644 index 0000000000..7a07ba7eb5 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ConsoleSwitch.cs @@ -0,0 +1,11 @@ +namespace EMU7800.Core +{ + internal enum ConsoleSwitch + { + GameReset, + GameSelect, + GameBW, + LeftDifficultyA, + RightDifficultyA, + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Controller.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Controller.cs new file mode 100644 index 0000000000..bc21cbcbd6 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Controller.cs @@ -0,0 +1,14 @@ +namespace EMU7800.Core +{ + public enum Controller + { + None, + Joystick, + Paddles, + Keypad, + Driving, + BoosterGrip, + ProLineJoystick, + Lightgun, + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ControllerAction.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ControllerAction.cs new file mode 100644 index 0000000000..bad0cc82ee --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ControllerAction.cs @@ -0,0 +1,17 @@ +namespace EMU7800.Core +{ + public enum ControllerAction + { + Up, + Down, + Left, + Right, + Trigger, // Interpretation: 7800 RFire; 2600 Fire, BoosterGrip top + Trigger2, // Interpretation: 7800 LFire, BoosterGrip trigger + Keypad1, Keypad2, Keypad3, + Keypad4, Keypad5, Keypad6, + Keypad7, Keypad8, Keypad9, + KeypadA, Keypad0, KeypadP, + Driving0, Driving1, Driving2, Driving3, + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/DeserializationContext.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/DeserializationContext.cs new file mode 100644 index 0000000000..d3f8acb806 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/DeserializationContext.cs @@ -0,0 +1,245 @@ +using System; +using System.IO; +using System.Linq; + +namespace EMU7800.Core +{ + /// + /// A context for deserializing objects. + /// + public class DeserializationContext + { + #region Fields + + readonly BinaryReader _binaryReader; + + #endregion + + public bool ReadBoolean() + { + return _binaryReader.ReadBoolean(); + } + + public byte ReadByte() + { + return _binaryReader.ReadByte(); + } + + public ushort ReadUInt16() + { + return _binaryReader.ReadUInt16(); + } + + public int ReadInt32() + { + return _binaryReader.ReadInt32(); + } + + public uint ReadUInt32() + { + return _binaryReader.ReadUInt32(); + } + + public long ReadInt64() + { + return _binaryReader.ReadInt64(); + } + + public ulong ReadUInt64() + { + return _binaryReader.ReadUInt64(); + } + + public double ReadDouble() + { + return _binaryReader.ReadDouble(); + } + + public BufferElement ReadBufferElement() + { + var be = new BufferElement(); + for (var i = 0; i < BufferElement.SIZE; i++) + be[i] = ReadByte(); + return be; + } + + public byte[] ReadBytes() + { + var count = _binaryReader.ReadInt32(); + if (count <= 0) + return new byte[0]; + if (count > 0x40000) + throw new Emu7800SerializationException("Byte array length too large."); + return _binaryReader.ReadBytes(count); + } + + public byte[] ReadExpectedBytes(params int[] expectedSizes) + { + var count = _binaryReader.ReadInt32(); + if (!expectedSizes.Any(t => t == count)) + throw new Emu7800SerializationException("Byte array length incorrect."); + return _binaryReader.ReadBytes(count); + } + + public byte[] ReadOptionalBytes(params int[] expectedSizes) + { + var hasBytes = _binaryReader.ReadBoolean(); + return (hasBytes) ? ReadExpectedBytes(expectedSizes) : null; + } + + public ushort[] ReadUnsignedShorts(params int[] expectedSizes) + { + var bytes = ReadExpectedBytes(expectedSizes.Select(t => t << 1).ToArray()); + var ushorts = new ushort[bytes.Length >> 1]; + Buffer.BlockCopy(bytes, 0, ushorts, 0, bytes.Length); + return ushorts; + } + + public int[] ReadIntegers(params int[] expectedSizes) + { + var bytes = ReadExpectedBytes(expectedSizes.Select(t => t << 2).ToArray()); + var integers = new int[bytes.Length >> 2]; + Buffer.BlockCopy(bytes, 0, integers, 0, bytes.Length); + return integers; + } + + public uint[] ReadUnsignedIntegers(params int[] expectedSizes) + { + var bytes = ReadExpectedBytes(expectedSizes.Select(t => t << 2).ToArray()); + var uints = new uint[bytes.Length >> 2]; + Buffer.BlockCopy(bytes, 0, uints, 0, bytes.Length); + return uints; + } + + public bool[] ReadBooleans(params int[] expectedSizes) + { + var bytes = ReadExpectedBytes(expectedSizes); + var booleans = new bool[bytes.Length]; + for (var i = 0; i < bytes.Length; i++) + booleans[i] = (bytes[i] != 0); + return booleans; + } + + public int CheckVersion(params int[] validVersions) + { + var magicNumber = _binaryReader.ReadInt32(); + if (magicNumber != 0x78000087) + throw new Emu7800SerializationException("Magic number not found."); + var version = _binaryReader.ReadInt32(); + if (!validVersions.Any(t => t == version)) + throw new Emu7800SerializationException("Invalid version number found."); + return version; + } + + public MachineBase ReadMachine() + { + var typeName = _binaryReader.ReadString(); + if (string.IsNullOrWhiteSpace(typeName)) + throw new Emu7800SerializationException("Invalid type name."); + + var type = Type.GetType(typeName); + if (type == null) + throw new Emu7800SerializationException("Unable to resolve type name: " + typeName); + + return (MachineBase)Activator.CreateInstance(type, new object[] { this }); + } + + public AddressSpace ReadAddressSpace(MachineBase m, int addrSpaceShift, int pageShift) + { + var addressSpace = new AddressSpace(this, m, addrSpaceShift, pageShift); + return addressSpace; + } + + public M6502 ReadM6502(MachineBase m, int runClocksMultiple) + { + var cpu = new M6502(this, m, runClocksMultiple); + return cpu; + } + + public Maria ReadMaria(Machine7800 m, int scanlines) + { + var maria = new Maria(this, m, scanlines); + return maria; + } + + public PIA ReadPIA(MachineBase m) + { + var pia = new PIA(this, m); + return pia; + } + + public TIA ReadTIA(MachineBase m) + { + var tia = new TIA(this, m); + return tia; + } + + public TIASound ReadTIASound(MachineBase m, int cpuClocksPerSample) + { + var tiaSound = new TIASound(this, m, cpuClocksPerSample); + return tiaSound; + } + + public RAM6116 ReadRAM6116() + { + var ram6116 = new RAM6116(this); + return ram6116; + } + + public InputState ReadInputState() + { + var inputState = new InputState(this); + return inputState; + } + + public HSC7800 ReadOptionalHSC7800() + { + var exist = ReadBoolean(); + return exist ? new HSC7800(this) : null; + } + + public Bios7800 ReadOptionalBios7800() + { + var exist = ReadBoolean(); + return exist ? new Bios7800(this) : null; + } + + public PokeySound ReadOptionalPokeySound(MachineBase m) + { + var exist = ReadBoolean(); + return exist ? new PokeySound(this, m) : null; + } + + public Cart ReadCart(MachineBase m) + { + var typeName = _binaryReader.ReadString(); + if (string.IsNullOrWhiteSpace(typeName)) + throw new Emu7800SerializationException("Invalid type name."); + + var type = Type.GetType(typeName); + if (type == null) + throw new Emu7800SerializationException("Unable to resolve type name: " + typeName); + + return (Cart)Activator.CreateInstance(type, new object[] { this, m }); + } + + #region Constructors + + private DeserializationContext() + { + } + + /// + /// Instantiates a new instance of . + /// + /// + internal DeserializationContext(BinaryReader binaryReader) + { + if (binaryReader == null) + throw new ArgumentNullException("binaryReader"); + _binaryReader = binaryReader; + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800Exception.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800Exception.cs new file mode 100644 index 0000000000..18de9a42a4 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800Exception.cs @@ -0,0 +1,19 @@ +using System; + +namespace EMU7800.Core +{ + public class Emu7800Exception : Exception + { + internal Emu7800Exception() + { + } + + internal Emu7800Exception(string message) : base(message) + { + } + + internal Emu7800Exception(string message, Exception ex) : base(message, ex) + { + } + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800SerializationException.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800SerializationException.cs new file mode 100644 index 0000000000..4f0827d13c --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Emu7800SerializationException.cs @@ -0,0 +1,19 @@ +using System; + +namespace EMU7800.Core +{ + public class Emu7800SerializationException : Emu7800Exception + { + private Emu7800SerializationException() + { + } + + internal Emu7800SerializationException(string message) : base(message) + { + } + + internal Emu7800SerializationException(string message, Exception ex) : base(message, ex) + { + } + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FontRenderer.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FontRenderer.cs new file mode 100644 index 0000000000..f0737b98ff --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FontRenderer.cs @@ -0,0 +1,170 @@ +/* + * FontRenderer + * + * A simple font renderer for displaying text during emulation. Font data and + * rendering algorithm courtesy of Bradford W. Mott's Stella source. + * + * Copyright © 2004 Mike Murphy + * + */ + +using System; + +namespace EMU7800.Core +{ + /// + /// A simple font renderer for displaying text during emulation. + /// + public class FontRenderer + { + static readonly uint[] AlphaFontData = + { + 0x699f999, // A + 0xe99e99e, // B + 0x6988896, // C + 0xe99999e, // D + 0xf88e88f, // E + 0xf88e888, // F + 0x698b996, // G + 0x999f999, // H + 0x7222227, // I + 0x72222a4, // J + 0x9accaa9, // K + 0x888888f, // L + 0x9ff9999, // M + 0x9ddbb99, // N + 0x6999996, // O + 0xe99e888, // P + 0x69999b7, // Q + 0xe99ea99, // R + 0x6986196, // S + 0x7222222, // T + 0x9999996, // U + 0x9999966, // V + 0x9999ff9, // W + 0x99fff99, // X + 0x9996244, // Y + 0xf12488f // Z + }; + + static readonly uint[] DigitFontData = + { + 0x69bd996, // 0 + 0x2622227, // 1 + 0x691248f, // 2 + 0x6916196, // 3 + 0xaaaf222, // 4 + 0xf88e11e, // 5 + 0x698e996, // 6 + 0xf112244, // 7 + 0x6996996, // 8 + 0x6997196 // 9 + }; + + /// + /// Draw specified text at specified position using the specified foreground and background colors. + /// + /// + /// + /// + /// + /// + /// + /// text must be non-null. + public void DrawText(FrameBuffer frameBuffer, string text, int xoffset, int yoffset, byte fore, byte back) + { + if (text == null) + throw new ArgumentNullException("text"); + + var textchars = text.ToUpper().ToCharArray(); + + for (var i = 0; i < text.Length + 1; i++) + { + for (var j = 0; j < 9; j++) + { + var pos = (j + yoffset) * frameBuffer.VisiblePitch + i * 5; + for (var k = 0; k < 5; k++) + { + while (pos >= frameBuffer.VideoBufferByteLength) + { + pos -= frameBuffer.VideoBufferByteLength; + } + while (pos < 0) + { + pos += frameBuffer.VideoBufferByteLength; + } + frameBuffer.VideoBuffer[pos >> BufferElement.SHIFT][pos++] = back; + } + } + } + + for (var i = 0; i < text.Length; i++) + { + var c = textchars[i]; + uint fdata; + + switch (c) + { + case '/': + case '\\': + fdata = 0x0122448; + break; + case '(': + fdata = 0x2488842; + break; + case ')': + fdata = 0x4211124; + break; + case '.': + fdata = 0x0000066; + break; + case ':': + fdata = 0x0660660; + break; + case '-': + fdata = 0x0007000; + break; + default: + if (c >= 'A' && c <= 'Z') + { + fdata = AlphaFontData[c - 'A']; + } + else if (c >= '0' && c <= '9') + { + fdata = DigitFontData[c - '0']; + } + else + { + fdata = 0; + } + break; + } + + var ypos = 8; + for (var j = 0; j < 32; j++) + { + var xpos = j & 3; + if (xpos == 0) + { + ypos--; + } + + var pos = (ypos + yoffset) * frameBuffer.VisiblePitch + (4 - xpos) + xoffset; + while (pos >= frameBuffer.VideoBufferByteLength) + { + pos -= frameBuffer.VideoBufferByteLength; + } + while (pos < 0) + { + pos += frameBuffer.VideoBufferByteLength; + } + if (((fdata >> j) & 1) != 0) + { + frameBuffer.VideoBuffer[pos >> BufferElement.SHIFT][pos] = fore; + } + } + xoffset += 5; + } + } + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FrameBuffer.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FrameBuffer.cs new file mode 100644 index 0000000000..6074518ab9 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/FrameBuffer.cs @@ -0,0 +1,79 @@ +using System; + +namespace EMU7800.Core +{ + public class FrameBuffer + { + /// + /// Number of visible pixels on a single horizontal line. + /// + public int VisiblePitch { get; private set; } + + /// + /// Number of s that represent VisiblePitch. + /// + public int VideoBufferElementVisiblePitch { get; private set; } + + /// + /// Number of visible scan lines. + /// + public int Scanlines { get; private set; } + + /// + /// The number of bytes contained by VideoBuffer. + /// + public int VideoBufferByteLength { get; private set; } + + /// + /// The number of s contained by VideoBuffer + /// + public int VideoBufferElementLength { get; private set; } + + /// + /// The number of bytes contained by SoundBuffer. + /// + public int SoundBufferByteLength { get; private set; } + + /// + /// The number of s contained by SoundBuffer + /// + public int SoundBufferElementLength { get; private set; } + + /// + /// The buffer containing computed pixel data. + /// + public BufferElement[] VideoBuffer { get; private set; } + + /// + /// The buffer containing computed PCM audio data. + /// + public BufferElement[] SoundBuffer { get; private set; } + + #region Constructors + + private FrameBuffer() + { + } + + internal FrameBuffer(int visiblePitch, int scanLines) + { + if (visiblePitch < 0) + throw new ArgumentException("visiblePitch must be non-negative."); + if (scanLines < 0) + throw new ArgumentException("scanLines must be non-negative."); + + VisiblePitch = visiblePitch; + VideoBufferElementVisiblePitch = VisiblePitch >> BufferElement.SHIFT; + Scanlines = scanLines; + VideoBufferByteLength = VisiblePitch * Scanlines; + VideoBufferElementLength = VideoBufferElementVisiblePitch * Scanlines; + SoundBufferByteLength = Scanlines << 1; + SoundBufferElementLength = SoundBufferByteLength >> BufferElement.SHIFT; + + VideoBuffer = new BufferElement[VideoBufferElementLength + (64 >> BufferElement.SHIFT)]; + SoundBuffer = new BufferElement[SoundBufferElementLength + (64 >> BufferElement.SHIFT)]; + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/HSC7800.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/HSC7800.cs new file mode 100644 index 0000000000..4186e66097 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/HSC7800.cs @@ -0,0 +1,85 @@ +/* + * HSC7800.cs + * + * The 7800 High Score cartridge--courtesy of Matthias . + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class HSC7800 : IDevice + { + readonly byte[] ROM; + readonly ushort Mask; + + public static ushort Size { get; private set; } + + #region IDevice Members + + public void Reset() + { + } + + public byte this[ushort addr] + { + get { return ROM[addr & Mask]; } + set { } + } + + #endregion + + public RAM6116 SRAM { get; private set; } + + #region Constructors + + private HSC7800() + { + } + + public HSC7800(byte[] hscRom, byte[] ram) + { + if (hscRom == null) + throw new ArgumentNullException("hscRom"); + if (ram == null) + throw new ArgumentNullException("ram"); + if (hscRom.Length != 4096) + throw new ArgumentException("ROM size not 4096", "hscRom"); + + ROM = hscRom; + SRAM = new RAM6116(ram); + + Size = Mask = (ushort)ROM.Length; + Mask--; + } + + #endregion + + #region Serialization Members + + public HSC7800(DeserializationContext input) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + ROM = input.ReadExpectedBytes(4096); + SRAM = input.ReadRAM6116(); + + Size = Mask = (ushort)ROM.Length; + Mask--; + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(ROM); + output.Write(SRAM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/IDevice.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/IDevice.cs new file mode 100644 index 0000000000..38a8b83980 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/IDevice.cs @@ -0,0 +1,16 @@ +/* + * IDevice.cs + * + * Defines interface for devices accessable via the AddressSpace class. + * + * Copyright © 2003, 2011 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public interface IDevice + { + void Reset(); + byte this[ushort addr] { get; set; } + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ILogger.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ILogger.cs new file mode 100644 index 0000000000..919c3b0b25 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/ILogger.cs @@ -0,0 +1,10 @@ +namespace EMU7800.Core +{ + public interface ILogger + { + void WriteLine(string format, params object[] args); + void WriteLine(object value); + void Write(string format, params object[] args); + void Write(object value); + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/InputState.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/InputState.cs new file mode 100644 index 0000000000..f78c0b9c1a --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/InputState.cs @@ -0,0 +1,368 @@ +/* + * InputState.cs + * + * Class containing the input state of the console and its controllers, + * mapping emulator input devices to external input. + * + * Copyright © 2003-2010 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public class InputState + { + #region Fields + + const int + PaddleOhmMin = 100000, + PaddleOhmMax = 800000; + + const int + LeftControllerJackIndex = 0, + RightControllerJackIndex = 1, + ConsoleSwitchIndex = 2, + ControllerActionStateIndex = 3, + OhmsIndex = ControllerActionStateIndex + 4, + LightgunPositionIndex = ControllerActionStateIndex + 4, + InputStateSize = ControllerActionStateIndex + 8 + 1; + + // For driving controllers + readonly byte[] _rotGrayCodes = new byte[] { 0x0f, 0x0d, 0x0c, 0x0e }; + readonly int[] _rotState = new int[2]; + + readonly int[] _nextInputState = new int[InputStateSize]; + readonly int[] _inputState = new int[InputStateSize]; + + #endregion + + #region Public Members + + /// + /// Enables the incoming input state buffer to be populated prior to the start of the frame. + /// Useful for input playback senarios. + /// + /// Return value is ignored. + public Func InputAdvancing { get; set; } + + /// + /// Enables access to the input state buffer. + /// Useful for input recording senarios. + /// + /// Return value is ignored. + public Func InputAdvanced { get; set; } + + public void CaptureInputState() + { + if (InputAdvancing != null) + InputAdvancing(_nextInputState); + Buffer.BlockCopy(_nextInputState, 0, _inputState, 0, InputStateSize * sizeof(int)); + if (InputAdvanced != null) + InputAdvanced(_inputState); + } + + public Controller LeftControllerJack + { + get { return (Controller)_nextInputState[LeftControllerJackIndex]; } + set { _nextInputState[LeftControllerJackIndex] = (int)value; } + } + + public Controller RightControllerJack + { + get { return (Controller)_nextInputState[RightControllerJackIndex]; } + set { _nextInputState[RightControllerJackIndex] = (int)value; } + } + + public bool IsGameBWConsoleSwitchSet + { + get { return (_nextInputState[ConsoleSwitchIndex] & (1 << (int) ConsoleSwitch.GameBW)) != 0; } + } + + public bool IsLeftDifficultyAConsoleSwitchSet + { + get { return (_nextInputState[ConsoleSwitchIndex] & (1 << (int)ConsoleSwitch.LeftDifficultyA)) != 0; } + } + + public bool IsRightDifficultyAConsoleSwitchSet + { + get { return (_nextInputState[ConsoleSwitchIndex] & (1 << (int)ConsoleSwitch.RightDifficultyA)) != 0; } + } + + public void RaiseInput(int playerNo, MachineInput input, bool down) + { + switch (input) + { + case MachineInput.Fire: + SetControllerActionState(playerNo, ControllerAction.Trigger, down); + break; + case MachineInput.Fire2: + SetControllerActionState(playerNo, ControllerAction.Trigger2, down); + break; + case MachineInput.Left: + SetControllerActionState(playerNo, ControllerAction.Left, down); + if (down) SetControllerActionState(playerNo, ControllerAction.Right, false); + break; + case MachineInput.Up: + SetControllerActionState(playerNo, ControllerAction.Up, down); + if (down) SetControllerActionState(playerNo, ControllerAction.Down, false); + break; + case MachineInput.Right: + SetControllerActionState(playerNo, ControllerAction.Right, down); + if (down) SetControllerActionState(playerNo, ControllerAction.Left, false); + break; + case MachineInput.Down: + SetControllerActionState(playerNo, ControllerAction.Down, down); + if (down) SetControllerActionState(playerNo, ControllerAction.Up, false); + break; + case MachineInput.NumPad7: + SetControllerActionState(playerNo, ControllerAction.Keypad7, down); + break; + case MachineInput.NumPad8: + SetControllerActionState(playerNo, ControllerAction.Keypad8, down); + break; + case MachineInput.NumPad9: + SetControllerActionState(playerNo, ControllerAction.Keypad9, down); + break; + case MachineInput.NumPad4: + SetControllerActionState(playerNo, ControllerAction.Keypad4, down); + break; + case MachineInput.NumPad5: + SetControllerActionState(playerNo, ControllerAction.Keypad5, down); + break; + case MachineInput.NumPad6: + SetControllerActionState(playerNo, ControllerAction.Keypad6, down); + break; + case MachineInput.NumPad1: + SetControllerActionState(playerNo, ControllerAction.Keypad1, down); + break; + case MachineInput.NumPad2: + SetControllerActionState(playerNo, ControllerAction.Keypad2, down); + break; + case MachineInput.NumPad3: + SetControllerActionState(playerNo, ControllerAction.Keypad3, down); + break; + case MachineInput.NumPadMult: + SetControllerActionState(playerNo, ControllerAction.KeypadA, down); + break; + case MachineInput.NumPad0: + SetControllerActionState(playerNo, ControllerAction.Keypad0, down); + break; + case MachineInput.NumPadHash: + SetControllerActionState(playerNo, ControllerAction.KeypadP, down); + break; + case MachineInput.Driving0: + SetControllerActionState(playerNo, ControllerAction.Driving0, true); + SetControllerActionState(playerNo, ControllerAction.Driving1, false); + SetControllerActionState(playerNo, ControllerAction.Driving2, false); + SetControllerActionState(playerNo, ControllerAction.Driving3, false); + break; + case MachineInput.Driving1: + SetControllerActionState(playerNo, ControllerAction.Driving0, false); + SetControllerActionState(playerNo, ControllerAction.Driving1, true); + SetControllerActionState(playerNo, ControllerAction.Driving2, false); + SetControllerActionState(playerNo, ControllerAction.Driving3, false); + break; + case MachineInput.Driving2: + SetControllerActionState(playerNo, ControllerAction.Driving0, false); + SetControllerActionState(playerNo, ControllerAction.Driving1, false); + SetControllerActionState(playerNo, ControllerAction.Driving2, true); + SetControllerActionState(playerNo, ControllerAction.Driving3, false); + break; + case MachineInput.Driving3: + SetControllerActionState(playerNo, ControllerAction.Driving0, false); + SetControllerActionState(playerNo, ControllerAction.Driving1, false); + SetControllerActionState(playerNo, ControllerAction.Driving2, false); + SetControllerActionState(playerNo, ControllerAction.Driving3, true); + break; + case MachineInput.Reset: + SetConsoleSwitchState(ConsoleSwitch.GameReset, down); + break; + case MachineInput.Select: + SetConsoleSwitchState(ConsoleSwitch.GameSelect, down); + break; + case MachineInput.Color: + if (down) ToggleConsoleSwitchState(ConsoleSwitch.GameBW); + break; + case MachineInput.LeftDifficulty: + if (down) ToggleConsoleSwitchState(ConsoleSwitch.LeftDifficultyA); + break; + case MachineInput.RightDifficulty: + if (down) ToggleConsoleSwitchState(ConsoleSwitch.RightDifficultyA); + break; + } + } + + public void RaisePaddleInput(int playerNo, int valMax, int val) + { + var ohms = PaddleOhmMax - (PaddleOhmMax - PaddleOhmMin) / valMax * val; + _nextInputState[OhmsIndex + (playerNo & 3)] = ohms; + } + + public void RaiseLightgunPos(int playerNo, int scanline, int hpos) + { + var i = LightgunPositionIndex + ((playerNo & 1) << 1); + _nextInputState[i++] = scanline; + _nextInputState[i] = hpos; + } + + public void ClearAllInput() + { + _nextInputState[ConsoleSwitchIndex] = 0; + ClearLeftJackInput(); + ClearRightJackInput(); + } + + public void ClearInputByPlayer(int playerNo) + { + _nextInputState[OhmsIndex + (playerNo & 3)] = 0; + _nextInputState[ControllerActionStateIndex + (playerNo & 3)] = 0; + _nextInputState[LightgunPositionIndex + ((playerNo & 1) << 1)] = _nextInputState[LightgunPositionIndex + ((playerNo & 1) << 1) + 1] = 0; + } + + public void ClearLeftJackInput() + { + _nextInputState[OhmsIndex] = _nextInputState[OhmsIndex + 1] = 0; + _nextInputState[ControllerActionStateIndex] = 0; + switch (LeftControllerJack) + { + case Controller.Paddles: + _nextInputState[ControllerActionStateIndex] = _nextInputState[ControllerActionStateIndex + 1] = 0; + break; + default: + _nextInputState[ControllerActionStateIndex] = 0; + break; + } + _nextInputState[LightgunPositionIndex] = _nextInputState[LightgunPositionIndex + 1] = 0; + } + + public void ClearRightJackInput() + { + _nextInputState[OhmsIndex + 2] = _nextInputState[OhmsIndex + 3] = 0; + switch (RightControllerJack) + { + case Controller.Paddles: + _nextInputState[ControllerActionStateIndex + 2] = _nextInputState[ControllerActionStateIndex + 3] = 0; + break; + default: + _nextInputState[ControllerActionStateIndex + 1] = 0; + break; + } + _nextInputState[LightgunPositionIndex + 2] = _nextInputState[LightgunPositionIndex + 3] = 0; + } + + #endregion + + #region Serialization Members + + public InputState() + { + } + + public InputState(DeserializationContext input) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + _rotState = input.ReadIntegers(2); + _nextInputState = input.ReadIntegers(InputStateSize); + _inputState = input.ReadIntegers(InputStateSize); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(_rotState); + output.Write(_nextInputState); + output.Write(_inputState); + } + + #endregion + + #region Internal Members + + internal bool SampleCapturedConsoleSwitchState(ConsoleSwitch consoleSwitch) + { + return (_inputState[ConsoleSwitchIndex] & (1 << (int)consoleSwitch)) != 0; + } + + internal bool SampleCapturedControllerActionState(int playerno, ControllerAction action) + { + return (_inputState[ControllerActionStateIndex + (playerno & 3)] & (1 << (int)action)) != 0; + } + + internal int SampleCapturedOhmState(int playerNo) + { + return _inputState[OhmsIndex + (playerNo & 3)]; + } + + internal void SampleCapturedLightGunPosition(int playerNo, out int scanline, out int hpos) + { + var i = LightgunPositionIndex + ((playerNo & 1) << 1); + scanline = _inputState[i++]; + hpos = _inputState[i]; + } + + internal byte SampleCapturedDrivingState(int playerNo) + { + if (SampleCapturedControllerActionState(playerNo, ControllerAction.Driving0)) + _rotState[playerNo] = 0; + else if (SampleCapturedControllerActionState(playerNo, ControllerAction.Driving1)) + _rotState[playerNo] = 1; + else if (SampleCapturedControllerActionState(playerNo, ControllerAction.Driving2)) + _rotState[playerNo] = 2; + else if (SampleCapturedControllerActionState(playerNo, ControllerAction.Driving3)) + _rotState[playerNo] = 3; + return _rotGrayCodes[_rotState[playerNo]]; + } + + #endregion + + #region Object Overrides + + public override string ToString() + { + return GetType().Name; + } + + #endregion + + #region Helpers + + void SetControllerActionState(int playerNo, ControllerAction action, bool value) + { + if (value) + { + _nextInputState[ControllerActionStateIndex + (playerNo & 3)] |= (1 << (int)action); + } + else + { + _nextInputState[ControllerActionStateIndex + (playerNo & 3)] &= ~(1 << (int)action); + } + } + + void SetConsoleSwitchState(ConsoleSwitch consoleSwitch, bool value) + { + if (value) + { + _nextInputState[ConsoleSwitchIndex] |= (byte)(1 << (byte)consoleSwitch); + } + else + { + _nextInputState[ConsoleSwitchIndex] &= (byte)~(1 << (byte)consoleSwitch); + } + } + + void ToggleConsoleSwitchState(ConsoleSwitch consoleSwitch) + { + var consoleSwitchState = (_nextInputState[ConsoleSwitchIndex] & (1 << (int) consoleSwitch)) != 0; + SetConsoleSwitchState(consoleSwitch, !consoleSwitchState); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502.cs new file mode 100644 index 0000000000..53df83446d --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502.cs @@ -0,0 +1,1099 @@ +/* + * M6502.cs + * + * CPU emulator for the MOS Technology 6502 microprocessor. + * + * Copyright © 2003-2005 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class M6502 + { + delegate void OpcodeHandler(); + + OpcodeHandler[] Opcodes; + + const ushort + // non-maskable interrupt vector + NMI_VEC = 0xfffa, + // reset vector + RST_VEC = 0xfffc, + // interrupt request vector + IRQ_VEC = 0xfffe; + + readonly MachineBase M; + AddressSpace Mem { get { return M.Mem; } } + + public ulong Clock { get; set; } + public int RunClocks { get; set; } + public int RunClocksMultiple { get; private set; } + + public bool EmulatorPreemptRequest { get; set; } + public bool Jammed { get; set; } + public bool IRQInterruptRequest { get; set; } + public bool NMIInterruptRequest { get; set; } + + // 16-bit register + // program counter + public ushort PC { get; set; } + + // 8-bit registers + // accumulator + public byte A { get; set; } + // x index register + public byte X { get; set; } + // y index register + public byte Y { get; set; } + // stack pointer + public byte S { get; set; } + // processor status + public byte P { get; set; } + + public void Reset() + { + Jammed = false; + + // clear the stack + S = 0xff; + + fI = fZ = true; + + // reset the program counter + PC = WORD(Mem[RST_VEC], Mem[RST_VEC + 1]); + + clk(6); + + Log("{0} (PC:${1:x4}) reset", this, PC); + } + + public override String ToString() + { + return "M6502 CPU"; + } + + public void Execute() + { + EmulatorPreemptRequest = false; + + while (RunClocks > 0 && !EmulatorPreemptRequest && !Jammed) + { + if (NMIInterruptRequest) + { + InterruptNMI(); + NMIInterruptRequest = false; + } + else if (IRQInterruptRequest) + { + InterruptIRQ(); + IRQInterruptRequest = false; + } + else + { + Opcodes[Mem[PC++]](); + } + } + } + + private M6502() + { + InstallOpcodes(); + + Clock = 0; + RunClocks = 0; + RunClocksMultiple = 1; + + // initialize processor status, bit 5 is always set + P = 1 << 5; + } + + public M6502(MachineBase m, int runClocksMultiple) : this() + { + if (m == null) + throw new ArgumentNullException("m"); + if (runClocksMultiple <= 0) + throw new ArgumentException("runClocksMultiple must be greater than zero."); + + M = m; + RunClocksMultiple = runClocksMultiple; + } + + static byte MSB(ushort u16) + { + return (byte)(u16 >> 8); + } + + static byte LSB(ushort u16) + { + return (byte)u16; + } + + static ushort WORD(byte lsb, byte msb) + { + return (ushort)(lsb | msb << 8); + } + + // Processor Status Flag Bits + // + + // Flag bit setters and getters + void fset(byte flag, bool value) + { + P = (byte)(value ? P | flag : P & ~flag); + } + + bool fget(byte flag) + { + return (P & flag) != 0; + } + + // Carry: set if the add produced a carry, if the subtraction + // produced a borrow. Also used in shift instructions. + bool fC + { + get { return fget(1 << 0); } + set { fset(1 << 0, value); } + } + + // Zero: set if the result of the last operation was zero + bool fZ + { + get { return fget(1 << 1); } + set { fset(1 << 1, value); } + } + + // Irq Disable: set if maskable interrupts are disabled + bool fI + { + get { return fget(1 << 2); } + set { fset(1 << 2, value); } + } + + // Decimal Mode: set if decimal mode active + bool fD + { + get { return fget(1 << 3); } + set { fset(1 << 3, value); } + } + + // Brk: set if an interrupt caused by a BRK instruction, + // reset if caused by an internal interrupt + bool fB + { + get { return fget(1 << 4); } + set { fset(1 << 4, value); } + } + + // Overflow: set if the addition of two-like-signed numbers + // or the subtraction of two unlike-signed numbers + // produces a result greater than +127 or less than -128. + bool fV + { + get { return fget(1 << 6); } + set { fset(1 << 6, value); } + } + + // Negative: set if bit 7 of the accumulator is set + bool fN + { + get { return fget(1 << 7); } + set { fset(1 << 7, value); } + } + + void set_fNZ(byte u8) + { + fN = (u8 & 0x80) != 0; + fZ = (u8 & 0xff) == 0; + } + + byte pull() + { + S++; + return Mem[(ushort)(0x0100 + S)]; + } + + void push(byte data) + { + Mem[(ushort)(0x0100 + S)] = data; + S--; + } + + void clk(int ticks) + { + Clock += (ulong)ticks; + RunClocks -= (ticks*RunClocksMultiple); + } + + void InterruptNMI() + { + push(MSB(PC)); + push(LSB(PC)); + fB = false; + push(P); + fI = true; + PC = WORD(Mem[NMI_VEC], Mem[NMI_VEC + 1]); + clk(7); + } + + void InterruptIRQ() + { + if (IRQInterruptRequest && !fI) + { + push(MSB(PC)); + push(LSB(PC)); + fB = false; + push(P); + fI = true; + PC = WORD(Mem[IRQ_VEC], Mem[IRQ_VEC + 1]); + } + clk(7); + } + + void br(bool cond, ushort ea) + { + if (cond) + { + clk( (MSB(PC) == MSB(ea)) ? 1 : 2 ); + PC = ea; + } + } + + + // Relative: Bxx $aa (branch instructions only) + ushort aREL() + { + var bo = (sbyte)Mem[PC]; + PC++; + return (ushort)(PC + bo); + } + + // Zero Page: $aa + ushort aZPG() + { + return WORD(Mem[PC++], 0x00); + } + + // Zero Page Indexed,X: $aa,X + ushort aZPX() + { + return WORD((byte)(Mem[PC++] + X), 0x00); + } + + // Zero Page Indexed,Y: $aa,Y + ushort aZPY() + { + return WORD((byte)(Mem[PC++] + Y), 0x00); + } + + // Absolute: $aaaa + ushort aABS() + { + var lsb = Mem[PC++]; + var msb = Mem[PC++]; + return WORD(lsb, msb); + } + + // Absolute Indexed,X: $aaaa,X + ushort aABX(int eclk) + { + var ea = aABS(); + if (LSB(ea) + X > 0xff) + { + clk(eclk); + } + return (ushort)(ea + X); + } + + // Absolute Indexed,Y: $aaaa,Y + ushort aABY(int eclk) + { + var ea = aABS(); + if (LSB(ea) + Y > 0xff) + { + clk(eclk); + } + return (ushort)(ea + Y); + } + + // Indexed Indirect: ($aa,X) + ushort aIDX() + { + var zpa = (byte)(Mem[PC++] + X); + var lsb = Mem[zpa++]; + var msb = Mem[zpa]; + return WORD(lsb, msb); + } + + // Indirect Indexed: ($aa),Y + ushort aIDY(int eclk) + { + var zpa = Mem[PC++]; + var lsb = Mem[zpa++]; + var msb = Mem[zpa]; + if (lsb + Y > 0xff) + { + clk(eclk); + } + return (ushort)(WORD(lsb, msb) + Y); + } + + // Indirect Absolute: ($aaaa) (only used by JMP) + ushort aIND() + { + var ea = aABS(); + var lsb = Mem[ea]; + ea = WORD((byte)(LSB(ea) + 1), MSB(ea)); // NMOS 6502/7 quirk: does not fetch across page boundaries + var msb = Mem[ea]; + return WORD(lsb, msb); + } + + // aACC = Accumulator + // aIMM = Immediate + // aIMP = Implied + + // ADC: Add with carry + void iADC(byte mem) + { + var c = fC ? 1 : 0; + var sum = A + mem + c; + fV = (~(A ^ mem) & (A ^ (sum & 0xff)) & 0x80) != 0; + if (fD) + { + // NMOS 6502/7 quirk: The N, V, and Z flags reflect the binary result, not the BCD result + var lo = (A & 0xf) + (mem & 0xf) + c; + var hi = (A >> 4) + (mem >> 4); + if (lo > 9) + { + lo += 6; + hi++; + } + if (hi > 9) + { + hi += 6; + } + A = (byte)((lo & 0xf) | (hi << 4)); + fC = (hi & 0x10) != 0; + } + else + { + A = (byte)sum; + fC = (sum & 0x100) != 0; + } + set_fNZ((byte)sum); + } + + // AND: Logical and + void iAND(byte mem) + { + A &= mem; + set_fNZ(A); + } + + // ASL: Arithmetic shift left: C <- [7][6][5][4][3][2][1][0] <- 0 + byte iASL(byte mem) + { + fC = (mem & 0x80) != 0; + mem <<= 1; + set_fNZ(mem); + return mem; + } + + // BIT: Bit test + void iBIT(byte mem) + { + fN = (mem & 0x80) != 0; + fV = (mem & 0x40) != 0; + fZ = (mem & A) == 0; + } + + // BRK Force Break (cause software interrupt) + void iBRK() + { + PC++; + fB = true; + push(MSB(PC)); + push(LSB(PC)); + push(P); + fI = true; + var lsb = Mem[IRQ_VEC]; + var msb = Mem[IRQ_VEC+1]; + PC = WORD(lsb, msb); + } + + // CLC: Clear carry flag + void iCLC() + { + fC = false; + } + + // CLD: Clear decimal mode + void iCLD() + { + fD = false; + } + + // CLI: Clear interrupt disable */ + void iCLI() + { + fI = false; + } + + // CLV: Clear overflow flag + void iCLV() + { + fV = false; + } + + // CMP: Compare accumulator + void iCMP(byte mem) + { + fC = A >= mem; + set_fNZ((byte)(A - mem)); + } + + // CPX: Compare index X + void iCPX(byte mem) + { + fC = X >= mem; + set_fNZ((byte)(X - mem)); + } + + // CPY: Compare index Y + void iCPY(byte mem) + { + fC = Y >= mem; + set_fNZ((byte)(Y - mem)); + } + + // DEC: Decrement memory + byte iDEC(byte mem) + { + mem--; + set_fNZ(mem); + return mem; + } + + // DEX: Decrement index x + void iDEX() + { + X--; + set_fNZ(X); + } + + // DEY: Decrement index y + void iDEY() + { + Y--; + set_fNZ(Y); + } + + // EOR: Logical exclusive or + void iEOR(byte mem) + { + A ^= mem; + set_fNZ(A); + } + + // INC: Increment memory + byte iINC(byte mem) + { + mem++; + set_fNZ(mem); + return mem; + } + + // INX: Increment index x + void iINX() + { + X++; + set_fNZ(X); + } + + // INY: Increment index y + void iINY() + { + Y++; + set_fNZ(Y); + } + + // JMP Jump to address + void iJMP(ushort ea) + { + PC = ea; + } + + // JSR Jump to subroutine + void iJSR(ushort ea) + { + PC--; // NMOS 6502/7 quirk: iRTS compensates + push(MSB(PC)); + push(LSB(PC)); + PC = ea; + } + + // LDA: Load accumulator + void iLDA(byte mem) + { + A = mem; + set_fNZ(A); + } + + // LDX: Load index X + void iLDX(byte mem) + { + X = mem; + set_fNZ(X); + } + + // LDY: Load index Y + void iLDY(byte mem) + { + Y = mem; + set_fNZ(Y); + } + + // LSR: Logic shift right: 0 -> [7][6][5][4][3][2][1][0] -> C + byte iLSR(byte mem) + { + fC = (mem & 0x01) != 0; + mem >>= 1; + set_fNZ(mem); + return mem; + } + + // NOP: No operation + void iNOP() + { + if (M.NOPRegisterDumping) + { + Log("NOP: {0}", M6502DASM.GetRegisters(this)); + } + } + + // ORA: Logical inclusive or + void iORA(byte mem) + { + A |= mem; + set_fNZ(A); + } + + // PHA: Push accumulator + void iPHA() + { + push(A); + } + + // PHP: Push processor status (flags) + void iPHP() + { + push(P); + } + + // PLA: Pull accumuator + void iPLA() + { + A = pull(); + set_fNZ(A); + } + + // PLP: Pull processor status (flags) + void iPLP() + { + P = pull(); + fB = true; + } + + // ROL: Rotate left: new C <- [7][6][5][4][3][2][1][0] <- C + byte iROL(byte mem) + { + var d0 = (byte)(fC ? 0x01 : 0x00); + + fC = (mem & 0x80) != 0; + mem <<= 1; + mem |= d0; + set_fNZ(mem); + return mem; + } + + // ROR: Rotate right: C -> [7][6][5][4][3][2][1][0] -> new C + byte iROR(byte mem) + { + var d7 = (byte)(fC ? 0x80 : 0x00); + + fC = (mem & 0x01) != 0; + mem >>= 1; + mem |= d7; + set_fNZ(mem); + return mem; + } + + // RTI: Return from interrupt + void iRTI() + { + P = pull(); + var lsb = pull(); + var msb = pull(); + PC = WORD(lsb, msb); + fB = true; + } + + // RTS: Return from subroutine + void iRTS() + { + var lsb = pull(); + var msb = pull(); + PC = WORD(lsb, msb); + PC++; // NMOS 6502/7 quirk: iJSR compensates + } + + // SBC: Subtract with carry (borrow) + void iSBC(byte mem) + { + var c = fC ? 0 : 1; + var sum = A - mem - c; + fV = ((A ^ mem) & (A ^ (sum & 0xff)) & 0x80) != 0; + if (fD) + { + // NMOS 6502/7 quirk: The N, V, and Z flags reflect the binary result, not the BCD result + var lo = (A & 0xf) - (mem & 0xf) - c; + var hi = (A >> 4) - (mem >> 4); + if ((lo & 0x10) != 0) + { + lo -= 6; + hi--; + } + if ((hi & 0x10) != 0) + { + hi -= 6; + } + A = (byte)((lo & 0xf) | (hi << 4)); + } + else + { + A = (byte)sum; + } + fC = (sum & 0x100) == 0; + set_fNZ((byte)sum); + } + + // SEC: Set carry flag + void iSEC() + { + fC = true; + } + + // SED: Set decimal mode + void iSED() + { + fD = true; + } + + // SEI: Set interrupt disable + void iSEI() + { + fI = true; + } + + // STA: Store accumulator + byte iSTA() + { + return A; + } + + // STX: Store index X + byte iSTX() + { + return X; + } + + // STY: Store index Y + byte iSTY() + { + return Y; + } + + // TAX: Transfer accumlator to index X + void iTAX() + { + X = A; + set_fNZ(X); + } + + // TAY: Transfer accumlator to index Y + void iTAY() + { + Y = A; + set_fNZ(Y); + } + + // TSX: Transfer stack to index X + void iTSX() + { + X = S; + set_fNZ(X); + } + + // TXA: Transfer index X to accumlator + void iTXA() + { + A = X; + set_fNZ(A); + } + + // TXS: Transfer index X to stack + void iTXS() + { + S = X; + // No flags set..! Weird, huh? + } + + // TYA: Transfer index Y to accumulator + void iTYA() + { + A = Y; + set_fNZ(A); + } + + // Illegal opcodes + + // KIL: Jam the processor + void iKIL() + { + Jammed = true; + Log("{0}: Processor jammed!", this); + } + + // LAX: Load accumulator and index x + void iLAX(byte mem) + { + A = X = mem; + set_fNZ(A); + } + + // ISB: Increment and subtract with carry + void iISB(byte mem) + { + mem++; + iSBC(mem); + } + + // RLA: Rotate left and logical and accumulator + // new C <- [7][6][5][4][3][2][1][0] <- C + void iRLA(byte mem) + { + var d0 = (byte)(fC ? 0x01 : 0x00); + + fC = (mem & 0x80) != 0; + mem <<= 1; + mem |= d0; + + A &= mem; + set_fNZ(A); + } + + // SAX: logical and accumulator with index X and store + byte iSAX() + { + return (byte)(A & X); + } + + void InstallOpcodes() + { + Opcodes = new OpcodeHandler[0x100]; + ushort EA; + + Opcodes[0x65] = delegate { EA = aZPG(); clk(3); iADC(Mem[EA]); }; + Opcodes[0x75] = delegate { EA = aZPX(); clk(4); iADC(Mem[EA]); }; + Opcodes[0x61] = delegate { EA = aIDX(); clk(6); iADC(Mem[EA]); }; + Opcodes[0x71] = delegate { EA = aIDY(1); clk(5); iADC(Mem[EA]); }; + Opcodes[0x79] = delegate { EA = aABY(1); clk(4); iADC(Mem[EA]); }; + Opcodes[0x6d] = delegate { EA = aABS(); clk(4); iADC(Mem[EA]); }; + Opcodes[0x7d] = delegate { EA = aABX(1); clk(4); iADC(Mem[EA]); }; + Opcodes[0x69] = delegate { /*aIMM*/ clk(2); iADC(Mem[PC++]); }; + + Opcodes[0x25] = delegate { EA = aZPG(); clk(3); iAND(Mem[EA]); }; // may be 2 clk + Opcodes[0x35] = delegate { EA = aZPX(); clk(4); iAND(Mem[EA]); }; // may be 3 clk + Opcodes[0x21] = delegate { EA = aIDX(); clk(6); iAND(Mem[EA]); }; + Opcodes[0x31] = delegate { EA = aIDY(1); clk(5); iAND(Mem[EA]); }; + Opcodes[0x2d] = delegate { EA = aABS(); clk(4); iAND(Mem[EA]); }; + Opcodes[0x39] = delegate { EA = aABY(1); clk(4); iAND(Mem[EA]); }; + Opcodes[0x3d] = delegate { EA = aABX(1); clk(4); iAND(Mem[EA]); }; + Opcodes[0x29] = delegate { /*aIMM*/ clk(2); iAND(Mem[PC++]); }; + + Opcodes[0x06] = delegate { EA = aZPG(); clk(5); Mem[EA] = iASL(Mem[EA]); }; + Opcodes[0x16] = delegate { EA = aZPX(); clk(6); Mem[EA] = iASL(Mem[EA]); }; + Opcodes[0x0e] = delegate { EA = aABS(); clk(6); Mem[EA] = iASL(Mem[EA]); }; + Opcodes[0x1e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iASL(Mem[EA]); }; + Opcodes[0x0a] = delegate { /*aACC*/ clk(2); A = iASL(A); }; + + Opcodes[0x24] = delegate { EA = aZPG(); clk(3); iBIT(Mem[EA]); }; + Opcodes[0x2c] = delegate { EA = aABS(); clk(4); iBIT(Mem[EA]); }; + + Opcodes[0x10] = delegate { EA = aREL(); clk(2); br(!fN, EA); /* BPL */ }; + Opcodes[0x30] = delegate { EA = aREL(); clk(2); br( fN, EA); /* BMI */ }; + Opcodes[0x50] = delegate { EA = aREL(); clk(2); br(!fV, EA); /* BVC */ }; + Opcodes[0x70] = delegate { EA = aREL(); clk(2); br( fV, EA); /* BVS */ }; + Opcodes[0x90] = delegate { EA = aREL(); clk(2); br(!fC, EA); /* BCC */ }; + Opcodes[0xb0] = delegate { EA = aREL(); clk(2); br( fC, EA); /* BCS */ }; + Opcodes[0xd0] = delegate { EA = aREL(); clk(2); br(!fZ, EA); /* BNE */ }; + Opcodes[0xf0] = delegate { EA = aREL(); clk(2); br( fZ, EA); /* BEQ */ }; + + Opcodes[0x00] = delegate { /*aIMP*/ clk(7); iBRK(); }; + + Opcodes[0x18] = delegate { /*aIMP*/ clk(2); iCLC(); }; + + Opcodes[0xd8] = delegate { /*aIMP*/ clk(2); iCLD(); }; + + Opcodes[0x58] = delegate { /*aIMP*/ clk(2); iCLI(); }; + + Opcodes[0xb8] = delegate { /*aIMP*/ clk(2); iCLV(); }; + + Opcodes[0xc5] = delegate { EA = aZPG(); clk(3); iCMP(Mem[EA]); }; + Opcodes[0xd5] = delegate { EA = aZPX(); clk(4); iCMP(Mem[EA]); }; + Opcodes[0xc1] = delegate { EA = aIDX(); clk(6); iCMP(Mem[EA]); }; + Opcodes[0xd1] = delegate { EA = aIDY(1); clk(5); iCMP(Mem[EA]); }; + Opcodes[0xcd] = delegate { EA = aABS(); clk(4); iCMP(Mem[EA]); }; + Opcodes[0xdd] = delegate { EA = aABX(1); clk(4); iCMP(Mem[EA]); }; + Opcodes[0xd9] = delegate { EA = aABY(1); clk(4); iCMP(Mem[EA]); }; + Opcodes[0xc9] = delegate { /*aIMM*/ clk(2); iCMP(Mem[PC++]); }; + + Opcodes[0xe4] = delegate { EA = aZPG(); clk(3); iCPX(Mem[EA]); }; + Opcodes[0xec] = delegate { EA = aABS(); clk(4); iCPX(Mem[EA]); }; + Opcodes[0xe0] = delegate { /*aIMM*/ clk(2); iCPX(Mem[PC++]); }; + + Opcodes[0xc4] = delegate { EA = aZPG(); clk(3); iCPY(Mem[EA]); }; + Opcodes[0xcc] = delegate { EA = aABS(); clk(4); iCPY(Mem[EA]); }; + Opcodes[0xc0] = delegate { /*aIMM*/ clk(2); iCPY(Mem[PC++]); }; + + Opcodes[0xc6] = delegate { EA = aZPG(); clk(5); Mem[EA] = iDEC(Mem[EA]); }; + Opcodes[0xd6] = delegate { EA = aZPX(); clk(6); Mem[EA] = iDEC(Mem[EA]); }; + Opcodes[0xce] = delegate { EA = aABS(); clk(6); Mem[EA] = iDEC(Mem[EA]); }; + Opcodes[0xde] = delegate { EA = aABX(0); clk(7); Mem[EA] = iDEC(Mem[EA]); }; + + Opcodes[0xca] = delegate { /*aIMP*/ clk(2); iDEX(); }; + + Opcodes[0x88] = delegate { /*aIMP*/ clk(2); iDEY(); }; + + Opcodes[0x45] = delegate { EA = aZPG(); clk(3); iEOR(Mem[EA]); }; + Opcodes[0x55] = delegate { EA = aZPX(); clk(4); iEOR(Mem[EA]); }; + Opcodes[0x41] = delegate { EA = aIDX(); clk(6); iEOR(Mem[EA]); }; + Opcodes[0x51] = delegate { EA = aIDY(1); clk(5); iEOR(Mem[EA]); }; + Opcodes[0x4d] = delegate { EA = aABS(); clk(4); iEOR(Mem[EA]); }; + Opcodes[0x5d] = delegate { EA = aABX(1); clk(4); iEOR(Mem[EA]); }; + Opcodes[0x59] = delegate { EA = aABY(1); clk(4); iEOR(Mem[EA]); }; + Opcodes[0x49] = delegate { /*aIMM*/ clk(2); iEOR(Mem[PC++]); }; + + Opcodes[0xe6] = delegate { EA = aZPG(); clk(5); Mem[EA] = iINC(Mem[EA]); }; + Opcodes[0xf6] = delegate { EA = aZPX(); clk(6); Mem[EA] = iINC(Mem[EA]); }; + Opcodes[0xee] = delegate { EA = aABS(); clk(6); Mem[EA] = iINC(Mem[EA]); }; + Opcodes[0xfe] = delegate { EA = aABX(0); clk(7); Mem[EA] = iINC(Mem[EA]); }; + + Opcodes[0xe8] = delegate { /*aIMP*/ clk(2); iINX(); }; + + Opcodes[0xc8] = delegate { /*aIMP*/ clk(2); iINY(); }; + + Opcodes[0xa5] = delegate { EA = aZPG(); clk(3); iLDA(Mem[EA]); }; + Opcodes[0xb5] = delegate { EA = aZPX(); clk(4); iLDA(Mem[EA]); }; + Opcodes[0xa1] = delegate { EA = aIDX(); clk(6); iLDA(Mem[EA]); }; + Opcodes[0xb1] = delegate { EA = aIDY(1); clk(5); iLDA(Mem[EA]); }; + Opcodes[0xad] = delegate { EA = aABS(); clk(4); iLDA(Mem[EA]); }; + Opcodes[0xbd] = delegate { EA = aABX(1); clk(4); iLDA(Mem[EA]); }; + Opcodes[0xb9] = delegate { EA = aABY(1); clk(4); iLDA(Mem[EA]); }; + Opcodes[0xa9] = delegate { /*aIMM*/ clk(2); iLDA(Mem[PC++]); }; + + Opcodes[0xa6] = delegate { EA = aZPG(); clk(3); iLDX(Mem[EA]); }; + Opcodes[0xb6] = delegate { EA = aZPY(); clk(4); iLDX(Mem[EA]); }; + Opcodes[0xae] = delegate { EA = aABS(); clk(4); iLDX(Mem[EA]); }; + Opcodes[0xbe] = delegate { EA = aABY(1); clk(4); iLDX(Mem[EA]); }; + Opcodes[0xa2] = delegate { /*aIMM*/ clk(2); iLDX(Mem[PC++]); }; + + Opcodes[0xa4] = delegate { EA = aZPG(); clk(3); iLDY(Mem[EA]); }; + Opcodes[0xb4] = delegate { EA = aZPX(); clk(4); iLDY(Mem[EA]); }; + Opcodes[0xac] = delegate { EA = aABS(); clk(4); iLDY(Mem[EA]); }; + Opcodes[0xbc] = delegate { EA = aABX(1); clk(4); iLDY(Mem[EA]); }; + Opcodes[0xa0] = delegate { /*aIMM*/ clk(2); iLDY(Mem[PC++]); }; + + Opcodes[0x46] = delegate { EA = aZPG(); clk(5); Mem[EA] = iLSR(Mem[EA]); }; + Opcodes[0x56] = delegate { EA = aZPX(); clk(6); Mem[EA] = iLSR(Mem[EA]); }; + Opcodes[0x4e] = delegate { EA = aABS(); clk(6); Mem[EA] = iLSR(Mem[EA]); }; + Opcodes[0x5e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iLSR(Mem[EA]); }; + Opcodes[0x4a] = delegate { /*aACC*/ clk(2); A = iLSR(A); }; + + Opcodes[0x4c] = delegate { EA = aABS(); clk(3); iJMP(EA); }; + Opcodes[0x6c] = delegate { EA = aIND(); clk(5); iJMP(EA); }; + + Opcodes[0x20] = delegate { EA = aABS(); clk(6); iJSR(EA); }; + + Opcodes[0xea] = delegate { /*aIMP*/ clk(2); iNOP(); }; + + Opcodes[0x05] = delegate { EA = aZPG(); clk(3); iORA(Mem[EA]); }; // may be 2 clk + Opcodes[0x15] = delegate { EA = aZPX(); clk(4); iORA(Mem[EA]); }; // may be 3 clk + Opcodes[0x01] = delegate { EA = aIDX(); clk(6); iORA(Mem[EA]); }; + Opcodes[0x11] = delegate { EA = aIDY(1); clk(5); iORA(Mem[EA]); }; + Opcodes[0x0d] = delegate { EA = aABS(); clk(4); iORA(Mem[EA]); }; + Opcodes[0x1d] = delegate { EA = aABX(1); clk(4); iORA(Mem[EA]); }; + Opcodes[0x19] = delegate { EA = aABY(1); clk(4); iORA(Mem[EA]); }; + Opcodes[0x09] = delegate { /*aIMM*/ clk(2); iORA(Mem[PC++]); }; + + Opcodes[0x48] = delegate { /*aIMP*/ clk(3); iPHA(); }; + + Opcodes[0x68] = delegate { /*aIMP*/ clk(4); iPLA(); }; + + Opcodes[0x08] = delegate { /*aIMP*/ clk(3); iPHP(); }; + + Opcodes[0x28] = delegate { /*aIMP*/ clk(4); iPLP(); }; + + Opcodes[0x26] = delegate { EA = aZPG(); clk(5); Mem[EA] = iROL(Mem[EA]); }; + Opcodes[0x36] = delegate { EA = aZPX(); clk(6); Mem[EA] = iROL(Mem[EA]); }; + Opcodes[0x2e] = delegate { EA = aABS(); clk(6); Mem[EA] = iROL(Mem[EA]); }; + Opcodes[0x3e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iROL(Mem[EA]); }; + Opcodes[0x2a] = delegate { /*aACC*/ clk(2); A = iROL(A); }; + + Opcodes[0x66] = delegate { EA = aZPG(); clk(5); Mem[EA] = iROR(Mem[EA]); }; + Opcodes[0x76] = delegate { EA = aZPX(); clk(6); Mem[EA] = iROR(Mem[EA]); }; + Opcodes[0x6e] = delegate { EA = aABS(); clk(6); Mem[EA] = iROR(Mem[EA]); }; + Opcodes[0x7e] = delegate { EA = aABX(0); clk(7); Mem[EA] = iROR(Mem[EA]); }; + Opcodes[0x6a] = delegate { /*aACC*/ clk(2); A = iROR(A); }; + + Opcodes[0x40] = delegate { /*aIMP*/ clk(6); iRTI(); }; + + Opcodes[0x60] = delegate { /*aIMP*/ clk(6); iRTS(); }; + + Opcodes[0xe5] = delegate { EA = aZPG(); clk(3); iSBC(Mem[EA]); }; + Opcodes[0xf5] = delegate { EA = aZPX(); clk(4); iSBC(Mem[EA]); }; + Opcodes[0xe1] = delegate { EA = aIDX(); clk(6); iSBC(Mem[EA]); }; + Opcodes[0xf1] = delegate { EA = aIDY(1); clk(5); iSBC(Mem[EA]); }; + Opcodes[0xed] = delegate { EA = aABS(); clk(4); iSBC(Mem[EA]); }; + Opcodes[0xfd] = delegate { EA = aABX(1); clk(4); iSBC(Mem[EA]); }; + Opcodes[0xf9] = delegate { EA = aABY(1); clk(4); iSBC(Mem[EA]); }; + Opcodes[0xe9] = delegate { /*aIMM*/ clk(2); iSBC(Mem[PC++]); }; + + Opcodes[0x38] = delegate { /*aIMP*/ clk(2); iSEC(); }; + + Opcodes[0xf8] = delegate { /*aIMP*/ clk(2); iSED(); }; + + Opcodes[0x78] = delegate { /*aIMP*/ clk(2); iSEI(); }; + + Opcodes[0x85] = delegate { EA = aZPG(); clk(3); Mem[EA] = iSTA(); }; + Opcodes[0x95] = delegate { EA = aZPX(); clk(4); Mem[EA] = iSTA(); }; + Opcodes[0x81] = delegate { EA = aIDX(); clk(6); Mem[EA] = iSTA(); }; + Opcodes[0x91] = delegate { EA = aIDY(0); clk(6); Mem[EA] = iSTA(); }; + Opcodes[0x8d] = delegate { EA = aABS(); clk(4); Mem[EA] = iSTA(); }; + Opcodes[0x99] = delegate { EA = aABY(0); clk(5); Mem[EA] = iSTA(); }; + Opcodes[0x9d] = delegate { EA = aABX(0); clk(5); Mem[EA] = iSTA(); }; + + Opcodes[0x86] = delegate { EA = aZPG(); clk(3); Mem[EA] = iSTX(); }; + Opcodes[0x96] = delegate { EA = aZPY(); clk(4); Mem[EA] = iSTX(); }; + Opcodes[0x8e] = delegate { EA = aABS(); clk(4); Mem[EA] = iSTX(); }; + + Opcodes[0x84] = delegate { EA = aZPG(); clk(3); Mem[EA] = iSTY(); }; + Opcodes[0x94] = delegate { EA = aZPX(); clk(4); Mem[EA] = iSTY(); }; + Opcodes[0x8c] = delegate { EA = aABS(); clk(4); Mem[EA] = iSTY(); }; + + Opcodes[0xaa] = delegate { /*aIMP*/ clk(2); iTAX(); }; + + Opcodes[0xa8] = delegate { /*aIMP*/ clk(2); iTAY(); }; + + Opcodes[0xba] = delegate { /*aIMP*/ clk(2); iTSX(); }; + + Opcodes[0x8a] = delegate { /*aIMP*/ clk(2); iTXA(); }; + + Opcodes[0x9a] = delegate { /*aIMP*/ clk(2); iTXS(); }; + + Opcodes[0x98] = delegate { /*aIMP*/ clk(2); iTYA(); }; + + // Illegal opcodes + foreach (int opCode in new ushort[] { 0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72, 0x92, 0xb2, 0xd2, 0xf2 }) + { + Opcodes[opCode] = delegate { clk(2); iKIL(); }; + } + Opcodes[0x3f] = delegate { EA = aABX(0); clk(4); iRLA(Mem[EA]); }; + Opcodes[0xa7] = delegate { EA = aZPX(); clk(3); iLAX(Mem[EA]); }; + Opcodes[0xb3] = delegate { EA = aIDY(0); clk(6); iLAX(Mem[EA]); }; + Opcodes[0xef] = delegate { EA = aABS(); clk(6); iISB(Mem[EA]); }; + Opcodes[0x0c] = delegate { EA = aABS(); clk(2); iNOP(); }; + foreach (int opCode in new ushort[] { 0x1c, 0x3c, 0x5c, 0x7c, 0x9c, 0xdc, 0xfc }) + { + Opcodes[opCode] = delegate { EA = aABX(0); clk(2); iNOP(); }; + } + Opcodes[0x83] = delegate { EA = aIDX(); clk(6); Mem[EA] = iSAX(); }; + Opcodes[0x87] = delegate { EA = aZPG(); clk(3); Mem[EA] = iSAX(); }; + Opcodes[0x8f] = delegate { EA = aABS(); clk(4); Mem[EA] = iSAX(); }; + Opcodes[0x97] = delegate { EA = aZPY(); clk(4); Mem[EA] = iSAX(); }; + Opcodes[0xa3] = delegate { EA = aIDX(); clk(6); iLAX(Mem[EA]); }; + Opcodes[0xb7] = delegate { EA = aZPY(); clk(4); iLAX(Mem[EA]); }; + Opcodes[0xaf] = delegate { EA = aABS(); clk(5); iLAX(Mem[EA]); }; + Opcodes[0xbf] = delegate { EA = aABY(0); clk(6); iLAX(Mem[EA]); }; + Opcodes[0xff] = delegate { EA = aABX(0); clk(7); iISB(Mem[EA]); }; + + OpcodeHandler opNULL = () => Log("{0}:**UNKNOWN OPCODE: ${1:x2} at ${2:x4}\n", this, Mem[(ushort)(PC - 1)], PC - 1); + + for (var i=0; i < Opcodes.Length; i++) + { + if (Opcodes[i] == null) + { + Opcodes[i] = opNULL; + } + } + } + + #region Serialization Members + + public M6502(DeserializationContext input, MachineBase m, int runClocksMultiple) : this(m, runClocksMultiple) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + Clock = input.ReadUInt64(); + RunClocks = input.ReadInt32(); + RunClocksMultiple = input.ReadInt32(); + EmulatorPreemptRequest = input.ReadBoolean(); + Jammed = input.ReadBoolean(); + IRQInterruptRequest = input.ReadBoolean(); + NMIInterruptRequest = input.ReadBoolean(); + PC = input.ReadUInt16(); + A = input.ReadByte(); + X = input.ReadByte(); + Y = input.ReadByte(); + S = input.ReadByte(); + P = input.ReadByte(); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(Clock); + output.Write(RunClocks); + output.Write(RunClocksMultiple); + output.Write(EmulatorPreemptRequest); + output.Write(Jammed); + output.Write(IRQInterruptRequest); + output.Write(NMIInterruptRequest); + output.Write(PC); + output.Write(A); + output.Write(X); + output.Write(Y); + output.Write(S); + output.Write(P); + } + + #endregion + + #region Helpers + + void Log(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502DASM.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502DASM.cs new file mode 100644 index 0000000000..4f36060edc --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/M6502DASM.cs @@ -0,0 +1,242 @@ +/* + * M6502DASM.cs + * + * Provides disassembly services. + * + * Copyright © 2003, 2004 Mike Murphy + * + */ +using System; +using System.Text; + +namespace EMU7800.Core +{ + public static class M6502DASM + { + // Instruction Mnemonics + enum m : uint + { + ADC = 1, AND, ASL, + BIT, BCC, BCS, BEQ, BMI, BNE, BPL, BRK, BVC, BVS, + CLC, CLD, CLI, CLV, CMP, CPX, CPY, + DEC, DEX, DEY, + EOR, + INC, INX, INY, + JMP, JSR, + LDA, LDX, LDY, LSR, + NOP, + ORA, + PLA, PLP, PHA, PHP, + ROL, ROR, RTI, RTS, + SEC, SEI, STA, SBC, SED, STX, STY, + TAX, TAY, TSX, TXA, TXS, TYA, + + // Illegal/undefined opcodes + isb, + kil, + lax, + rla, + sax, + top + } + + // Addressing Modes + enum a : uint + { + REL, // Relative: $aa (branch instructions only) + ZPG, // Zero Page: $aa + ZPX, // Zero Page Indexed X: $aa,X + ZPY, // Zero Page Indexed Y: $aa,Y + ABS, // Absolute: $aaaa + ABX, // Absolute Indexed X: $aaaa,X + ABY, // Absolute Indexed Y: $aaaa,Y + IDX, // Indexed Indirect: ($aa,X) + IDY, // Indirect Indexed: ($aa),Y + IND, // Indirect Absolute: ($aaaa) (JMP only) + IMM, // Immediate: #aa + IMP, // Implied + ACC // Accumulator + } + + static readonly m[] MnemonicMatrix = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +/*0*/ m.BRK, m.ORA, m.kil, 0, 0, m.ORA, m.ASL, 0, m.PHP, m.ORA, m.ASL, 0, m.top, m.ORA, m.ASL, 0,/*0*/ +/*1*/ m.BPL, m.ORA, m.kil, 0, 0, m.ORA, m.ASL, 0, m.CLC, m.ORA, 0, 0, m.top, m.ORA, m.ASL, 0,/*1*/ +/*2*/ m.JSR, m.AND, m.kil, 0, m.BIT, m.AND, m.ROL, 0, m.PLP, m.AND, m.ROL, 0, m.BIT, m.AND, m.ROL, 0,/*2*/ +/*3*/ m.BMI, m.AND, m.kil, 0, 0, m.AND, m.ROL, 0, m.SEC, m.AND, 0, 0, m.top, m.AND, m.ROL, m.rla,/*3*/ +/*4*/ m.RTI, m.EOR, m.kil, 0, 0, m.EOR, m.LSR, 0, m.PHA, m.EOR, m.LSR, 0, m.JMP, m.EOR, m.LSR, 0,/*4*/ +/*5*/ m.BVC, m.EOR, m.kil, 0, 0, m.EOR, m.LSR, 0, m.CLI, m.EOR, 0, 0, m.top, m.EOR, m.LSR, 0,/*5*/ +/*6*/ m.RTS, m.ADC, m.kil, 0, 0, m.ADC, m.ROR, 0, m.PLA, m.ADC, m.ROR, 0, m.JMP, m.ADC, m.ROR, 0,/*6*/ +/*7*/ m.BVS, m.ADC, m.kil, 0, 0, m.ADC, m.ROR, 0, m.SEI, m.ADC, 0, 0, m.top, m.ADC, m.ROR, 0,/*7*/ +/*8*/ 0, m.STA, 0, m.sax, m.STY, m.STA, m.STX, m.sax, m.DEY, 0, m.TXA, 0, m.STY, m.STA, m.STX, m.sax,/*8*/ +/*9*/ m.BCC, m.STA, m.kil, 0, m.STY, m.STA, m.STX, m.sax, m.TYA, m.STA, m.TXS, 0, m.top, m.STA, 0, 0,/*9*/ +/*A*/ m.LDY, m.LDA, m.LDX, m.lax, m.LDY, m.LDA, m.LDX, m.lax, m.TAY, m.LDA, m.TAX, 0, m.LDY, m.LDA, m.LDX, m.lax,/*A*/ +/*B*/ m.BCS, m.LDA, m.kil, m.lax, m.LDY, m.LDA, m.LDX, m.lax, m.CLV, m.LDA, m.TSX, 0, m.LDY, m.LDA, m.LDX, m.lax,/*B*/ +/*C*/ m.CPY, m.CMP, 0, 0, m.CPY, m.CMP, m.DEC, 0, m.INY, m.CMP, m.DEX, 0, m.CPY, m.CMP, m.DEC, 0,/*C*/ +/*D*/ m.BNE, m.CMP, m.kil, 0, 0, m.CMP, m.DEC, 0, m.CLD, m.CMP, 0, 0, m.top, m.CMP, m.DEC, 0,/*D*/ +/*E*/ m.CPX, m.SBC, 0, 0, m.CPX, m.SBC, m.INC, 0, m.INX, m.SBC, m.NOP, 0, m.CPX, m.SBC, m.INC, m.isb,/*E*/ +/*F*/ m.BEQ, m.SBC, m.kil, 0, 0, m.SBC, m.INC, 0, m.SED, m.SBC, 0, 0, m.top, m.SBC, m.INC, m.isb /*F*/ +}; + + static readonly a[] AddressingModeMatrix = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +/*0*/ a.IMP, a.IDX, a.IMP, 0, 0, a.ZPG, a.ZPG, 0, a.IMP, a.IMM, a.ACC, 0, a.ABS, a.ABS, a.ABS, 0,/*0*/ +/*1*/ a.REL, a.IDY, a.IMP, 0, 0, a.ZPG, a.ZPG, 0, a.IMP, a.ABY, 0, 0, a.ABS, a.ABX, a.ABX, 0,/*1*/ +/*2*/ a.ABS, a.IDX, a.IMP, 0, a.ZPG, a.ZPG, a.ZPG, 0, a.IMP, a.IMM, a.ACC, 0, a.ABS, a.ABS, a.ABS, 0,/*2*/ +/*3*/ a.REL, a.IDY, a.IMP, 0, 0, a.ZPG, a.ZPG, 0, a.IMP, a.ABY, 0, 0, a.ABS, a.ABX, a.ABX, a.ABX,/*3*/ +/*4*/ a.IMP, a.IDY, a.IMP, 0, 0, a.ZPG, a.ZPG, 0, a.IMP, a.IMM, a.ACC, 0, a.ABS, a.ABS, a.ABS, 0,/*4*/ +/*5*/ a.REL, a.IDY, a.IMP, 0, 0, a.ZPG, a.ZPG, 0, a.IMP, a.ABY, 0, 0, a.ABS, a.ABX, a.ABX, 0,/*5*/ +/*6*/ a.IMP, a.IDX, a.IMP, 0, 0, a.ZPG, a.ZPG, 0, a.IMP, a.IMM, a.ACC, 0, a.IND, a.ABS, a.ABS, 0,/*6*/ +/*7*/ a.REL, a.IDY, a.IMP, 0, 0, a.ZPX, a.ZPX, 0, a.IMP, a.ABY, 0, 0, a.ABS, a.ABX, a.ABX, 0,/*7*/ +/*8*/ 0, a.IDY, 0, a.IDX, a.ZPG, a.ZPG, a.ZPG, a.ZPG, a.IMP, 0, a.IMP, 0, a.ABS, a.ABS, a.ABS, a.ABS,/*8*/ +/*9*/ a.REL, a.IDY, a.IMP, 0, a.ZPX, a.ZPX, a.ZPY, a.ZPY, a.IMP, a.ABY, a.IMP, 0, a.ABS, a.ABX, 0, 0,/*9*/ +/*A*/ a.IMM, a.IND, a.IMM, a.IDX, a.ZPG, a.ZPG, a.ZPG, a.ZPX, a.IMP, a.IMM, a.IMP, 0, a.ABS, a.ABS, a.ABS, a.ABS,/*A*/ +/*B*/ a.REL, a.IDY, a.IMP, a.IDY, a.ZPX, a.ZPX, a.ZPY, a.ZPY, a.IMP, a.ABY, a.IMP, 0, a.ABX, a.ABX, a.ABY, a.ABY,/*B*/ +/*C*/ a.IMM, a.IDX, 0, 0, a.ZPG, a.ZPG, a.ZPG, 0, a.IMP, a.IMM, a.IMP, 0, a.ABS, a.ABS, a.ABS, 0,/*C*/ +/*D*/ a.REL, a.IDY, a.IMP, 0, 0, a.ZPX, a.ZPX, 0, a.IMP, a.ABY, 0, 0, a.ABS, a.ABX, a.ABX, 0,/*D*/ +/*E*/ a.IMM, a.IDX, 0, 0, a.ZPG, a.ZPG, a.ZPG, 0, a.IMP, a.IMM, a.IMP, 0, a.ABS, a.ABS, a.ABS, a.ABS,/*E*/ +/*F*/ a.REL, a.IDY, a.IMP, 0, 0, a.ZPX, a.ZPX, 0, a.IMP, a.ABY, 0, 0, a.ABS, a.ABX, a.ABX, a.ABX /*F*/ +}; + + public static string GetRegisters(M6502 cpu) + { + var dSB = new StringBuilder(); + dSB.Append(String.Format( + "PC:{0:x4} A:{1:x2} X:{2:x2} Y:{3:x2} S:{4:x2} P:", + cpu.PC, cpu.A, cpu.X, cpu.Y, cpu.S)); + + const string flags = "nv0bdizcNV1BDIZC"; + + for (var i = 0; i < 8; i++) + { + dSB.Append(((cpu.P & (1 << (7 - i))) == 0) ? flags[i] : flags[i + 8]); + } + return dSB.ToString(); + } + + public static string Disassemble(AddressSpace addrSpace, ushort atAddr, ushort untilAddr) + { + var dSB = new StringBuilder(); + var dPC = atAddr; + while (atAddr < untilAddr) + { + dSB.AppendFormat("{0:x4}: ", dPC); + var len = GetInstructionLength(addrSpace, dPC); + for (var i = 0; i < 3; i++) + { + if (i < len) + { + dSB.AppendFormat("{0:x2} ", addrSpace[atAddr++]); + } + else + { + dSB.Append(" "); + } + } + dSB.AppendFormat("{0,-15}{1}", RenderOpCode(addrSpace, dPC), Environment.NewLine); + dPC += (ushort)len; + } + if (dSB.Length > 0) + { + dSB.Length--; // Trim trailing newline + } + return dSB.ToString(); + } + + public static string MemDump(AddressSpace addrSpace, ushort atAddr, ushort untilAddr) + { + var dSB = new StringBuilder(); + var len = untilAddr - atAddr; + while (len-- >= 0) + { + dSB.AppendFormat("{0:x4}: ", atAddr); + for (var i = 0; i < 8; i++) + { + dSB.AppendFormat("{0:x2} ", addrSpace[atAddr++]); + if (i == 3) + { + dSB.Append(" "); + } + } + dSB.Append("\n"); + } + if (dSB.Length > 0) + { + dSB.Length--; // Trim trailing newline + } + return dSB.ToString(); + } + + public static string RenderOpCode(AddressSpace addrSpace, ushort PC) + { + var num_operands = GetInstructionLength(addrSpace, PC) - 1; + var PC1 = (ushort)(PC + 1); + string addrmodeStr; + + switch (AddressingModeMatrix[addrSpace[PC]]) + { + case a.REL: + addrmodeStr = String.Format("${0:x4}", (ushort)(PC + (sbyte)(addrSpace[PC1]) + 2)); + break; + case a.ZPG: + case a.ABS: + addrmodeStr = RenderEA(addrSpace, PC1, num_operands); + break; + case a.ZPX: + case a.ABX: + addrmodeStr = RenderEA(addrSpace, PC1, num_operands) + ",X"; + break; + case a.ZPY: + case a.ABY: + addrmodeStr = RenderEA(addrSpace, PC1, num_operands) + ",Y"; + break; + case a.IDX: + addrmodeStr = "(" + RenderEA(addrSpace, PC1, num_operands) + ",X)"; + break; + case a.IDY: + addrmodeStr = "(" + RenderEA(addrSpace, PC1, num_operands) + "),Y"; + break; + case a.IND: + addrmodeStr = "(" + RenderEA(addrSpace, PC1, num_operands) + ")"; + break; + case a.IMM: + addrmodeStr = "#" + RenderEA(addrSpace, PC1, num_operands); + break; + default: + // a.IMP, a.ACC + addrmodeStr = string.Empty; + break; + } + + return string.Format("{0} {1}", MnemonicMatrix[addrSpace[PC]], addrmodeStr); + } + + static int GetInstructionLength(AddressSpace addrSpace, ushort PC) + { + switch (AddressingModeMatrix[addrSpace[PC]]) + { + case a.ACC: + case a.IMP: + return 1; + case a.REL: + case a.ZPG: + case a.ZPX: + case a.ZPY: + case a.IDX: + case a.IDY: + case a.IMM: + return 2; + default: + return 3; + } + } + + static string RenderEA(AddressSpace addrSpace, ushort PC, int bytes) + { + var lsb = addrSpace[PC]; + var msb = (bytes == 2) ? addrSpace[(ushort)(PC + 1)] : (byte)0; + var ea = (ushort)(lsb | (msb << 8)); + return string.Format((bytes == 1) ? "${0:x2}" : "${0:x4}", ea); + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600.cs new file mode 100644 index 0000000000..bb62a02692 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600.cs @@ -0,0 +1,112 @@ +/* + * Machine2600.cs + * + * The realization of a 2600 machine. + * + * Copyright © 2003, 2004 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public class Machine2600 : MachineBase + { + #region Fields + + protected TIA TIA { get; set; } + + #endregion + + public override void Reset() + { + base.Reset(); + TIA.Reset(); + PIA.Reset(); + CPU.Reset(); + } + + public override void ComputeNextFrame(FrameBuffer frameBuffer) + { + base.ComputeNextFrame(frameBuffer); + TIA.StartFrame(); + CPU.RunClocks = (FrameBuffer.Scanlines + 3) * 76; + while (CPU.RunClocks > 0 && !CPU.Jammed) + { + if (TIA.WSYNCDelayClocks > 0) + { + CPU.Clock += (ulong)TIA.WSYNCDelayClocks / 3; + CPU.RunClocks -= TIA.WSYNCDelayClocks / 3; + TIA.WSYNCDelayClocks = 0; + } + if (TIA.EndOfFrame) + { + break; + } + CPU.Execute(); + } + TIA.EndFrame(); + } + + public Machine2600(Cart cart, ILogger logger, int slines, int startl, int fHZ, int sRate, int[] p) + : base(logger, slines, startl, fHZ, sRate, p, 160) + { + Mem = new AddressSpace(this, 13, 6); // 2600: 13bit, 64byte pages + + CPU = new M6502(this, 1); + + TIA = new TIA(this); + for (ushort i = 0; i < 0x1000; i += 0x100) + { + Mem.Map(i, 0x0080, TIA); + } + + PIA = new PIA(this); + for (ushort i = 0x0080; i < 0x1000; i += 0x100) + { + Mem.Map(i, 0x0080, PIA); + } + + Cart = cart; + Mem.Map(0x1000, 0x1000, Cart); + } + + #region Serialization Members + + public Machine2600(DeserializationContext input, int[] palette) : base(input, palette) + { + input.CheckVersion(1); + + Mem = input.ReadAddressSpace(this, 13, 6); // 2600: 13bit, 64byte pages + + CPU = input.ReadM6502(this, 1); + + TIA = input.ReadTIA(this); + for (ushort i = 0; i < 0x1000; i += 0x100) + { + Mem.Map(i, 0x0080, TIA); + } + + PIA = input.ReadPIA(this); + for (ushort i = 0x0080; i < 0x1000; i += 0x100) + { + Mem.Map(i, 0x0080, PIA); + } + + Cart = input.ReadCart(this); + Mem.Map(0x1000, 0x1000, Cart); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(Mem); + output.Write(CPU); + output.Write(TIA); + output.Write(PIA); + output.Write(Cart); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600NTSC.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600NTSC.cs new file mode 100644 index 0000000000..52c5a5b395 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600NTSC.cs @@ -0,0 +1,30 @@ +namespace EMU7800.Core +{ + public sealed class Machine2600NTSC : Machine2600 + { + public override string ToString() + { + return GetType().Name; + } + + public Machine2600NTSC(Cart cart, ILogger logger) + : base(cart, logger, 262, 16, 60, 31440 /* NTSC_SAMPLES_PER_SEC */, TIATables.NTSCPalette) + { + } + + #region Serialization Members + + public Machine2600NTSC(DeserializationContext input) : base(input, TIATables.NTSCPalette) + { + input.CheckVersion(1); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + output.WriteVersion(1); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600PAL.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600PAL.cs new file mode 100644 index 0000000000..ab32b28ba5 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine2600PAL.cs @@ -0,0 +1,30 @@ +namespace EMU7800.Core +{ + public sealed class Machine2600PAL : Machine2600 + { + public override string ToString() + { + return GetType().Name; + } + + public Machine2600PAL(Cart cart, ILogger logger) + : base(cart, logger, 312, 32, 50, 31200 /* PAL_SAMPLES_PER_SEC */, TIATables.PALPalette) + { + } + + #region Serialization Members + + public Machine2600PAL(DeserializationContext input) : base(input, TIATables.PALPalette) + { + input.CheckVersion(1); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + output.WriteVersion(1); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800.cs new file mode 100644 index 0000000000..af20d4af33 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800.cs @@ -0,0 +1,244 @@ +/* + * Machine7800.cs + * + * The realization of a 7800 machine. + * + * Copyright © 2003-2005 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public class Machine7800 : MachineBase + { + #region Fields + + protected Maria Maria { get; set; } + protected RAM6116 RAM1 { get; set; } + protected RAM6116 RAM2 { get; set; } + protected Bios7800 BIOS { get; private set; } + protected HSC7800 HSC { get; private set; } + + #endregion + + public void SwapInBIOS() + { + if (BIOS == null) + return; + Mem.Map((ushort)(0x10000 - BIOS.Size), BIOS.Size, BIOS); + } + + public void SwapOutBIOS() + { + if (BIOS == null) + return; + Mem.Map((ushort)(0x10000 - BIOS.Size), BIOS.Size, Cart); + } + + public override void Reset() + { + base.Reset(); + SwapInBIOS(); + if (HSC != null) + HSC.Reset(); + Cart.Reset(); + Maria.Reset(); + PIA.Reset(); + CPU.Reset(); + } + + public override void ComputeNextFrame(FrameBuffer frameBuffer) + { + base.ComputeNextFrame(frameBuffer); + + AssertDebug(CPU.Jammed || CPU.RunClocks <= 0 && (CPU.RunClocks % CPU.RunClocksMultiple) == 0); + AssertDebug(CPU.Jammed || ((CPU.Clock + (ulong)(CPU.RunClocks / CPU.RunClocksMultiple)) % (114 * (ulong)FrameBuffer.Scanlines)) == 0); + + ulong startOfScanlineCpuClock = 0; + + Maria.StartFrame(); + Cart.StartFrame(); + for (var i = 0; i < FrameBuffer.Scanlines && !CPU.Jammed; i++) + { + AssertDebug(CPU.RunClocks <= 0 && (CPU.RunClocks % CPU.RunClocksMultiple) == 0); + var newStartOfScanlineCpuClock = CPU.Clock + (ulong)(CPU.RunClocks / CPU.RunClocksMultiple); + + AssertDebug(startOfScanlineCpuClock == 0 || newStartOfScanlineCpuClock == startOfScanlineCpuClock + 114); + startOfScanlineCpuClock = newStartOfScanlineCpuClock; + + CPU.RunClocks += (7 * CPU.RunClocksMultiple); + var remainingRunClocks = (114 - 7) * CPU.RunClocksMultiple; + + CPU.Execute(); + if (CPU.Jammed) + break; + if (CPU.EmulatorPreemptRequest) + { + Maria.DoDMAProcessing(); + var remainingCpuClocks = 114 - (CPU.Clock - startOfScanlineCpuClock); + CPU.Clock += remainingCpuClocks; + CPU.RunClocks = 0; + continue; + } + + var dmaClocks = Maria.DoDMAProcessing(); + + // CHEAT: Ace of Aces: Title screen has a single scanline flicker without this. Maria DMA clock counting probably not 100% accurate. + if (i == 203 && FrameBuffer.Scanlines == 262 /*NTSC*/ || i == 228 && FrameBuffer.Scanlines == 312 /*PAL*/) + if (dmaClocks == 152 && remainingRunClocks == 428 && (CPU.RunClocks == -4 || CPU.RunClocks == -8)) + dmaClocks -= 4; + + // Unsure exactly what to do if Maria DMA processing extends past the current scanline. + // For now, throw away half remaining until we are within the current scanline. + // KLAX initialization starts DMA without initializing the DLL data structure. + // Maria processing then runs away causing an invalid CPU opcode to be executed that jams the machine. + // So Maria must give up at some point, but not clear exactly how. + // Anyway, this makes KLAX work without causing breakage elsewhere. + while ((CPU.RunClocks + remainingRunClocks) < dmaClocks) + { + dmaClocks >>= 1; + } + + // Assume the CPU waits until the next div4 boundary to proceed after DMA processing. + if ((dmaClocks & 3) != 0) + { + dmaClocks += 4; + dmaClocks -= (dmaClocks & 3); + } + + CPU.Clock += (ulong)(dmaClocks / CPU.RunClocksMultiple); + CPU.RunClocks -= dmaClocks; + + CPU.RunClocks += remainingRunClocks; + + CPU.Execute(); + if (CPU.Jammed) + break; + if (CPU.EmulatorPreemptRequest) + { + var remainingCpuClocks = 114 - (CPU.Clock - startOfScanlineCpuClock); + CPU.Clock += remainingCpuClocks; + CPU.RunClocks = 0; + } + } + Cart.EndFrame(); + Maria.EndFrame(); + } + + public Machine7800(Cart cart, Bios7800 bios, HSC7800 hsc, ILogger logger, int scanlines, int startl, int fHZ, int sRate, int[] p) + : base(logger, scanlines, startl, fHZ, sRate, p, 320) + { + Mem = new AddressSpace(this, 16, 6); // 7800: 16bit, 64byte pages + + CPU = new M6502(this, 4); + + Maria = new Maria(this, scanlines); + Mem.Map(0x0000, 0x0040, Maria); + Mem.Map(0x0100, 0x0040, Maria); + Mem.Map(0x0200, 0x0040, Maria); + Mem.Map(0x0300, 0x0040, Maria); + + PIA = new PIA(this); + Mem.Map(0x0280, 0x0080, PIA); + Mem.Map(0x0480, 0x0080, PIA); + Mem.Map(0x0580, 0x0080, PIA); + + RAM1 = new RAM6116(); + RAM2 = new RAM6116(); + Mem.Map(0x1800, 0x0800, RAM1); + Mem.Map(0x2000, 0x0800, RAM2); + + Mem.Map(0x0040, 0x00c0, RAM2); // page 0 shadow + Mem.Map(0x0140, 0x00c0, RAM2); // page 1 shadow + Mem.Map(0x2800, 0x0800, RAM2); // shadow1 + Mem.Map(0x3000, 0x0800, RAM2); // shadow2 + Mem.Map(0x3800, 0x0800, RAM2); // shadow3 + + BIOS = bios; + HSC = hsc; + + if (HSC != null) + { + Mem.Map(0x1000, 0x800, HSC.SRAM); + Mem.Map(0x3000, 0x1000, HSC); + Logger.WriteLine("7800 Highscore Cartridge Installed"); + } + + Cart = cart; + Mem.Map(0x4000, 0xc000, Cart); + } + + #region Serialization Members + + public Machine7800(DeserializationContext input, int[] palette, int scanlines) : base(input, palette) + { + input.CheckVersion(1); + + Mem = input.ReadAddressSpace(this, 16, 6); // 7800: 16bit, 64byte pages + + CPU = input.ReadM6502(this, 4); + + Maria = input.ReadMaria(this, scanlines); + Mem.Map(0x0000, 0x0040, Maria); + Mem.Map(0x0100, 0x0040, Maria); + Mem.Map(0x0200, 0x0040, Maria); + Mem.Map(0x0300, 0x0040, Maria); + + PIA = input.ReadPIA(this); + Mem.Map(0x0280, 0x0080, PIA); + Mem.Map(0x0480, 0x0080, PIA); + Mem.Map(0x0580, 0x0080, PIA); + + RAM1 = input.ReadRAM6116(); + RAM2 = input.ReadRAM6116(); + Mem.Map(0x1800, 0x0800, RAM1); + Mem.Map(0x2000, 0x0800, RAM2); + + Mem.Map(0x0040, 0x00c0, RAM2); // page 0 shadow + Mem.Map(0x0140, 0x00c0, RAM2); // page 1 shadow + Mem.Map(0x2800, 0x0800, RAM2); // shadow1 + Mem.Map(0x3000, 0x0800, RAM2); // shadow2 + Mem.Map(0x3800, 0x0800, RAM2); // shadow3 + + BIOS = input.ReadOptionalBios7800(); + HSC = input.ReadOptionalHSC7800(); + + if (HSC != null) + { + Mem.Map(0x1000, 0x800, HSC.SRAM); + Mem.Map(0x3000, 0x1000, HSC); + } + + Cart = input.ReadCart(this); + Mem.Map(0x4000, 0xc000, Cart); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + + output.WriteVersion(1); + output.Write(Mem); + output.Write(CPU); + output.Write(Maria); + output.Write(PIA); + output.Write(RAM1); + output.Write(RAM2); + output.WriteOptional(BIOS); + output.WriteOptional(HSC); + output.Write(Cart); + } + + #endregion + + #region Helpers + + [System.Diagnostics.Conditional("DEBUG")] + void AssertDebug(bool cond) + { + if (!cond) + System.Diagnostics.Debugger.Break(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800NTSC.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800NTSC.cs new file mode 100644 index 0000000000..933336740f --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800NTSC.cs @@ -0,0 +1,30 @@ +namespace EMU7800.Core +{ + public sealed class Machine7800NTSC : Machine7800 + { + public override string ToString() + { + return GetType().Name; + } + + public Machine7800NTSC(Cart cart, Bios7800 bios, HSC7800 hsc, ILogger logger) + : base(cart, bios, hsc, logger, 262, 16, 60, 31440 /* NTSC_SAMPLES_PER_SEC */, MariaTables.NTSCPalette) + { + } + + #region Serialization Members + + public Machine7800NTSC(DeserializationContext input) : base(input, MariaTables.NTSCPalette, 262) + { + input.CheckVersion(1); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + output.WriteVersion(1); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800PAL.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800PAL.cs new file mode 100644 index 0000000000..4de452f892 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Machine7800PAL.cs @@ -0,0 +1,30 @@ +namespace EMU7800.Core +{ + public sealed class Machine7800PAL : Machine7800 + { + public override string ToString() + { + return GetType().Name; + } + + public Machine7800PAL(Cart cart, Bios7800 bios, HSC7800 hsc, ILogger logger) + : base(cart, bios, hsc, logger, 312, 34, 50, 31200 /* PAL_SAMPLES_PER_SEC */, MariaTables.PALPalette) + { + } + + #region Serialization Members + + public Machine7800PAL(DeserializationContext input) : base(input, MariaTables.PALPalette, 312) + { + input.CheckVersion(1); + } + + public override void GetObjectData(SerializationContext output) + { + base.GetObjectData(output); + output.WriteVersion(1); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineBase.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineBase.cs new file mode 100644 index 0000000000..41abf497be --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineBase.cs @@ -0,0 +1,331 @@ +/* +/* + * MachineBase.cs + * + * Abstraction of an emulated machine. + * + * Copyright © 2003, 2004 Mike Murphy + * + */ +using System; +using System.IO; +using System.Reflection; + +namespace EMU7800.Core +{ + public abstract class MachineBase + { + #region Fields + + ILogger _Logger; + FrameBuffer _FrameBuffer; + + bool _MachineHalt; + int _FrameHZ; + readonly int _VisiblePitch, _Scanlines; + + protected Cart Cart { get; set; } + + #endregion + + #region Internal Properties + + internal FrameBuffer FrameBuffer + { + get + { + AssertDebug(_FrameBuffer != null); + return _FrameBuffer; + } + } + + #endregion + + #region Public Properties + + /// + /// The machine's Central Processing Unit. + /// + public M6502 CPU { get; protected set; } + + /// + /// The machine's Address Space. + /// + public AddressSpace Mem { get; protected set; } + + /// + /// The machine's Peripheral Interface Adaptor device. + /// + public PIA PIA { get; protected set; } + + /// + /// Reports whether the machine has been halted due to an internal condition or error. + /// + public bool MachineHalt + { + get { return _MachineHalt; } + internal set { if (value) _MachineHalt = true; } + } + + /// + /// The machine input state. + /// + public InputState InputState { get; private set; } + + /// + /// The current frame number. + /// + public long FrameNumber { get; private set; } + + /// + /// The first scanline that is visible. + /// + public int FirstScanline { get; private set; } + + /// + /// Frame rate. + /// + public int FrameHZ + { + get { return _FrameHZ < 1 ? 1 : _FrameHZ; } + set { _FrameHZ = value < 1 ? 1 : value; } + } + + /// + /// Number of sound samples per second. + /// + public int SoundSampleFrequency { get; private set; } + + /// + /// The color palette for the configured machine. + /// + public int[] Palette { get; internal set; } + + /// + /// Dumps CPU registers to the log when NOP instructions are encountered. + /// + public bool NOPRegisterDumping { get; set; } + + /// + /// The configured logger sink. + /// + public ILogger Logger + { + get { return _Logger ?? (_Logger = new NullLogger()); } + set { _Logger = value; } + } + + #endregion + + #region Public Methods + + /// + /// Creates an instance of the specified machine. + /// + /// + /// + /// 7800 BIOS, optional. + /// 7800 High Score cart, optional. + /// Left controller, optional. + /// Right controller, optional. + /// + /// Cart must not be null. + /// Specified MachineType is unexpected. + public static MachineBase Create(MachineType machineType, Cart cart, Bios7800 bios, HSC7800 hsc, Controller p1, Controller p2, ILogger logger) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + MachineBase m; + switch (machineType) + { + case MachineType.A2600NTSC: + m = new Machine2600NTSC(cart, logger); + break; + case MachineType.A2600PAL: + m = new Machine2600PAL(cart, logger); + break; + case MachineType.A7800NTSC: + m = new Machine7800NTSC(cart, bios, hsc, logger); + break; + case MachineType.A7800PAL: + m = new Machine7800PAL(cart, bios, hsc, logger); + break; + default: + throw new Emu7800Exception("Unexpected MachineType: " + machineType); + } + + m.InputState.LeftControllerJack = p1; + m.InputState.RightControllerJack = p2; + + m.Reset(); + + return m; + } + + /// + /// Deserialize a from the specified stream. + /// + /// + /// + /// + public static MachineBase Deserialize(BinaryReader binaryReader) + { + var context = new DeserializationContext(binaryReader); + MachineBase m; + try + { + m = context.ReadMachine(); + } + catch (Emu7800SerializationException) + { + throw; + } + catch (TargetInvocationException ex) + { + // TargetInvocationException wraps exceptions that unwind an Activator.CreateInstance() frame. + throw new Emu7800SerializationException("Serialization stream does not describe a valid machine.", ex.InnerException); + } + catch (Exception ex) + { + throw new Emu7800SerializationException("Serialization stream does not describe a valid machine.", ex); + } + return m; + } + + /// + /// Resets the state of the machine. + /// + public virtual void Reset() + { + Logger.WriteLine("Machine {0} reset ({1} HZ {2} scanlines)", this, FrameHZ, _Scanlines); + FrameNumber = 0; + _MachineHalt = false; + InputState.ClearAllInput(); + } + + /// + /// Computes the next machine frame, updating contents of the provided . + /// + /// The framebuffer to contain the computed output. + /// + /// frameBuffer is incompatible with machine. + public virtual void ComputeNextFrame(FrameBuffer frameBuffer) + { + if (MachineHalt) + return; + + InputState.CaptureInputState(); + + _FrameBuffer = frameBuffer; + FrameNumber++; + + for (var i = 0; i < _FrameBuffer.SoundBufferElementLength; i++) + _FrameBuffer.SoundBuffer[i].ClearAll(); + } + + /// + /// Create a with compatible dimensions for this machine. + /// + public FrameBuffer CreateFrameBuffer() + { + var fb = new FrameBuffer(_VisiblePitch, _Scanlines); + return fb; + } + + /// + /// Serialize the state of the machine to the specified stream. + /// + /// + /// + /// + public void Serialize(BinaryWriter binaryWriter) + { + var context = new SerializationContext(binaryWriter); + try + { + context.Write(this); + } + catch (Emu7800SerializationException) + { + throw; + } + catch (Exception ex) + { + throw new Emu7800SerializationException("Problem serializing specified machine.", ex); + } + } + + #endregion + + #region Constructors + + private MachineBase() + { + } + + protected MachineBase(ILogger logger, int scanLines, int firstScanline, int fHZ, int soundSampleFreq, int[] palette, int vPitch) : this() + { + InputState = new InputState(); + Logger = logger; + _Scanlines = scanLines; + FirstScanline = firstScanline; + FrameHZ = fHZ; + SoundSampleFrequency = soundSampleFreq; + Palette = palette; + _VisiblePitch = vPitch; + } + + #endregion + + #region Serialization Members + + protected MachineBase(DeserializationContext input, int[] palette) + { + if (input == null) + throw new ArgumentNullException("input"); + if (palette == null) + throw new ArgumentNullException("palette"); + if (palette.Length != 0x100) + throw new ArgumentException("palette incorrect size, must be 256."); + + input.CheckVersion(1); + _MachineHalt = input.ReadBoolean(); + _FrameHZ = input.ReadInt32(); + _VisiblePitch = input.ReadInt32(); + _Scanlines = input.ReadInt32(); + FirstScanline = input.ReadInt32(); + SoundSampleFrequency = input.ReadInt32(); + NOPRegisterDumping = input.ReadBoolean(); + InputState = input.ReadInputState(); + + Palette = palette; + Logger = null; + } + + public virtual void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(_MachineHalt); + output.Write(_FrameHZ); + output.Write(_VisiblePitch); + output.Write(_Scanlines); + output.Write(FirstScanline); + output.Write(SoundSampleFrequency); + output.Write(NOPRegisterDumping); + output.Write(InputState); + } + + #endregion + + [System.Diagnostics.Conditional("DEBUG")] + void AssertDebug(bool cond) + { + if (!cond) + System.Diagnostics.Debugger.Break(); + } + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineInput.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineInput.cs new file mode 100644 index 0000000000..05fe19c556 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineInput.cs @@ -0,0 +1,42 @@ +/* + * HostInput.cs + * + * Copyright © 2009 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public enum MachineInput + { + End, + Pause, + Mute, + Fire, + Fire2, + Left, + Right, + Up, + Down, + NumPad1, NumPad2, NumPad3, + NumPad4, NumPad5, NumPad6, + NumPad7, NumPad8, NumPad9, + NumPadMult, NumPad0, NumPadHash, + Driving0, Driving1, Driving2, Driving3, + Reset, + Select, + Color, + LeftDifficulty, + RightDifficulty, + SetKeyboardToPlayer1, + SetKeyboardToPlayer2, + SetKeyboardToPlayer3, + SetKeyboardToPlayer4, + PanLeft, PanRight, PanUp, PanDown, + SaveMachine, + TakeScreenshot, + LeftPaddleSwap, + GameControllerSwap, + RightPaddleSwap, + ShowFrameStats, + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineType.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineType.cs new file mode 100644 index 0000000000..0fcfdd2d52 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MachineType.cs @@ -0,0 +1,19 @@ +/* + * MachineType.cs + * + * The set of known machines. + * + * Copyright © 2010 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public enum MachineType + { + None, + A2600NTSC, + A2600PAL, + A7800NTSC, + A7800PAL + }; +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Maria.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Maria.cs new file mode 100644 index 0000000000..41685d7714 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/Maria.cs @@ -0,0 +1,1156 @@ +/* + * Maria.cs + * + * The Maria display device. + * + * Derived from much of Dan Boris' work with 7800 emulation + * within the MESS emulator. + * + * Thanks to Matthias Luedtke for correcting + * the BuildLineRAM320B() method to correspond closer to real hardware. + * (Matthias credited an insightful response by Eckhard Stolberg on a forum on + * Atari Age circa June 2005.) + * + * Copyright © 2004-2012 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class Maria : IDevice + { + #region Constants + + const int + INPTCTRL= 0x01, // Write: input port control (VBLANK in TIA) + INPT0 = 0x08, // Read pot port: D7 + INPT1 = 0x09, // Read pot port: D7 + INPT2 = 0x0a, // Read pot port: D7 + INPT3 = 0x0b, // Read pot port: D7 + INPT4 = 0x0c, // Read P1 joystick trigger: D7 + INPT5 = 0x0d, // Read P2 joystick trigger: D7 + AUDC0 = 0x15, // Write: audio control 0 (D3-0) + AUDC1 = 0x16, // Write: audio control 1 (D4-0) + AUDF0 = 0x17, // Write: audio frequency 0 (D4-0) + AUDF1 = 0x18, // Write: audio frequency 1 (D3-0) + AUDV0 = 0x19, // Write: audio volume 0 (D3-0) + AUDV1 = 0x1a, // Write: audio volume 1 (D3-0) + + BACKGRND= 0x20, // Background color + P0C1 = 0x21, // Palette 0 - color 1 + P0C2 = 0x22, // Palette 0 - color 2 + P0C3 = 0x23, // Palette 0 - color 3 + WSYNC = 0x24, // Wait for sync + P1C1 = 0x25, // Palette 1 - color 1 + P1C2 = 0x26, // Palette 1 - color 2 + P1C3 = 0x27, // Palette 1 - color 3 + MSTAT = 0x28, // Maria status + P2C1 = 0x29, // Palette 2 - color 1 + P2C2 = 0x2a, // Palette 2 - color 2 + P2C3 = 0x2b, // Palette 2 - color 3 + DPPH = 0x2c, // Display list list point high + P3C1 = 0x2d, // Palette 3 - color 1 + P3C2 = 0x2e, // Palette 3 - color 2 + P3C3 = 0x2f, // Palette 3 - color 3 + DPPL = 0x30, // Display list list point low + P4C1 = 0x31, // Palette 4 - color 1 + P4C2 = 0x32, // Palette 4 - color 2 + P4C3 = 0x33, // Palette 4 - color 3 + CHARBASE= 0x34, // Character base address + P5C1 = 0x35, // Palette 5 - color 1 + P5C2 = 0x36, // Palette 5 - color 2 + P5C3 = 0x37, // Palette 5 - color 3 + OFFSET = 0x38, // Future expansion (store zero here) + P6C1 = 0x39, // Palette 6 - color 1 + P6C2 = 0x3a, // Palette 6 - color 2 + P6C3 = 0x3b, // Palette 6 - color 3 + CTRL = 0x3c, // Maria control register + P7C1 = 0x3d, // Palette 7 - color 1 + P7C2 = 0x3e, // Palette 7 - color 2 + P7C3 = 0x3f; // Palette 7 - color 3 + + const int CPU_TICKS_PER_AUDIO_SAMPLE = 57; + + #endregion + + #region Fields + + readonly byte[] LineRAM = new byte[0x200]; + readonly byte[] Registers = new byte[0x40]; + + readonly Machine7800 M; + readonly TIASound TIASound; + + ulong _startOfFrameCpuClock; + int Scanline { get { return (int)(M.CPU.Clock - _startOfFrameCpuClock) / 114; } } + int HPos { get { return (int)(M.CPU.Clock - _startOfFrameCpuClock) % 114; } } + + int FirstVisibleScanline, LastVisibleScanline; + int _dmaClocks; + bool _isPal; + + // For lightgun emulation. + // Transient state, serialization unnecessary. + ulong _lightgunFirstSampleCpuClock; + int _lightgunFrameSamples, _lightgunSampledScanline, _lightgunSampledVisibleHpos; + + bool WM; + ushort DLL; + ushort DL; + int Offset; + int Holey; + int Width; + byte HPOS; + int PaletteNo; + bool INDMode; + + bool CtrlLock; + + // MARIA CNTL + bool DMAEnabled; + bool ColorKill; + bool CWidth; + bool BCntl; + bool Kangaroo; + byte RM; + + #endregion + + #region Public Members + + public void Reset() + { + CtrlLock = false; + + DMAEnabled = false; + ColorKill = false; + CWidth = false; + BCntl = false; + Kangaroo = false; + RM = 0; + + TIASound.Reset(); + + Log("{0} reset", this); + } + + public byte this[ushort addr] + { + get { return peek(addr); } + set { poke(addr, value); } + } + + public override string ToString() + { + return GetType().Name; + } + + public void StartFrame() + { + _startOfFrameCpuClock = M.CPU.Clock + (ulong)(M.CPU.RunClocks / M.CPU.RunClocksMultiple); + _lightgunFirstSampleCpuClock = 0; + + AssertDebug(M.CPU.RunClocks <= 0 && (M.CPU.RunClocks % M.CPU.RunClocksMultiple) == 0); + AssertDebug((_startOfFrameCpuClock % (114 * (ulong)M.FrameBuffer.Scanlines)) == 0); + + TIASound.StartFrame(); + } + + public int DoDMAProcessing() + { + OutputLineRAM(); + + var sl = Scanline; + + if (!DMAEnabled || sl < FirstVisibleScanline || sl >= LastVisibleScanline) + return 0; + + _dmaClocks = 0; + + if (DMAEnabled && sl == FirstVisibleScanline) + { + // DMA TIMING: End of VBLANK: DMA Startup + long shutdown + _dmaClocks += 15; + + DLL = WORD(Registers[DPPL], Registers[DPPH]); + + ConsumeNextDLLEntry(); + } + + // DMA TIMING: DMA Startup, 5-9 cycles + _dmaClocks += 5; + + BuildLineRAM(); + + if (--Offset < 0) + { + ConsumeNextDLLEntry(); + + // DMA TIMING: DMA Shutdown: Last line of zone, 10-13 cycles + _dmaClocks += 10; + } + else + { + // DMA TIMING: DMA Shutdown: Other line of zone, 4-7 cycles + _dmaClocks += 4; + } + + return _dmaClocks; + } + + public void EndFrame() + { + TIASound.EndFrame(); + } + + #endregion + + #region Constructors + + private Maria() + { + } + + public Maria(Machine7800 m, int scanlines) + { + if (m == null) + throw new ArgumentNullException("m"); + + M = m; + InitializeVisibleScanlineValues(scanlines); + TIASound = new TIASound(M, CPU_TICKS_PER_AUDIO_SAMPLE); + } + + #endregion + + #region Scanline Builders + + void BuildLineRAM() + { + var dl = DL; + + // Iterate through Display List (DL) + while (true) + { + var modeByte = DmaRead(dl + 1); + if ((modeByte & 0x5f) == 0) + break; + + INDMode = false; + ushort graphaddr; + + if ((modeByte & 0x1f) == 0) + { + // Extended DL header + var dl0 = DmaRead(dl++); // low address + var dl1 = DmaRead(dl++); // mode + var dl2 = DmaRead(dl++); // high address + var dl3 = DmaRead(dl++); // palette(7-5)/width(4-0) + var dl4 = DmaRead(dl++); // horizontal position + + graphaddr = WORD(dl0, dl2); + WM = (dl1 & 0x80) != 0; + INDMode = (dl1 & 0x20) != 0; + PaletteNo = (dl3 & 0xe0) >> 3; + Width = (~dl3 & 0x1f) + 1; + HPOS = dl4; + + // DMA TIMING: DL 5 byte header + _dmaClocks += 10; + } + else + { + // Normal DL header + var dl0 = DmaRead(dl++); // low address + var dl1 = DmaRead(dl++); // palette(7-5)/width(4-0) + var dl2 = DmaRead(dl++); // high address + var dl3 = DmaRead(dl++); // horizontal position + + graphaddr = WORD(dl0, dl2); + PaletteNo = (dl1 & 0xe0) >> 3; + Width = (~dl1 & 0x1f) + 1; + HPOS = dl3; + + // DMA TIMING: DL 4 byte header + _dmaClocks += 8; + } + + // DMA TIMING: Graphic reads + if (RM != 1) + _dmaClocks += (Width * (INDMode ? (CWidth ? 9 : 6) : 3)); + + switch (RM) + { + case 0: + if (WM) BuildLineRAM160B(graphaddr); else BuildLineRAM160A(graphaddr); + break; + case 1: + continue; + case 2: + if (WM) BuildLineRAM320B(graphaddr); else BuildLineRAM320D(graphaddr); + break; + case 3: + if (WM) BuildLineRAM320C(graphaddr); else BuildLineRAM320A(graphaddr); + break; + } + } + } + + void BuildLineRAM160A(ushort graphaddr) + { + var indbytes = (INDMode && CWidth) ? 2 : 1; + var hpos = HPOS << 1; + var dataaddr = (ushort)(graphaddr + (Offset << 8)); + + for (var i=0; i < Width; i++) + { + if (INDMode) + { + dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset); + } + + for (var j=0; j < indbytes; j++) + { + if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800)) + { + hpos += 8; + dataaddr++; + AssertDebug(!Kangaroo); + continue; + } + + int d = DmaRead(dataaddr++); + + var c = (d & 0xc0) >> 6; + if (c != 0) + { + var val = (byte)(PaletteNo | c); + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val; + } + AssertDebug(c != 0 || c == 0 && !Kangaroo); + + hpos += 2; + + c = (d & 0x30) >> 4; + if (c != 0) + { + var val = (byte)(PaletteNo | c); + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val; + } + AssertDebug(c != 0 || c == 0 && !Kangaroo); + + hpos += 2; + + c = (d & 0x0c) >> 2; + if (c != 0) + { + var val = (byte)(PaletteNo | c); + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val; + } + AssertDebug(c != 0 || c == 0 && !Kangaroo); + + hpos += 2; + + c = d & 0x03; + if (c != 0) + { + var val = (byte)(PaletteNo | c); + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val; + } + AssertDebug(c != 0 || c == 0 && !Kangaroo); + + hpos += 2; + } + } + } + + void BuildLineRAM160B(ushort graphaddr) + { + var indbytes = (INDMode && CWidth) ? 2 : 1; + var hpos = HPOS << 1; + var dataaddr = (ushort)(graphaddr + (Offset << 8)); + + for (var i = 0; i < Width; i++) + { + if (INDMode) + { + dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset); + } + + for (var j=0; j < indbytes; j++) + { + if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800)) + { + hpos += 4; + dataaddr++; + continue; + } + + int d = DmaRead(dataaddr++); + + var c = (d & 0xc0) >> 6; + if (c != 0) + { + var p = ((PaletteNo >> 2) & 0x04) | ((d & 0x0c) >> 2); + var val = (byte)((p << 2) | c); + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val; + } + else if (Kangaroo) + { + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = 0; + } + + hpos += 2; + + c = (d & 0x30) >> 4; + if (c != 0) + { + var p = ((PaletteNo >> 2) & 0x04) | (d & 0x03); + var val = (byte)((p << 2) | c); + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val; + } + else if (Kangaroo) + { + LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = 0; + } + + hpos += 2; + } + } + } + + void BuildLineRAM320A(ushort graphaddr) + { + var color = (byte)(PaletteNo | 2); + var hpos = HPOS << 1; + var dataaddr = (ushort)(graphaddr + (Offset << 8)); + + AssertDebug(!CWidth); + + for (var i = 0; i < Width; i++) + { + if (INDMode) + { + dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset); + } + + if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800)) + { + hpos += 8; + dataaddr++; + continue; + } + + int d = DmaRead(dataaddr++); + + if ((d & 0x80) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x40) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x20) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x10) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x08) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x04) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x02) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x01) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + } + } + + void BuildLineRAM320B(ushort graphaddr) + { + var indbytes = (INDMode && CWidth) ? 2 : 1; + var hpos = HPOS << 1; + var dataaddr = (ushort)(graphaddr + (Offset << 8)); + + for (var i = 0; i < Width; i++) + { + if (INDMode) + { + dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset); + } + + for (var j=0; j < indbytes; j++) + { + if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800)) + { + hpos += 4; + dataaddr++; + continue; + } + + int d = DmaRead(dataaddr++); + + var c = ((d & 0x80) >> 6) | ((d & 0x08) >> 3); + if (c != 0) + { + if ((d & 0xc0) != 0 || Kangaroo) + LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c); + } + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + else if ((d & 0xcc) != 0) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x40) >> 5) | ((d & 0x04) >> 2); + if (c != 0) + { + if ((d & 0xc0) != 0 || Kangaroo) + LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c); + } + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + else if ((d & 0xcc) != 0) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x20) >> 4) | ((d & 0x02) >> 1); + if (c != 0) + { + if ((d & 0x30) != 0 || Kangaroo) + LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c); + } + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + else if ((d & 0x33) != 0) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x10) >> 3) | (d & 0x01); + if (c != 0) + { + if ((d & 0x30) != 0 || Kangaroo) + LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c); + } + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + else if ((d & 0x33) != 0) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + } + } + } + + void BuildLineRAM320C(ushort graphaddr) + { + var hpos = HPOS << 1; + var dataaddr = (ushort)(graphaddr + (Offset << 8)); + + AssertDebug(!CWidth); + + for (var i = 0; i < Width; i++) + { + if (INDMode) + { + dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset); + } + + if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800)) + { + hpos += 4; + dataaddr++; + continue; + } + + int d = DmaRead(dataaddr++); + + var color = (byte)(((((d & 0x0c) >> 2) | ((PaletteNo >> 2) & 0x04)) << 2) | 2); + + if ((d & 0x80) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x40) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + color = (byte)((((d & 0x03) | ((PaletteNo >> 2) & 0x04)) << 2) | 2); + + if ((d & 0x20) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + if ((d & 0x10) != 0) + LineRAM[hpos & 0x1ff] = color; + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + } + } + + void BuildLineRAM320D(ushort graphaddr) + { + var indbytes = (INDMode && CWidth) ? 2 : 1; + var hpos = HPOS << 1; + var dataaddr = (ushort)(graphaddr + (Offset << 8)); + + for (var i = 0; i < Width; i++) + { + if (INDMode) + { + dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset); + } + + for (var j=0; j < indbytes; j++) + { + if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800)) + { + hpos += 8; + dataaddr++; + continue; + } + + int d = DmaRead(dataaddr++); + + var c = ((d & 0x80) >> 6) | (((PaletteNo >> 2) & 2) >> 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x40) >> 5) | ((PaletteNo >> 2) & 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x20) >> 4) | (((PaletteNo >> 2) & 2) >> 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x10) >> 3) | ((PaletteNo >> 2) & 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x08) >> 2) | (((PaletteNo >> 2) & 2) >> 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x04) >> 1) | ((PaletteNo >> 2) & 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = (d & 0x02) | (((PaletteNo >> 2) & 2) >> 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + + c = ((d & 0x01) << 1) | ((PaletteNo >> 2) & 1); + if (c != 0) + LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c); + else if (Kangaroo) + LineRAM[hpos & 0x1ff] = 0; + + hpos++; + } + } + } + + void OutputLineRAM() + { + var bufferElement = new BufferElement(); + var fbi = ((Scanline + 1) * M.FrameBuffer.VideoBufferElementVisiblePitch) % M.FrameBuffer.VideoBufferElementLength; + + for (int i = 0, s = 0; i < M.FrameBuffer.VideoBufferElementVisiblePitch; i++) + { + for (var j = 0; j < BufferElement.SIZE; j++, s++) + { + var colorIndex = LineRAM[s]; + bufferElement[j] = Registers[BACKGRND + ((colorIndex & 3) == 0 ? 0 : colorIndex)]; + } + M.FrameBuffer.VideoBuffer[fbi] = bufferElement; + if (++fbi == M.FrameBuffer.VideoBufferElementLength) + fbi = 0; + } + + for (var i = 0; i < LineRAM.Length; i++) + { + LineRAM[i] = 0; + } + } + + #endregion + + #region Maria Peek + + byte peek(ushort addr) + { + addr &= 0x3f; + var mi = M.InputState; + + switch(addr) + { + case MSTAT: + var sl = Scanline; + return (sl < FirstVisibleScanline || sl >= LastVisibleScanline) + ? (byte)0x80 // VBLANK ON + : (byte)0; // VBLANK OFF + case INPT0: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? (byte)0x80 : (byte)0; // player1,button R + case INPT1: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger2) ? (byte)0x80 : (byte)0; // player1,button L + case INPT2: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? (byte)0x80 : (byte)0; // player2,button R + case INPT3: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger2) ? (byte)0x80 : (byte)0; // player2,button L + case INPT4: return SampleINPTLatched(4) ? (byte)0 : (byte)0x80; // player1,button L/R + case INPT5: return SampleINPTLatched(5) ? (byte)0 : (byte)0x80; // player2,button L/R + default: + LogDebug("Maria: Unhandled peek at ${0:x4}, PC=${1:x4}", addr, M.CPU.PC); + var retval = Registers[addr]; + return retval; + } + } + + #endregion + + #region Maria Poke + + void poke(ushort addr, byte data) + { + addr &= 0x3f; + + switch (addr) + { + // INPUT PORT CONTROL + // Only the first four bits of INPTCTRL are used: + // D0: lock mode (after this bit has been set high, no more mode changes can be done until the console is turned off) + // D1: 0=disable MARIA (only RIOT RAM is available); 1=enable MARIA (also enables system RAM) + // D2: 0=enable BIOS at $8000-$FFFF (actually NTSC only uses 4KB and PAL uses 16KB); 1=disable BIOS and enable cartridge + // D3: 0=disable TIA video pull-ups (video output is MARIA instead of TIA); 1=enable TIA video pull-ups (video output is TIA instead of MARIA) + // + case INPTCTRL: + if (CtrlLock) + { + Log("Maria: INPTCTRL: LOCKED: Ignoring: ${0:x2}, PC=${1:x4}", data, M.CPU.PC); + break; + } + + CtrlLock = (data & (1 << 0)) != 0; + var mariaEnable = (data & (1 << 1)) != 0; + var biosDisable = (data & (1 << 2)) != 0; + var tiaopEnable = (data & (1 << 3)) != 0; + + Log("Maria: INPTCTRL: ${0:x2}, PC=${1:x4}, lockMode={2}, mariaEnable={3} biosDisable={4} tiaOutput={5}", + data, M.CPU.PC, CtrlLock, mariaEnable, biosDisable, tiaopEnable); + + if (biosDisable) + { + M.SwapOutBIOS(); + } + else + { + M.SwapInBIOS(); + } + break; + case WSYNC: + // Request a CPU preemption to service the delay request + M.CPU.EmulatorPreemptRequest = true; + break; + case CTRL: + ColorKill = (data & 0x80) != 0; + DMAEnabled = (data & 0x60) == 0x40; + CWidth = (data & 0x10) != 0; + BCntl = (data & 0x08) != 0; + Kangaroo = (data & 0x04) != 0; + RM = (byte)(data & 0x03); + break; + case MSTAT: + break; + case CHARBASE: + case DPPH: + case DPPL: + Registers[addr] = data; + break; + case BACKGRND: + case P0C1: + case P0C2: + case P0C3: + case P1C1: + case P1C2: + case P1C3: + case P2C1: + case P2C2: + case P2C3: + case P3C1: + case P3C2: + case P3C3: + case P4C1: + case P4C2: + case P4C3: + case P5C1: + case P5C2: + case P5C3: + case P6C1: + case P6C2: + case P6C3: + case P7C1: + case P7C2: + case P7C3: + Registers[addr] = data; + break; + case AUDC0: + case AUDC1: + case AUDF0: + case AUDF1: + case AUDV0: + case AUDV1: + TIASound.Update(addr, data); + break; + case OFFSET: + Log("Maria: OFFSET: ROM wrote ${0:x2}, PC=${1:x4} (reserved for future expansion)", data, M.CPU.PC); + break; + default: + Registers[addr] = data; + LogDebug("Maria: Unhandled poke:${0:x4} w/${1:x2}, PC=${2:x4}", addr, data, M.CPU.PC); + break; + } + } + + #endregion + + #region Input Helpers + + bool SampleINPTLatched(int inpt) + { + var mi = M.InputState; + var playerNo = inpt - 4; + + switch (playerNo == 0 ? mi.LeftControllerJack : mi.RightControllerJack) + { + case Controller.Joystick: + return mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger); + case Controller.ProLineJoystick: + var portbline = 4 << (playerNo << 1); + if ((M.PIA.DDRB & portbline) != 0 && (M.PIA.WrittenPortB & portbline) == 0) + return false; + return mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger) + || mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger2); + case Controller.Lightgun: + + // This is one area where always running fixed at the faster CPU frequency creates emulation challenges. + // Fortunately since lightgun sampling is a dedicated activity on a frame, the job of compensating is tractable. + + // Track the number of samples this frame, the time of the first sample, and capture the lightgun location. + if (_lightgunFirstSampleCpuClock == 0) + { + _lightgunFirstSampleCpuClock = M.CPU.Clock; + _lightgunFrameSamples = 0; + mi.SampleCapturedLightGunPosition(playerNo, out _lightgunSampledScanline, out _lightgunSampledVisibleHpos); + } + _lightgunFrameSamples++; + + // Magic Adjustment Factor + // Seems sufficient to account for the timing impact of successive lightrun reads (i.e., 'slow' memory accesses.) + // Obtained through through trial-and-error. + const float magicAdjustmentFactor = 2.135f; + + var firstLightgunSampleMariaFrameClock = (int)((_lightgunFirstSampleCpuClock - _startOfFrameCpuClock) << 2); + var mariaClocksSinceFirstLightgunSample = (int)((M.CPU.Clock - _lightgunFirstSampleCpuClock) << 2); + var adjustmentMariaClocks = (int)Math.Round(_lightgunFrameSamples * magicAdjustmentFactor); + var actualMariaFrameClock = firstLightgunSampleMariaFrameClock + mariaClocksSinceFirstLightgunSample + adjustmentMariaClocks; + var actualScanline = actualMariaFrameClock / 456; + var actualHpos = actualMariaFrameClock % 456; + + // Lightgun sampling looks intended to begin at the start of the scanline. + // Compensate with another magic constant since we're always off by a fixed amount. + actualHpos -= 62; + if (actualHpos < 0) + { + actualHpos += 456; + actualScanline--; + } + + var sampledScanline = _lightgunSampledScanline; + var sampledVisibleHpos = _lightgunSampledVisibleHpos; + + // Seems reasonable the gun sees more than a single pixel (more like a circle or oval) and triggers sooner accordingly. + // These adjustments were obtained through trial-and-error. + if (_isPal) + { + sampledScanline -= 19; + } + else + { + sampledScanline -= 16; + sampledVisibleHpos += 4; + } + return (actualScanline >= sampledScanline) && (actualHpos >= (sampledVisibleHpos + 136 /* HBLANK clocks */)); + } + return false; + } + + #endregion + + #region Serialization Members + + public Maria(DeserializationContext input, Machine7800 m, int scanlines) + { + if (input == null) + throw new ArgumentNullException("input"); + if (m == null) + throw new ArgumentNullException("m"); + + M = m; + InitializeVisibleScanlineValues(scanlines); + TIASound = new TIASound(input, M, CPU_TICKS_PER_AUDIO_SAMPLE); + + var version = input.CheckVersion(1, 2); + LineRAM = input.ReadExpectedBytes(512); + if (version == 1) + { + // formerly persisted values, MariaPalette[8,4] + for (var i = 0; i < 32; i++) + input.ReadByte(); + } + Registers = input.ReadExpectedBytes(0x40); + if (version == 1) + { + // formerly persisted value, Scanline + input.ReadInt32(); + } + switch (version) + { + case 1: + WM = (input.ReadByte() != 0); + break; + case 2: + WM = input.ReadBoolean(); + break; + } + DLL = input.ReadUInt16(); + DL = input.ReadUInt16(); + Offset = input.ReadInt32(); + Holey = input.ReadInt32(); + Width = input.ReadInt32(); + HPOS = input.ReadByte(); + PaletteNo = input.ReadByte(); + INDMode = input.ReadBoolean(); + if (version == 1) + { + // formerly persisted value (DLI) + input.ReadBoolean(); + } + CtrlLock = input.ReadBoolean(); + if (version == 1) + { + // formerly persisted value (VBLANK) + input.ReadByte(); + } + DMAEnabled = input.ReadBoolean(); + if (version == 1) + { + // formerly persisted value (DMAOn) + input.ReadBoolean(); + } + ColorKill = input.ReadBoolean(); + CWidth = input.ReadBoolean(); + BCntl = input.ReadBoolean(); + Kangaroo = input.ReadBoolean(); + RM = input.ReadByte(); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.Write(TIASound); + + output.WriteVersion(2); + output.Write(LineRAM); + output.Write(Registers); + output.Write(WM); + output.Write(DLL); + output.Write(DL); + output.Write(Offset); + output.Write(Holey); + output.Write(Width); + output.Write(HPOS); + output.Write((byte)PaletteNo); + output.Write(INDMode); + output.Write(CtrlLock); + output.Write(DMAEnabled); + output.Write(ColorKill); + output.Write(CWidth); + output.Write(BCntl); + output.Write(Kangaroo); + output.Write(RM); + } + + #endregion + + #region Helpers + + void ConsumeNextDLLEntry() + { + // Display List List (DLL) entry + var dll0 = DmaRead(DLL++); // DLI, Holey, Offset + var dll1 = DmaRead(DLL++); // High DL address + var dll2 = DmaRead(DLL++); // Low DL address + + var dli = (dll0 & 0x80) != 0; + Holey = (dll0 & 0x60) >> 5; + Offset = dll0 & 0x0f; + + // Update current Display List (DL) + DL = WORD(dll2, dll1); + + if (dli) + { + M.CPU.NMIInterruptRequest = true; + + // DMA TIMING: One tick between DMA Shutdown and DLI + _dmaClocks += 1; + } + } + + void InitializeVisibleScanlineValues(int scanlines) + { + switch (scanlines) + { + case 262: // NTSC + FirstVisibleScanline = 11; + LastVisibleScanline = FirstVisibleScanline + 242; + _isPal = false; + break; + case 312: // PAL + FirstVisibleScanline = 11; + LastVisibleScanline = FirstVisibleScanline + 292; + _isPal = true; + break; + default: + throw new ArgumentException("scanlines must be 262 or 312."); + } + } + + void Log(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + // convenience overload + static ushort WORD(int lsb, int msb) + { + return WORD((byte)lsb, (byte)msb); + } + + static ushort WORD(byte lsb, byte msb) + { + return (ushort)(lsb | msb << 8); + } + + // convenience overload + byte DmaRead(int addr) + { + return DmaRead((ushort)addr); + } + + byte DmaRead(ushort addr) + { +#if DEBUG + if (addr < 0x1800) + LogDebug("Maria: Questionable DMA read at ${0:x4} by PC=${1:x4}", addr, M.CPU.PC); +#endif + return M.Mem[addr]; + } + + [System.Diagnostics.Conditional("DEBUG")] + void LogDebug(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + [System.Diagnostics.Conditional("DEBUG")] + void AssertDebug(bool cond) + { + if (!cond) + System.Diagnostics.Debugger.Break(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MariaTables.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MariaTables.cs new file mode 100644 index 0000000000..aa8745ee64 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/MariaTables.cs @@ -0,0 +1,180 @@ +/* + * MariaTables.cs + * + * Palette tables for the Maria class. + * All derived from Dan Boris' 7800/MAME code. + * + * Copyright © 2004 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public static class MariaTables + { + public static readonly int[] NTSCPalette = + { + 0x000000, 0x1c1c1c, 0x393939, 0x595959, // Grey + 0x797979, 0x929292, 0xababab, 0xbcbcbc, + 0xcdcdcd, 0xd9d9d9, 0xe6e6e6, 0xececec, + 0xf2f2f2, 0xf8f8f8, 0xffffff, 0xffffff, + + 0x391701, 0x5e2304, 0x833008, 0xa54716, // Gold + 0xc85f24, 0xe37820, 0xff911d, 0xffab1d, + 0xffc51d, 0xffce34, 0xffd84c, 0xffe651, + 0xfff456, 0xfff977, 0xffff98, 0xffff98, + + 0x451904, 0x721e11, 0x9f241e, 0xb33a20, // Orange + 0xc85122, 0xe36920, 0xff811e, 0xff8c25, + 0xff982c, 0xffae38, 0xffc545, 0xffc559, + 0xffc66d, 0xffd587, 0xffe4a1, 0xffe4a1, + + 0x4a1704, 0x7e1a0d, 0xb21d17, 0xc82119, // Red Orange + 0xdf251c, 0xec3b38, 0xfa5255, 0xfc6161, + 0xff706e, 0xff7f7e, 0xff8f8f, 0xff9d9e, + 0xffabad, 0xffb9bd, 0xffc7ce, 0xffc7ce, + + 0x050568, 0x3b136d, 0x712272, 0x8b2a8c, // Pink + 0xa532a6, 0xb938ba, 0xcd3ecf, 0xdb47dd, + 0xea51eb, 0xf45ff5, 0xfe6dff, 0xfe7afd, + 0xff87fb, 0xff95fd, 0xffa4ff, 0xffa4ff, + + 0x280479, 0x400984, 0x590f90, 0x70249d, // Purple + 0x8839aa, 0xa441c3, 0xc04adc, 0xd054ed, + 0xe05eff, 0xe96dff, 0xf27cff, 0xf88aff, + 0xff98ff, 0xfea1ff, 0xfeabff, 0xfeabff, + + 0x35088a, 0x420aad, 0x500cd0, 0x6428d0, // Purple Blue + 0x7945d0, 0x8d4bd4, 0xa251d9, 0xb058ec, + 0xbe60ff, 0xc56bff, 0xcc77ff, 0xd183ff, + 0xd790ff, 0xdb9dff, 0xdfaaff, 0xdfaaff, + + 0x051e81, 0x0626a5, 0x082fca, 0x263dd4, // Blue1 + 0x444cde, 0x4f5aee, 0x5a68ff, 0x6575ff, + 0x7183ff, 0x8091ff, 0x90a0ff, 0x97a9ff, + 0x9fb2ff, 0xafbeff, 0xc0cbff, 0xc0cbff, + + 0x0c048b, 0x2218a0, 0x382db5, 0x483ec7, // Blue2 + 0x584fda, 0x6159ec, 0x6b64ff, 0x7a74ff, + 0x8a84ff, 0x918eff, 0x9998ff, 0xa5a3ff, + 0xb1aeff, 0xb8b8ff, 0xc0c2ff, 0xc0c2ff, + + 0x1d295a, 0x1d3876, 0x1d4892, 0x1c5cac, // Light Blue + 0x1c71c6, 0x3286cf, 0x489bd9, 0x4ea8ec, + 0x55b6ff, 0x70c7ff, 0x8cd8ff, 0x93dbff, + 0x9bdfff, 0xafe4ff, 0xc3e9ff, 0xc3e9ff, + + 0x2f4302, 0x395202, 0x446103, 0x417a12, // Turquoise + 0x3e9421, 0x4a9f2e, 0x57ab3b, 0x5cbd55, + 0x61d070, 0x69e27a, 0x72f584, 0x7cfa8d, + 0x87ff97, 0x9affa6, 0xadffb6, 0xadffb6, + + 0x0a4108, 0x0d540a, 0x10680d, 0x137d0f, // Green Blue + 0x169212, 0x19a514, 0x1cb917, 0x1ec919, + 0x21d91b, 0x47e42d, 0x6ef040, 0x78f74d, + 0x83ff5b, 0x9aff7a, 0xb2ff9a, 0xb2ff9a, + + 0x04410b, 0x05530e, 0x066611, 0x077714, // Green + 0x088817, 0x099b1a, 0x0baf1d, 0x48c41f, + 0x86d922, 0x8fe924, 0x99f927, 0xa8fc41, + 0xb7ff5b, 0xc9ff6e, 0xdcff81, 0xdcff81, + + 0x02350f, 0x073f15, 0x0c4a1c, 0x2d5f1e, // Yellow Green + 0x4f7420, 0x598324, 0x649228, 0x82a12e, + 0xa1b034, 0xa9c13a, 0xb2d241, 0xc4d945, + 0xd6e149, 0xe4f04e, 0xf2ff53, 0xf2ff53, + + 0x263001, 0x243803, 0x234005, 0x51541b, // Orange Green + 0x806931, 0x978135, 0xaf993a, 0xc2a73e, + 0xd5b543, 0xdbc03d, 0xe1cb38, 0xe2d836, + 0xe3e534, 0xeff258, 0xfbff7d, 0xfbff7d, + + 0x401a02, 0x581f05, 0x702408, 0x8d3a13, // Light Orange + 0xab511f, 0xb56427, 0xbf7730, 0xd0853a, + 0xe19344, 0xeda04e, 0xf9ad58, 0xfcb75c, + 0xffc160, 0xffc671, 0xffcb83, 0xffcb83 + }; + + public static readonly int[] PALPalette = + { + 0x000000, 0x1c1c1c, 0x393939, 0x595959, // Grey + 0x797979, 0x929292, 0xababab, 0xbcbcbc, + 0xcdcdcd, 0xd9d9d9, 0xe6e6e6, 0xececec, + 0xf2f2f2, 0xf8f8f8, 0xffffff, 0xffffff, + + 0x263001, 0x243803, 0x234005, 0x51541b, // Orange Green + 0x806931, 0x978135, 0xaf993a, 0xc2a73e, + 0xd5b543, 0xdbc03d, 0xe1cb38, 0xe2d836, + 0xe3e534, 0xeff258, 0xfbff7d, 0xfbff7d, + + 0x263001, 0x243803, 0x234005, 0x51541b, // Orange Green + 0x806931, 0x978135, 0xaf993a, 0xc2a73e, + 0xd5b543, 0xdbc03d, 0xe1cb38, 0xe2d836, + 0xe3e534, 0xeff258, 0xfbff7d, 0xfbff7d, + + 0x401a02, 0x581f05, 0x702408, 0x8d3a13, // Light Orange + 0xab511f, 0xb56427, 0xbf7730, 0xd0853a, + 0xe19344, 0xeda04e, 0xf9ad58, 0xfcb75c, + 0xffc160, 0xffc671, 0xffcb83, 0xffcb83, + + 0x391701, 0x5e2304, 0x833008, 0xa54716, // Gold + 0xc85f24, 0xe37820, 0xff911d, 0xffab1d, + 0xffc51d, 0xffce34, 0xffd84c, 0xffe651, + 0xfff456, 0xfff977, 0xffff98, 0xffff98, + + 0x451904, 0x721e11, 0x9f241e, 0xb33a20, // Orange + 0xc85122, 0xe36920, 0xff811e, 0xff8c25, + 0xff982c, 0xffae38, 0xffc545, 0xffc559, + 0xffc66d, 0xffd587, 0xffe4a1, 0xffe4a1, + + 0x4a1704, 0x7e1a0d, 0xb21d17, 0xc82119, // Red Orange + 0xdf251c, 0xec3b38, 0xfa5255, 0xfc6161, + 0xff706e, 0xff7f7e, 0xff8f8f, 0xff9d9e, + 0xffabad, 0xffb9bd, 0xffc7ce, 0xffc7ce, + + 0x050568, 0x3b136d, 0x712272, 0x8b2a8c, // Pink + 0xa532a6, 0xb938ba, 0xcd3ecf, 0xdb47dd, + 0xea51eb, 0xf45ff5, 0xfe6dff, 0xfe7afd, + 0xff87fb, 0xff95fd, 0xffa4ff, 0xffa4ff, + + 0x280479, 0x400984, 0x590f90, 0x70249d, // Purple + 0x8839aa, 0xa441c3, 0xc04adc, 0xd054ed, + 0xe05eff, 0xe96dff, 0xf27cff, 0xf88aff, + 0xff98ff, 0xfea1ff, 0xfeabff, 0xfeabff, + + 0x051e81, 0x0626a5, 0x082fca, 0x263dd4, // Blue1 + 0x444cde, 0x4f5aee, 0x5a68ff, 0x6575ff, + 0x7183ff, 0x8091ff, 0x90a0ff, 0x97a9ff, + 0x9fb2ff, 0xafbeff, 0xc0cbff, 0xc0cbff, + + 0x0c048b, 0x2218a0, 0x382db5, 0x483ec7, // Blue2 + 0x584fda, 0x6159ec, 0x6b64ff, 0x7a74ff, + 0x8a84ff, 0x918eff, 0x9998ff, 0xa5a3ff, + 0xb1aeff, 0xb8b8ff, 0xc0c2ff, 0xc0c2ff, + + 0x1d295a, 0x1d3876, 0x1d4892, 0x1c5cac, // Light Blue + 0x1c71c6, 0x3286cf, 0x489bd9, 0x4ea8ec, + 0x55b6ff, 0x70c7ff, 0x8cd8ff, 0x93dbff, + 0x9bdfff, 0xafe4ff, 0xc3e9ff, 0xc3e9ff, + + 0x2f4302, 0x395202, 0x446103, 0x417a12, // Turquoise + 0x3e9421, 0x4a9f2e, 0x57ab3b, 0x5cbd55, + 0x61d070, 0x69e27a, 0x72f584, 0x7cfa8d, + 0x87ff97, 0x9affa6, 0xadffb6, 0xadffb6, + + 0x0a4108, 0x0d540a, 0x10680d, 0x137d0f, // Green Blue + 0x169212, 0x19a514, 0x1cb917, 0x1ec919, + 0x21d91b, 0x47e42d, 0x6ef040, 0x78f74d, + 0x83ff5b, 0x9aff7a, 0xb2ff9a, 0xb2ff9a, + + 0x04410b, 0x05530e, 0x066611, 0x077714, // Green + 0x088817, 0x099b1a, 0x0baf1d, 0x48c41f, + 0x86d922, 0x8fe924, 0x99f927, 0xa8fc41, + 0xb7ff5b, 0xc9ff6e, 0xdcff81, 0xdcff81, + + 0x02350f, 0x073f15, 0x0c4a1c, 0x2d5f1e, // Yellow Green + 0x4f7420, 0x598324, 0x649228, 0x82a12e, + 0xa1b034, 0xa9c13a, 0xb2d241, 0xc4d945, + 0xd6e149, 0xe4f04e, 0xf2ff53, 0xf2ff53 + }; + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullDevice.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullDevice.cs new file mode 100644 index 0000000000..d0c691543d --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullDevice.cs @@ -0,0 +1,78 @@ +/* + * NullDevice.cs + * + * Default memory mappable device. + * + * Copyright © 2003, 2004 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class NullDevice : IDevice + { + MachineBase M { get; set; } + + #region IDevice Members + + public void Reset() + { + Log("{0} reset", this); + } + + public byte this[ushort addr] + { + get + { + LogDebug("NullDevice: Peek at ${0:x4}, PC=${1:x4}", addr, M.CPU.PC); + return 0; + } + set + { + LogDebug("NullDevice: Poke at ${0:x4},${1:x2}, PC=${2:x4}", addr, value, M.CPU.PC); + } + } + + #endregion + + public override String ToString() + { + return "NullDevice"; + } + + #region Constructors + + private NullDevice() + { + } + + public NullDevice(MachineBase m) + { + if (m == null) + throw new ArgumentNullException("m"); + M = m; + } + + #endregion + + #region Helpers + + void Log(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + [System.Diagnostics.Conditional("DEBUG")] + void LogDebug(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullLogger.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullLogger.cs new file mode 100644 index 0000000000..1604d3b883 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/NullLogger.cs @@ -0,0 +1,21 @@ +namespace EMU7800.Core +{ + public class NullLogger : ILogger + { + public void WriteLine(string format, params object[] args) + { + } + + public void WriteLine(object value) + { + } + + public void Write(string format, params object[] args) + { + } + + public void Write(object value) + { + } + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PIA.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PIA.cs new file mode 100644 index 0000000000..1da65519db --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PIA.cs @@ -0,0 +1,348 @@ +/* + * PIA.cs + * + * The Peripheral Interface Adapter (6532) device. + * a.k.a. RIOT (RAM I/O Timer?) + * + * Copyright © 2003, 2004, 2012 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class PIA : IDevice + { + readonly MachineBase M; + + readonly byte[] RAM = new byte[0x80]; + + ulong TimerTarget; + int TimerShift; + bool IRQEnabled, IRQTriggered; + + public byte DDRA { get; private set; } + public byte DDRB { get; private set; } + + public byte WrittenPortA { get; private set; } + public byte WrittenPortB { get; private set; } + + #region IDevice Members + + public void Reset() + { + // Some games will loop/hang on $0284 if these are initialized to zero + TimerShift = 10; + TimerTarget = M.CPU.Clock + (ulong)(0xff << TimerShift); + + IRQEnabled = false; + IRQTriggered = false; + + DDRA = 0; + + Log("{0} reset", this); + } + + public byte this[ushort addr] + { + get { return peek(addr); } + set { poke(addr, value); } + } + + #endregion + + public override string ToString() + { + return "PIA/RIOT M6532"; + } + + #region Constructors + + private PIA() + { + } + + public PIA(MachineBase m) + { + if (m == null) + throw new ArgumentNullException("m"); + M = m; + } + + #endregion + + byte peek(ushort addr) + { + if ((addr & 0x200) == 0) + { + return RAM[addr & 0x7f]; + } + + switch ((byte)(addr & 7)) + { + case 0: // SWCHA: Controllers + return ReadPortA(); + case 1: // SWCHA DDR: 0=input, 1=output + return DDRA; + case 2: // SWCHB: Console switches (on 7800, PB2 & PB4 are used) + return ReadPortB(); + case 3: // SWCHB DDR: 0=input, 1=output + return 0; + case 4: // INTIM + case 6: + return ReadTimerRegister(); + case 5: // INTFLG + case 7: + return ReadInterruptFlag(); + default: + LogDebug("PIA: Unhandled peek ${0:x4}, PC=${1:x4}", addr, M.CPU.PC); + return 0; + } + } + + void poke(ushort addr, byte data) + { + if ((addr & 0x200) == 0) + { + RAM[addr & 0x7f] = data; + return; + } + + // A2 Distinguishes I/O registers from the Timer + if ((addr & 0x04) != 0) + { + if ((addr & 0x10) != 0) + { + IRQEnabled = (addr & 0x08) != 0; + SetTimerRegister(data, addr & 3); + } + else + { + LogDebug("PIA: Timer: Unhandled poke ${0:x4} w/${1:x2}, PC=${2:x4}", addr, data, M.CPU.PC); + } + } + else + { + switch ((byte)(addr & 3)) + { + case 0: // SWCHA: Port A + WritePortA(data); + break; + case 1: // SWACNT: Port A DDR + DDRA = data; + break; + case 2: // SWCHB: Port B + WritePortB(data); + break; + case 3: // SWBCNT: Port B DDR + DDRB = data; + break; + } + } + } + + // 0: TIM1T: set 1 clock interval ( 838 nsec/interval) + // 1: TIM8T: set 8 clock interval ( 6.7 usec/interval) + // 2: TIM64T: set 64 clock interval ( 53.6 usec/interval) + // 3: T1024T: set 1024 clock interval (858.2 usec/interval) + void SetTimerRegister(byte data, int interval) + { + IRQTriggered = false; + TimerShift = new[] { 0, 3, 6, 10 }[interval]; + TimerTarget = M.CPU.Clock + (ulong)(data << TimerShift); + } + + byte ReadTimerRegister() + { + IRQTriggered = false; + var delta = (int)(TimerTarget - M.CPU.Clock); + if (delta >= 0) + { + return (byte)(delta >> TimerShift); + } + if (delta != -1) + { + IRQTriggered = true; + } + return (byte)(delta >= -256 ? delta : 0); + } + + byte ReadInterruptFlag() + { + var delta = (int)(TimerTarget - M.CPU.Clock); + return (byte)((delta >= 0 || IRQEnabled && IRQTriggered) ? 0x00 : 0x80); + } + + // PortA: Controller Jacks + // + // Left Jack Right Jack + // ------------- ------------- + // \ 1 2 3 4 5 / \ 1 2 3 4 5 / + // \ 6 7 8 9 / \ 6 7 8 9 / + // --------- --------- + // + // pin 1 D4 PIA SWCHA D0 PIA SWCHA + // pin 2 D5 PIA SWCHA D1 PIA SWCHA + // pin 3 D6 PIA SWCHA D2 PIA SWCHA + // pin 4 D7 PIA SWCHA D3 PIA SWCHA + // pin 5 D7 TIA INPT1 (Dumped) D7 TIA INPT3 (Dumped) 7800: Right Fire + // pin 6 D7 TIA INPT4 (Latched) D7 TIA INPT5 (Latched) 2600: Fire + // pin 7 +5 +5 + // pin 8 GND GND + // pin 9 D7 TIA INPT0 (Dumped) D7 TIA INPT2 (Dumped) 7800: Left Fire + // + byte ReadPortA() + { + var porta = 0; + var mi = M.InputState; + + switch (mi.LeftControllerJack) + { + case Controller.Joystick: + case Controller.ProLineJoystick: + case Controller.BoosterGrip: + porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Up) ? 0 : (1 << 4); + porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Down) ? 0 : (1 << 5); + porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Left) ? 0 : (1 << 6); + porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Right) ? 0 : (1 << 7); + break; + case Controller.Driving: + porta |= mi.SampleCapturedDrivingState(0) << 4; + break; + case Controller.Paddles: + porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? 0 : (1 << 7); + porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? 0 : (1 << 6); + break; + case Controller.Lightgun: + porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? (1 << 4) : 0; + break; + } + + switch (mi.RightControllerJack) + { + case Controller.Joystick: + case Controller.ProLineJoystick: + case Controller.BoosterGrip: + porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Up) ? 0 : (1 << 0); + porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Down) ? 0 : (1 << 1); + porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Left) ? 0 : (1 << 2); + porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Right) ? 0 : (1 << 3); + break; + case Controller.Driving: + porta |= mi.SampleCapturedDrivingState(1); + break; + case Controller.Paddles: + porta |= mi.SampleCapturedControllerActionState(2, ControllerAction.Trigger) ? 0 : (1 << 3); + porta |= mi.SampleCapturedControllerActionState(3, ControllerAction.Trigger) ? 0 : (1 << 2); + break; + case Controller.Lightgun: + porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? (1 << 0) : 0; + break; + } + + return (byte)porta; + } + + void WritePortA(byte porta) + { + WrittenPortA = (byte)((porta & DDRA) | (WrittenPortA & (~DDRA))); + } + + void WritePortB(byte portb) + { + WrittenPortB = (byte)((portb & DDRB) | (WrittenPortB & (~DDRB))); + } + + // PortB: Console Switches + // + // D0 Game Reset 0=on + // D1 Game Select 0=on + // D2 (used on 7800) + // D3 Console Color 1=Color, 0=B/W + // D4 (used on 7800) + // D5 (unused) + // D6 Left Difficulty A 1=A (pro), 0=B (novice) + // D7 Right Difficulty A 1=A (pro), 0=B (novice) + // + byte ReadPortB() + { + var portb = 0; + var mi = M.InputState; + + portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.GameReset) ? 0 : (1 << 0); + portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.GameSelect) ? 0 : (1 << 1); + portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.GameBW) ? 0 : (1 << 3); + portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.LeftDifficultyA) ? (1 << 6) : 0; + portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.RightDifficultyA) ? (1 << 7) : 0; + + return (byte)portb; + } + + #region Serialization Members + + public PIA(DeserializationContext input, MachineBase m) : this(m) + { + if (input == null) + throw new ArgumentNullException("input"); + + var version = input.CheckVersion(1, 2); + RAM = input.ReadExpectedBytes(0x80); + TimerTarget = input.ReadUInt64(); + TimerShift = input.ReadInt32(); + IRQEnabled = input.ReadBoolean(); + IRQTriggered = input.ReadBoolean(); + DDRA = input.ReadByte(); + WrittenPortA = input.ReadByte(); + if (version > 1) + { + DDRB = input.ReadByte(); + WrittenPortB = input.ReadByte(); + } + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(2); + output.Write(RAM); + output.Write(TimerTarget); + output.Write(TimerShift); + output.Write(IRQEnabled); + output.Write(IRQTriggered); + output.Write(DDRA); + output.Write(WrittenPortA); + output.Write(DDRB); + output.Write(WrittenPortB); + } + + #endregion + + #region Helpers + + void Log(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + [System.Diagnostics.Conditional("DEBUG")] + void LogDebug(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + [System.Diagnostics.Conditional("DEBUG")] + void AssertDebug(bool cond) + { + if (!cond) + System.Diagnostics.Debugger.Break(); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PokeySound.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PokeySound.cs new file mode 100644 index 0000000000..02466de2c6 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/PokeySound.cs @@ -0,0 +1,437 @@ +/* + * PokeySound.cs + * + * Emulation of the audio features of the Atari Pot Keyboard Integrated Circuit (POKEY, C012294). + * + * Implementation inspired by prior works of Greg Stanton (ProSystem Emulator) and Ron Fries. + * + * Copyright © 2012 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class PokeySound + { + #region Constants and Tables + + const int + AUDF1 = 0x00, // write reg: channel 1 frequency + AUDC1 = 0x01, // write reg: channel 1 generator + AUDF2 = 0x02, // write reg: channel 2 frequency + AUDC2 = 0x03, // write reg: channel 2 generator + AUDF3 = 0x04, // write reg: channel 3 frequency + AUDC3 = 0x05, // write reg: channel 3 generator + AUDF4 = 0x06, // write reg: channel 4 frequency + AUDC4 = 0x07, // write reg: channel 4 generator + AUDCTL = 0x08, // write reg: control over audio channels + SKCTL = 0x0f, // write reg: control over serial port + RANDOM = 0x0a; // read reg: random number generator value + + const int + AUDCTL_POLY9 = 0x80, // make 17-bit poly counter into a 9-bit poly counter + AUDCTL_CH1_179 = 0x40, // clocks channel 1 with 1.79 MHz, instead of 64 kHz + AUDCTL_CH3_179 = 0x20, // clocks channel 3 with 1.79 MHz, instead of 64 kHz + AUDCTL_CH1_CH2 = 0x10, // clock channel 2 with channel 1, instead of 64 kHz (16-bit) + AUDCTL_CH3_CH4 = 0x08, // clock channel 4 with channel 3, instead of 64 kHz (16-bit) + AUDCTL_CH1_FILTER = 0x04, // inserts high-pass filter into channel 1, clocked by channel 3 + AUDCTL_CH2_FILTER = 0x02, // inserts high-pass filter into channel 2, clocked by channel 4 + AUDCTL_CLOCK_15 = 0x01; // change normal clock base from 64 kHz to 15 kHz + + const int + AUDC_NOTPOLY5 = 0x80, + AUDC_POLY4 = 0x40, + AUDC_PURE = 0x20, + AUDC_VOLUME_ONLY = 0x10, + AUDC_VOLUME_MASK = 0x0f; + + const int + DIV_64 = 28, + DIV_15 = 114, + POLY9_SIZE = 0x01ff, + POLY17_SIZE = 0x0001ffff, + POKEY_FREQ = 1787520, + SKCTL_RESET = 3; + + const int CPU_TICKS_PER_AUDIO_SAMPLE = 57; + + readonly byte[] _poly04 = { 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0 }; + readonly byte[] _poly05 = { 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1 }; + readonly byte[] _poly17 = new byte[POLY9_SIZE]; // should be POLY17_SIZE, but instead wrapping around to conserve storage + + readonly Random _random = new Random(); + + #endregion + + #region Object State + + readonly MachineBase M; + + readonly int _pokeyTicksPerSample; + int _pokeyTicks; + + ulong _lastUpdateCpuClock; + int _bufferIndex; + + readonly byte[] _audf = new byte[4]; + readonly byte[] _audc = new byte[4]; + byte _audctl, _skctl; + + int _baseMultiplier; + int _poly04Counter; + int _poly05Counter; + int _poly17Counter, _poly17Size; + + readonly int[] _divideMax = new int[4]; + readonly int[] _divideCount = new int[4]; + readonly byte[] _output = new byte[4]; + readonly byte[] _outvol = new byte[4]; + + #endregion + + #region Public Members + + public void Reset() + { + _poly04Counter = _poly05Counter = _poly17Counter = _audctl = _skctl = 0; + + _baseMultiplier = DIV_64; + _poly17Size = POLY17_SIZE; + + _pokeyTicks = 0; + + for (var ch = 0; ch < 4; ch++) + { + _outvol[ch] = _output[ch] = _audc[ch] = _audf[ch] = 0; + _divideCount[ch] = Int32.MaxValue; + _divideMax[ch] = Int32.MaxValue; + } + } + + public void StartFrame() + { + _lastUpdateCpuClock = M.CPU.Clock; + _bufferIndex = 0; + } + + public void EndFrame() + { + RenderSamples(M.FrameBuffer.SoundBufferByteLength - _bufferIndex); + } + + public byte Read(ushort addr) + { + addr &= 0xf; + + switch (addr) + { + // If the 2 least significant bits of SKCTL are 0, the random number generator is disabled (return all 1s.) + // Ballblazer music relies on this. + case RANDOM: + return (_skctl & SKCTL_RESET) == 0 ? (byte)0xff : (byte)_random.Next(0xff); + default: + return 0; + } + } + + public void Update(ushort addr, byte data) + { + if (M.CPU.Clock > _lastUpdateCpuClock) + { + var updCpuClocks = (int)(M.CPU.Clock - _lastUpdateCpuClock); + var samples = updCpuClocks / CPU_TICKS_PER_AUDIO_SAMPLE; + RenderSamples(samples); + _lastUpdateCpuClock += (ulong)(samples * CPU_TICKS_PER_AUDIO_SAMPLE); + } + + addr &= 0xf; + + switch (addr) + { + case AUDF1: + _audf[0] = data; + ResetChannel1(); + if ((_audctl & AUDCTL_CH1_CH2) != 0) + ResetChannel2(); + break; + case AUDC1: + _audc[0] = data; + ResetChannel1(); + break; + case AUDF2: + _audf[1] = data; + ResetChannel2(); + break; + case AUDC2: + _audc[1] = data; + ResetChannel2(); + break; + case AUDF3: + _audf[2] = data; + ResetChannel3(); + if ((_audctl & AUDCTL_CH3_CH4) != 0) + ResetChannel4(); + break; + case AUDC3: + _audc[2] = data; + ResetChannel3(); + break; + case AUDF4: + _audf[3] = data; + ResetChannel4(); + break; + case AUDC4: + _audc[3] = data; + ResetChannel4(); + break; + case AUDCTL: + _audctl = data; + _poly17Size = ((_audctl & AUDCTL_POLY9) != 0) ? POLY9_SIZE : POLY17_SIZE; + _baseMultiplier = ((_audctl & AUDCTL_CLOCK_15) != 0) ? DIV_15 : DIV_64; + ResetChannel1(); + ResetChannel2(); + ResetChannel3(); + ResetChannel4(); + break; + case SKCTL: + _skctl = data; + break; + } + } + + #endregion + + #region Constructors + + private PokeySound() + { + _random.NextBytes(_poly17); + for (var i = 0; i < _poly17.Length; i++) + _poly17[i] &= 0x01; + + Reset(); + } + + public PokeySound(MachineBase m) : this() + { + if (m == null) + throw new ArgumentNullException("m"); + + M = m; + + // Add 8-bits of fractional representation to reduce distortion on output + _pokeyTicksPerSample = (POKEY_FREQ << 8) / M.SoundSampleFrequency; + } + + #endregion + + #region Serialization Members + + public PokeySound(DeserializationContext input, MachineBase m) : this(m) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + _lastUpdateCpuClock = input.ReadUInt64(); + _bufferIndex = input.ReadInt32(); + _audf = input.ReadBytes(); + _audc = input.ReadBytes(); + _audctl = input.ReadByte(); + _skctl = input.ReadByte(); + _output = input.ReadBytes(); + _outvol = input.ReadBytes(); + _divideMax = input.ReadIntegers(4); + _divideCount = input.ReadIntegers(4); + _pokeyTicks = input.ReadInt32(); + _pokeyTicksPerSample = input.ReadInt32(); + _baseMultiplier = input.ReadInt32(); + _poly04Counter = input.ReadInt32(); + _poly05Counter = input.ReadInt32(); + _poly17Counter = input.ReadInt32(); + _poly17Size = input.ReadInt32(); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(_lastUpdateCpuClock); + output.Write(_bufferIndex); + output.Write(_audf); + output.Write(_audc); + output.Write(_audctl); + output.Write(_skctl); + output.Write(_output); + output.Write(_outvol); + output.Write(_divideMax); + output.Write(_divideCount); + output.Write(_pokeyTicks); + output.Write(_pokeyTicksPerSample); + output.Write(_baseMultiplier); + output.Write(_poly04Counter); + output.Write(_poly05Counter); + output.Write(_poly17Counter); + output.Write(_poly17Size); + } + + #endregion + + #region Helpers + + void RenderSamples(int count) + { + const int POKEY_SAMPLE = 4; + var poly17Length = (_poly17Size > _poly17.Length ? _poly17.Length : _poly17Size); + + while (count > 0 && _bufferIndex < M.FrameBuffer.SoundBufferByteLength) + { + var nextEvent = POKEY_SAMPLE; + var wholeTicksToConsume = (_pokeyTicks >> 8); + + for (var ch = 0; ch < 4; ch++) + { + if (_divideCount[ch] <= wholeTicksToConsume) + { + wholeTicksToConsume = _divideCount[ch]; + nextEvent = ch; + } + } + + for (var ch = 0; ch < 4; ch++) + _divideCount[ch] -= wholeTicksToConsume; + + _pokeyTicks -= (wholeTicksToConsume << 8); + + if (nextEvent == POKEY_SAMPLE) + { + _pokeyTicks += _pokeyTicksPerSample; + + byte sample = 0; + for (var ch = 0; ch < 4; ch++) + sample += _outvol[ch]; + + M.FrameBuffer.SoundBuffer[_bufferIndex >> BufferElement.SHIFT][_bufferIndex++] += sample; + count--; + + continue; + } + + _divideCount[nextEvent] += _divideMax[nextEvent]; + + _poly04Counter += wholeTicksToConsume; + _poly04Counter %= _poly04.Length; + + _poly05Counter += wholeTicksToConsume; + _poly05Counter %= _poly05.Length; + + _poly17Counter += wholeTicksToConsume; + _poly17Counter %= poly17Length; + + if ((_audc[nextEvent] & AUDC_NOTPOLY5) != 0 || _poly05[_poly05Counter] != 0) + { + if ((_audc[nextEvent] & AUDC_PURE) != 0) + _output[nextEvent] ^= 1; + else if ((_audc[nextEvent] & AUDC_POLY4) != 0) + _output[nextEvent] = _poly04[_poly04Counter]; + else + _output[nextEvent] = _poly17[_poly17Counter]; + } + + _outvol[nextEvent] = (_output[nextEvent] != 0) ? (byte)(_audc[nextEvent] & AUDC_VOLUME_MASK) : (byte)0; + } + } + + // As defined in the manual, the exact divider values are different depending on the frequency and resolution: + // 64 kHz or 15 kHz AUDF + 1 + // 1 MHz, 8-bit AUDF + 4 + // 1 MHz, 16-bit AUDF[CHAN1] + 256 * AUDF[CHAN2] + 7 + + void ResetChannel1() + { + var val = ((_audctl & AUDCTL_CH1_179) != 0) ? (_audf[0] + 4) : ((_audf[0] + 1) * _baseMultiplier); + if (val != _divideMax[0]) + { + _divideMax[0] = val; + if (val < _divideCount[0]) + _divideCount[0] = val; + } + UpdateVolumeSettingsForChannel(0); + } + + void ResetChannel2() + { + int val; + if ((_audctl & AUDCTL_CH1_CH2) != 0) + { + val = ((_audctl & AUDCTL_CH1_179) != 0) ? (_audf[1] * 256 + _audf[0] + 7) : ((_audf[1] * 256 + _audf[0] + 1) * _baseMultiplier); + } + else + { + val = ((_audf[1] + 1) * _baseMultiplier); + } + if (val != _divideMax[1]) + { + _divideMax[1] = val; + if (val < _divideCount[1]) + _divideCount[1] = val; + } + UpdateVolumeSettingsForChannel(1); + } + + void ResetChannel3() + { + var val = ((_audctl & AUDCTL_CH3_179) != 0) ? (_audf[2] + 4) : ((_audf[2] + 1) * _baseMultiplier); + if (val != _divideMax[2]) + { + _divideMax[2] = val; + if (val < _divideCount[2]) + _divideCount[2] = val; + } + UpdateVolumeSettingsForChannel(2); + } + + void ResetChannel4() + { + int val; + if ((_audctl & AUDCTL_CH3_CH4) != 0) + { + val = ((_audctl & AUDCTL_CH3_179) != 0) ? (_audf[3] * 256 + _audf[2] + 7) : ((_audf[3] * 256 + _audf[2] + 1) * _baseMultiplier); + } + else + { + val = ((_audf[3] + 1) * _baseMultiplier); + } + if (val != _divideMax[3]) + { + _divideMax[3] = val; + if (val < _divideCount[3]) + _divideCount[3] = val; + } + UpdateVolumeSettingsForChannel(3); + } + + void UpdateVolumeSettingsForChannel(int ch) + { + if (((_audc[ch] & AUDC_VOLUME_ONLY) != 0) || ((_audc[ch] & AUDC_VOLUME_MASK) == 0) || (_divideMax[ch] < (_pokeyTicksPerSample >> 8))) + { + _outvol[ch] = (byte)(_audc[ch] & AUDC_VOLUME_MASK); + _divideCount[ch] = Int32.MaxValue; + _divideMax[ch] = Int32.MaxValue; + } + } + + [System.Diagnostics.Conditional("DEBUG")] + void LogDebug(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/RAM6116.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/RAM6116.cs new file mode 100644 index 0000000000..66d5296f99 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/RAM6116.cs @@ -0,0 +1,65 @@ +/* + * RAM6116.cs + * + * Implements a 6116 RAM device found in the 7800. + * + * Copyright © 2004 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + public sealed class RAM6116 : IDevice + { + readonly byte[] RAM; + + #region IDevice Members + + public void Reset() {} + + public byte this[ushort addr] + { + get { return RAM[addr & 0x07ff]; } + set { RAM[addr & 0x07ff] = value; } + } + + #endregion + + #region Constructors + + public RAM6116() + { + RAM = new byte[0x800]; + } + + public RAM6116(byte[] ram) + { + RAM = ram; + } + + #endregion + + #region Serialization Members + + public RAM6116(DeserializationContext input) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + RAM = input.ReadExpectedBytes(0x800); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(RAM); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/SerializationContext.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/SerializationContext.cs new file mode 100644 index 0000000000..1c018e41ce --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/SerializationContext.cs @@ -0,0 +1,230 @@ +using System; +using System.IO; + +namespace EMU7800.Core +{ + /// + /// A context for serializing objects. + /// + public class SerializationContext + { + #region Fields + + readonly BinaryWriter _binaryWriter; + + #endregion + + public void Write(byte value) + { + _binaryWriter.Write(value); + } + + public void Write(ushort value) + { + _binaryWriter.Write(value); + } + + public void Write(int value) + { + _binaryWriter.Write(value); + } + + public void Write(uint value) + { + _binaryWriter.Write(value); + } + + public void Write(long value) + { + _binaryWriter.Write(value); + } + + public void Write(ulong value) + { + _binaryWriter.Write(value); + } + + public void Write(bool value) + { + _binaryWriter.Write(value); + } + + public void Write(double value) + { + _binaryWriter.Write(value); + } + + public void Write(BufferElement bufferElement) + { + for (var i = 0; i < BufferElement.SIZE; i++) + Write(bufferElement[i]); + } + + public void Write(byte[] bytes) + { + _binaryWriter.Write(bytes.Length); + if (bytes.Length > 0) + _binaryWriter.Write(bytes); + } + + public void Write(ushort[] ushorts) + { + var bytes = new byte[ushorts.Length << 1]; + Buffer.BlockCopy(ushorts, 0, bytes, 0, bytes.Length); + Write(bytes); + } + + public void Write(int[] ints) + { + var bytes = new byte[ints.Length << 2]; + Buffer.BlockCopy(ints, 0, bytes, 0, bytes.Length); + Write(bytes); + } + + public void Write(uint[] uints) + { + var bytes = new byte[uints.Length << 2]; + Buffer.BlockCopy(uints, 0, bytes, 0, bytes.Length); + Write(bytes); + } + + public void Write(bool[] booleans) + { + var bytes = new byte[booleans.Length]; + for (var i = 0; i < bytes.Length; i++) + { + bytes[i] = (byte)(booleans[i] ? 0xff : 0x00); + } + Write(bytes); + } + + public void Write(MachineBase m) + { + WriteTypeName(m); + m.GetObjectData(this); + } + + public void Write(AddressSpace mem) + { + mem.GetObjectData(this); + } + + public void Write(M6502 cpu) + { + cpu.GetObjectData(this); + } + + public void Write(PIA pia) + { + pia.GetObjectData(this); + } + + public void Write(TIA tia) + { + tia.GetObjectData(this); + } + + public void Write(TIASound tiaSound) + { + tiaSound.GetObjectData(this); + } + + public void Write(Maria maria) + { + maria.GetObjectData(this); + } + + public void Write(Cart cart) + { + WriteTypeName(cart); + cart.GetObjectData(this); + } + + public void Write(RAM6116 ram6116) + { + ram6116.GetObjectData(this); + } + + public void Write(InputState inputState) + { + inputState.GetObjectData(this); + } + + public void WriteVersion(int version) + { + Write(0x78000087); + Write(version); + } + + public void WriteOptional(byte[] bytes) + { + var hasBytes = (bytes != null); + _binaryWriter.Write(hasBytes); + if (!hasBytes) + return; + _binaryWriter.Write(bytes.Length); + if (bytes.Length > 0) + _binaryWriter.Write(bytes); + } + + public void WriteOptional(HSC7800 hsc7800) + { + var exist = (hsc7800 != null); + Write(exist); + if (!exist) + return; + hsc7800.GetObjectData(this); + } + + public void WriteOptional(Bios7800 bios7800) + { + var exist = (bios7800 != null); + Write(exist); + if (!exist) + return; + bios7800.GetObjectData(this); + } + + public void WriteOptional(PokeySound pokeySound) + { + var exist = (pokeySound != null); + Write(exist); + if (!exist) + return; + pokeySound.GetObjectData(this); + } + + #region Constructors + + private SerializationContext() + { + } + + /// + /// Instantiates a new instance of . + /// + /// + internal SerializationContext(BinaryWriter binaryWriter) + { + if (binaryWriter == null) + throw new ArgumentNullException("binaryWriter"); + _binaryWriter = binaryWriter; + } + + #endregion + + #region Helpers + + void WriteTypeName(object o) + { + if (o == null) + throw new Emu7800SerializationException("Type unexpectedly null."); + var typeName = o.GetType().FullName; + if (string.IsNullOrWhiteSpace(typeName)) + throw new Emu7800SerializationException("Unable to discover type name."); + _binaryWriter.Write(typeName); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIA.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIA.cs new file mode 100644 index 0000000000..6af0602101 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIA.cs @@ -0,0 +1,1355 @@ +/* + * TIA.cs + * + * The Television Interface Adaptor device. + * + * Copyright © 2003-2008 Mike Murphy + * + */ +using System; + +namespace EMU7800.Core +{ + #region Collision Flags + + [Flags] + public enum TIACxFlags + { + PF = 1 << 0, + BL = 1 << 1, + M0 = 1 << 2, + M1 = 1 << 3, + P0 = 1 << 4, + P1 = 1 << 5 + }; + + [Flags] + public enum TIACxPairFlags + { + M0P1 = 1 << 0, + M0P0 = 1 << 1, + M1P0 = 1 << 2, + M1P1 = 1 << 3, + P0PF = 1 << 4, + P0BL = 1 << 5, + P1PF = 1 << 6, + P1BL = 1 << 7, + M0PF = 1 << 8, + M0BL = 1 << 9, + M1PF = 1 << 10, + M1BL = 1 << 11, + BLPF = 1 << 12, + P0P1 = 1 << 13, + M0M1 = 1 << 14 + }; + + #endregion + + public sealed class TIA : IDevice + { + #region Constants + + const int + VSYNC = 0x00, // Write: vertical sync set-clear (D1) + VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1) + WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe) + RSYNC = 0x03, // Write: reset hrz. sync counter (strobe) + NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0) + NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0) + COLUP0 = 0x06, // Write: color-lum player 0 (D7-1) + COLUP1 = 0x07, // Write: color-lum player 1 (D7-1) + COLUPF = 0x08, // Write: color-lum playfield (D7-1) + COLUBK = 0x09, // Write: color-lum background (D7-1) + CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0) + REFP0 = 0x0b, // Write: reflect player 0 (D3) + REFP1 = 0x0c, // Write: reflect player 1 (D3) + PF0 = 0x0d, // Write: playfield register byte 0 (D7-4) + PF1 = 0x0e, // Write: playfield register byte 1 (D7-0) + PF2 = 0x0f, // Write: playfield register byte 2 (D7-0) + RESP0 = 0x10, // Write: reset player 0 (strobe) + RESP1 = 0x11, // Write: reset player 1 (strobe) + RESM0 = 0x12, // Write: reset missle 0 (strobe) + RESM1 = 0x13, // Write: reset missle 1 (strobe) + RESBL = 0x14, // Write: reset ball (strobe) + AUDC0 = 0x15, // Write: audio control 0 (D3-0) + AUDC1 = 0x16, // Write: audio control 1 (D4-0) + AUDF0 = 0x17, // Write: audio frequency 0 (D4-0) + AUDF1 = 0x18, // Write: audio frequency 1 (D3-0) + AUDV0 = 0x19, // Write: audio volume 0 (D3-0) + AUDV1 = 0x1a, // Write: audio volume 1 (D3-0) + GRP0 = 0x1b, // Write: graphics player 0 (D7-0) + GRP1 = 0x1c, // Write: graphics player 1 (D7-0) + ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1) + ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1) + ENABL = 0x1f, // Write: graphics (enable) ball (D1) + HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4) + HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4) + HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4) + HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4) + HMBL = 0x24, // Write: horizontal motion ball (D7-4) + VDELP0 = 0x25, // Write: vertical delay player 0 (D0) + VDELP1 = 0x26, // Write: vertical delay player 1 (D0) + VDELBL = 0x27, // Write: vertical delay ball (D0) + RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1) + RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1) + HMOVE = 0x2a, // Write: apply horizontal motion (strobe) + HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe) + CXCLR = 0x2c, // Write: clear collision latches (strobe) + + CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0) + CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1) + CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL) + CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL) + CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL) + CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL) + CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused) + CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1) + INPT0 = 0x08, // Read pot port: D7 + INPT1 = 0x09, // Read pot port: D7 + INPT2 = 0x0a, // Read pot port: D7 + INPT3 = 0x0b, // Read pot port: D7 + INPT4 = 0x0c, // Read P1 joystick trigger: D7 + INPT5 = 0x0d; // Read P2 joystick trigger: D7 + + const int CPU_TICKS_PER_AUDIO_SAMPLE = 38; + + #endregion + + #region Data Structures + + readonly byte[] RegW = new byte[0x40]; + readonly MachineBase M; + readonly TIASound TIASound; + + delegate void PokeOpTyp(ushort addr, byte data); + + PokeOpTyp[] PokeOp; + + #endregion + + #region Position Counters + + // backing fields for properties--dont reference directly + int _HSync, _P0, _P1, _M0, _M1, _BL, _HMoveCounter; + + // Horizontal Sync Counter + // this represents HSync of the last rendered CLK + // has period of 57 counts, 0-56, at 1/4 CLK (57*4=228 CLK) + // provide all horizontal timing for constructing a valid TV signal + // other movable object counters can be reset out-of-phase with HSync, hence %228 and not %57 + int HSync + { + get { return _HSync; } + set { _HSync = value % 228; } + } + + // determines the difference between HSync and PokeOpHSync + int PokeOpHSyncDelta + { + get { return (int)(Clock - LastEndClock); } + } + + // this represents the current HSync + int PokeOpHSync + { + get { return (HSync + PokeOpHSyncDelta) % 228; } + } + + // scanline last rendered to + int ScanLine; + + // current position in the frame buffer + int FrameBufferIndex; + + // bytes are batched here for writing to the FrameBuffer + BufferElement FrameBufferElement; + + // signals when to start an HMOVE + ulong StartHMOVEClock; + + // indicates where in the HMOVE operation it is + int HMoveCounter + { + get { return _HMoveCounter; } + set { _HMoveCounter = value < 0 ? -1 : value & 0xf; } + } + + // true when there is an HMOVE executing on the current scanline + bool HMoveLatch; + + // represents the TIA color clock (CLK) + // computed off of the CPU clock, but in real life, the CPU is driven by the color CLK signal + ulong Clock + { + get { return 3 * M.CPU.Clock; } + } + + // represents the first CLK of the unrendered scanline segment + ulong StartClock; + + // represents the last CLK of the previously rendered scanline segment + ulong LastEndClock + { + get { return StartClock - 1; } + } + + #endregion + + #region Player 0 Object + + // Player 0 Horizontal Position Counter + // has period of 40 counts, 0-39, at 1/4 CLK (40*4=160 CLK=visible scanline length) + // player position counter controls the position of the respective player graphics object on each scanline + // can be reset out-of-phase with HSync, hence %160 and not %40 + int P0 + { + get { return _P0; } + set { _P0 = value % 160; } + } + + // HMOVE "more motion required" latch + bool P0mmr; + + // Player 0 graphics registers + byte EffGRP0, OldGRP0; + + // Player 0 stretch mode + int P0type; + + // 1=currently suppressing copy 1 on Player 0 + int P0suppress; + + #endregion + + #region Player 1 Object + + // Player 1 Horizontal Position Counter (identical to P0) + int P1 + { + get { return _P1; } + set { _P1 = value % 160; } + } + + // HMOVE "more motion required" latch + bool P1mmr; + + // Player 1 graphics registers + byte EffGRP1, OldGRP1; + + // Player 1 stretch mode + int P1type; + + // 1=currently suppressing copy 1 on Player 1 + int P1suppress; + + #endregion + + #region Missile 0 Object + + // Missile 0 Horizontal Position Counter + // similar to player position counters + int M0 + { + get { return _M0; } + set { _M0 = value % 160; } + } + + // HMOVE "more motion required" latch + bool M0mmr; + + int M0type, M0size; + + bool m0on; + + #endregion + + #region Missle 1 Object + + // Missile 1 Horizontal Position Counter (identical to M0) + int M1 + { + get { return _M1; } + set { _M1 = value % 160; } + } + + // HMOVE "more motion required" latch + bool M1mmr; + + int M1type, M1size; + + bool m1on; + + #endregion + + #region Ball Object + + // Ball Horizontal Position Counter + // similar to player position counters + int BL + { + get { return _BL; } + set { _BL = value % 160; } + } + + // HMOVE "more motion required" latch + bool BLmmr; + + bool OldENABL; + + int BLsize; + + bool blon; + + #endregion + + #region Misc + + uint PF210; + int PFReflectionState; + + // color-luminance for background, playfield, player 0, player 1 + byte colubk, colupf, colup0, colup1; + + bool vblankon, scoreon, pfpriority; + + bool DumpEnabled; + ulong DumpDisabledCycle; + + TIACxPairFlags Collisions; + + #endregion + + #region Public Members + + public int WSYNCDelayClocks { get; set; } + public bool EndOfFrame { get; private set; } + + public void Reset() + { + for (var i = 0; i < RegW.Length; i++) + { + RegW[i] = 0; + } + vblankon = scoreon = pfpriority = false; + m0on = m1on = blon = false; + colubk = colupf = colup0 = colup1 = 0; + PFReflectionState = 0; + + StartClock = Clock; + HSync = -1; + P0 = P1 = M0 = M1 = BL = -1; + P0mmr = P1mmr = M0mmr = M1mmr = BLmmr = false; + StartHMOVEClock = ulong.MaxValue; + HMoveCounter = -1; + + FrameBufferIndex = 0; + + TIASound.Reset(); + + Log("{0} reset", this); + } + + public override String ToString() + { + return "TIA 1A"; + } + + public void StartFrame() + { + WSYNCDelayClocks = 0; + EndOfFrame = false; + ScanLine = 0; + FrameBufferIndex %= 160; + RenderFromStartClockTo(Clock); + TIASound.StartFrame(); + } + + public byte this[ushort addr] + { + get { return peek(addr); } + set { poke(addr, value); } + } + + public void EndFrame() + { + TIASound.EndFrame(); + } + + #endregion + + #region Constructors + + private TIA() + { + BuildPokeOpTable(); + } + + public TIA(MachineBase m) : this() + { + if (m == null) + throw new ArgumentNullException("m"); + + M = m; + TIASound = new TIASound(M, CPU_TICKS_PER_AUDIO_SAMPLE); + } + + #endregion + + #region Scanline Segment Renderer + + void RenderFromStartClockTo(ulong endClock) + { + + RenderClock: + if (StartClock >= endClock) + return; + + ++HSync; + + if (StartClock == StartHMOVEClock) + { + // turn on HMOVE + HMoveLatch = true; + HMoveCounter = 0xf; + P0mmr = P1mmr = M0mmr = M1mmr = BLmmr = true; + } + else if (HSync == 0) + { + // just wrapped around, clear late HBLANK + HMoveLatch = false; + } + + // position counters are incremented during the visible portion of the scanline + if (HSync >= 68 + (HMoveLatch ? 8 : 0)) + { + ++P0; ++P1; ++M0; ++M1; ++BL; + } + + // HMOVE compare, once every 1/4 CLK when on + if (HMoveCounter >= 0 && (HSync & 3) == 0) + { + if (((HMoveCounter ^ RegW[HMP0]) & 0xf) == 0xf) P0mmr = false; + if (((HMoveCounter ^ RegW[HMP1]) & 0xf) == 0xf) P1mmr = false; + if (((HMoveCounter ^ RegW[HMM0]) & 0xf) == 0xf) M0mmr = false; + if (((HMoveCounter ^ RegW[HMM1]) & 0xf) == 0xf) M1mmr = false; + if (((HMoveCounter ^ RegW[HMBL]) & 0xf) == 0xf) BLmmr = false; + if (HMoveCounter >= 0) HMoveCounter--; + } + + // HMOVE increment, once every 1/4 CLK, 2 CLK after first compare when on + if (HMoveCounter < 0xf && (HSync & 3) == 2) + { + if (HSync < 68 + (HMoveLatch ? 8 : 0)) + { + if (P0mmr) ++P0; + if (P1mmr) ++P1; + if (M0mmr) ++M0; + if (M1mmr) ++M1; + if (BLmmr) ++BL; + } + } + + if (HSync == 68 + 76) PFReflectionState = RegW[CTRLPF] & 0x01; + + var fbyte = (byte)0; + var fbyte_colupf = colupf; + TIACxFlags cxflags = 0; + + if (vblankon || HSync < 68 + (HMoveLatch ? 8 : 0)) goto WritePixel; + + fbyte = colubk; + + var colupfon = false; + if ((PF210 & TIATables.PFMask[PFReflectionState][HSync - 68]) != 0) + { + if (scoreon) fbyte_colupf = (HSync - 68) < 80 ? colup0 : colup1; + colupfon = true; + cxflags |= TIACxFlags.PF; + } + if (blon && BL >= 0 && TIATables.BLMask[BLsize][BL]) + { + colupfon = true; + cxflags |= TIACxFlags.BL; + } + if (!pfpriority && colupfon) + { + fbyte = fbyte_colupf; + } + if (m1on && M1 >= 0 && TIATables.MxMask[M1size][M1type][M1]) + { + fbyte = colup1; + cxflags |= TIACxFlags.M1; + } + if (P1 >= 0 && (TIATables.PxMask[P1suppress][P1type][P1] & EffGRP1) != 0) + { + fbyte = colup1; + cxflags |= TIACxFlags.P1; + } + if (m0on && M0 >= 0 && TIATables.MxMask[M0size][M0type][M0]) + { + fbyte = colup0; + cxflags |= TIACxFlags.M0; + } + if (P0 >= 0 && (TIATables.PxMask[P0suppress][P0type][P0] & EffGRP0) != 0) + { + fbyte = colup0; + cxflags |= TIACxFlags.P0; + } + if (pfpriority && colupfon) + { + fbyte = fbyte_colupf; + } + + WritePixel: + Collisions |= TIATables.CollisionMask[(int)cxflags]; + + if (HSync >= 68) + { + var i = FrameBufferIndex++; + FrameBufferElement[i] = fbyte; + if ((i & BufferElement.MASK) == BufferElement.MASK) + { + M.FrameBuffer.VideoBuffer[i >> BufferElement.SHIFT] = FrameBufferElement; + if (FrameBufferIndex == M.FrameBuffer.VideoBufferByteLength) + FrameBufferIndex = 0; + } + if (HSync == 227) + ScanLine++; + } + + if (P0 >= 156) P0suppress = 0; + if (P1 >= 156) P1suppress = 0; + + // denote this CLK has been completed by incrementing to the next + StartClock++; + + goto RenderClock; + } + + #endregion + + #region TIA Peek + + byte peek(ushort addr) + { + var retval = 0; + addr &= 0xf; + + RenderFromStartClockTo(Clock); + + switch (addr) + { + case CXM0P: + retval |= ((Collisions & TIACxPairFlags.M0P1) != 0 ? 0x80 : 0); + retval |= ((Collisions & TIACxPairFlags.M0P0) != 0 ? 0x40 : 0); + break; + case CXM1P: + retval |= ((Collisions & TIACxPairFlags.M1P0) != 0 ? 0x80 : 0); + retval |= ((Collisions & TIACxPairFlags.M1P1) != 0 ? 0x40 : 0); + break; + case CXP0FB: + retval |= ((Collisions & TIACxPairFlags.P0PF) != 0 ? 0x80 : 0); + retval |= ((Collisions & TIACxPairFlags.P0BL) != 0 ? 0x40 : 0); + break; + case CXP1FB: + retval |= ((Collisions & TIACxPairFlags.P1PF) != 0 ? 0x80 : 0); + retval |= ((Collisions & TIACxPairFlags.P1BL) != 0 ? 0x40 : 0); + break; + case CXM0FB: + retval |= ((Collisions & TIACxPairFlags.M0PF) != 0 ? 0x80 : 0); + retval |= ((Collisions & TIACxPairFlags.M0BL) != 0 ? 0x40 : 0); + break; + case CXM1FB: + retval |= ((Collisions & TIACxPairFlags.M1PF) != 0 ? 0x80 : 0); + retval |= ((Collisions & TIACxPairFlags.M1BL) != 0 ? 0x40 : 0); + break; + case CXBLPF: + retval |= ((Collisions & TIACxPairFlags.BLPF) != 0 ? 0x80 : 0); + break; + case CXPPMM: + retval |= ((Collisions & TIACxPairFlags.P0P1) != 0 ? 0x80 : 0); + retval |= ((Collisions & TIACxPairFlags.M0M1) != 0 ? 0x40 : 0); + break; + case INPT0: + retval = DumpedInputPort(SampleINPT(0)); + break; + case INPT1: + retval = DumpedInputPort(SampleINPT(1)); + break; + case INPT2: + retval = DumpedInputPort(SampleINPT(2)); + break; + case INPT3: + retval = DumpedInputPort(SampleINPT(3)); + break; + case INPT4: + var scanline = ScanLine; + var hpos = PokeOpHSync - 68; + if (hpos < 0) + { + hpos += 228; + scanline--; + } + if (SampleINPTLatched(4, scanline, hpos)) + { + retval &= 0x7f; + } + else + { + retval |= 0x80; + } + break; + case INPT5: + scanline = ScanLine; + hpos = PokeOpHSync - 68; + if (hpos < 0) + { + hpos += 228; + scanline--; + } + if (SampleINPTLatched(5, scanline, hpos)) + { + retval &= 0x7f; + } + else + { + retval |= 0x80; + } + break; + } + return (byte)(retval | (M.Mem.DataBusState & 0x3f)); + } + + byte DumpedInputPort(int resistance) + { + byte retval = 0; + + if (resistance == 0) + { + retval = 0x80; + } + else if (DumpEnabled || resistance == Int32.MaxValue) + { + retval = 0x00; + } + else + { + var chargeTime = 1.6 * resistance * 0.01e-6; + var needed = (ulong)(chargeTime * M.FrameBuffer.Scanlines * 228 * M.FrameHZ / 3); + if (M.CPU.Clock > DumpDisabledCycle + needed) + { + retval = 0x80; + } + } + return retval; + } + + #endregion + + #region TIA Poke + + void poke(ushort addr, byte data) + { + addr &= 0x3f; + + var endClock = Clock; + + // writes to the TIA may take a few extra CLKs to actually affect TIA state + // such a delay would seem to be applicable across all possible TIA writes + // without hardware to confirm conclusively, this is updated only as seemingly required + switch (addr) + { + case GRP0: + case GRP1: + // stem in the T in activision logo on older titles + endClock += 1; + break; + case PF0: + case PF1: + case PF2: + // +2 prevents minor notching in berzerk walls + // +4 prevents shield fragments floating in fuzz field in yars revenge, + // but creates minor artifact in chopper command + switch (PokeOpHSync & 3) + { + case 0: endClock += 4; break; + case 1: endClock += 3; break; + case 2: endClock += 2; break; + case 3: endClock += 5; break; + } + break; + } + + RenderFromStartClockTo(endClock); + + PokeOp[addr](addr, data); + } + + static void opNULL(ushort addr, byte data) + { + } + + void opVSYNC(ushort addr, byte data) + { + // Many games don't appear to supply 3 scanlines of + // VSYNC in accordance with the Atari documentation. + // Enduro turns on VSYNC, then turns it off twice. + // Centipede turns off VSYNC several times, in addition to normal usage. + // One of the Atari Bowling ROMs turns it off, but never turns it on. + // So, we always end the frame if VSYNC is turned on and then off. + // We also end the frame if VSYNC is turned off after scanline 258 to accomodate Bowling. + if ((data & 0x02) == 0) + { + if ((RegW[VSYNC] & 0x02) != 0 || ScanLine > 258) + { + EndOfFrame = true; + M.CPU.EmulatorPreemptRequest = true; + } + } + + RegW[VSYNC] = data; + } + + void opVBLANK(ushort addr, byte data) + { + if ((RegW[VBLANK] & 0x80) == 0) + { + // dump to ground is clear and will be set + // thus discharging all INPTx capacitors + if ((data & 0x80) != 0) + { + DumpEnabled = true; + } + } + else + { + // dump to ground is set and will be cleared + // thus starting all INPTx capacitors charging + if ((data & 0x80) == 0) + { + DumpEnabled = false; + DumpDisabledCycle = M.CPU.Clock; + } + } + RegW[VBLANK] = data; + vblankon = (data & 0x02) != 0; + } + + void opWSYNC(ushort addr, byte data) + { + // on scanline=44, tetris seems to occasionally not get a WSYNC in until 3 clk in on scanline=45 causing jitter + if (PokeOpHSync > 0) + { + // report the number of remaining system clocks on the scanline to delay the CPU + WSYNCDelayClocks = 228 - PokeOpHSync; + // request a CPU preemption to service the delay request (only if there is a delay necessary) + M.CPU.EmulatorPreemptRequest = true; + } + } + + void opRSYNC(ushort addr, byte data) + { + LogDebug("TIA RSYNC: frame={0} scanline={0} hsync={0}", M.FrameNumber, ScanLine, PokeOpHSync); + } + + void opNUSIZ0(ushort addr, byte data) + { + RegW[NUSIZ0] = (byte)(data & 0x37); + + M0size = (RegW[NUSIZ0] & 0x30) >> 4; + M0type = RegW[NUSIZ0] & 0x07; + P0type = M0type; + + P0suppress = 0; + } + + void opNUSIZ1(ushort addr, byte data) + { + RegW[NUSIZ1] = (byte)(data & 0x37); + + M1size = (RegW[NUSIZ1] & 0x30) >> 4; + M1type = RegW[NUSIZ1] & 0x07; + P1type = M1type; + + P1suppress = 0; + } + + void opCOLUBK(ushort addr, byte data) + { + colubk = data; + } + + void opCOLUPF(ushort addr, byte data) + { + colupf = data; + } + + void opCOLUP0(ushort addr, byte data) + { + colup0 = data; + } + + void opCOLUP1(ushort addr, byte data) + { + colup1 = data; + } + + void opCTRLPF(ushort addr, byte data) + { + RegW[CTRLPF] = data; + + BLsize = (data & 0x30) >> 4; + scoreon = (data & 0x02) != 0; + pfpriority = (data & 0x04) != 0; + } + + void SetEffGRP0() + { + var grp0 = RegW[VDELP0] != 0 ? OldGRP0 : RegW[GRP0]; + EffGRP0 = RegW[REFP0] != 0 ? TIATables.GRPReflect[grp0] : grp0; + } + + void SetEffGRP1() + { + var grp1 = RegW[VDELP1] != 0 ? OldGRP1 : RegW[GRP1]; + EffGRP1 = RegW[REFP1] != 0 ? TIATables.GRPReflect[grp1] : grp1; + } + + void Setblon() + { + blon = RegW[VDELBL] != 0 ? OldENABL : RegW[ENABL] != 0; + } + + void opREFP0(ushort addr, byte data) + { + RegW[REFP0] = (byte)(data & 0x08); + SetEffGRP0(); + } + + void opREFP1(ushort addr, byte data) + { + RegW[REFP1] = (byte)(data & 0x08); + SetEffGRP1(); + } + + void opPF(ushort addr, byte data) + { + RegW[addr] = data; + PF210 = (uint)((RegW[PF2] << 12) | (RegW[PF1] << 4) |((RegW[PF0] >> 4) & 0x0f)); + } + + void opRESP0(ushort addr, byte data) + { + if (PokeOpHSync < 68) + { + P0 = 0; + } + else if (HMoveLatch && PokeOpHSync >= 68 && PokeOpHSync < 76) + { + // this is an attempt to model observed behavior--may not be completely correct + // only three hsync values are really possible: + // 69: parkerbros actionman + // 72: activision grandprix + // 75: barnstorming, riverraid + P0 = -((PokeOpHSync - 68) >> 1); + } + else + { + P0 = -4; + } + P0 -= PokeOpHSyncDelta; + P0suppress = 1; + } + + void opRESP1(ushort addr, byte data) + { + if (PokeOpHSync < 68) + { + P1 = 0; + } + else if (HMoveLatch && PokeOpHSync >= 68 && PokeOpHSync < 76) + { + // this is an attempt to model observed behavior--may not be completely correct + // only three hsync values are really possible: + // 69: parkerbros actionman + // 72: parkerbros actionman + // 75: parkerbros actionman + P1 = -((PokeOpHSync - 68) >> 1); + } + else + { + P1 = -4; + } + P1 -= PokeOpHSyncDelta; + P1suppress = 1; + } + + void opRESM0(ushort addr, byte data) + { + // -2 to mirror M1 + M0 = PokeOpHSync < 68 ? -2 : -4; + M0 -= PokeOpHSyncDelta; + } + + void opRESM1(ushort addr, byte data) + { + // -2 cleans up edges on activision pitfall ii + M1 = PokeOpHSync < 68 ? -2 : -4; + M1 -= PokeOpHSyncDelta; + } + + void opRESBL(ushort addr, byte data) + { + // -2 cleans up edges on activision boxing + // -4 confirmed via activision choppercommand; used to clean up edges + BL = PokeOpHSync < 68 ? -2 : -4; + BL -= PokeOpHSyncDelta; + } + + void opAUD(ushort addr, byte data) + { + RegW[addr] = data; + TIASound.Update(addr, data); + } + + void opGRP0(ushort addr, byte data) + { + RegW[GRP0] = data; + OldGRP1 = RegW[GRP1]; + + SetEffGRP0(); + SetEffGRP1(); + } + + void opGRP1(ushort addr, byte data) + { + RegW[GRP1] = data; + OldGRP0 = RegW[GRP0]; + + OldENABL = RegW[ENABL] != 0; + + SetEffGRP0(); + SetEffGRP1(); + Setblon(); + } + + void opENAM0(ushort addr, byte data) + { + RegW[ENAM0] = (byte)(data & 0x02); + m0on = RegW[ENAM0] != 0 && RegW[RESMP0] == 0; + } + + void opENAM1(ushort addr, byte data) + { + RegW[ENAM1] = (byte)(data & 0x02); + m1on = RegW[ENAM1] != 0 && RegW[RESMP1] == 0; + } + + void opENABL(ushort addr, byte data) + { + RegW[ENABL] = (byte)(data & 0x02); + Setblon(); + } + + void SetHmr(int hmr, byte data) + { + // marshal via >>4 for compare convenience + RegW[hmr] = (byte)((data ^ 0x80) >> 4); + } + + void opHM(ushort addr, byte data) + { + SetHmr(addr, data); + } + + void opVDELP0(ushort addr, byte data) + { + RegW[VDELP0] = (byte)(data & 0x01); + SetEffGRP0(); + } + + void opVDELP1(ushort addr, byte data) + { + RegW[VDELP1] = (byte)(data & 0x01); + SetEffGRP1(); + } + + void opVDELBL(ushort addr, byte data) + { + RegW[VDELBL] = (byte)(data & 0x01); + Setblon(); + } + + void opRESMP0(ushort addr, byte data) + { + if (RegW[RESMP0] != 0 && (data & 0x02) == 0) + { + var middle = 4; + switch (RegW[NUSIZ0] & 0x07) + { + case 0x05: middle <<= 1; break; // double size + case 0x07: middle <<= 2; break; // quad size + } + M0 = P0 - middle; + } + RegW[RESMP0] = (byte)(data & 0x02); + m0on = RegW[ENAM0] != 0 && RegW[RESMP0] == 0; + } + + void opRESMP1(ushort addr, byte data) + { + if (RegW[RESMP1] != 0 && (data & 0x02) == 0) + { + var middle = 4; + switch (RegW[NUSIZ1] & 0x07) + { + case 0x05: middle <<= 1; break; // double size + case 0x07: middle <<= 2; break; // quad size + } + M1 = P1 - middle; + } + RegW[RESMP1] = (byte)(data & 0x02); + m1on = RegW[ENAM1] != 0 && RegW[RESMP1] == 0; + } + + void opHMOVE(ushort addr, byte data) + { + P0suppress = 0; + P1suppress = 0; + StartHMOVEClock = Clock + 3; + + // Activision Spiderfighter Cheat (Score and Logo) + // Delaying the start of the HMOVE here results in it completing on the next scanline (to have visible effect.) + // HMOVEing during the visible scanline probably has extra consequences, + // however, it seems not many carts try to do this. + if (PokeOpHSync == 201) StartHMOVEClock++; // any increment >0 works + } + + void opHMCLR(ushort addr, byte data) + { + SetHmr(HMP0, 0); + SetHmr(HMP1, 0); + SetHmr(HMM0, 0); + SetHmr(HMM1, 0); + SetHmr(HMBL, 0); + } + + void opCXCLR(ushort addr, byte data) + { + Collisions = 0; + } + + void BuildPokeOpTable() + { + PokeOp = new PokeOpTyp[64]; + for (var i = 0; i < PokeOp.Length; i++) + { + PokeOp[i] = opNULL; + } + PokeOp[VSYNC] = opVSYNC; + PokeOp[VBLANK] = opVBLANK; + PokeOp[WSYNC] = opWSYNC; + PokeOp[RSYNC] = opRSYNC; + PokeOp[NUSIZ0] = opNUSIZ0; + PokeOp[NUSIZ1] = opNUSIZ1; + PokeOp[COLUP0] = opCOLUP0; + PokeOp[COLUP1] = opCOLUP1; + PokeOp[COLUPF] = opCOLUPF; + PokeOp[COLUBK] = opCOLUBK; + PokeOp[CTRLPF] = opCTRLPF; + PokeOp[REFP0] = opREFP0; + PokeOp[REFP1] = opREFP1; + PokeOp[PF0] = opPF; + PokeOp[PF1] = opPF; + PokeOp[PF2] = opPF; + PokeOp[RESP0] = opRESP0; + PokeOp[RESP1] = opRESP1; + PokeOp[RESM0] = opRESM0; + PokeOp[RESM1] = opRESM1; + PokeOp[RESBL] = opRESBL; + PokeOp[AUDC0] = opAUD; + PokeOp[AUDC1] = opAUD; + PokeOp[AUDF0] = opAUD; + PokeOp[AUDF1] = opAUD; + PokeOp[AUDV0] = opAUD; + PokeOp[AUDV1] = opAUD; + PokeOp[GRP0] = opGRP0; + PokeOp[GRP1] = opGRP1; + PokeOp[ENAM0] = opENAM0; + PokeOp[ENAM1] = opENAM1; + PokeOp[ENABL] = opENABL; + PokeOp[HMP0] = opHM; + PokeOp[HMP1] = opHM; + PokeOp[HMM0] = opHM; + PokeOp[HMM1] = opHM; + PokeOp[HMBL] = opHM; + PokeOp[VDELP0] = opVDELP0; + PokeOp[VDELP1] = opVDELP1; + PokeOp[VDELBL] = opVDELBL; + PokeOp[RESMP0] = opRESMP0; + PokeOp[RESMP1] = opRESMP1; + PokeOp[HMOVE] = opHMOVE; + PokeOp[HMCLR] = opHMCLR; + PokeOp[CXCLR] = opCXCLR; + } + + #endregion + + #region Input Helpers + + int SampleINPT(int inpt) + { + var mi = M.InputState; + + switch (inpt <= 1 ? mi.LeftControllerJack : mi.RightControllerJack) + { + case Controller.Paddles: + // playerno = inpt + return mi.SampleCapturedOhmState(inpt & 3); + case Controller.ProLineJoystick: + // playerno = inpt/2 + switch (inpt & 3) + { + case 0: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? 0 : Int32.MaxValue; + case 1: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger2) ? 0 : Int32.MaxValue; + case 2: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? 0 : Int32.MaxValue; + case 3: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger2) ? 0 : Int32.MaxValue; + } + break; + case Controller.BoosterGrip: + // playerno = inpt + return mi.SampleCapturedControllerActionState(inpt & 3, ControllerAction.Trigger2) ? 0 : Int32.MaxValue; + case Controller.Keypad: + return SampleKeypadStateDumped(inpt & 3); + } + return int.MaxValue; + } + + bool SampleINPTLatched(int inpt, int scanline, int hpos) + { + var mi = M.InputState; + var playerNo = inpt - 4; + + switch (playerNo == 0 ? mi.LeftControllerJack : mi.RightControllerJack) + { + case Controller.Joystick: + case Controller.ProLineJoystick: + case Controller.Driving: + case Controller.BoosterGrip: + return mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger); + case Controller.Keypad: + return SampleKeypadStateLatched(playerNo); + case Controller.Lightgun: + int sampledScanline, sampledHpos; + mi.SampleCapturedLightGunPosition(playerNo, out sampledScanline, out sampledHpos); + return ((scanline - 4) >= sampledScanline && (hpos - 23) >= sampledHpos); + } + return false; + } + + bool SampleKeypadStateLatched(int deviceno) + { + ControllerAction action; + + if ((M.PIA.WrittenPortA & 0x01) == 0) + { + action = ControllerAction.Keypad3; + } + else if ((M.PIA.WrittenPortA & 0x02) == 0) + { + action = ControllerAction.Keypad6; + } + else if ((M.PIA.WrittenPortA & 0x04) == 0) + { + action = ControllerAction.Keypad9; + } + else if ((M.PIA.WrittenPortA & 0x08) == 0) + { + action = ControllerAction.KeypadP; + } + else + { + return false; + } + + return M.InputState.SampleCapturedControllerActionState(deviceno, action); + } + + int SampleKeypadStateDumped(int inpt) + { + ControllerAction action; + + if ((M.PIA.WrittenPortA & 0x01) == 0) + { + action = (inpt & 1) == 0 ? ControllerAction.Keypad1 : ControllerAction.Keypad2; + } + else if ((M.PIA.WrittenPortA & 0x02) == 0) + { + action = (inpt & 1) == 0 ? ControllerAction.Keypad4 : ControllerAction.Keypad5; + } + else if ((M.PIA.WrittenPortA & 0x04) == 0) + { + action = (inpt & 1) == 0 ? ControllerAction.Keypad7 : ControllerAction.Keypad8; + } + else if ((M.PIA.WrittenPortA & 0x08) == 0) + { + action = (inpt & 1) == 0 ? ControllerAction.KeypadA : ControllerAction.Keypad0; + } + else + { + return Int32.MaxValue; + } + + // playerno = inpt/2 + return M.InputState.SampleCapturedControllerActionState(inpt >> 1, action) ? Int32.MaxValue : 0; + } + + #endregion + + #region Serialization Members + + public TIA(DeserializationContext input, MachineBase m) : this() + { + if (input == null) + throw new ArgumentNullException("input"); + if (m == null) + throw new ArgumentNullException("m"); + + M = m; + TIASound = input.ReadTIASound(M, CPU_TICKS_PER_AUDIO_SAMPLE); + + input.CheckVersion(1); + RegW = input.ReadExpectedBytes(0x40); + HSync = input.ReadInt32(); + HMoveCounter = input.ReadInt32(); + ScanLine = input.ReadInt32(); + FrameBufferIndex = input.ReadInt32(); + FrameBufferElement = input.ReadBufferElement(); + StartHMOVEClock = input.ReadUInt64(); + HMoveLatch = input.ReadBoolean(); + StartClock = input.ReadUInt64(); + P0 = input.ReadInt32(); + P0mmr = input.ReadBoolean(); + EffGRP0 = input.ReadByte(); + OldGRP0 = input.ReadByte(); + P0type = input.ReadInt32(); + P0suppress = input.ReadInt32(); + P1 = input.ReadInt32(); + P1mmr = input.ReadBoolean(); + EffGRP1 = input.ReadByte(); + OldGRP1 = input.ReadByte(); + P1type = input.ReadInt32(); + P1suppress = input.ReadInt32(); + M0 = input.ReadInt32(); + M0mmr = input.ReadBoolean(); + M0type = input.ReadInt32(); + M0size = input.ReadInt32(); + m0on = input.ReadBoolean(); + M1 = input.ReadInt32(); + M1mmr = input.ReadBoolean(); + M1type = input.ReadInt32(); + M1size = input.ReadInt32(); + m1on = input.ReadBoolean(); + BL = input.ReadInt32(); + BLmmr = input.ReadBoolean(); + OldENABL = input.ReadBoolean(); + BLsize = input.ReadInt32(); + blon = input.ReadBoolean(); + PF210 = input.ReadUInt32(); + PFReflectionState = input.ReadInt32(); + colubk = input.ReadByte(); + colupf = input.ReadByte(); + colup0 = input.ReadByte(); + colup1 = input.ReadByte(); + vblankon = input.ReadBoolean(); + scoreon = input.ReadBoolean(); + pfpriority = input.ReadBoolean(); + DumpEnabled = input.ReadBoolean(); + DumpDisabledCycle = input.ReadUInt64(); + Collisions = (TIACxPairFlags)input.ReadInt32(); + WSYNCDelayClocks = input.ReadInt32(); + EndOfFrame = input.ReadBoolean(); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.Write(TIASound); + + output.WriteVersion(1); + output.Write(RegW); + output.Write(HSync); + output.Write(HMoveCounter); + output.Write(ScanLine); + output.Write(FrameBufferIndex); + output.Write(FrameBufferElement); + output.Write(StartHMOVEClock); + output.Write(HMoveLatch); + output.Write(StartClock); + output.Write(P0); + output.Write(P0mmr); + output.Write(EffGRP0); + output.Write(OldGRP0); + output.Write(P0type); + output.Write(P0suppress); + output.Write(P1); + output.Write(P1mmr); + output.Write(EffGRP1); + output.Write(OldGRP1); + output.Write(P1type); + output.Write(P1suppress); + output.Write(M0); + output.Write(M0mmr); + output.Write(M0type); + output.Write(M0size); + output.Write(m0on); + output.Write(M1); + output.Write(M1mmr); + output.Write(M1type); + output.Write(M1size); + output.Write(m1on); + output.Write(BL); + output.Write(BLmmr); + output.Write(OldENABL); + output.Write(BLsize); + output.Write(blon); + output.Write(PF210); + output.Write(PFReflectionState); + output.Write(colubk); + output.Write(colupf); + output.Write(colup0); + output.Write(colup1); + output.Write(vblankon); + output.Write(scoreon); + output.Write(pfpriority); + output.Write(DumpEnabled); + output.Write(DumpDisabledCycle); + output.Write((int)Collisions); + output.Write(WSYNCDelayClocks); + output.Write(EndOfFrame); + } + + #endregion + + #region Helpers + + void Log(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + [System.Diagnostics.Conditional("DEBUG")] + void LogDebug(string format, params object[] args) + { + if (M == null || M.Logger == null) + return; + M.Logger.WriteLine(format, args); + } + + #endregion + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIASound.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIASound.cs new file mode 100644 index 0000000000..f9a57b5635 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIASound.cs @@ -0,0 +1,361 @@ +/* + * TIASound.cs + * + * Sound emulation for the 2600. Based upon TIASound © 1997 by Ron Fries. + * + * Copyright © 2003, 2004 Mike Murphy + * + */ + +/*****************************************************************************/ +/* */ +/* License Information and Copyright Notice */ +/* ======================================== */ +/* */ +/* TiaSound is Copyright(c) 1997 by Ron Fries */ +/* */ +/* This library is free software; you can redistribute it and/or modify it */ +/* under the terms of version 2 of the GNU Library General Public License */ +/* as published by the Free Software Foundation. */ +/* */ +/* This library is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library */ +/* General Public License for more details. */ +/* To obtain a copy of the GNU Library General Public License, write to the */ +/* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* */ +/* Any permitted reproduction of these routines, in whole or in part, must */ +/* bear this legend. */ +/* */ +/*****************************************************************************/ + +using System; + +namespace EMU7800.Core +{ + public sealed class TIASound + { + #region Constants and Tables + + // Clock Source Clock Modifier Source Pattern + const int + SET_TO_1 = 0x00, // 0 0 0 0 3.58 Mhz/114 none (pure) none + //POLY4 = 0x01, // 0 0 0 1 3.58 Mhz/114 none (pure) 4-bit poly + //DIV31_POLY4 = 0x02, // 0 0 1 0 3.58 Mhz/114 divide by 31 4-bit poly + //POLY5_POLY4 = 0x03, // 0 0 1 1 3.58 Mhz/114 5-bit poly 4-bit poly + //PURE = 0x04, // 0 1 0 0 3.58 Mhz/114 none (pure) pure (~Q) + //PURE2 = 0x05, // 0 1 0 1 3.58 Mhz/114 none (pure) pure (~Q) + //DIV31_PURE = 0x06, // 0 1 1 0 3.58 Mhz/114 divide by 31 pure (~Q) + //POLY5_2 = 0x07, // 0 1 1 1 3.58 Mhz/114 5-bit poly pure (~Q) + POLY9 = 0x08; // 1 0 0 0 3.58 Mhz/114 none (pure) 9-bit poly + //POLY5 = 0x09, // 1 0 0 1 3.58 Mhz/114 none (pure) 5-bit poly + //DIV31_POLY5 = 0x0a, // 1 0 1 0 3.58 Mhz/114 divide by 31 5-bit poly + //POLY5_POLY5 = 0x0b, // 1 0 1 1 3.58 Mhz/114 5-bit poly 5-bit poly + //DIV3_PURE = 0x0c, // 1 1 0 0 1.19 Mhz/114 none (pure) pure (~Q) + //DIV3_PURE2 = 0x0d, // 1 1 0 1 1.19 Mhz/114 none (pure) pure (~Q) + //DIV93_PURE = 0x0e, // 1 1 1 0 1.19 Mhz/114 divide by 31 pure (~Q) + //DIV3_POLY5 = 0x0f; // 1 1 1 1 1.19 Mhz/114 5-bit poly pure (~Q) + + const int + AUDC0 = 0x15, // audio control 0 (D3-0) + AUDC1 = 0x16, // audio control 1 (D4-0) + AUDF0 = 0x17, // audio frequency 0 (D4-0) + AUDF1 = 0x18, // audio frequency 1 (D3-0) + AUDV0 = 0x19, // audio volume 0 (D3-0) + AUDV1 = 0x1a; // audio volume 1 (D3-0) + + // The 4bit and 5bit patterns are the identical ones used in the tia chip. + readonly byte[] Bit4 = new byte[] { 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0 }; // 2^4 - 1 = 15 + readonly byte[] Bit5 = new byte[] { 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1 }; // 2^5 - 1 = 31 + + // [Ron] treated the 'Div by 31' counter as another polynomial because of + // the way it operates. It does not have a 50% duty cycle, but instead + // has a 13:18 ratio (of course, 13+18 = 31). This could also be + // implemented by using counters. + readonly byte[] Div31 = new byte[] { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + // Rather than have a table with 511 entries, I use a random number + readonly byte[] Bit9 = new byte[511]; // 2^9 - 1 = 511 + + readonly int[] P4 = new int[2]; // Position counter for the 4-bit POLY array + readonly int[] P5 = new int[2]; // Position counter for the 5-bit POLY array + readonly int[] P9 = new int[2]; // Position counter for the 9-bit POLY array + + readonly int[] DivByNCounter = new int[2]; // Divide by n counter, one for each channel + readonly int[] DivByNMaximum = new int[2]; // Divide by n maximum, one for each channel + + readonly int _cpuClocksPerSample; + + #endregion + + #region Object State + + readonly MachineBase M; + + // The TIA Sound registers + readonly byte[] AUDC = new byte[2]; + readonly byte[] AUDF = new byte[2]; + readonly byte[] AUDV = new byte[2]; + + // The last output volume for each channel + readonly byte[] OutputVol = new byte[2]; + + // Used to determine how much sound to render + ulong LastUpdateCPUClock; + + int BufferIndex; + + #endregion + + #region Public Members + + public void Reset() + { + for (var chan = 0; chan < 2; chan++) + { + OutputVol[chan] = 0; + DivByNCounter[chan] = 0; + DivByNMaximum[chan] = 0; + AUDC[chan] = 0; + AUDF[chan] = 0; + AUDV[chan] = 0; + P4[chan] = 0; + P5[chan] = 0; + P9[chan] = 0; + } + } + + public void StartFrame() + { + LastUpdateCPUClock = M.CPU.Clock; + BufferIndex = 0; + } + + public void EndFrame() + { + RenderSamples(M.FrameBuffer.SoundBufferByteLength - BufferIndex); + } + + public void Update(ushort addr, byte data) + { + if (M.CPU.Clock > LastUpdateCPUClock) + { + var updCPUClocks = (int)(M.CPU.Clock - LastUpdateCPUClock); + var samples = updCPUClocks / _cpuClocksPerSample; + RenderSamples(samples); + LastUpdateCPUClock += (ulong)(samples * _cpuClocksPerSample); + } + + byte chan; + + switch (addr) + { + case AUDC0: + AUDC[0] = (byte)(data & 0x0f); + chan = 0; + break; + case AUDC1: + AUDC[1] = (byte)(data & 0x0f); + chan = 1; + break; + case AUDF0: + AUDF[0] = (byte)(data & 0x1f); + chan = 0; + break; + case AUDF1: + AUDF[1] = (byte)(data & 0x1f); + chan = 1; + break; + case AUDV0: + AUDV[0] = (byte)(data & 0x0f); + chan = 0; + break; + case AUDV1: + AUDV[1] = (byte)(data & 0x0f); + chan = 1; + break; + default: + return; + } + + byte new_divn_max; + + if (AUDC[chan] == SET_TO_1) + { + // indicate the clock is zero so no process will occur + new_divn_max = 0; + // and set the output to the selected volume + OutputVol[chan] = AUDV[chan]; + } + else + { + // otherwise calculate the 'divide by N' value + new_divn_max = (byte)(AUDF[chan] + 1); + // if bits D2 & D3 are set, then multiply the 'div by n' count by 3 + if ((AUDC[chan] & 0x0c) == 0x0c) + { + new_divn_max *= 3; + } + } + + // only reset those channels that have changed + if (new_divn_max != DivByNMaximum[chan]) + { + DivByNMaximum[chan] = new_divn_max; + + // if the channel is now volume only or was volume only... + if (DivByNCounter[chan] == 0 || new_divn_max == 0) + { + // reset the counter (otherwise let it complete the previous) + DivByNCounter[chan] = new_divn_max; + } + } + } + + #endregion + + #region Constructors + + private TIASound() + { + var r = new Random(); + r.NextBytes(Bit9); + for (var i = 0; i < Bit9.Length; i++) + { + Bit9[i] &= 0x01; + } + } + + public TIASound(MachineBase m, int cpuClocksPerSample) : this() + { + if (m == null) + throw new ArgumentNullException("m"); + if (cpuClocksPerSample <= 0) + throw new ArgumentException("cpuClocksPerSample must be positive."); + + M = m; + _cpuClocksPerSample = cpuClocksPerSample; + } + + #endregion + + #region Serialization Members + + public TIASound(DeserializationContext input, MachineBase m, int cpuClocksPerSample) : this(m, cpuClocksPerSample) + { + if (input == null) + throw new ArgumentNullException("input"); + + input.CheckVersion(1); + Bit9 = input.ReadExpectedBytes(511); + P4 = input.ReadIntegers(2); + P5 = input.ReadIntegers(2); + P9 = input.ReadIntegers(2); + DivByNCounter = input.ReadIntegers(2); + DivByNMaximum = input.ReadIntegers(2); + AUDC = input.ReadExpectedBytes(2); + AUDF = input.ReadExpectedBytes(2); + AUDV = input.ReadExpectedBytes(2); + OutputVol = input.ReadExpectedBytes(2); + LastUpdateCPUClock = input.ReadUInt64(); + BufferIndex = input.ReadInt32(); + } + + public void GetObjectData(SerializationContext output) + { + if (output == null) + throw new ArgumentNullException("output"); + + output.WriteVersion(1); + output.Write(Bit9); + output.Write(P4); + output.Write(P5); + output.Write(P9); + output.Write(DivByNCounter); + output.Write(DivByNMaximum); + output.Write(AUDC); + output.Write(AUDF); + output.Write(AUDV); + output.Write(OutputVol); + output.Write(LastUpdateCPUClock); + output.Write(BufferIndex); + } + + #endregion + + #region Helpers + + void RenderSamples(int count) + { + for (; BufferIndex < M.FrameBuffer.SoundBufferByteLength && count-- > 0; BufferIndex++) + { + if (DivByNCounter[0] > 1) + { + DivByNCounter[0]--; + } + else if (DivByNCounter[0] == 1) + { + DivByNCounter[0] = DivByNMaximum[0]; + ProcessChannel(0); + } + if (DivByNCounter[1] > 1) + { + DivByNCounter[1]--; + } + else if (DivByNCounter[1] == 1) + { + DivByNCounter[1] = DivByNMaximum[1]; + ProcessChannel(1); + } + + M.FrameBuffer.SoundBuffer[BufferIndex >> BufferElement.SHIFT][BufferIndex] += (byte)(OutputVol[0] + OutputVol[1]); + } + } + + void ProcessChannel(int chan) + { + // the P5 counter has multiple uses, so we inc it here + if (++P5[chan] >= 31) + { // POLY5 size: 2^5 - 1 = 31 + P5[chan] = 0; + } + + // check clock modifier for clock tick + if ((AUDC[chan] & 0x02) == 0 || + ((AUDC[chan] & 0x01) == 0 && Div31[P5[chan]] == 1) || + ((AUDC[chan] & 0x01) == 1 && Bit5[P5[chan]] == 1)) + { + if ((AUDC[chan] & 0x04) != 0) + { // pure modified clock selected + OutputVol[chan] = (OutputVol[chan] != 0) ? (byte)0 : AUDV[chan]; + } + else if ((AUDC[chan] & 0x08) != 0) + { // check for poly5/poly9 + if (AUDC[chan] == POLY9) + { // check for poly9 + if (++P9[chan] >= 511) + { // poly9 size: 2^9 - 1 = 511 + P9[chan] = 0; + } + OutputVol[chan] = (Bit9[P9[chan]] == 1) ? AUDV[chan] : (byte)0; + } + else + { // must be poly5 + OutputVol[chan] = (Bit5[P5[chan]] == 1) ? AUDV[chan] : (byte)0; + } + } + else + { // poly4 is the only remaining possibility + if (++P4[chan] >= 15) + { // POLY4 size: 2^4 - 1 = 15 + P4[chan] = 0; + } + OutputVol[chan] = (Bit4[P4[chan]] == 1) ? AUDV[chan] : (byte)0; + } + } + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIATables.cs b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIATables.cs new file mode 100644 index 0000000000..4a38a2b482 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Atari/7800/EMU7800/TIATables.cs @@ -0,0 +1,424 @@ +/* + * TIATables.cs + * + * Mask tables for the Television Interface Adaptor class. All derived from + * Bradford Mott's Stella code. + * + * Copyright © 2003, 2004 Mike Murphy + * + */ +namespace EMU7800.Core +{ + public static class TIATables + { + public static readonly TIACxPairFlags[] CollisionMask = BuildCollisionMaskTable(); + public static readonly uint[][] PFMask = BuildPFMaskTable(); + public static readonly bool[][] BLMask = BuildBLMaskTable(); + public static readonly bool[][][] MxMask = BuildMxMaskTable(); + public static readonly byte[][][] PxMask = BuildPxMaskTable(); + public static readonly byte[] GRPReflect = BuildGRPReflectTable(); + + public static readonly int[] NTSCPalette = + { + 0x000000, 0x000000, 0x4a4a4a, 0x4a4a4a, + 0x6f6f6f, 0x6f6f6f, 0x8e8e8e, 0x8e8e8e, + 0xaaaaaa, 0xaaaaaa, 0xc0c0c0, 0xc0c0c0, + 0xd6d6d6, 0xd6d6d6, 0xececec, 0xececec, + + 0x484800, 0x484800, 0x69690f, 0x69690f, + 0x86861d, 0x86861d, 0xa2a22a, 0xa2a22a, + 0xbbbb35, 0xbbbb35, 0xd2d240, 0xd2d240, + 0xe8e84a, 0xe8e84a, 0xfcfc54, 0xfcfc54, + + 0x7c2c00, 0x7c2c00, 0x904811, 0x904811, + 0xa26221, 0xa26221, 0xb47a30, 0xb47a30, + 0xc3903d, 0xc3903d, 0xd2a44a, 0xd2a44a, + 0xdfb755, 0xdfb755, 0xecc860, 0xecc860, + + 0x901c00, 0x901c00, 0xa33915, 0xa33915, + 0xb55328, 0xb55328, 0xc66c3a, 0xc66c3a, + 0xd5824a, 0xd5824a, 0xe39759, 0xe39759, + 0xf0aa67, 0xf0aa67, 0xfcbc74, 0xfcbc74, + + 0x940000, 0x940000, 0xa71a1a, 0xa71a1a, + 0xb83232, 0xb83232, 0xc84848, 0xc84848, + 0xd65c5c, 0xd65c5c, 0xe46f6f, 0xe46f6f, + 0xf08080, 0xf08080, 0xfc9090, 0xfc9090, + + 0x840064, 0x840064, 0x97197a, 0x97197a, + 0xa8308f, 0xa8308f, 0xb846a2, 0xb846a2, + 0xc659b3, 0xc659b3, 0xd46cc3, 0xd46cc3, + 0xe07cd2, 0xe07cd2, 0xec8ce0, 0xec8ce0, + + 0x500084, 0x500084, 0x68199a, 0x68199a, + 0x7d30ad, 0x7d30ad, 0x9246c0, 0x9246c0, + 0xa459d0, 0xa459d0, 0xb56ce0, 0xb56ce0, + 0xc57cee, 0xc57cee, 0xd48cfc, 0xd48cfc, + + 0x140090, 0x140090, 0x331aa3, 0x331aa3, + 0x4e32b5, 0x4e32b5, 0x6848c6, 0x6848c6, + 0x7f5cd5, 0x7f5cd5, 0x956fe3, 0x956fe3, + 0xa980f0, 0xa980f0, 0xbc90fc, 0xbc90fc, + + 0x000094, 0x000094, 0x181aa7, 0x181aa7, + 0x2d32b8, 0x2d32b8, 0x4248c8, 0x4248c8, + 0x545cd6, 0x545cd6, 0x656fe4, 0x656fe4, + 0x7580f0, 0x7580f0, 0x8490fc, 0x8490fc, + + 0x001c88, 0x001c88, 0x183b9d, 0x183b9d, + 0x2d57b0, 0x2d57b0, 0x4272c2, 0x4272c2, + 0x548ad2, 0x548ad2, 0x65a0e1, 0x65a0e1, + 0x75b5ef, 0x75b5ef, 0x84c8fc, 0x84c8fc, + + 0x003064, 0x003064, 0x185080, 0x185080, + 0x2d6d98, 0x2d6d98, 0x4288b0, 0x4288b0, + 0x54a0c5, 0x54a0c5, 0x65b7d9, 0x65b7d9, + 0x75cceb, 0x75cceb, 0x84e0fc, 0x84e0fc, + + 0x004030, 0x004030, 0x18624e, 0x18624e, + 0x2d8169, 0x2d8169, 0x429e82, 0x429e82, + 0x54b899, 0x54b899, 0x65d1ae, 0x65d1ae, + 0x75e7c2, 0x75e7c2, 0x84fcd4, 0x84fcd4, + + 0x004400, 0x004400, 0x1a661a, 0x1a661a, + 0x328432, 0x328432, 0x48a048, 0x48a048, + 0x5cba5c, 0x5cba5c, 0x6fd26f, 0x6fd26f, + 0x80e880, 0x80e880, 0x90fc90, 0x90fc90, + + 0x143c00, 0x143c00, 0x355f18, 0x355f18, + 0x527e2d, 0x527e2d, 0x6e9c42, 0x6e9c42, + 0x87b754, 0x87b754, 0x9ed065, 0x9ed065, + 0xb4e775, 0xb4e775, 0xc8fc84, 0xc8fc84, + + 0x303800, 0x303800, 0x505916, 0x505916, + 0x6d762b, 0x6d762b, 0x88923e, 0x88923e, + 0xa0ab4f, 0xa0ab4f, 0xb7c25f, 0xb7c25f, + 0xccd86e, 0xccd86e, 0xe0ec7c, 0xe0ec7c, + + 0x482c00, 0x482c00, 0x694d14, 0x694d14, + 0x866a26, 0x866a26, 0xa28638, 0xa28638, + 0xbb9f47, 0xbb9f47, 0xd2b656, 0xd2b656, + 0xe8cc63, 0xe8cc63, 0xfce070, 0xfce070 + }; + + public static readonly int[] PALPalette = + { + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, + + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, + + 0x805800, 0x000000, 0x96711a, 0x2b2b2b, + 0xab8732, 0x525252, 0xbe9c48, 0x767676, + 0xcfaf5c, 0x979797, 0xdfc06f, 0xb6b6b6, + 0xeed180, 0xd2d2d2, 0xfce090, 0xececec, + + 0x445c00, 0x000000, 0x5e791a, 0x2b2b2b, + 0x769332, 0x525252, 0x8cac48, 0x767676, + 0xa0c25c, 0x979797, 0xb3d76f, 0xb6b6b6, + 0xc4ea80, 0xd2d2d2, 0xd4fc90, 0xececec, + + 0x703400, 0x000000, 0x89511a, 0x2b2b2b, + 0xa06b32, 0x525252, 0xb68448, 0x767676, + 0xc99a5c, 0x979797, 0xdcaf6f, 0xb6b6b6, + 0xecc280, 0xd2d2d2, 0xfcd490, 0xececec, + + 0x006414, 0x000000, 0x1a8035, 0x2b2b2b, + 0x329852, 0x525252, 0x48b06e, 0x767676, + 0x5cc587, 0x979797, 0x6fd99e, 0xb6b6b6, + 0x80ebb4, 0xd2d2d2, 0x90fcc8, 0xececec, + + 0x700014, 0x000000, 0x891a35, 0x2b2b2b, + 0xa03252, 0x525252, 0xb6486e, 0x767676, + 0xc95c87, 0x979797, 0xdc6f9e, 0xb6b6b6, + 0xec80b4, 0xd2d2d2, 0xfc90c8, 0xececec, + + 0x005c5c, 0x000000, 0x1a7676, 0x2b2b2b, + 0x328e8e, 0x525252, 0x48a4a4, 0x767676, + 0x5cb8b8, 0x979797, 0x6fcbcb, 0xb6b6b6, + 0x80dcdc, 0xd2d2d2, 0x90ecec, 0xececec, + + 0x70005c, 0x000000, 0x841a74, 0x2b2b2b, + 0x963289, 0x525252, 0xa8489e, 0x767676, + 0xb75cb0, 0x979797, 0xc66fc1, 0xb6b6b6, + 0xd380d1, 0xd2d2d2, 0xe090e0, 0xececec, + + 0x003c70, 0x000000, 0x195a89, 0x2b2b2b, + 0x2f75a0, 0x525252, 0x448eb6, 0x767676, + 0x57a5c9, 0x979797, 0x68badc, 0xb6b6b6, + 0x79ceec, 0xd2d2d2, 0x88e0fc, 0xececec, + + 0x580070, 0x000000, 0x6e1a89, 0x2b2b2b, + 0x8332a0, 0x525252, 0x9648b6, 0x767676, + 0xa75cc9, 0x979797, 0xb76fdc, 0xb6b6b6, + 0xc680ec, 0xd2d2d2, 0xd490fc, 0xececec, + + 0x002070, 0x000000, 0x193f89, 0x2b2b2b, + 0x2f5aa0, 0x525252, 0x4474b6, 0x767676, + 0x578bc9, 0x979797, 0x68a1dc, 0xb6b6b6, + 0x79b5ec, 0xd2d2d2, 0x88c8fc, 0xececec, + + 0x340080, 0x000000, 0x4a1a96, 0x2b2b2b, + 0x5f32ab, 0x525252, 0x7248be, 0x767676, + 0x835ccf, 0x979797, 0x936fdf, 0xb6b6b6, + 0xa280ee, 0xd2d2d2, 0xb090fc, 0xececec, + + 0x000088, 0x000000, 0x1a1a9d, 0x2b2b2b, + 0x3232b0, 0x525252, 0x4848c2, 0x767676, + 0x5c5cd2, 0x979797, 0x6f6fe1, 0xb6b6b6, + 0x8080ef, 0xd2d2d2, 0x9090fc, 0xececec, + + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec, + + 0x000000, 0x000000, 0x2b2b2b, 0x2b2b2b, + 0x525252, 0x525252, 0x767676, 0x767676, + 0x979797, 0x979797, 0xb6b6b6, 0xb6b6b6, + 0xd2d2d2, 0xd2d2d2, 0xececec, 0xececec + }; + + static uint[][] BuildPFMaskTable() + { + var tabl = new uint[2][]; + tabl[0] = new uint[160]; + tabl[1] = new uint[160]; + + for (var i = 0; i < 20; i++) + { + uint mask = 0; + if (i < 4) + { + mask = (uint)(1 << i); + } + else if (i < 12) + { + mask = (uint)(1 << (11 + 4 - i)); + } + else if (i < 20) + { + mask = (uint)(1 << i); + } + for (var j = 0; j < 4; j++) + { + // for non-reflected mode + tabl[0][4 * i + j] = mask; + tabl[0][80 + 4 * i + j] = mask; + + // for reflected mode + tabl[1][4 * i + j] = mask; + tabl[1][159 - 4 * i - j] = mask; + } + } + return tabl; + } + + static bool[][] BuildBLMaskTable() + { + var tabl = new bool[4][]; + for (var size = 0; size < 4; size++) + { + tabl[size] = new bool[160]; + for (var i = 0; i < 160; i++) + { + tabl[size][i] = false; + } + for (var i = 0; i < (1 << size); i++) + { + tabl[size][i] = true; + } + } + return tabl; + } + + static bool[][][] BuildMxMaskTable() + { + var tabl = new bool[4][][]; + for (var i = 0; i < 4; i++) + { + tabl[i] = new bool[8][]; + for (var j = 0; j < 8; j++) + { + tabl[i][j] = new bool[160]; + for (var k = 0; k < 160; k++) + { + tabl[i][j][k] = false; + } + } + } + + for (var size = 0; size < 4; size++) + { + for (var i = 0; i < (1 << size); i++) + { + tabl[size][0][i] = true; + + tabl[size][1][i] = true; + tabl[size][1][i + 16] = true; + + tabl[size][2][i] = true; + tabl[size][2][i + 32] = true; + + tabl[size][3][i] = true; + tabl[size][3][i + 16] = true; + tabl[size][3][i + 32] = true; + + tabl[size][4][i] = true; + tabl[size][4][i + 64] = true; + + tabl[size][5][i] = true; + + tabl[size][6][i] = true; + tabl[size][6][i + 32] = true; + tabl[size][6][i + 64] = true; + + tabl[size][7][i] = true; + } + } + return tabl; + } + + static byte[][][] BuildPxMaskTable() + { + // [suppress mode, nusiz, pixel] + // suppress=1: suppress on + // suppress=0: suppress off + var tabl = new byte[2][][]; //2 8 160 + tabl[0] = new byte[8][]; + tabl[1] = new byte[8][]; + for (var nusiz = 0; nusiz < 8; nusiz++) + { + tabl[0][nusiz] = new byte[160]; + tabl[1][nusiz] = new byte[160]; + for (var hpos = 0; hpos < 160; hpos++) + { + // nusiz: + // 0: one copy + // 1: two copies-close + // 2: two copies-med + // 3: three copies-close + // 4: two copies-wide + // 5: double size player + // 6: 3 copies medium + // 7: quad sized player + tabl[0][nusiz][hpos] = tabl[1][nusiz][hpos] = 0; + if (nusiz >= 0 && nusiz <= 4 || nusiz == 6) + { + if (hpos >= 0 && hpos < 8) + { + tabl[0][nusiz][hpos] = (byte)(1 << (7 - hpos)); + } + } + if (nusiz == 1 || nusiz == 3) + { + if (hpos >= 16 && hpos < 24) + { + tabl[0][nusiz][hpos] = (byte)(1 << (23 - hpos)); + tabl[1][nusiz][hpos] = (byte)(1 << (23 - hpos)); + } + } + if (nusiz == 2 || nusiz == 3 || nusiz == 6) + { + if (hpos >= 32 && hpos < 40) + { + tabl[0][nusiz][hpos] = (byte)(1 << (39 - hpos)); + tabl[1][nusiz][hpos] = (byte)(1 << (39 - hpos)); + } + } + if (nusiz == 4 || nusiz == 6) + { + if (hpos >= 64 && hpos < 72) + { + tabl[0][nusiz][hpos] = (byte)(1 << (71 - hpos)); + tabl[1][nusiz][hpos] = (byte)(1 << (71 - hpos)); + } + } + if (nusiz == 5) + { + if (hpos >= 0 && hpos < 16) + { + tabl[0][nusiz][hpos] = (byte)(1 << ((15 - hpos) >> 1)); + } + } + if (nusiz == 7) + { + if (hpos >= 0 && hpos < 32) + { + tabl[0][nusiz][hpos] = (byte)(1 << ((31 - hpos) >> 2)); + } + } + } + + var shift = nusiz == 5 || nusiz == 7 ? 2 : 1; + while (shift-- > 0) + { + for (var i = 159; i > 0; i--) + { + tabl[0][nusiz][i] = tabl[0][nusiz][i - 1]; + tabl[1][nusiz][i] = tabl[1][nusiz][i - 1]; + } + tabl[0][nusiz][0] = tabl[1][nusiz][0] = 0; + } + } + return tabl; + } + + static byte[] BuildGRPReflectTable() + { + var tabl = new byte[256]; + + for (var i = 0; i < 256; i++) + { + var s = (byte)i; + var r = (byte)0; + for (var j = 0; j < 8; j++) + { + r <<= 1; + r |= (byte)(s & 1); + s >>= 1; + } + tabl[i] = r; + } + return tabl; + } + + static bool tstCx(int i, TIACxFlags cxf1, TIACxFlags cxf2) + { + var f1 = (int)cxf1; + var f2 = (int)cxf2; + return ((i & f1) != 0) && ((i & f2) != 0); + } + + static TIACxPairFlags[] BuildCollisionMaskTable() + { + var tabl = new TIACxPairFlags[64]; + + for (var i = 0; i < 64; i++) + { + tabl[i] = 0; + if (tstCx(i, TIACxFlags.M0, TIACxFlags.P1)) { tabl[i] |= TIACxPairFlags.M0P1; } + if (tstCx(i, TIACxFlags.M0, TIACxFlags.P0)) { tabl[i] |= TIACxPairFlags.M0P0; } + if (tstCx(i, TIACxFlags.M1, TIACxFlags.P0)) { tabl[i] |= TIACxPairFlags.M1P0; } + if (tstCx(i, TIACxFlags.M1, TIACxFlags.P1)) { tabl[i] |= TIACxPairFlags.M1P1; } + if (tstCx(i, TIACxFlags.P0, TIACxFlags.PF)) { tabl[i] |= TIACxPairFlags.P0PF; } + if (tstCx(i, TIACxFlags.P0, TIACxFlags.BL)) { tabl[i] |= TIACxPairFlags.P0BL; } + if (tstCx(i, TIACxFlags.P1, TIACxFlags.PF)) { tabl[i] |= TIACxPairFlags.P1PF; } + if (tstCx(i, TIACxFlags.P1, TIACxFlags.BL)) { tabl[i] |= TIACxPairFlags.P1BL; } + if (tstCx(i, TIACxFlags.M0, TIACxFlags.PF)) { tabl[i] |= TIACxPairFlags.M0PF; } + if (tstCx(i, TIACxFlags.M0, TIACxFlags.BL)) { tabl[i] |= TIACxPairFlags.M0BL; } + if (tstCx(i, TIACxFlags.M1, TIACxFlags.PF)) { tabl[i] |= TIACxPairFlags.M1PF; } + if (tstCx(i, TIACxFlags.M1, TIACxFlags.BL)) { tabl[i] |= TIACxPairFlags.M1BL; } + if (tstCx(i, TIACxFlags.BL, TIACxFlags.PF)) { tabl[i] |= TIACxPairFlags.BLPF; } + if (tstCx(i, TIACxFlags.P0, TIACxFlags.P1)) { tabl[i] |= TIACxPairFlags.P0P1; } + if (tstCx(i, TIACxFlags.M0, TIACxFlags.M1)) { tabl[i] |= TIACxPairFlags.M0M1; } + } + return tabl; + } + } +} diff --git a/BizHawk.Emulation/Database/Database.cs b/BizHawk.Emulation/Database/Database.cs index e736ee8e0c..d92c257fce 100644 --- a/BizHawk.Emulation/Database/Database.cs +++ b/BizHawk.Emulation/Database/Database.cs @@ -150,6 +150,7 @@ namespace BizHawk case ".MD": case ".SMD": Game.System = "GEN"; break; case ".A26": Game.System = "A26"; break; + case ".A78": Game.System = "A78"; break; case ".COL": Game.System = "COLV"; break; case ".ROM": case ".INT": Game.System = "INTV"; break; diff --git a/BizHawk.MultiClient/Config.cs b/BizHawk.MultiClient/Config.cs index 31eaf0cedd..892999ce31 100644 --- a/BizHawk.MultiClient/Config.cs +++ b/BizHawk.MultiClient/Config.cs @@ -155,6 +155,7 @@ namespace BizHawk.MultiClient public string PathINTVGROM = Path.Combine(".", "grom.bin"); public string PathINTVEROM = Path.Combine(".", "erom.bin"); public string PathFDSBios = Path.Combine(".", "disksys.rom"); + public string PathAtari7800NTSCBIOS = Path.Combine("C:\\Repos", "7800NTSCBIOS.bin"); public string FFMpegPath = "%exe%/dll/ffmpeg.exe"; diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index e9f535d82d..4ee169a629 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -1303,7 +1303,7 @@ namespace BizHawk.MultiClient if (path == null) return false; using (var file = new HawkFile()) { - string[] romExtensions = new string[] { "SMS", "SMC", "SFC", "PCE", "SGX", "GG", "SG", "BIN", "GEN", "MD", "SMD", "GB", "NES", "ROM", "INT", "GBC", "UNF" }; + string[] romExtensions = new string[] { "SMS", "SMC", "SFC", "PCE", "SGX", "GG", "SG", "BIN", "GEN", "MD", "SMD", "GB", "NES", "ROM", "INT", "GBC", "UNF", "A78" }; //lets not use this unless we need to //file.NonArchiveExtensions = romExtensions; @@ -1571,6 +1571,13 @@ namespace BizHawk.MultiClient nextEmulator = intv; } break; + case "A78": + string biospath = Global.Config.PathAtari7800NTSCBIOS; //TODO + byte[] BIOS7800 = File.ReadAllBytes(biospath); + byte[] HighScoreBIOS = File.ReadAllBytes("C:\\Repos\\7800highscore.bin"); + Atari7800 a78 = new Atari7800(game, rom.RomData, BIOS7800, HighScoreBIOS); + nextEmulator = a78; + break; } }