diff --git a/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 133539eb2f..b5472ef955 100644 --- a/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/src/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -280,6 +280,8 @@ namespace BizHawk.Emulation.Common var jp_mcd_beta = File("F30D109D1C2F7C9FEAF38600C65834261DB73D1F", 131072, "MCD_jp_beta.bin", "Mega CD JP (Beta)"); var eu_mcd_221 = File("9DE4EDA59F544DB2D5FD7E6514601F7B648D8EB4", 131072, "MCD_eu_221.bin", "Mega CD EU (v2.21)"); + FirmwareAndOption("1C470A9A8D0B211C5FEEA1C1C2376AA1F7934B16", 4096, "GEN", "TMSS", "TMSS.md", "Mega Drive TMSS Boot Rom (Japan)"); + Firmware("GEN", "CD_BIOS_EU", "Mega CD Bios (Europe)"); Firmware("GEN", "CD_BIOS_JP", "Mega CD Bios (Japan)"); Firmware("GEN", "CD_BIOS_US", "Sega CD Bios (USA)"); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs index 6647386c46..1d201d55fe 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs @@ -11,20 +11,19 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx { public IDictionary GetCpuFlagsAndRegisters() { - LibGPGX.RegisterInfo[] regs = new LibGPGX.RegisterInfo[Core.gpgx_getmaxnumregs()]; - - int n = Core.gpgx_getregs(regs); + var regs = new LibGPGX.RegisterInfo[Core.gpgx_getmaxnumregs()]; + var n = Core.gpgx_getregs(regs); if (n > regs.Length) throw new InvalidOperationException("A buffer overrun has occured!"); var ret = new Dictionary(); using (_elf.EnterExit()) { - for (int i = 0; i < n; i++) + for (var i = 0; i < n; i++) { // el hacko - string name = Marshal.PtrToStringAnsi(regs[i].Name); + var name = Marshal.PtrToStringAnsi(regs[i].Name); byte size = 32; - if (name.Contains("68K SR") || name.StartsWithOrdinal("Z80")) + if (name!.Contains("68K SR") || name.StartsWithOrdinal("Z80")) size = 16; ret[name] = new RegisterValue((ulong)regs[i].Value, size); @@ -40,17 +39,28 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx throw new NotImplementedException(); } - public IMemoryCallbackSystem MemoryCallbacks => _memoryCallbacks; + public IMemoryCallbackSystem MemoryCallbacks + { + get + { + if (SystemId == VSystemID.Raw.GEN) + { + return _memoryCallbacks; + } - public bool CanStep(StepType type) { return false; } + throw new NotImplementedException(); + } + } + + public bool CanStep(StepType type) => false; [FeatureNotImplemented] - public void Step(StepType type) { throw new NotImplementedException(); } + public void Step(StepType type) => throw new NotImplementedException(); [FeatureNotImplemented] public long TotalExecutedCycles => throw new NotImplementedException(); - private readonly MemoryCallbackSystem _memoryCallbacks = new MemoryCallbackSystem(new[] { "M68K BUS" }); + private readonly MemoryCallbackSystem _memoryCallbacks = new(new[] { "M68K BUS" }); private LibGPGX.mem_cb ExecCallback; private LibGPGX.mem_cb ReadCallback; @@ -59,30 +69,30 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx private void InitMemCallbacks() { - ExecCallback = new LibGPGX.mem_cb(a => + ExecCallback = a => { if (MemoryCallbacks.HasExecutes) { - uint flags = (uint)MemoryCallbackFlags.AccessExecute; + const uint flags = (uint)MemoryCallbackFlags.AccessExecute; MemoryCallbacks.CallMemoryCallbacks(a, 0, flags, "M68K BUS"); } - }); - ReadCallback = new LibGPGX.mem_cb(a => + }; + ReadCallback = a => { if (MemoryCallbacks.HasReads) { - uint flags = (uint)MemoryCallbackFlags.AccessRead; + const uint flags = (uint)MemoryCallbackFlags.AccessRead; MemoryCallbacks.CallMemoryCallbacks(a, 0, flags, "M68K BUS"); } - }); - WriteCallback = new LibGPGX.mem_cb(a => + }; + WriteCallback = a => { if (MemoryCallbacks.HasWrites) { - uint flags = (uint)MemoryCallbackFlags.AccessWrite; + const uint flags = (uint)MemoryCallbackFlags.AccessWrite; MemoryCallbacks.CallMemoryCallbacks(a, 0, flags, "M68K BUS"); } - }); + }; _memoryCallbacks.ActiveChanged += RefreshMemCallbacks; } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IMemoryDomains.cs index 49f16344e8..5bc9074045 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IMemoryDomains.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IMemoryDomains.cs @@ -25,7 +25,10 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx continue; var name = Marshal.PtrToStringAnsi(pName)!; - var endian = name == "Z80 RAM" + // typically Genesis domains will be 2 bytes large (and thus big endian and byteswapped) + var oneByteWidth = name is "Z80 RAM" or "Main RAM" or "ROM" or "Cart (Volatile) RAM" or "SRAM"; + + var endian = oneByteWidth ? MemoryDomain.Endian.Little : MemoryDomain.Endian.Big; @@ -33,44 +36,86 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx { // vram pokes need to go through hook which invalidates cached tiles var p = (byte*)area; - mm.Add(new MemoryDomainDelegate(name, size, MemoryDomain.Endian.Big, - addr => - { - if (addr is < 0 or > 0xFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); - using (_elf.EnterExit()) - return p![addr ^ 1]; - }, - (addr, val) => - { - if (addr is < 0 or > 0xFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); - Core.gpgx_poke_vram((int)addr ^ 1, val); - }, - wordSize: 2)); + if (SystemId == VSystemID.Raw.GEN) + { + // Genesis has more VRAM, and GPGX byteswaps it + mm.Add(new MemoryDomainDelegate(name, size, MemoryDomain.Endian.Big, + addr => + { + if (addr is < 0 or > 0xFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + using (_elf.EnterExit()) + return p![addr ^ 1]; + }, + (addr, val) => + { + if (addr is < 0 or > 0xFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + Core.gpgx_poke_vram((int)addr ^ 1, val); + }, + wordSize: 1)); + } + else + { + mm.Add(new MemoryDomainDelegate(name, size, MemoryDomain.Endian.Big, + addr => + { + if (addr is < 0 or > 0x3FFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + using (_elf.EnterExit()) + return p![addr]; + }, + (addr, val) => + { + if (addr is < 0 or > 0x3FFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + Core.gpgx_poke_vram((int)addr, val); + }, + wordSize: 1)); + } } else if (name == "CRAM") { - // CRAM in the core is internally a different format than what it is natively - // this internal format isn't really useful, so let's convert it back var p = (byte*)area; - mm.Add(new MemoryDomainDelegate(name, size, MemoryDomain.Endian.Big, - addr => - { - if (addr is < 0 or > 0x7F) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); - using (_elf.EnterExit()) + if (SystemId == VSystemID.Raw.GEN) + { + // CRAM for Genesis in the core is internally a different format than what it is natively + // this internal format isn't really useful, so let's convert it back + mm.Add(new MemoryDomainDelegate(name, size, MemoryDomain.Endian.Big, + addr => { - var c = *(ushort*)&p![addr & ~1]; - c = (ushort)(((c & 0x1C0) << 3) | ((c & 0x038) << 2) | ((c & 0x007) << 1)); - return (byte)((addr & 1) != 0 ? c & 0xFF : c >> 8); - } - }, - (addr, val) => - { - if (addr is < 0 or > 0x7F) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); - Core.gpgx_poke_cram((int)addr, val); - }, - wordSize: 2)); + if (addr is < 0 or > 0x7F) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + using (_elf.EnterExit()) + { + var c = *(ushort*)&p![addr & ~1]; + c = (ushort)(((c & 0x1C0) << 3) | ((c & 0x038) << 2) | ((c & 0x007) << 1)); + return (byte)((addr & 1) != 0 ? c & 0xFF : c >> 8); + } + }, + (addr, val) => + { + if (addr is < 0 or > 0x7F) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + Core.gpgx_poke_cram((int)addr, val); + }, + wordSize: 2)); + } + else + { + mm.Add(new MemoryDomainDelegate(name, size, MemoryDomain.Endian.Big, + addr => + { + if (addr is < 0 or > 0x3F) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + using (_elf.EnterExit()) + { + var c = *(ushort*)&p![addr & ~1]; + return (byte)((addr & 1) != 0 ? c & 0xFF : c >> 8); + } + }, + (addr, val) => + { + if (addr is < 0 or > 0x3F) throw new ArgumentOutOfRangeException(paramName: nameof(addr), addr, message: "address out of range"); + Core.gpgx_poke_cram((int)addr, val); + }, + wordSize: 2)); + } } - else if (name.Contains("Z80")) + else if (oneByteWidth) { mm.Add(new MemoryDomainIntPtrMonitor(name, endian, area, size, true, 1, _elf)); } @@ -79,44 +124,64 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx mm.Add(new MemoryDomainIntPtrSwap16Monitor(name, endian, area, size, true, _elf)); } } - var m68Bus = new MemoryDomainDelegate("M68K BUS", 0x1000000, MemoryDomain.Endian.Big, - addr => - { - var a = (uint)addr; - if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); - return Core.gpgx_peek_m68k_bus(a); - }, - (addr, val) => - { - var a = (uint)addr; - if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); - Core.gpgx_write_m68k_bus(a, val); - }, 2); - mm.Add(m68Bus); - - if (IsMegaCD) + MemoryDomain systemBus; + if (SystemId == VSystemID.Raw.GEN) { - var s68Bus = new MemoryDomainDelegate("S68K BUS", 0x1000000, MemoryDomain.Endian.Big, - addr => - { - var a = (uint)addr; - if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); - return Core.gpgx_peek_s68k_bus(a); - }, - (addr, val) => - { - var a = (uint)addr; - if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); - Core.gpgx_write_s68k_bus(a, val); - }, 2); + systemBus = new MemoryDomainDelegate("M68K BUS", 0x1000000, MemoryDomain.Endian.Big, + addr => + { + var a = (uint)addr; + if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); + return Core.gpgx_peek_m68k_bus(a); + }, + (addr, val) => + { + var a = (uint)addr; + if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); + Core.gpgx_write_m68k_bus(a, val); + }, 2); + mm.Add(systemBus); - mm.Add(s68Bus); + if (IsMegaCD) + { + var s68Bus = new MemoryDomainDelegate("S68K BUS", 0x1000000, MemoryDomain.Endian.Big, + addr => + { + var a = (uint)addr; + if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); + return Core.gpgx_peek_s68k_bus(a); + }, + (addr, val) => + { + var a = (uint)addr; + if (a > 0xFFFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); + Core.gpgx_write_s68k_bus(a, val); + }, 2); + + mm.Add(s68Bus); + } + } + else + { + systemBus = new MemoryDomainDelegate("Z80 BUS", 0x10000, MemoryDomain.Endian.Little, + addr => + { + var a = (uint)addr; + if (a > 0xFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); + return Core.gpgx_peek_z80_bus(a); + }, + (addr, val) => + { + var a = (uint)addr; + if (a > 0xFFFF) throw new ArgumentOutOfRangeException(paramName: nameof(addr), a, message: "address out of range"); + Core.gpgx_write_z80_bus(a, val); + }, 1); } - mm.Add(_elf.GetPagesDomain()); - _memoryDomains = new MemoryDomainList(mm) { SystemBus = m68Bus }; + mm.Add(_elf.GetPagesDomain()); + _memoryDomains = new MemoryDomainList(mm) { SystemBus = systemBus }; ((BasicServiceProvider) ServiceProvider).Register(_memoryDomains); } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISettable.cs index 9616c11485..540eeb3849 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.ISettable.cs @@ -263,7 +263,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx [DefaultValue(LibGPGX.Region.Autodetect)] public LibGPGX.Region Region { get; set; } - [DisplayName("[SMS/GG] Load BIOS")] + [DisplayName("Load BIOS")] [Description("Indicates whether to load the system BIOS rom.")] [DefaultValue(false)] public bool LoadBIOS { get; set; } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs index 664e26637d..058aeebf04 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs @@ -104,6 +104,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx var initSettings = _syncSettings.GetNativeSettings(lp.Game); var initResult = Core.gpgx_init(romExtension, LoadCallback, ref initSettings); + // if a firmware request failed and we're recording a movie, fail now + // we should do this as to enforce the sync settings of the movie + // init might still work fine, so don't throw for more casual users + if (_firmwareRequestFailed && lp.DeterministicEmulationRequested) + { + throw new MissingFirmwareException("A GPGX firmware request failed in deterministic mode."); + } + if (!initResult) { throw new Exception($"{nameof(Core.gpgx_init)}() failed"); @@ -183,6 +191,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx private bool _disposed = false; private LibGPGX.load_archive_cb LoadCallback; + private bool _firmwareRequestFailed; private readonly LibGPGX.InputData input = new LibGPGX.InputData(); @@ -252,6 +261,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx FirmwareID? firmwareID = filename switch { + "MD_BIOS" => new(system: VSystemID.Raw.GEN, firmware: "TMSS"), "CD_BIOS_EU" => new(system: VSystemID.Raw.GEN, firmware: "CD_BIOS_EU"), "CD_BIOS_JP" => new(system: VSystemID.Raw.GEN, firmware: "CD_BIOS_JP"), "CD_BIOS_US" => new(system: VSystemID.Raw.GEN, firmware: "CD_BIOS_US"), @@ -268,6 +278,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx srcdata = CoreComm.CoreFileProvider.GetFirmware(firmwareID.Value, "GPGX firmwares are usually required."); if (srcdata == null) { + _firmwareRequestFailed = true; Console.WriteLine($"Frontend couldn't satisfy firmware request {firmwareID}"); return 0; } @@ -407,7 +418,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx { private readonly IMonitor _m; - public VDPView(LibGPGX.VDPView v, IMonitor m) + public VDPView(in LibGPGX.VDPView v, IMonitor m) { _m = m; VRAM = v.VRAM; @@ -439,12 +450,11 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx public VDPView UpdateVDPViewContext() { - var v = new LibGPGX.VDPView(); - Core.gpgx_get_vdp_view(v); + Core.gpgx_get_vdp_view(out var v); Core.gpgx_flush_vram(); // fully regenerate internal caches as needed - return new VDPView(v, _elf); + return new VDPView(in v, _elf); } - + public int AddDeepFreezeValue(int address, byte value) { return Core.gpgx_add_deepfreeze_list_entry(address, value); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/LibGPGX.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/LibGPGX.cs index 24216f1c2f..f6780b9b57 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/LibGPGX.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/LibGPGX.cs @@ -98,6 +98,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx [BizImport(CallingConvention.Cdecl, Compatibility = true)] public abstract bool gpgx_get_control([Out]InputData dest, int bytes); + [BizImport(CallingConvention.Cdecl, Compatibility = true)] public abstract bool gpgx_put_control([In]InputData src, int bytes); @@ -331,7 +332,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx public abstract void gpgx_set_cdd_callback(cd_read_cb cddcb); [BizImport(CallingConvention.Cdecl, Compatibility = true)] - public abstract void gpgx_swap_disc(CDData toc); + public abstract void gpgx_swap_disc([In] CDData toc); [StructLayout(LayoutKind.Sequential)] public struct VDPNameTable @@ -342,7 +343,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx } [StructLayout(LayoutKind.Sequential)] - public class VDPView + public struct VDPView { public IntPtr VRAM; public IntPtr PatternCache; @@ -352,8 +353,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx public VDPNameTable NTW; } - [BizImport(CallingConvention.Cdecl, Compatibility = true)] - public abstract void gpgx_get_vdp_view([Out] VDPView view); + [BizImport(CallingConvention.Cdecl)] + public abstract void gpgx_get_vdp_view(out VDPView view); [BizImport(CallingConvention.Cdecl)] public abstract void gpgx_poke_cram(int addr, byte value); @@ -383,8 +384,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx [BizImport(CallingConvention.Cdecl)] public abstract int gpgx_getmaxnumregs(); - [BizImport(CallingConvention.Cdecl, Compatibility = true)] - public abstract int gpgx_getregs([Out] RegisterInfo[] regs); + [BizImport(CallingConvention.Cdecl)] + public abstract int gpgx_getregs(RegisterInfo[] regs); [Flags] public enum DrawMask : int @@ -398,15 +399,25 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx [BizImport(CallingConvention.Cdecl)] public abstract void gpgx_set_draw_mask(DrawMask mask); + [BizImport(CallingConvention.Cdecl)] public abstract void gpgx_set_sprite_limit_enabled(bool enabled); + [BizImport(CallingConvention.Cdecl)] + public abstract void gpgx_write_z80_bus(uint addr, byte data); + [BizImport(CallingConvention.Cdecl)] public abstract void gpgx_write_m68k_bus(uint addr, byte data); + [BizImport(CallingConvention.Cdecl)] public abstract void gpgx_write_s68k_bus(uint addr, byte data); + + [BizImport(CallingConvention.Cdecl)] + public abstract byte gpgx_peek_z80_bus(uint addr); + [BizImport(CallingConvention.Cdecl)] public abstract byte gpgx_peek_m68k_bus(uint addr); + [BizImport(CallingConvention.Cdecl)] public abstract byte gpgx_peek_s68k_bus(uint addr); }