diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IDebuggable.cs new file mode 100644 index 0000000000..45809e725e --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IDebuggable.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class A7800Hawk : IDebuggable + { + public IDictionary GetCpuFlagsAndRegisters() + { + return new Dictionary + { + ["A"] = cpu.A, + ["X"] = cpu.X, + ["Y"] = cpu.Y, + ["S"] = cpu.S, + ["PC"] = cpu.PC, + ["Flag C"] = cpu.FlagC, + ["Flag Z"] = cpu.FlagZ, + ["Flag I"] = cpu.FlagI, + ["Flag D"] = cpu.FlagD, + ["Flag B"] = cpu.FlagB, + ["Flag V"] = cpu.FlagV, + ["Flag N"] = cpu.FlagN, + ["Flag T"] = cpu.FlagT + }; + } + + public void SetCpuRegister(string register, int value) + { + switch (register) + { + default: + throw new InvalidOperationException(); + case "A": + cpu.A = (byte)value; + break; + case "X": + cpu.X = (byte)value; + break; + case "Y": + cpu.Y = (byte)value; + break; + case "S": + cpu.S = (byte)value; + break; + case "PC": + cpu.PC = (ushort)value; + break; + case "Flag I": + cpu.FlagI = value > 0; + break; + } + } + + public IMemoryCallbackSystem MemoryCallbacks + { + [FeatureNotImplemented] + get { throw new NotImplementedException(); } + } + + public bool CanStep(StepType type) + { + return false; + } + + [FeatureNotImplemented] + public void Step(StepType type) + { + throw new NotImplementedException(); + } + + public int TotalExecutedCycles + { + get { return cpu.TotalExecutedCycles; } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IEmulator.cs new file mode 100644 index 0000000000..5beafd7006 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IEmulator.cs @@ -0,0 +1,51 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class A7800Hawk : IEmulator + { + public IEmulatorServiceProvider ServiceProvider { get; } + + public ControllerDefinition ControllerDefinition { get; private set; } + + public void FrameAdvance(IController controller, bool render, bool rendersound) + { + _frame++; + + if (controller.IsPressed("Power")) + { + // it seems that theMachine.Reset() doesn't clear ram, etc + // this should leave hsram intact but clear most other things + HardReset(); + } + + if (_islag) + { + _lagcount++; + } + + } + + public int Frame => _frame; + + public string SystemId => "A7800"; + + public bool DeterministicEmulation { get; set; } + + public void ResetCounters() + { + _frame = 0; + _lagcount = 0; + _islag = false; + } + + public CoreComm CoreComm { get; } + + public void Dispose() + { + maria = null; + tia = null; + + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IInputPollable.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IInputPollable.cs new file mode 100644 index 0000000000..f2c5b97360 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IInputPollable.cs @@ -0,0 +1,24 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class A7800Hawk : IInputPollable + { + public int LagCount + { + get { return _lagcount; } + set { _lagcount = value; } + } + + public bool IsLagFrame + { + get { return _islag; } + set { _islag = value; } + } + + public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem(); + + private bool _islag = true; + private int _lagcount; + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IMemoryDomains.cs new file mode 100644 index 0000000000..72735171c0 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IMemoryDomains.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class A7800Hawk + { + private IMemoryDomains MemoryDomains; + + public void SetupMemoryDomains() + { + var domains = new List + { + new MemoryDomainDelegate( + "Main RAM", + RAM.Length, + MemoryDomain.Endian.Little, + addr => RAM[addr], + (addr, value) => RAM[addr] = value, + 1), + new MemoryDomainDelegate( + "TIA Registers", + TIA_regs.Length, + MemoryDomain.Endian.Little, + addr => TIA_regs[addr], + (addr, value) => TIA_regs[addr] = value, + 1), + new MemoryDomainDelegate( + "Maria Registers", + Maria_regs.Length, + MemoryDomain.Endian.Little, + addr => Maria_regs[addr], + (addr, value) => Maria_regs[addr] = value, + 1), + new MemoryDomainDelegate( + "6532 Registers", + regs_6532.Length, + MemoryDomain.Endian.Little, + addr => regs_6532[addr], + (addr, value) => regs_6532[addr] = value, + 1), + new MemoryDomainDelegate( + "Ram Block 0", + 0xB, + MemoryDomain.Endian.Little, + addr => RAM[addr-0x840], + (addr, value) => RAM[addr-0x840] = value, + 1 + ), + new MemoryDomainDelegate( + "Ram Block 1", + 0xB, + MemoryDomain.Endian.Little, + addr => RAM[addr-0x940], + (addr, value) => RAM[addr-0x940] = value, + 1 + ), + new MemoryDomainDelegate( + "System Bus", + 0X10000, + MemoryDomain.Endian.Little, + addr => PeekSystemBus(addr), + (addr, value) => PokeSystemBus(addr, value), + 1 + ) + }; + + MemoryDomains = new MemoryDomainList(domains); + (ServiceProvider as BasicServiceProvider).Register(MemoryDomains); + } + + private byte PeekSystemBus(long addr) + { + return 0; + } + + private void PokeSystemBus(long addr, byte value) + { + + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISaveRam.cs new file mode 100644 index 0000000000..b3a08cde2b --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.ISaveRam.cs @@ -0,0 +1,26 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class A7800Hawk : ISaveRam + { + public byte[] CloneSaveRam() + { + return (byte[])_hsram.Clone(); + } + + public void StoreSaveRam(byte[] data) + { + Buffer.BlockCopy(data, 0, _hsram, 0, data.Length); + } + + public bool SaveRamModified + { + get + { + return false; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IStatable.cs new file mode 100644 index 0000000000..96356abb5b --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.IStatable.cs @@ -0,0 +1,59 @@ +using System.IO; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public partial class A7800Hawk : IStatable + { + public bool BinarySaveStatesPreferred => true; + + public void SaveStateText(TextWriter writer) + { + SyncState(new Serializer(writer)); + } + + public void LoadStateText(TextReader reader) + { + SyncState(new Serializer(reader)); + } + + public void SaveStateBinary(BinaryWriter bw) + { + SyncState(new Serializer(bw)); + } + + public void LoadStateBinary(BinaryReader br) + { + SyncState(new Serializer(br)); + } + + public byte[] SaveStateBinary() + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + return ms.ToArray(); + } + + private void SyncState(Serializer ser) + { + byte[] core = null; + if (ser.IsWriter) + { + var ms = new MemoryStream(); + ms.Close(); + core = ms.ToArray(); + } + + ser.BeginSection("Atari7800"); + ser.Sync("core", ref core, false); + ser.Sync("Lag", ref _lagcount); + ser.Sync("Frame", ref _frame); + ser.Sync("IsLag", ref _islag); + ser.EndSection(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs new file mode 100644 index 0000000000..255f3ea4bd --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800Hawk.cs @@ -0,0 +1,298 @@ +using System; + +using BizHawk.Common.BufferExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components.M6502; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + [CoreAttributes( + "A7800Hawk", + "", + isPorted: false, + isReleased: true)] + [ServiceNotApplicable(typeof(ISettable<,>), typeof(IDriveLight))] + public partial class A7800Hawk : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable + { + // memory domains + public byte[] TIA_regs = new byte[0x20]; + public byte[] Maria_regs = new byte[0x20]; + public byte[] RAM = new byte[0x1000]; + public byte[] regs_6532 = new byte[0x80]; + + private readonly byte[] _rom; + private readonly byte[] _hsbios; + private readonly byte[] _bios; + private readonly byte[] _hsram = new byte[2048]; + + private int _frame = 0; + + public MOS6502X cpu; + public Maria maria; + private bool _isPAL; + public M6532 m6532; + public TIA tia; + + public A7800Hawk(CoreComm comm, GameInfo game, byte[] rom, string gameDbFn) + { + var ser = new BasicServiceProvider(this); + ser.Register(maria); + ser.Register(tia); + ServiceProvider = ser; + + CoreComm = comm; + byte[] highscoreBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_HSC", false, "Some functions may not work without the high score BIOS."); + byte[] palBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_PAL", false, "The game will not run if the correct region BIOS is not available."); + byte[] ntscBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_NTSC", false, "The game will not run if the correct region BIOS is not available."); + + if (rom.Length % 1024 == 128) + { + Console.WriteLine("Trimming 128 byte .a78 header..."); + byte[] newrom = new byte[rom.Length - 128]; + Buffer.BlockCopy(rom, 128, newrom, 0, newrom.Length); + rom = newrom; + } + + _isPAL = false; + + // look up hash in gamedb to see what mapper to use + // if none found default is zero + // also check for PAL region + string hash_md5 = null; + string s_mapper = null; + hash_md5 = "md5:" + rom.HashMD5(0, rom.Length); + + var gi = Database.CheckDatabase(hash_md5); + + if (gi != null) + { + var dict = gi.GetOptionsDict(); + if (!dict.ContainsKey("PAL")) + { + _isPAL = true; + } + if (!dict.ContainsKey("board")) + { + s_mapper = dict["board"]; + } + else + throw new Exception("No Board selected for this mapper"); + } + else + { + throw new Exception("ROM not in gamedb"); + } + + _rom = rom; + _hsbios = highscoreBios; + _bios = _isPAL ? palBios : ntscBios; + + if (_bios == null) + { + throw new MissingFirmwareException("The BIOS corresponding to the region of the game you loaded is required to run Atari 7800 games."); + } + + // set up palette and frame rate + if (_isPAL) + { + maria._frameHz = 50; + maria._palette = PALPalette; + } + else + { + maria._frameHz = 60; + maria._palette = NTSCPalette; + } + + + HardReset(); + } + + public DisplayType Region => _isPAL ? DisplayType.PAL : DisplayType.NTSC; + + public A7800HawkControl ControlAdapter { get; private set; } + + private void HardReset() + { + + + cpu.Reset(); + } + + /* + * MariaTables.cs + * + * Palette tables for the Maria class. + * All derived from Dan Boris' 7800/MAME code. + * + * Copyright © 2004 Mike Murphy + * + */ + + 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.Cores/Consoles/Atari/A7800Hawk/A7800HawkControl.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControl.cs new file mode 100644 index 0000000000..ddb0815b14 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/A7800HawkControl.cs @@ -0,0 +1,409 @@ +using System; + +using EMU7800.Core; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + public class A7800HawkControl + { + private static readonly ControllerDefinition Joystick = new ControllerDefinition + { + Name = "Atari 7800 Joystick Controller", + BoolButtons = + { + // hard reset, not passed to EMU7800 + "Power", + + // on the console + "Reset", + "Select", + "BW", // should be "Color"?? + "Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Right Difficulty", + + // ports + "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Trigger", + "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Trigger" + } + }; + + private static readonly ControllerDefinition Paddles = new ControllerDefinition + { + Name = "Atari 7800 Paddle Controller", + BoolButtons = + { + // hard reset, not passed to EMU7800 + "Power", + + // on the console + "Reset", + "Select", + "BW", // should be "Color"?? + "Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Right Difficulty", + + // ports + "P1 Trigger", + "P2 Trigger", + "P3 Trigger", + "P4 Trigger" + }, + FloatControls = // should be in [0..700000] + { + "P1 Paddle", + "P2 Paddle", + "P3 Paddle", + "P4 Paddle" + }, + FloatRanges = + { + // what is the center point supposed to be here? + new[] { 0.0f, 0.0f, 700000.0f }, + new[] { 0.0f, 0.0f, 700000.0f }, + new[] { 0.0f, 0.0f, 700000.0f }, + new[] { 0.0f, 0.0f, 700000.0f } + } + }; + + private static readonly ControllerDefinition Keypad = new ControllerDefinition + { + Name = "Atari 7800 Keypad Controller", + BoolButtons = + { + // hard reset, not passed to EMU7800 + "Power", + + // on the console + "Reset", + "Select", + "BW", // should be "Color"?? + "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Toggle Right Difficulty", + + // ports + "P1 Keypad1", "P1 Keypad2", "P1 Keypad3", + "P1 Keypad4", "P1 Keypad5", "P1 Keypad6", + "P1 Keypad7", "P1 Keypad8", "P1 Keypad9", + "P1 KeypadA", "P1 Keypad0", "P1 KeypadP", + "P2 Keypad1", "P2 Keypad2", "P2 Keypad3", + "P2 Keypad4", "P2 Keypad5", "P2 Keypad6", + "P2 Keypad7", "P2 Keypad8", "P2 Keypad9", + "P2 KeypadA", "P2 Keypad0", "P2 KeypadP", + "P3 Keypad1", "P3 Keypad2", "P3 Keypad3", + "P3 Keypad4", "P3 Keypad5", "P3 Keypad6", + "P3 Keypad7", "P3 Keypad8", "P3 Keypad9", + "P3 KeypadA", "P3 Keypad0", "P3 KeypadP", + "P4 Keypad1", "P4 Keypad2", "P4 Keypad3", + "P4 Keypad4", "P4 Keypad5", "P4 Keypad6", + "P4 Keypad7", "P4 Keypad8", "P4 Keypad9", + "P4 KeypadA", "P4 Keypad0", "P4 KeypadP" + } + }; + + private static readonly ControllerDefinition Driving = new ControllerDefinition + { + Name = "Atari 7800 Driving Controller", + BoolButtons = + { + // hard reset, not passed to EMU7800 + "Power", + + // on the console + "Reset", + "Select", + "BW", // should be "Color"?? + "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Toggle Right Difficulty", + + // ports + "P1 Trigger", + "P2 Trigger" + }, + FloatControls = // should be in [0..3] + { + "P1 Driving", + "P2 Driving" + }, + FloatRanges = + { + new[] { 0.0f, 0.0f, 3.0f }, + new[] { 0.0f, 0.0f, 3.0f }, + new[] { 0.0f, 0.0f, 3.0f } + } + }; + + private static readonly ControllerDefinition BoosterGrip = new ControllerDefinition + { + Name = "Atari 7800 Booster Grip Controller", + BoolButtons = + { + // hard reset, not passed to EMU7800 + "Power", + + // on the console + "Reset", + "Select", + "BW", // should be "Color"?? + "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Toggle Right Difficulty", + + // ports + // NB: as referenced by the emu, p1t2 = p1t2, p1t3 = p2t2, p2t2 = p3t2, p2t3 = p4t2 + "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Trigger", "P1 Trigger 2", "P1 Trigger 3", + "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Trigger", "P2 Trigger 2", "P2 Trigger 3" + } + }; + + private static readonly ControllerDefinition ProLineJoystick = new ControllerDefinition + { + Name = "Atari 7800 ProLine Joystick Controller", + BoolButtons = + { + // hard reset, not passed to EMU7800 + "Power", + + // on the console + "Reset", + "Select", + "Pause", + "Toggle Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Toggle Right Difficulty", + + // ports + "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Trigger", "P1 Trigger 2", + "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Trigger", "P2 Trigger 2" + } + }; + + private static readonly ControllerDefinition Lightgun = new ControllerDefinition + { + Name = "Atari 7800 Light Gun Controller", + BoolButtons = + { + // hard reset, not passed to EMU7800 + "Power", + + // on the console + "Reset", + "Select", + "Pause", + "Left Difficulty", // better not put P# on these as they might not correspond to player numbers + "Right Difficulty", + + // ports + "P1 Trigger", + "P2 Trigger" + }, + FloatControls = // vpos should be actual scanline number. hpos should be in [0..319]?? + { + "P1 VPos", "P1 HPos", + "P2 VPos", "P2 HPos" + }, + FloatRanges = + { + // how many scanlines are there again?? + new[] { 0.0f, 0.0f, 240.0f }, + new[] { 0.0f, 0.0f, 319.0f }, + new[] { 0.0f, 0.0f, 240.0f }, + new[] { 0.0f, 0.0f, 319.0f } + } + }; + + private struct ControlAdapter + { + public readonly ControllerDefinition Type; + public readonly Controller Left; + public readonly Controller Right; + public readonly Action Convert; + + public ControlAdapter(ControllerDefinition type, Controller left, Controller right, Action convert) + { + Type = type; + Left = left; + Right = right; + Convert = convert; + } + } + + private static readonly ControlAdapter[] Adapters = + { + new ControlAdapter(Joystick, Controller.Joystick, Controller.Joystick, ConvertJoystick), + new ControlAdapter(Paddles, Controller.Paddles, Controller.Paddles, ConvertPaddles), + new ControlAdapter(Keypad, Controller.Keypad, Controller.Keypad, ConvertKeypad), + new ControlAdapter(Driving, Controller.Driving, Controller.Driving, ConvertDriving), + new ControlAdapter(BoosterGrip, Controller.BoosterGrip, Controller.BoosterGrip, ConvertBoosterGrip), + new ControlAdapter(ProLineJoystick, Controller.ProLineJoystick, Controller.ProLineJoystick, ConvertProLineJoystick), + new ControlAdapter(Lightgun, Controller.Lightgun, Controller.Lightgun, ConvertLightgun), + }; + + private static void ConvertConsoleButtons(IController c, InputState s) + { + s.RaiseInput(0, MachineInput.Reset, c.IsPressed("Reset")); + s.RaiseInput(0, MachineInput.Select, c.IsPressed("Select")); + s.RaiseInput(0, MachineInput.Color, c.IsPressed("BW")); + if (c.IsPressed("Toggle Left Difficulty")) + { + s.RaiseInput(0, MachineInput.LeftDifficulty, c.IsPressed("Toggle Left Difficulty")); + } + + if (c.IsPressed("Toggle Right Difficulty")) + { + s.RaiseInput(0, MachineInput.RightDifficulty, c.IsPressed("Toggle Right Difficulty")); + } + } + + private static void ConvertConsoleButtons7800(IController c, InputState s) + { + s.RaiseInput(0, MachineInput.Reset, c.IsPressed("Reset")); + s.RaiseInput(0, MachineInput.Select, c.IsPressed("Select")); + s.RaiseInput(0, MachineInput.Color, c.IsPressed("Pause")); + if (c.IsPressed("Toggle Left Difficulty")) + { + s.RaiseInput(0, MachineInput.LeftDifficulty, c.IsPressed("Toggle Left Difficulty")); + } + + if (c.IsPressed("Toggle Right Difficulty")) + { + s.RaiseInput(0, MachineInput.RightDifficulty, c.IsPressed("Toggle Right Difficulty")); + } + } + + private static void ConvertDirections(IController c, InputState s, int p) + { + string ps = $"P{p + 1} "; + s.RaiseInput(p, MachineInput.Up, c.IsPressed(ps + "Up")); + s.RaiseInput(p, MachineInput.Down, c.IsPressed(ps + "Down")); + s.RaiseInput(p, MachineInput.Left, c.IsPressed(ps + "Left")); + s.RaiseInput(p, MachineInput.Right, c.IsPressed(ps + "Right")); + } + + private static void ConvertTrigger(IController c, InputState s, int p) + { + string ps = $"P{p + 1} "; + s.RaiseInput(p, MachineInput.Fire, c.IsPressed(ps + "Trigger")); + } + + private static void ConvertJoystick(IController c, InputState s) + { + s.ClearControllerInput(); + ConvertConsoleButtons(c, s); + ConvertDirections(c, s, 0); + ConvertDirections(c, s, 1); + ConvertTrigger(c, s, 0); + ConvertTrigger(c, s, 1); + } + + private static void ConvertPaddles(IController c, InputState s) + { + s.ClearControllerInput(); + ConvertConsoleButtons(c, s); + for (int i = 0; i < 4; i++) + { + string ps = $"P{i + 1} "; + ConvertTrigger(c, s, i); + s.RaisePaddleInput(i, 700000, (int)c.GetFloat(ps + "Trigger")); + } + } + + private static void ConvertKeypad(IController c, InputState s) + { + s.ClearControllerInput(); + ConvertConsoleButtons(c, s); + for (int i = 0; i < 4; i++) + { + string ps = $"P{i + 1} "; + s.RaiseInput(i, MachineInput.NumPad1, c.IsPressed(ps + "Keypad1")); + s.RaiseInput(i, MachineInput.NumPad2, c.IsPressed(ps + "Keypad2")); + s.RaiseInput(i, MachineInput.NumPad3, c.IsPressed(ps + "Keypad3")); + s.RaiseInput(i, MachineInput.NumPad4, c.IsPressed(ps + "Keypad4")); + s.RaiseInput(i, MachineInput.NumPad5, c.IsPressed(ps + "Keypad5")); + s.RaiseInput(i, MachineInput.NumPad6, c.IsPressed(ps + "Keypad6")); + s.RaiseInput(i, MachineInput.NumPad7, c.IsPressed(ps + "Keypad7")); + s.RaiseInput(i, MachineInput.NumPad8, c.IsPressed(ps + "Keypad8")); + s.RaiseInput(i, MachineInput.NumPad9, c.IsPressed(ps + "Keypad9")); + s.RaiseInput(i, MachineInput.NumPadMult, c.IsPressed(ps + "KeypadA")); + s.RaiseInput(i, MachineInput.NumPad0, c.IsPressed(ps + "Keypad0")); + s.RaiseInput(i, MachineInput.NumPadHash, c.IsPressed(ps + "KeypadP")); + } + } + + private static readonly MachineInput[] Drvlut = + { + MachineInput.Driving0, + MachineInput.Driving1, + MachineInput.Driving2, + MachineInput.Driving3 + }; + + private static void ConvertDriving(IController c, InputState s) + { + s.ClearControllerInput(); + ConvertConsoleButtons(c, s); + ConvertTrigger(c, s, 0); + ConvertTrigger(c, s, 1); + s.RaiseInput(0, Drvlut[(int)c.GetFloat("P1 Driving")], true); + s.RaiseInput(1, Drvlut[(int)c.GetFloat("P2 Driving")], true); + } + + private static void ConvertBoosterGrip(IController c, InputState s) + { + s.ClearControllerInput(); + ConvertConsoleButtons(c, s); + ConvertDirections(c, s, 0); + ConvertDirections(c, s, 1); + + // weird mapping is intentional + s.RaiseInput(0, MachineInput.Fire, c.IsPressed("P1 Trigger")); + s.RaiseInput(0, MachineInput.Fire2, c.IsPressed("P1 Trigger 2")); + s.RaiseInput(1, MachineInput.Fire2, c.IsPressed("P1 Trigger 3")); + s.RaiseInput(1, MachineInput.Fire, c.IsPressed("P2 Trigger")); + s.RaiseInput(2, MachineInput.Fire2, c.IsPressed("P2 Trigger 2")); + s.RaiseInput(3, MachineInput.Fire2, c.IsPressed("P2 Trigger 3")); + } + + private static void ConvertProLineJoystick(IController c, InputState s) + { + s.ClearControllerInput(); + ConvertConsoleButtons7800(c, s); + ConvertDirections(c, s, 0); + ConvertDirections(c, s, 1); + s.RaiseInput(0, MachineInput.Fire, c.IsPressed("P1 Trigger")); + s.RaiseInput(0, MachineInput.Fire2, c.IsPressed("P1 Trigger 2")); + s.RaiseInput(1, MachineInput.Fire, c.IsPressed("P2 Trigger")); + s.RaiseInput(1, MachineInput.Fire2, c.IsPressed("P2 Trigger 2")); + } + + private static void ConvertLightgun(IController c, InputState s) + { + s.ClearControllerInput(); + ConvertConsoleButtons7800(c, s); + ConvertTrigger(c, s, 0); + ConvertTrigger(c, s, 1); + s.RaiseLightgunPos(0, (int)c.GetFloat("P1 VPos"), (int)c.GetFloat("P1 HPos")); + s.RaiseLightgunPos(1, (int)c.GetFloat("P2 VPos"), (int)c.GetFloat("P2 HPos")); + } + + public Action Convert { get; private set; } + + public ControllerDefinition ControlType { get; private set; } + + public A7800HawkControl(MachineBase mac) + { + var l = mac.InputState.LeftControllerJack; + var r = mac.InputState.RightControllerJack; + + foreach (var a in Adapters) + { + if (a.Left == l && a.Right == r) + { + Convert = a.Convert; + ControlType = a.Type; + return; + } + } + + throw new Exception($"Couldn't connect Atari 7800 controls \"{l}\" and \"{r}\""); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/M6532.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/M6532.cs new file mode 100644 index 0000000000..58a54af72f --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/M6532.cs @@ -0,0 +1,225 @@ +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + // Emulates the M6532 RIOT Chip + public class M6532 + { + private byte _ddRa = 0x00; + private byte _ddRb = 0x00; + private byte _outputA = 0x00; + + public TimerData Timer; + + public M6532() + { + + // arbitrary value to start with. + Timer.Value = 0x73; + Timer.PrescalerShift = 10; + Timer.PrescalerCount = 1 << Timer.PrescalerShift; + } + + public byte ReadMemory(ushort addr, bool peek) + { + if ((addr & 0x0200) == 0) // If not register select, read Ram + { + //return _core.Ram[(ushort)(addr & 0x007f)]; + return 0; + } + + var registerAddr = (ushort)(addr & 0x0007); + if (registerAddr == 0x00) + { + // Read Output reg A + // Combine readings from player 1 and player 2 + // actually depends on setting in SWCHCNTA (aka DDRa) + byte temp = 0;// (byte)(_core.ReadControls1(peek) & 0xF0 | ((_core.ReadControls2(peek) >> 4) & 0x0F)); + temp = (byte)(temp & ~_ddRa); + temp = (byte)(temp + (_outputA & _ddRa)); + return temp; + } + + if (registerAddr == 0x01) + { + // Read DDRA + return _ddRa; + } + + if (registerAddr == 0x02) + { + // Read Output reg B + byte temp = 0;// _core.ReadConsoleSwitches(peek); + temp = (byte)(temp & ~_ddRb); + return temp; + } + + if (registerAddr == 0x03) // Read DDRB + { + return _ddRb; + } + + if ((registerAddr & 0x5) == 0x4) + { + // Bit 0x0080 contains interrupt enable/disable + Timer.InterruptEnabled = (addr & 0x0080) != 0; + + // The interrupt flag will be reset whenever the Timer is access by a read or a write + // However, the reading of the timer at the same time the interrupt occurs will not reset the interrupt flag + // (M6532 Datasheet) + if (!(Timer.PrescalerCount == 0 && Timer.Value == 0)) + { + Timer.InterruptFlag = false; + } + + return Timer.Value; + } + + // TODO: fix this to match real behaviour + // This is an undocumented instruction whose behaviour is more dynamic then indicated here + if ((registerAddr & 0x5) == 0x5) + { + // Read interrupt flag + if (Timer.InterruptFlag) // Timer.InterruptEnabled && ) + { + return 0xC0; + } + + return 0x00; + } + + return 0x3A; + } + + public void WriteMemory(ushort addr, byte value) + { + if ((addr & 0x0200) == 0) // If the RS bit is not set, this is a ram write + { + //_core.Ram[(ushort)(addr & 0x007f)] = value; + } + else + { + // If bit 0x0010 is set, and bit 0x0004 is set, this is a timer write + if ((addr & 0x0014) == 0x0014) + { + var registerAddr = (ushort)(addr & 0x0007); + + // Bit 0x0080 contains interrupt enable/disable + Timer.InterruptEnabled = (addr & 0x0080) != 0; + + // The interrupt flag will be reset whenever the Timer is access by a read or a write + // (M6532 datasheet) + if (registerAddr == 0x04) + { + // Write to Timer/1 + Timer.PrescalerShift = 0; + Timer.Value = value; + Timer.PrescalerCount = 0; // 1 << Timer.PrescalerShift; + Timer.InterruptFlag = false; + } + else if (registerAddr == 0x05) + { + // Write to Timer/8 + Timer.PrescalerShift = 3; + Timer.Value = value; + Timer.PrescalerCount = 0; // 1 << Timer.PrescalerShift; + Timer.InterruptFlag = false; + } + else if (registerAddr == 0x06) + { + // Write to Timer/64 + Timer.PrescalerShift = 6; + Timer.Value = value; + Timer.PrescalerCount = 0; // 1 << Timer.PrescalerShift; + Timer.InterruptFlag = false; + } + else if (registerAddr == 0x07) + { + // Write to Timer/1024 + Timer.PrescalerShift = 10; + Timer.Value = value; + Timer.PrescalerCount = 0; // 1 << Timer.PrescalerShift; + Timer.InterruptFlag = false; + } + } + + // If bit 0x0004 is not set, bit 0x0010 is ignored and + // these are register writes + else if ((addr & 0x0004) == 0) + { + var registerAddr = (ushort)(addr & 0x0007); + + if (registerAddr == 0x00) + { + // Write Output reg A + _outputA = value; + } + else if (registerAddr == 0x01) + { + // Write DDRA + _ddRa = value; + } + else if (registerAddr == 0x02) + { + // Write Output reg B + // But is read only + } + else if (registerAddr == 0x03) + { + // Write DDRB + _ddRb = value; + } + } + } + } + + public void SyncState(Serializer ser) + { + ser.BeginSection("M6532"); + ser.Sync("ddra", ref _ddRa); + ser.Sync("ddrb", ref _ddRb); + ser.Sync("OutputA", ref _outputA); + Timer.SyncState(ser); + ser.EndSection(); + } + + public struct TimerData + { + public int PrescalerCount; + public byte PrescalerShift; + + public byte Value; + + public bool InterruptEnabled; + public bool InterruptFlag; + + public void Tick() + { + if (PrescalerCount == 0) + { + Value--; + PrescalerCount = 1 << PrescalerShift; + } + + PrescalerCount--; + if (PrescalerCount == 0) + { + if (Value == 0) + { + InterruptFlag = true; + PrescalerShift = 0; + } + } + } + + public void SyncState(Serializer ser) + { + ser.Sync("prescalerCount", ref PrescalerCount); + ser.Sync("prescalerShift", ref PrescalerShift); + ser.Sync("value", ref Value); + ser.Sync("interruptEnabled", ref InterruptEnabled); + ser.Sync("interruptFlag", ref InterruptFlag); + } + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Maria.cs b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Maria.cs new file mode 100644 index 0000000000..41687abf5a --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Atari/A7800Hawk/Maria.cs @@ -0,0 +1,39 @@ +using System; + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Atari.A7800Hawk +{ + // Emulates the Atari 7800 Maria graphics chip + public class Maria : IVideoProvider + { + public int _frameHz; + + public int[] _vidbuffer; + public int[] _palette; + + public int[] GetVideoBuffer() + { + return _vidbuffer; + } + + public int VirtualWidth => 275; + public int VirtualHeight => BufferHeight; + public int BufferWidth { get; private set; } + public int BufferHeight { get; private set; } + public int BackgroundColor => unchecked((int)0xff000000); + public int VsyncNumerator => _frameHz; + public int VsyncDenominator => 1; + + public void FrameAdvance() + { + + } + + public void Reset() + { + + } + + } +}