From 29fded30253d41a1b2cbf8ea0ff552d2d7339ad7 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Sun, 29 Mar 2020 10:21:34 -0400 Subject: [PATCH] GBHawk: add testing framework --- .../GBHawk_new/GBHawkNew.ICodeDataLog.cs | 185 +++++++++++++++ .../GBHawk_new/GBHawkNew.IDebuggable.cs | 67 ++++++ .../GBHawk_new/GBHawkNew.IEmulator.cs | 196 ++++++++++++++++ .../GBHawk_new/GBHawkNew.IInputPollable.cs | 24 ++ .../GBHawk_new/GBHawkNew.IMemoryDomains.cs | 69 ++++++ .../Nintendo/GBHawk_new/GBHawkNew.ISaveRam.cs | 24 ++ .../GBHawk_new/GBHawkNew.ISettable.cs | 155 +++++++++++++ .../GBHawk_new/GBHawkNew.IStatable.cs | 35 +++ .../Consoles/Nintendo/GBHawk_new/GBHawkNew.cs | 216 ++++++++++++++++++ .../GBHawk_new/GBHawkNewControllerDeck.cs | 81 +++++++ .../GBHawk_new/GBHawkNewControllers.cs | 216 ++++++++++++++++++ .../{GBHawk => GBHawk_new}/LibGBHawk.cs | 2 +- 12 files changed, 1269 insertions(+), 1 deletion(-) create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ICodeDataLog.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IDebuggable.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IEmulator.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IInputPollable.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IMemoryDomains.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISaveRam.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISettable.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IStatable.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllerDeck.cs create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllers.cs rename BizHawk.Emulation.Cores/Consoles/Nintendo/{GBHawk => GBHawk_new}/LibGBHawk.cs (99%) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ICodeDataLog.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ICodeDataLog.cs new file mode 100644 index 0000000000..3bae50108f --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ICodeDataLog.cs @@ -0,0 +1,185 @@ +using System.IO; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components.LR35902; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + /* + public partial class GBHawk : ICodeDataLogger + { + private ICodeDataLog _cdl; + + public void SetCDL(ICodeDataLog cdl) + { + _cdl = cdl; + if (cdl == null) + this.cpu.CDLCallback = null; + else this.cpu.CDLCallback = CDLCpuCallback; + } + + public void NewCDL(ICodeDataLog cdl) + { + cdl["ROM"] = new byte[MemoryDomains["ROM"].Size]; + cdl["HRAM"] = new byte[MemoryDomains["Zero Page RAM"].Size]; + + cdl["WRAM"] = new byte[MemoryDomains["Main RAM"].Size]; + + if (MemoryDomains.Has("Cart RAM")) + { + cdl["CartRAM"] = new byte[MemoryDomains["Cart RAM"].Size]; + } + + cdl.SubType = "GB"; + cdl.SubVer = 0; + } + + [FeatureNotImplemented] + void ICodeDataLogger.DisassembleCDL(Stream s, ICodeDataLog cdl) + { + } + + public void SetCDL(LR35902.eCDLogMemFlags flags, string type, int cdladdr) + { + if (type == null) return; + byte val = (byte)flags; + _cdl[type][cdladdr] |= (byte)flags; + } + + void CDLCpuCallback(ushort addr, LR35902.eCDLogMemFlags flags) + { + if (addr < 0x8000) + { + //don't record writes to the ROM, it's just noisy + //NOTE: in principle a mapper could mount a useful resource here, but I doubt it) + if ((flags & LR35902.eCDLogMemFlags.Write) != 0) return; + } + + if (ppu.DMA_start) + { + // some of gekkio's tests require these to be accessible during DMA + if (addr < 0x8000) + { + if (ppu.DMA_addr < 0x80) + { + return; + } + else + { + mapper.MapCDL(addr, flags); + return; + } + } + else if ((addr >= 0xE000) && (addr < 0xF000)) + { + SetCDL(flags, "WRAM", addr - 0xE000); + } + else if ((addr >= 0xF000) && (addr < 0xFE00)) + { + SetCDL(flags, "WRAM", (RAM_Bank * 0x1000) + (addr - 0xF000)); + } + else if ((addr >= 0xFE00) && (addr < 0xFEA0) && ppu.DMA_OAM_access) + { + return; + } + else if ((addr >= 0xFF00) && (addr < 0xFF80)) // The game GOAL! Requires Hardware Regs to be accessible + { + return; + } + else if ((addr >= 0xFF80)) + { + SetCDL(flags, "HRAM", addr - 0xFF80); + } + + } + + if (addr < 0x900) + { + if (addr < 0x100) + { + // return Either BIOS ROM or Game ROM + if ((GB_bios_register & 0x1) == 0) + { + return; + } + else + { + mapper.MapCDL(addr, flags); + return; + } + } + else if (addr >= 0x200) + { + // return Either BIOS ROM or Game ROM + if (((GB_bios_register & 0x1) == 0) && is_GBC) + { + return; + } + else + { + mapper.MapCDL(addr, flags); + return; + } + } + else + { + mapper.MapCDL(addr, flags); + return; + } + } + else if (addr < 0x8000) + { + mapper.MapCDL(addr, flags); + return; + } + else if (addr < 0xA000) + { + return; + } + else if (addr < 0xC000) + { + mapper.MapCDL(addr, flags); + return; + } + else if (addr < 0xD000) + { + return; + } + else if (addr < 0xE000) + { + SetCDL(flags, "WRAM", (RAM_Bank * 0x1000) + (addr - 0xD000)); + } + else if (addr < 0xF000) + { + SetCDL(flags, "WRAM", addr - 0xE000); + } + else if (addr < 0xFE00) + { + SetCDL(flags, "WRAM", (RAM_Bank * 0x1000) + (addr - 0xF000)); + } + else if (addr < 0xFEA0) + { + return; + } + else if (addr < 0xFF00) + { + return; + } + else if (addr < 0xFF80) + { + return; + } + else if (addr < 0xFFFF) + { + SetCDL(flags, "HRAM", addr - 0xFF80); + } + else + { + return; + } + + } + + + } + */ +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IDebuggable.cs new file mode 100644 index 0000000000..ba2549c719 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IDebuggable.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public partial class GBHawkNew : IDebuggable + { + public IDictionary GetCpuFlagsAndRegisters() + { + return new Dictionary + { + ["PCl"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 0), + ["PCh"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 1), + ["SPl"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 2), + ["SPh"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 3), + ["A"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 4), + ["F"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 5), + ["B"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 6), + ["C"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 7), + ["D"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 8), + ["E"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 9), + ["H"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 10), + ["L"] = LibGBHawk.GB_cpu_get_regs(GB_Pntr, 11), + ["Flag I"] = LibGBHawk.GB_cpu_get_flags(GB_Pntr, 0), + ["Flag C"] = LibGBHawk.GB_cpu_get_flags(GB_Pntr, 1), + ["Flag H"] = LibGBHawk.GB_cpu_get_flags(GB_Pntr, 2), + ["Flag N"] = LibGBHawk.GB_cpu_get_flags(GB_Pntr, 3), + ["Flag Z"] = LibGBHawk.GB_cpu_get_flags(GB_Pntr, 4) + }; + } + + public void SetCpuRegister(string register, int value) + { + switch (register) + { + case ("PCl"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 0, (byte)value); break; + case ("PCh"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 1, (byte)value); break; + case ("SPl"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 2, (byte)value); break; + case ("SPh"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 3, (byte)value); break; + case ("A"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 4, (byte)value); break; + case ("F"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 5, (byte)value); break; + case ("B"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 6, (byte)value); break; + case ("C"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 7, (byte)value); break; + case ("D"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 8, (byte)value); break; + case ("E"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 9, (byte)value); break; + case ("H"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 10, (byte)value); break; + case ("L"): LibGBHawk.GB_cpu_set_regs(GB_Pntr, 11, (byte)value); break; + + case ("Flag I"): LibGBHawk.GB_cpu_set_flags(GB_Pntr, 0, value > 0); break; + case ("Flag C"): LibGBHawk.GB_cpu_set_flags(GB_Pntr, 1, value > 0); break; + case ("Flag H"): LibGBHawk.GB_cpu_set_flags(GB_Pntr, 2, value > 0); break; + case ("Flag N"): LibGBHawk.GB_cpu_set_flags(GB_Pntr, 3, value > 0); break; + case ("Flag Z"): LibGBHawk.GB_cpu_set_flags(GB_Pntr, 4, value > 0); break; + } + } + + public IMemoryCallbackSystem MemoryCallbacks { get; } = new MemoryCallbackSystem(new[] { "System Bus" }); + + public bool CanStep(StepType type) => false; + + [FeatureNotImplemented] + public void Step(StepType type) => throw new NotImplementedException(); + + public long TotalExecutedCycles => (long)LibGBHawk.GB_cpu_cycles(GB_Pntr); + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IEmulator.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IEmulator.cs new file mode 100644 index 0000000000..75b23f5814 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IEmulator.cs @@ -0,0 +1,196 @@ +using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; +using System; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public partial class GBHawkNew : IEmulator, IVideoProvider, ISoundProvider + { + public IEmulatorServiceProvider ServiceProvider { get; } + + public ControllerDefinition ControllerDefinition => _controllerDeck.Definition; + + public byte controller_state; + public ushort Acc_X_state; + public ushort Acc_Y_state; + public bool in_vblank_old; + public bool in_vblank; + public bool vblank_rise; + + public bool FrameAdvance(IController controller, bool render, bool rendersound) + { + //Console.WriteLine("-----------------------FRAME-----------------------"); + + //Update the color palette if a setting changed + if (_settings.Palette == GBSettings.PaletteType.BW) + { + color_palette[0] = color_palette_BW[0]; + color_palette[1] = color_palette_BW[1]; + color_palette[2] = color_palette_BW[2]; + color_palette[3] = color_palette_BW[3]; + } + else + { + color_palette[0] = color_palette_Gr[0]; + color_palette[1] = color_palette_Gr[1]; + color_palette[2] = color_palette_Gr[2]; + color_palette[3] = color_palette_Gr[3]; + } + + if (Tracer.Enabled) + { + tracecb = MakeTrace; + } + else + { + tracecb = null; + } + + LibGBHawk.GB_settracecallback(GB_Pntr, tracecb); + + _frame++; + + if (controller.IsPressed("P1 Power")) + { + HardReset(); + } + + _islag = true; + + do_frame(controller); + + if (_islag) + { + _lagcount++; + } + + return true; + } + + public void do_frame(IController controller) + { + LibGBHawk.GB_frame_advance(GB_Pntr, _controllerDeck.ReadPort1(controller), + _controllerDeck.ReadAccX1(controller), + _controllerDeck.ReadAccY1(controller), true, true); + } + + public void do_single_step() + { + LibGBHawk.GB_do_single_step(GB_Pntr); + } + + public void do_controller_check() + { + + } + + public void GetControllerState(IController controller) + { + InputCallbacks.Call(); + controller_state = _controllerDeck.ReadPort1(controller); + + Acc_X_state = _controllerDeck.ReadAccX1(controller); + Acc_Y_state = _controllerDeck.ReadAccY1(controller); + } + + public int Frame => _frame; + + public string SystemId => "GB"; + + public bool DeterministicEmulation { get; set; } + + public void ResetCounters() + { + _frame = 0; + _lagcount = 0; + _islag = false; + } + + public void Dispose() + { + + } + + #region Video provider + + public int[] _vidbuffer; + + public int[] frame_buffer; + + public int[] GetVideoBuffer() + { + return frame_buffer; + } + + public void SendVideoBuffer() + { + + } + + public int VirtualWidth => 160; + public int VirtualHeight => 144; + public int BufferWidth => 160; + public int BufferHeight => 144; + public int BackgroundColor => unchecked((int)0xFF000000); + public int VsyncNumerator => 262144; + public int VsyncDenominator => 4389; + + public static readonly uint[] color_palette_BW = { 0xFFFFFFFF , 0xFFAAAAAA, 0xFF555555, 0xFF000000 }; + public static readonly uint[] color_palette_Gr = { 0xFFA4C505, 0xFF88A905, 0xFF1D551D, 0xFF052505 }; + + public uint[] color_palette = new uint[4]; + + #endregion + + #region Audio + + public BlipBuffer blip = new BlipBuffer(4500); + + public int[] Aud = new int[9000]; + public uint num_samp; + + const int blipbuffsize = 4500; + + public bool CanProvideAsync => false; + + public void SetSyncMode(SyncSoundMode mode) + { + if (mode != SyncSoundMode.Sync) + { + throw new NotSupportedException("Only sync mode is supported"); + } + } + + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async not supported"); + } + + public SyncSoundMode SyncMode => SyncSoundMode.Sync; + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + uint f_clock = LibGBHawk.GB_get_audio(GB_Pntr, Aud, ref num_samp); + + for (int i = 0; i < num_samp; i++) + { + blip.AddDelta((uint)Aud[i * 2], Aud[i * 2 + 1]); + } + + blip.EndFrame(f_clock); + + nsamp = blip.SamplesAvailable(); + samples = new short[nsamp * 2]; + + blip.ReadSamples(samples, nsamp, true); + } + + public void DiscardSamples() + { + blip.Clear(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IInputPollable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IInputPollable.cs new file mode 100644 index 0000000000..3c35b77ff6 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IInputPollable.cs @@ -0,0 +1,24 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public partial class GBHawkNew : IInputPollable + { + public int LagCount + { + get => _lagcount; + set => _lagcount = value; + } + + public bool IsLagFrame + { + get => _islag; + set => _islag = value; + } + + public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem(); + + public bool _islag = true; + private int _lagcount; + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IMemoryDomains.cs new file mode 100644 index 0000000000..04c8a909a4 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IMemoryDomains.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public partial class GBHawkNew + { + private IMemoryDomains MemoryDomains; + + public void SetupMemoryDomains() + { + var domains = new List + { + new MemoryDomainDelegate( + "WRAM", + 0x8000, + MemoryDomain.Endian.Little, + addr => LibGBHawk.GB_getram(GB_Pntr, (int)(addr & 0xFFFF)), + (addr, value) => LibGBHawk.GB_setram(GB_Pntr, (int)(addr & 0xFFFF), value), + 1), + new MemoryDomainDelegate( + "ROM", + _rom.Length, + MemoryDomain.Endian.Little, + addr => _rom[addr], + (addr, value) => _rom[addr] = value, + 1), + new MemoryDomainDelegate( + "VRAM", + 0x4000, + MemoryDomain.Endian.Little, + addr => LibGBHawk.GB_getvram(GB_Pntr, (int)(addr & 0xFFFF)), + (addr, value) => LibGBHawk.GB_setvram(GB_Pntr, (int)(addr & 0xFFFF), value), + 1), + new MemoryDomainDelegate( + "OAM", + 0xA0, + MemoryDomain.Endian.Little, + addr => LibGBHawk.GB_getoam(GB_Pntr, (int)(addr & 0xFFFF)), + (addr, value) => LibGBHawk.GB_setoam(GB_Pntr, (int)(addr & 0xFFFF), value), + 1), + new MemoryDomainDelegate( + "HRAM", + 0x80, + MemoryDomain.Endian.Little, + addr => LibGBHawk.GB_gethram(GB_Pntr, (int)(addr & 0xFFFF)), + (addr, value) => LibGBHawk.GB_sethram(GB_Pntr, (int)(addr & 0xFFFF), value), + 1), + new MemoryDomainDelegate( + "System Bus", + 0X10000, + MemoryDomain.Endian.Little, + addr => LibGBHawk.GB_getsysbus(GB_Pntr, (int)(addr & 0xFFFF)), + (addr, value) => LibGBHawk.GB_setsysbus(GB_Pntr, (int)(addr & 0xFFFF), value), + 1), + }; + /* + if (cart_RAM != null) + { + var CartRam = new MemoryDomainByteArray("CartRAM", MemoryDomain.Endian.Little, cart_RAM, true, 1); + domains.Add(CartRam); + } + */ + + MemoryDomains = new MemoryDomainList(domains); + (ServiceProvider as BasicServiceProvider).Register(MemoryDomains); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISaveRam.cs new file mode 100644 index 0000000000..78b086362b --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISaveRam.cs @@ -0,0 +1,24 @@ +using System; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public partial class GBHawkNew : ISaveRam + { + public byte[] CloneSaveRam() + { + return null;// (byte[])cart_RAM?.Clone(); + } + + public void StoreSaveRam(byte[] data) + { + if (_syncSettings.Use_SRAM) + { + //Buffer.BlockCopy(data, 0, cart_RAM, 0, data.Length); + Console.WriteLine("loading SRAM here"); + } + } + + public bool SaveRamModified => false;//has_bat & _syncSettings.Use_SRAM; + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISettable.cs new file mode 100644 index 0000000000..3f688ccedd --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.ISettable.cs @@ -0,0 +1,155 @@ +using System; +using System.ComponentModel; + +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public partial class GBHawkNew : IEmulator, ISettable + { + public GBSettings GetSettings() + { + return _settings.Clone(); + } + + public GBSyncSettings GetSyncSettings() + { + return _syncSettings.Clone(); + } + + public bool PutSettings(GBSettings o) + { + _settings = o; + return false; + } + + public bool PutSyncSettings(GBSyncSettings o) + { + bool ret = GBSyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; + return ret; + } + + private GBSettings _settings = new GBSettings(); + public GBSyncSettings _syncSettings = new GBSyncSettings(); + + public class GBSettings + { + public enum PaletteType + { + BW, + Gr + } + + [DisplayName("Color Mode")] + [Description("Pick Between Green scale and Grey scale colors")] + [DefaultValue(PaletteType.BW)] + public PaletteType Palette { get; set; } + + public GBSettings Clone() + { + return (GBSettings)MemberwiseClone(); + } + + public GBSettings() + { + SettingsUtil.SetDefaultValues(this); + } + } + + public class GBSyncSettings + { + [JsonIgnore] + public string Port1 = GBHawkNewControllerDeck.DefaultControllerName; + + public enum ControllerType + { + Default, + Tilt + } + + [JsonIgnore] + private ControllerType _GBController; + + [DisplayName("Controller")] + [Description("Select Controller Type")] + [DefaultValue(ControllerType.Default)] + public ControllerType GBController + { + get => _GBController; + set + { + if (value == ControllerType.Default) { Port1 = GBHawkNewControllerDeck.DefaultControllerName; } + else { Port1 = "Gameboy Controller + Tilt"; } + + _GBController = value; + } + } + + public enum ConsoleModeType + { + Auto, + GB, + GBC + } + + [DisplayName("Console Mode")] + [Description("Pick which console to run, 'Auto' chooses from ROM extension, 'GB' and 'GBC' chooses the respective system")] + [DefaultValue(ConsoleModeType.Auto)] + public ConsoleModeType ConsoleMode { get; set; } + + [DisplayName("CGB in GBA")] + [Description("Emulate GBA hardware running a CGB game, instead of CGB hardware. Relevant only for titles that detect the presense of a GBA, such as Shantae.")] + [DefaultValue(false)] + public bool GBACGB { get; set; } + + [DisplayName("RTC Initial Time")] + [Description("Set the initial RTC time in terms of elapsed seconds.")] + [DefaultValue(0)] + public int RTCInitialTime + { + get => _RTCInitialTime; + set => _RTCInitialTime = Math.Max(0, Math.Min(1024 * 24 * 60 * 60, value)); + } + + [DisplayName("RTC Offset")] + [Description("Set error in RTC clocking (-127 to 127)")] + [DefaultValue(0)] + public int RTCOffset + { + get => _RTCOffset; + set => _RTCOffset = Math.Max(-127, Math.Min(127, value)); + } + + [DisplayName("Use Existing SaveRAM")] + [Description("When true, existing SaveRAM will be loaded at boot up")] + [DefaultValue(false)] + public bool Use_SRAM { get; set; } + + [JsonIgnore] + private int _RTCInitialTime; + [JsonIgnore] + private int _RTCOffset; + [JsonIgnore] + public ushort _DivInitialTime = 8; + + public GBSyncSettings Clone() + { + return (GBSyncSettings)MemberwiseClone(); + } + + public GBSyncSettings() + { + SettingsUtil.SetDefaultValues(this); + } + + public static bool NeedsReboot(GBSyncSettings x, GBSyncSettings y) + { + return !DeepEquality.DeepEquals(x, y); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IStatable.cs new file mode 100644 index 0000000000..4b76beeb54 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.IStatable.cs @@ -0,0 +1,35 @@ +using System.IO; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public partial class GBHawkNew + { + private void SyncState(Serializer ser) + { + byte[] core = null; + if (ser.IsWriter) + { + using var ms = new MemoryStream(); + ms.Close(); + core = ms.ToArray(); + } + + ser.Sync("Frame", ref _frame); + ser.Sync("LagCount", ref _lagCount); + + ser.EndSection(); + + if (ser.IsReader) + { + ser.Sync(nameof(GB_core), ref GB_core, false); + LibGBHawk.GB_load_state(GB_Pntr, GB_core); + } + else + { + LibGBHawk.GB_save_state(GB_Pntr, GB_core); + ser.Sync(nameof(GB_core), ref GB_core, false); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.cs new file mode 100644 index 0000000000..664100144f --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNew.cs @@ -0,0 +1,216 @@ +using System; +using System.Text; + +using BizHawk.Common.BufferExtensions; +using BizHawk.Emulation.Common; + +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + [Core( + "GBHawkNew", + "", + isPorted: false, + isReleased: true)] + [ServiceNotApplicable(new[] { typeof(IDriveLight) })] + public partial class GBHawkNew : IEmulator, ISaveRam, IDebuggable, IInputPollable, IRegionable, IGameboyCommon, + ISettable + { + public IntPtr GB_Pntr { get; set; } = IntPtr.Zero; + byte[] GB_core = new byte[0x200000]; + + private int _frame = 0; + public int _lagCount = 0; + public bool is_GBC = false; + + public byte[] _bios; + public readonly byte[] _rom; + + [CoreConstructor(new[] { "GB", "GBC" })] + public GBHawkNew(CoreComm comm, GameInfo game, byte[] rom, /*string gameDbFn,*/ object settings, object syncSettings) + { + var ser = new BasicServiceProvider(this); + + _settings = (GBSettings)settings ?? new GBSettings(); + _syncSettings = (GBSyncSettings)syncSettings ?? new GBSyncSettings(); + _controllerDeck = new GBHawkNewControllerDeck(_syncSettings.Port1); + + byte[] Bios = null; + + // Load up a BIOS and initialize the correct PPU + if (_syncSettings.ConsoleMode == GBSyncSettings.ConsoleModeType.Auto) + { + if (game.System == "GB") + { + Bios = comm.CoreFileProvider.GetFirmware("GB", "World", true, "BIOS Not Found, Cannot Load"); + } + else + { + Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", true, "BIOS Not Found, Cannot Load"); + is_GBC = true; + } + + } + else if (_syncSettings.ConsoleMode == GBSyncSettings.ConsoleModeType.GB) + { + Bios = comm.CoreFileProvider.GetFirmware("GB", "World", true, "BIOS Not Found, Cannot Load"); + } + else + { + Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", true, "BIOS Not Found, Cannot Load"); + is_GBC = true; + } + + if (Bios == null) + { + throw new MissingFirmwareException("Missing Gamboy Bios"); + } + + _bios = Bios; + + GB_Pntr = LibGBHawk.GB_create(); + + char[] MD5_temp = rom.HashMD5(0, rom.Length).ToCharArray(); + + LibGBHawk.GB_load_bios(GB_Pntr, _bios, is_GBC, _syncSettings.GBACGB); + LibGBHawk.GB_load(GB_Pntr, rom, (uint)rom.Length, MD5_temp, (uint)_syncSettings.RTCInitialTime, (uint)_syncSettings.RTCOffset); + + blip.SetRates(3579545, 44100); + + (ServiceProvider as BasicServiceProvider).Register(this); + + SetupMemoryDomains(); + + Header_Length = LibGBHawk.GB_getheaderlength(GB_Pntr); + Disasm_Length = LibGBHawk.GB_getdisasmlength(GB_Pntr); + Reg_String_Length = LibGBHawk.GB_getregstringlength(GB_Pntr); + + var newHeader = new StringBuilder(Header_Length); + LibGBHawk.GB_getheader(GB_Pntr, newHeader, Header_Length); + + Console.WriteLine(Header_Length + " " + Disasm_Length + " " + Reg_String_Length); + + Tracer = new TraceBuffer { Header = newHeader.ToString() }; + + var serviceProvider = ServiceProvider as BasicServiceProvider; + serviceProvider.Register(Tracer); + serviceProvider.Register(new StateSerializer(SyncState)); + + Console.WriteLine("MD5: " + rom.HashMD5(0, rom.Length)); + Console.WriteLine("SHA1: " + rom.HashSHA1(0, rom.Length)); + + ser.Register(this); + + ServiceProvider = ser; + + HardReset(); + + iptr0 = LibGBHawk.GB_get_ppu_pntrs(GB_Pntr, 0); + iptr1 = LibGBHawk.GB_get_ppu_pntrs(GB_Pntr, 1); + iptr2 = LibGBHawk.GB_get_ppu_pntrs(GB_Pntr, 2); + iptr3 = LibGBHawk.GB_get_ppu_pntrs(GB_Pntr, 3); + + _scanlineCallback = null; + } + + #region GPUViewer + + public bool IsCGBMode() => is_GBC; + + public IntPtr iptr0 = IntPtr.Zero; + public IntPtr iptr1 = IntPtr.Zero; + public IntPtr iptr2 = IntPtr.Zero; + public IntPtr iptr3 = IntPtr.Zero; + + private GPUMemoryAreas _gpuMemory + { + get + { + return new GPUMemoryAreas(iptr0, iptr1, iptr2, iptr3); + } + } + + public LibGBHawk.ScanlineCallback _scanlineCallback; + + public GPUMemoryAreas GetGPU() => _gpuMemory; + + public void SetScanlineCallback(ScanlineCallback callback, int line) + { + if ((callback == null) || (line == -1) || (line == -2)) + { + _scanlineCallback = null; + LibGBHawk.GB_setscanlinecallback(GB_Pntr, null, 0); + } + else + { + _scanlineCallback = delegate + { + callback(LibGBHawk.GB_get_LCDC(GB_Pntr)); + }; + LibGBHawk.GB_setscanlinecallback(GB_Pntr, _scanlineCallback, line); + } + + if (line == -2) + { + GetGPU(); + callback(LibGBHawk.GB_get_LCDC(GB_Pntr)); + } + } + + private PrinterCallback _printerCallback = null; + + public void SetPrinterCallback(PrinterCallback callback) + { + _printerCallback = null; + } + + #endregion + + public DisplayType Region => DisplayType.NTSC; + + private readonly GBHawkNewControllerDeck _controllerDeck; + + public void HardReset() + { + LibGBHawk.GB_Reset(GB_Pntr); + + _vidbuffer = new int[VirtualWidth * VirtualHeight]; + frame_buffer = new int[VirtualWidth * VirtualHeight]; + } + + private void ExecFetch(ushort addr) + { + uint flags = (uint)(MemoryCallbackFlags.AccessRead); + MemoryCallbacks.CallMemoryCallbacks(addr, 0, flags, "System Bus"); + } + + #region Trace Logger + public ITraceable Tracer; + + public LibGBHawk.TraceCallback tracecb; + + // these will be constant values assigned during core construction + public int Header_Length; + public int Disasm_Length; + public int Reg_String_Length; + + public void MakeTrace(int t) + { + StringBuilder new_d = new StringBuilder(Disasm_Length); + StringBuilder new_r = new StringBuilder(Reg_String_Length); + + LibGBHawk.GB_getdisassembly(GB_Pntr, new_d, t, Disasm_Length); + LibGBHawk.GB_getregisterstate(GB_Pntr, new_r, t, Reg_String_Length); + + Tracer.Put(new TraceInfo + { + Disassembly = new_d.ToString().PadRight(36), + RegisterInfo = new_r.ToString() + }); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllerDeck.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllerDeck.cs new file mode 100644 index 0000000000..414904c797 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllerDeck.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Common.ReflectionExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + public class GBHawkNewControllerDeck + { + public GBHawkNewControllerDeck(string controller1Name) + { + if (!ValidControllerTypes.ContainsKey(controller1Name)) + { + throw new InvalidOperationException("Invalid controller type: " + controller1Name); + } + + Port1 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller1Name], 1); + + Definition = new ControllerDefinition + { + Name = Port1.Definition.Name, + BoolButtons = Port1.Definition.BoolButtons + .ToList() + }; + + Definition.FloatControls.AddRange(Port1.Definition.FloatControls); + + Definition.FloatRanges.AddRange(Port1.Definition.FloatRanges); + } + + public byte ReadPort1(IController c) + { + return Port1.Read(c); + } + + public ushort ReadAccX1(IController c) + { + return Port1.ReadAccX(c); + } + + public ushort ReadAccY1(IController c) + { + return Port1.ReadAccY(c); + } + + public ControllerDefinition Definition { get; } + + public void SyncState(Serializer ser) + { + ser.BeginSection(nameof(Port1)); + Port1.SyncState(ser); + ser.EndSection(); + } + + private readonly IPort Port1; + + private static Dictionary _controllerTypes; + + public static Dictionary ValidControllerTypes + { + get + { + if (_controllerTypes == null) + { + _controllerTypes = typeof(GBHawkNewControllerDeck).Assembly + .GetTypes() + .Where(t => typeof(IPort).IsAssignableFrom(t)) + .Where(t => !t.IsAbstract && !t.IsInterface) + .ToDictionary(tkey => tkey.DisplayName()); + } + + return _controllerTypes; + } + } + + public static string DefaultControllerName => typeof(StandardControls).DisplayName(); + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllers.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllers.cs new file mode 100644 index 0000000000..9f66505901 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/GBHawkNewControllers.cs @@ -0,0 +1,216 @@ +using System; +using System.ComponentModel; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew +{ + /// + /// Represents a GB add on + /// + public interface IPort + { + byte Read(IController c); + + ushort ReadAccX(IController c); + + ushort ReadAccY(IController c); + + ControllerDefinition Definition { get; } + + void SyncState(Serializer ser); + + int PortNum { get; } + } + + [DisplayName("Gameboy Controller")] + public class StandardControls : IPort + { + public StandardControls(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition + { + Name = "Gameboy Controller H", + BoolButtons = BaseDefinition + .Select(b => "P" + PortNum + " " + b) + .ToList() + }; + } + + public int PortNum { get; } + + public ControllerDefinition Definition { get; } + + public byte Read(IController c) + { + byte result = 0xFF; + + if (c.IsPressed(Definition.BoolButtons[0])) + { + result -= 4; + } + if (c.IsPressed(Definition.BoolButtons[1])) + { + result -= 8; + } + if (c.IsPressed(Definition.BoolButtons[2])) + { + result -= 2; + } + if (c.IsPressed(Definition.BoolButtons[3])) + { + result -= 1; + } + if (c.IsPressed(Definition.BoolButtons[4])) + { + result -= 128; + } + if (c.IsPressed(Definition.BoolButtons[5])) + { + result -= 64; + } + if (c.IsPressed(Definition.BoolButtons[6])) + { + result -= 32; + } + if (c.IsPressed(Definition.BoolButtons[7])) + { + result -= 16; + } + + return result; + } + + public ushort ReadAccX(IController c) + { + return 0; + } + + public ushort ReadAccY(IController c) + { + return 0; + } + + private static readonly string[] BaseDefinition = + { + "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Power" + }; + + public void SyncState(Serializer ser) + { + //nothing + } + } + + [DisplayName("Gameboy Controller + Tilt")] + public class StandardTilt : IPort + { + public StandardTilt(int portNum) + { + PortNum = portNum; + Definition = new ControllerDefinition + { + Name = "Gameboy Controller + Tilt", + BoolButtons = BaseDefinition + .Select(b => "P" + PortNum + " " + b) + .ToList(), + FloatControls = { "P" + PortNum + " Tilt X", "P" + PortNum + " Tilt Y" }, + FloatRanges = ControllerDefinition.CreateAxisRangePair(-45, 0, 45, ControllerDefinition.AxisPairOrientation.RightAndUp) //TODO verify direction against hardware + }; + } + + public int PortNum { get; } + + public float theta, phi, theta_prev, phi_prev; + + public ControllerDefinition Definition { get; } + + public byte Read(IController c) + { + byte result = 0xFF; + + if (c.IsPressed(Definition.BoolButtons[0])) + { + result -= 4; + } + if (c.IsPressed(Definition.BoolButtons[1])) + { + result -= 8; + } + if (c.IsPressed(Definition.BoolButtons[2])) + { + result -= 2; + } + if (c.IsPressed(Definition.BoolButtons[3])) + { + result -= 1; + } + if (c.IsPressed(Definition.BoolButtons[4])) + { + result -= 128; + } + if (c.IsPressed(Definition.BoolButtons[5])) + { + result -= 64; + } + if (c.IsPressed(Definition.BoolButtons[6])) + { + result -= 32; + } + if (c.IsPressed(Definition.BoolButtons[7])) + { + result -= 16; + } + + return result; + } + + // acc x is the result of rotating around body y AFTER rotating around body x + // therefore this control scheme gives decreasing sensitivity in X as Y rotation inscreases + public ushort ReadAccX(IController c) + { + theta_prev = theta; + phi_prev = phi; + + theta = (float)(c.GetFloat(Definition.FloatControls[1]) * Math.PI / 180.0); + phi = (float)(c.GetFloat(Definition.FloatControls[0]) * Math.PI / 180.0); + + float temp = (float)(Math.Cos(theta) * Math.Sin(phi)); + + // here we add in rates of change parameters. + // a typical rate of change for a fast rotation is guessed at 0.5 rad / frame + // since rotations about X have less of a moment arm compared to by, we take 1/5 of the effect as a baseline + float temp2 = (float)((phi - phi_prev) / 0.5 * 25); + + return (ushort)(0x81D0 - Math.Floor(temp * 125) - temp2); + } + + // acc y is just the sine of the angle + // we assume that ReadAccX is called first, which updates the the states + public ushort ReadAccY(IController c) + { + float temp = (float)Math.Sin(theta); + + // here we add in rates of change parameters. + // a typical rate of change for a fast rotation is guessed at 0.5 rad / frame + // further it will be assumed that the resulting acceleration is roughly eqvuivalent to gravity + float temp2 = (float)((theta - theta_prev)/0.5 * 125); + + return (ushort)(0x81D0 - Math.Floor(temp * 125) + temp2); + } + + private static readonly string[] BaseDefinition = + { + "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Power" + }; + + public void SyncState(Serializer ser) + { + // since we need rate of change of angle, need to savestate them + ser.Sync(nameof(theta), ref theta); + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/LibGBHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/LibGBHawk.cs similarity index 99% rename from BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/LibGBHawk.cs rename to BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/LibGBHawk.cs index 93b62f004a..7606e4f3b2 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/LibGBHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk_new/LibGBHawk.cs @@ -2,7 +2,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace BizHawk.Emulation.Cores.Nintendo.GBHawk +namespace BizHawk.Emulation.Cores.Nintendo.GBHawkNew { /// /// static bindings into GBHawk.dll