using System; using System.IO; using System.Runtime.InteropServices; using BizHawk.Common.BizInvoke; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Waterbox; using BizHawk.Common; using BizHawk.Emulation.DiscSystem; using System.Collections.Generic; using System.Linq; namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx { [Core( "Genplus-gx", "", isPorted: true, isReleased: true, portedVersion: "r874", portedUrl: "https://code.google.com/p/genplus-gx/", singleInstance: false)] public partial class GPGX : IEmulator, IVideoProvider, ISaveRam, IStatable, IRegionable, IInputPollable, IDebuggable, IDriveLight, ICodeDataLogger, IDisassemblable { [CoreConstructor("GEN")] public GPGX(CoreComm comm, byte[] file, object settings, object syncSettings) : this(comm, file, null, settings, syncSettings) { } public GPGX(CoreComm comm, byte[] rom, IEnumerable cds, object settings, object syncSettings) { ServiceProvider = new BasicServiceProvider(this); // this can influence some things internally (autodetect romtype, etc) string romextension = "GEN"; // three or six button? // http://www.sega-16.com/forum/showthread.php?4398-Forgotten-Worlds-giving-you-GAME-OVER-immediately-Fix-inside&highlight=forgotten%20worlds //hack, don't use if (rom != null && rom.Length > 32 * 1024 * 1024) { throw new InvalidOperationException("ROM too big! Did you try to load a CD as a ROM?"); } _elf = new PeRunner(new PeRunnerOptions { Path = comm.CoreFileProvider.DllPath(), Filename = "gpgx.wbx", SbrkHeapSizeKB = 512, SealedHeapSizeKB = 36 * 1024, InvisibleHeapSizeKB = 4 * 1024, PlainHeapSizeKB = 64, MmapHeapSizeKB = 1 * 1024 }); using (_elf.EnterExit()) { Core = BizInvoker.GetInvoker(_elf, _elf, CallingConventionAdapters.Waterbox); _syncSettings = (GPGXSyncSettings)syncSettings ?? new GPGXSyncSettings(); _settings = (GPGXSettings)settings ?? new GPGXSettings(); CoreComm = comm; LoadCallback = new LibGPGX.load_archive_cb(load_archive); _romfile = rom; if (cds != null) { _cds = cds.ToArray(); _cdReaders = cds.Select(c => new DiscSectorReader(c)).ToArray(); cd_callback_handle = new LibGPGX.cd_read_cb(CDRead); Core.gpgx_set_cdd_callback(cd_callback_handle); DriveLightEnabled = true; } LibGPGX.INPUT_SYSTEM system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_NONE; LibGPGX.INPUT_SYSTEM system_b = LibGPGX.INPUT_SYSTEM.SYSTEM_NONE; switch (_syncSettings.ControlType) { case ControlType.None: default: break; case ControlType.Activator: system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_ACTIVATOR; system_b = LibGPGX.INPUT_SYSTEM.SYSTEM_ACTIVATOR; break; case ControlType.Normal: system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD; system_b = LibGPGX.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD; break; case ControlType.OnePlayer: system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD; break; case ControlType.Xea1p: system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_XE_A1P; break; case ControlType.Teamplayer: system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_TEAMPLAYER; system_b = LibGPGX.INPUT_SYSTEM.SYSTEM_TEAMPLAYER; break; case ControlType.Wayplay: system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_WAYPLAY; system_b = LibGPGX.INPUT_SYSTEM.SYSTEM_WAYPLAY; break; case ControlType.Mouse: system_a = LibGPGX.INPUT_SYSTEM.SYSTEM_MD_GAMEPAD; // seems like mouse in port 1 would be supported, but not both at the same time system_b = LibGPGX.INPUT_SYSTEM.SYSTEM_MOUSE; break; } if (!Core.gpgx_init(romextension, LoadCallback, _syncSettings.UseSixButton, system_a, system_b, _syncSettings.Region, _settings.GetNativeSettings())) throw new Exception("gpgx_init() failed"); { int fpsnum = 60; int fpsden = 1; Core.gpgx_get_fps(ref fpsnum, ref fpsden); VsyncNumerator = fpsnum; VsyncDenominator = fpsden; Region = VsyncNumerator / VsyncDenominator > 55 ? DisplayType.NTSC : DisplayType.PAL; } // when we call Seal, ANY pointer passed from managed code must be 0. // this is so the initial state is clean // the only two pointers set so far are LoadCallback, which the core zeroed itself, // and CdCallback Core.gpgx_set_cdd_callback(null); _elf.Seal(); Core.gpgx_set_cdd_callback(cd_callback_handle); SetControllerDefinition(); // pull the default video size from the core UpdateVideo(); SetMemoryDomains(); InputCallback = new LibGPGX.input_cb(input_callback); Core.gpgx_set_input_callback(InputCallback); // process the non-init settings now PutSettings(_settings); //TODO - this hits performance, we need to make it controllable CDCallback = new LibGPGX.CDCallback(CDCallbackProc); InitMemCallbacks(); KillMemCallbacks(); Tracer = new GPGXTraceBuffer(this, MemoryDomains, this); (ServiceProvider as BasicServiceProvider).Register(Tracer); } _romfile = null; } private LibGPGX Core; private PeRunner _elf; private Disc[] _cds; private int _discIndex; private DiscSectorReader[] _cdReaders; private bool _prevDiskPressed; private bool _nextDiskPressed; byte[] _romfile; private bool _disposed = false; LibGPGX.load_archive_cb LoadCallback = null; LibGPGX.InputData input = new LibGPGX.InputData(); public enum ControlType { None, OnePlayer, Normal, Xea1p, Activator, Teamplayer, Wayplay, Mouse }; /// /// core callback for file loading /// /// string identifying file to be loaded /// buffer to load file to /// maximum length buffer can hold /// actual size loaded, or 0 on failure int load_archive(string filename, IntPtr buffer, int maxsize) { byte[] srcdata = null; if (buffer == IntPtr.Zero) { Console.WriteLine("Couldn't satisfy firmware request {0} because buffer == NULL", filename); return 0; } if (filename == "PRIMARY_ROM") { if (_romfile == null) { Console.WriteLine("Couldn't satisfy firmware request PRIMARY_ROM because none was provided."); return 0; } srcdata = _romfile; } else if (filename == "PRIMARY_CD" || filename == "SECONDARY_CD") { if (filename == "PRIMARY_CD" && _romfile != null) { Console.WriteLine("Declined to satisfy firmware request PRIMARY_CD because PRIMARY_ROM was provided."); return 0; } else { if (_cds == null) { Console.WriteLine("Couldn't satisfy firmware request {0} because none was provided.", filename); return 0; } srcdata = GetCDData(_cds[0]); if (srcdata.Length != maxsize) { Console.WriteLine("Couldn't satisfy firmware request {0} because of struct size.", filename); return 0; } } } else { // use fromtend firmware interface string firmwareID = null; switch (filename) { case "CD_BIOS_EU": firmwareID = "CD_BIOS_EU"; break; case "CD_BIOS_JP": firmwareID = "CD_BIOS_JP"; break; case "CD_BIOS_US": firmwareID = "CD_BIOS_US"; break; default: break; } if (firmwareID != null) { // this path will be the most common PEBKAC error, so be a bit more vocal about the problem srcdata = CoreComm.CoreFileProvider.GetFirmware("GEN", firmwareID, false, "GPGX firmwares are usually required."); if (srcdata == null) { Console.WriteLine("Frontend couldn't satisfy firmware request GEN:{0}", firmwareID); return 0; } } else { Console.WriteLine("Unrecognized firmware request {0}", filename); return 0; } } if (srcdata != null) { if (srcdata.Length > maxsize) { Console.WriteLine("Couldn't satisfy firmware request {0} because {1} > {2}", filename, srcdata.Length, maxsize); return 0; } else { Marshal.Copy(srcdata, 0, buffer, srcdata.Length); Console.WriteLine("Firmware request {0} satisfied at size {1}", filename, srcdata.Length); return srcdata.Length; } } else { throw new InvalidOperationException("Unknown error processing firmware"); } } void CDRead(int lba, IntPtr dest, bool audio) { if ((uint)_discIndex < _cds.Length) { if (audio) { byte[] data = new byte[2352]; if (lba < _cds[_discIndex].Session1.LeadoutLBA) { _cdReaders[_discIndex].ReadLBA_2352(lba, data, 0); } else { // audio seems to read slightly past the end of disks; probably innoculous // just send back 0s. // Console.WriteLine("!!{0} >= {1}", lba, CD.LBACount); } Marshal.Copy(data, 0, dest, 2352); } else { byte[] data = new byte[2048]; _cdReaders[_discIndex].ReadLBA_2048(lba, data, 0); Marshal.Copy(data, 0, dest, 2048); _drivelight = true; } } } LibGPGX.cd_read_cb cd_callback_handle; public static LibGPGX.CDData GetCDDataStruct(Disc cd) { var ret = new LibGPGX.CDData(); var ses = cd.Session1; int ntrack = ses.InformationTrackCount; // bet you a dollar this is all wrong //zero 07-jul-2015 - throws a dollar in the pile, since he probably messed it up worse for (int i = 0; i < LibGPGX.CD_MAX_TRACKS; i++) { if (i < ntrack) { ret.tracks[i].start = ses.Tracks[i + 1].LBA; ret.tracks[i].end = ses.Tracks[i + 2].LBA; if (i == ntrack - 1) { ret.end = ret.tracks[i].end; ret.last = ntrack; } } else { ret.tracks[i].start = 0; ret.tracks[i].end = 0; } } return ret; } public static unsafe byte[] GetCDData(Disc cd) { var ret = GetCDDataStruct(cd); int size = Marshal.SizeOf(ret); byte[] retdata = new byte[size]; fixed (byte* p = &retdata[0]) { Marshal.StructureToPtr(ret, (IntPtr)p, false); } return retdata; } /// /// size of native input struct /// int inputsize; GPGXControlConverter ControlConverter; private void SetControllerDefinition() { inputsize = Marshal.SizeOf(typeof(LibGPGX.InputData)); if (!Core.gpgx_get_control(input, inputsize)) throw new Exception("gpgx_get_control() failed"); ControlConverter = new GPGXControlConverter(input, false); // _cds != null); ControllerDefinition = ControlConverter.ControllerDef; } public LibGPGX.INPUT_DEVICE[] GetDevices() { return (LibGPGX.INPUT_DEVICE[])input.dev.Clone(); } public bool IsMegaCD { get { return _cds != null; } } public class VDPView : IMonitor { private readonly IMonitor _m; public VDPView(LibGPGX.VDPView v, IMonitor m) { _m = m; VRAM = v.VRAM; PatternCache = v.PatternCache; ColorCache = v.ColorCache; NTA = v.NTA; NTB = v.NTB; NTW = v.NTW; } public IntPtr VRAM; public IntPtr PatternCache; public IntPtr ColorCache; public LibGPGX.VDPNameTable NTA; public LibGPGX.VDPNameTable NTB; public LibGPGX.VDPNameTable NTW; public void Enter() { _m.Enter(); } public void Exit() { _m.Exit(); } } public VDPView UpdateVDPViewContext() { var v = new LibGPGX.VDPView(); Core.gpgx_get_vdp_view(v); Core.gpgx_flush_vram(); // fully regenerate internal caches as needed return new VDPView(v, _elf); } public DisplayType Region { get; private set; } } }