From 96fc7c9ec1db4978de39aec9f5061be03254e7fe Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 17 Feb 2016 20:17:49 -0500 Subject: [PATCH] Add experimental interop library to quicknes. Still needs work... --- .../BizHawk.Emulation.Common.csproj | 3 + .../BizInvoke/BizInvoker.cs | 209 ++++++++++++++ .../BizInvoke/IImportResolver.cs | 12 + .../BizInvoke/Win32LibraryImportResolver.cs | 55 ++++ .../Consoles/Nintendo/QuickNES/LibQuickNES.cs | 256 +++++++++++++++++- .../Nintendo/QuickNES/QuickNES.IDebuggable.cs | 2 +- .../QuickNES/QuickNES.IMemoryDomains.cs | 6 +- .../QuickNES/QuickNES.INESPPUViewable.cs | 10 +- .../Nintendo/QuickNES/QuickNES.ISaveRam.cs | 8 +- .../Nintendo/QuickNES/QuickNES.ISettable.cs | 4 +- .../Nintendo/QuickNES/QuickNES.IStatable.cs | 6 +- .../QuickNES/QuickNES.IVideoProvider.cs | 2 +- .../Consoles/Nintendo/QuickNES/QuickNES.cs | 40 ++- quicknes/bizinterface.cpp | 4 + 14 files changed, 583 insertions(+), 34 deletions(-) create mode 100644 BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs create mode 100644 BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs create mode 100644 BizHawk.Emulation.Common/BizInvoke/Win32LibraryImportResolver.cs diff --git a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj index 416ba033db..17510a2b30 100644 --- a/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj +++ b/BizHawk.Emulation.Common/BizHawk.Emulation.Common.csproj @@ -64,6 +64,9 @@ + + + diff --git a/BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs b/BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs new file mode 100644 index 0000000000..d5498c2d1e --- /dev/null +++ b/BizHawk.Emulation.Common/BizInvoke/BizInvoker.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Common.BizInvoke +{ + public static class BizInvoker + { + public static T GetInvoker(IImportResolver dll) + where T : class + { + var baseType = typeof(T); + if (baseType.IsSealed) + throw new InvalidOperationException("Can't proxy a sealed type"); + if (!baseType.IsPublic) + // the proxy type will be in a new assembly, so public is required here + throw new InvalidOperationException("Type must be public"); + + var baseConstructor = baseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); + if (baseConstructor == null) + throw new InvalidOperationException("Base type must have a zero arg constructor"); + + var baseMethods = baseType.GetMethods(BindingFlags.Instance | BindingFlags.Public) + .Select(m => new + { + Info = m, + Attr = m.GetCustomAttributes(true).OfType().FirstOrDefault() + }) + .Where(a => a.Attr != null) + .ToList(); + + if (baseMethods.Count == 0) + throw new InvalidOperationException("Couldn't find any [BizImport] methods to proxy"); + + { + var uo = baseMethods.FirstOrDefault(a => !a.Info.IsVirtual || a.Info.IsFinal); + if (uo != null) + throw new InvalidOperationException("Method " + uo.Info.Name + " cannot be overriden!"); + + // there's no technical reason to disallow this, but we wouldn't be doing anything + // with the base implementation, so it's probably a user error + var na = baseMethods.FirstOrDefault(a => !a.Info.IsAbstract); + if (na != null) + throw new InvalidOperationException("Method " + na.Info.Name + " is not abstract!"); + } + + var aname = new AssemblyName(baseType.Name + Guid.NewGuid().ToString("N")); + var assy = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run); + var module = assy.DefineDynamicModule("BizInvoker"); + var type = module.DefineType("Bizhawk.BizInvokeProxy", TypeAttributes.Class | TypeAttributes.Public, baseType); + + foreach (var mi in baseMethods) + { + var paramInfos = mi.Info.GetParameters(); + var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); + var nativeParamTypes = new List(); + var returnType = mi.Info.ReturnType; + var method = type.DefineMethod(mi.Info.Name, MethodAttributes.Virtual | MethodAttributes.Public, + CallingConventions.HasThis, returnType, paramTypes); + + var entryPointName = mi.Attr.EntryPoint ?? mi.Info.Name; + var entryPtr = dll.Resolve(entryPointName); + if (entryPtr == IntPtr.Zero) + throw new InvalidOperationException("Resolver returned NULL for entry point " + entryPointName); + + if (returnType != typeof(void) && !returnType.IsPrimitive) + throw new InvalidOperationException("Only primitive return types are supported"); + + var il = method.GetILGenerator(); + for (int i = 0; i < paramTypes.Length; i++) + { + // arg 0 is this, so + 1 + nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i])); + } + LoadConstant(il, entryPtr); + il.EmitCalli(OpCodes.Calli, mi.Attr.CallingConvention, returnType, nativeParamTypes.ToArray()); + + // either there's a primitive on the stack and we're expected to return that primitive, + // or there's nothing on the stack and we're expected to return nothing + il.Emit(OpCodes.Ret); + + type.DefineMethodOverride(method, mi.Info); + } + + return (T)Activator.CreateInstance(type.CreateType()); + } + + private static void LoadConstant(ILGenerator il, IntPtr p) + { + if (p == IntPtr.Zero) + il.Emit(OpCodes.Ldc_I4_0); + else if (IntPtr.Size == 4) + il.Emit(OpCodes.Ldc_I4, (int)p); + else + il.Emit(OpCodes.Ldc_I8, (long)p); + il.Emit(OpCodes.Conv_I); + } + + private static void LoadConstant(ILGenerator il, UIntPtr p) + { + if (p == UIntPtr.Zero) + il.Emit(OpCodes.Ldc_I4_0); + else if (UIntPtr.Size == 4) + il.Emit(OpCodes.Ldc_I4, (int)p); + else + il.Emit(OpCodes.Ldc_I8, (long)p); + il.Emit(OpCodes.Conv_U); + } + + private static Type EmitParamterLoad(ILGenerator il, int idx, Type type) + { + if (type.IsGenericType) + throw new InvalidOperationException("Generic types not supported"); + if (type.IsByRef) + { + var et = type.GetElementType(); + if (!et.IsPrimitive) + throw new InvalidOperationException("Only refs of primitive types are supported!"); + var loc = il.DeclareLocal(type, true); + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Conv_I); + return typeof(IntPtr); + } + else if (type.IsArray) + { + var et = type.GetElementType(); + if (!et.IsPrimitive) + throw new InvalidOperationException("Only arrays of primitive types are supported!"); + + // these two cases aren't too hard to add + if (type.GetArrayRank() > 1) + throw new InvalidOperationException("Multidimensional arrays are not supported!"); + if (type.Name.Contains('*')) + throw new InvalidOperationException("Only 0-based 1-dimensional arrays are supported!"); + + var loc = il.DeclareLocal(type, true); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, loc); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ldelema, et); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Br, end); + + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + + return typeof(IntPtr); + } + else if (typeof(Delegate).IsAssignableFrom(type)) + { + var mi = typeof(Marshal).GetMethod("GetFunctionPointerForDelegate", new[] { typeof(Delegate) }); + var end = il.DefineLabel(); + var isNull = il.DefineLabel(); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Brfalse, isNull); + + il.Emit(OpCodes.Ldarg, (short)idx); + il.Emit(OpCodes.Call, mi); + il.Emit(OpCodes.Br, end); + + il.MarkLabel(isNull); + LoadConstant(il, IntPtr.Zero); + il.MarkLabel(end); + return typeof(IntPtr); + } + else if (type.IsPrimitive) + { + il.Emit(OpCodes.Ldarg, (short)idx); + return type; + } + else + { + throw new InvalidOperationException("Unrecognized parameter type!"); + } + } + } + + [AttributeUsage(AttributeTargets.Method)] + public class BizImportAttribute : Attribute + { + public CallingConvention CallingConvention + { + get { return _callingConvention; } + } + private readonly CallingConvention _callingConvention; + + public string EntryPoint { get; set; } + + public BizImportAttribute(CallingConvention c) + { + _callingConvention = c; + } + } +} diff --git a/BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs b/BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs new file mode 100644 index 0000000000..bb33bd06cf --- /dev/null +++ b/BizHawk.Emulation.Common/BizInvoke/IImportResolver.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BizHawk.Emulation.Common.BizInvoke +{ + public interface IImportResolver + { + IntPtr Resolve(string entryPoint); + } +} diff --git a/BizHawk.Emulation.Common/BizInvoke/Win32LibraryImportResolver.cs b/BizHawk.Emulation.Common/BizInvoke/Win32LibraryImportResolver.cs new file mode 100644 index 0000000000..5bc21d8763 --- /dev/null +++ b/BizHawk.Emulation.Common/BizInvoke/Win32LibraryImportResolver.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Common.BizInvoke +{ + public class Win32LibraryImportResolver : IImportResolver, IDisposable + { + private IntPtr _p; + + public Win32LibraryImportResolver(string dllName) + { + _p = Win32.LoadLibrary(dllName); + if (_p == IntPtr.Zero) + throw new InvalidOperationException("LoadLibrary returned NULL"); + } + + public IntPtr Resolve(string entryPoint) + { + return Win32.GetProcAddress(_p, entryPoint); + } + + private void Free() + { + if (_p != IntPtr.Zero) + { + Win32.FreeLibrary(_p); + _p = IntPtr.Zero; + } + } + + public void Dispose() + { + Free(); + GC.SuppressFinalize(this); + } + + ~Win32LibraryImportResolver() + { + Free(); + } + + private static class Win32 + { + [DllImport("kernel32.dll")] + public static extern IntPtr LoadLibrary(string dllToLoad); + [DllImport("kernel32.dll")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + [DllImport("kernel32.dll")] + public static extern bool FreeLibrary(IntPtr hModule); + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs index 4d3f40a22f..82e573126e 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; +using BizHawk.Emulation.Common.BizInvoke; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { - public static class LibQuickNES + public static class LibQuickNESOld { public const string dllname = "libquicknes.dll"; @@ -258,4 +259,257 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES } } } + + public abstract class LibQuickNES + { + public const string dllname = "libquicknes.dll"; + + /// + /// setup extra mappers. should be done before anything else + /// + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_setup_mappers(); + /// + /// create a new quicknes context + /// + /// NULL on failure + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_new(); + /// + /// destroy a quicknes context + /// + /// context previously returned from qn_new() + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_delete(IntPtr e); + /// + /// load an ines file + /// + /// context + /// file + /// length of file + /// + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_loadines(IntPtr e, byte[] data, int length); + /// + /// set audio sample rate + /// + /// context + /// hz + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_set_sample_rate(IntPtr e, int rate); + /// + /// emulate a single frame + /// + /// context + /// pad 1 input + /// pad 2 input + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_emulate_frame(IntPtr e, int pad1, int pad2); + /// + /// blit to rgb32 + /// + /// Context + /// rgb32 256x240 packed + /// rgb32 colors, 512 of them + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_blit(IntPtr e, int[] dest, int[] colors, int cropleft, int croptop, int cropright, int cropbottom); + /// + /// get quicknes's default palette + /// + /// 1536 bytes suitable for qn_blit + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_get_default_colors(); + /// + /// get number of times joypad was read in most recent frame + /// + /// context + /// 0 means lag + [BizImport(CallingConvention.Cdecl)] + public abstract int qn_get_joypad_read_count(IntPtr e); + /// + /// get audio info for most recent frame + /// + /// context + /// number of samples actually created + /// 1 for mono, 2 for stereo + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_get_audio_info(IntPtr e, ref int sample_count, ref int chan_count); + /// + /// get audio for most recent frame. must not be called more than once per frame! + /// + /// context + /// sample buffer + /// length to read into sample buffer + /// length actually read + [BizImport(CallingConvention.Cdecl)] + public abstract int qn_read_audio(IntPtr e, short[] dest, int max_samples); + /// + /// reset the console + /// + /// context + /// true for powercycle, false for reset button + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_reset(IntPtr e, bool hard); + /// + /// get the required byte size of a savestate + /// + /// context + /// size is returned + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_state_size(IntPtr e, ref int size); + /// + /// save state to buffer + /// + /// context + /// buffer + /// length of buffer + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_state_save(IntPtr e, byte[] dest, int size); + /// + /// load state from buffer + /// + /// context + /// buffer + /// length of buffer + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_state_load(IntPtr e, byte[] src, int size); + /// + /// query battery ram state + /// + /// context + /// true if battery backup sram exists + [BizImport(CallingConvention.Cdecl)] + public abstract bool qn_has_battery_ram(IntPtr e); + /// + /// query battery ram size + /// + /// context + /// size is returned + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_battery_ram_size(IntPtr e, ref int size); + /// + /// save battery ram to buffer + /// + /// context + /// buffer + /// size + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_battery_ram_save(IntPtr e, byte[] dest, int size); + /// + /// load battery ram from buffer + /// + /// context + /// buffer + /// size + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_battery_ram_load(IntPtr e, byte[] src, int size); + /// + /// clear battery ram + /// + /// context + /// string error + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_battery_ram_clear(IntPtr e); + /// + /// set sprite limit; does not affect emulation + /// + /// context + /// 0 to hide, 8 for normal, 64 for all + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_set_sprite_limit(IntPtr e, int n); + /// + /// get memory area for debugging + /// + /// Context + /// + /// + /// + /// + /// + /// + [BizImport(CallingConvention.Cdecl)] + public abstract bool qn_get_memory_area(IntPtr e, int which, ref IntPtr data, ref int size, ref bool writable, ref IntPtr name); + /// + /// peek the system bus + /// + /// Context + /// 0000:ffff, but non-ram/rom addresses won't work + /// + [BizImport(CallingConvention.Cdecl)] + public abstract byte qn_peek_prgbus(IntPtr e, int addr); + /// + /// poke the system bus + /// + /// Context + /// 0000:ffff, but non-ram/rom addresses won't work + /// + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_poke_prgbus(IntPtr e, int addr, byte val); + /// + /// get internal registers + /// + /// Context + /// a, x, y, sp, pc, p + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_get_cpuregs(IntPtr e, [Out] int[] dest); + /// + /// get the mapper that's loaded + /// + /// Context + /// recieves mapper number + /// mapper name + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_get_mapper(IntPtr e, ref int number); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void TraceCallback(IntPtr data); + + /// + /// set a trace callback to be run on each cycle + /// + /// Context + /// + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_set_tracecb(IntPtr e, TraceCallback cb); + + + + [BizImport(CallingConvention.Cdecl)] + public abstract byte qn_get_reg2000(IntPtr e); + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_get_palmem(IntPtr e); + [BizImport(CallingConvention.Cdecl)] + public abstract IntPtr qn_get_oammem(IntPtr e); + [BizImport(CallingConvention.Cdecl)] + public abstract byte qn_peek_ppu(IntPtr e, int addr); + [BizImport(CallingConvention.Cdecl)] + public abstract void qn_peek_ppubus(IntPtr e, byte[] dest); + + /// + /// handle "string error" as returned by some quicknes functions + /// + /// + public static void ThrowStringError(IntPtr p) + { + if (p == IntPtr.Zero) + return; + string s = Marshal.PtrToStringAnsi(p); + if (s == "Unsupported mapper" || s == "Not an iNES file") // Not worth making a new exception for the iNES error, they ultimately are the same problem + { + throw new Emulation.Common.UnsupportedGameException("Quicknes unsupported mapper"); + } + else + { + throw new InvalidOperationException("LibQuickNES error: " + s); + } + } + } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IDebuggable.cs index fe05914eb0..e5d5d12b63 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IDebuggable.cs @@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { int[] regs = new int[6]; var ret = new Dictionary(); - LibQuickNES.qn_get_cpuregs(Context, regs); + QN.qn_get_cpuregs(Context, regs); ret["A"] = (byte)regs[0]; ret["X"] = (byte)regs[1]; ret["Y"] = (byte)regs[2]; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IMemoryDomains.cs index 56dc157515..cc998d3c51 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IMemoryDomains.cs @@ -18,7 +18,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES bool writable = false; IntPtr name = IntPtr.Zero; - if (!LibQuickNES.qn_get_memory_area(Context, i, ref data, ref size, ref writable, ref name)) + if (!QN.qn_get_memory_area(Context, i, ref data, ref size, ref writable, ref name)) break; if (data != IntPtr.Zero && size > 0 && name != IntPtr.Zero) @@ -39,7 +39,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES throw new ArgumentOutOfRangeException(); } - return LibQuickNES.qn_peek_prgbus(Context, (int)addr); + return QN.qn_peek_prgbus(Context, (int)addr); }, delegate(long addr, byte val) { @@ -48,7 +48,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES throw new ArgumentOutOfRangeException(); } - LibQuickNES.qn_poke_prgbus(Context, (int)addr, val); + QN.qn_poke_prgbus(Context, (int)addr, val); } )); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.INESPPUViewable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.INESPPUViewable.cs index de9382909c..40251ec9be 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.INESPPUViewable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.INESPPUViewable.cs @@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES return VideoPalette; } - private byte R2000 { get { return LibQuickNES.qn_get_reg2000(Context); } } + private byte R2000 { get { return QN.qn_get_reg2000(Context); } } public bool BGBaseHigh { @@ -39,27 +39,27 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES private byte[] ppubusbuf = new byte[0x3000]; public byte[] GetPPUBus() { - LibQuickNES.qn_peek_ppubus(Context, ppubusbuf); + QN.qn_peek_ppubus(Context, ppubusbuf); return ppubusbuf; } private byte[] palrambuf = new byte[0x20]; public byte[] GetPalRam() { - Marshal.Copy(LibQuickNES.qn_get_palmem(Context), palrambuf, 0, 0x20); + Marshal.Copy(QN.qn_get_palmem(Context), palrambuf, 0, 0x20); return palrambuf; } byte[] oambuf = new byte[0x100]; public byte[] GetOam() { - Marshal.Copy(LibQuickNES.qn_get_oammem(Context), oambuf, 0, 0x100); + Marshal.Copy(QN.qn_get_oammem(Context), oambuf, 0, 0x100); return oambuf; } public byte PeekPPU(int addr) { - return LibQuickNES.qn_peek_ppu(Context, addr); + return QN.qn_peek_ppu(Context, addr); } // we don't use quicknes's MMC5 at all, so these three methods are just stubs diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISaveRam.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISaveRam.cs index afbd2d0f04..3c58707f43 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISaveRam.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISaveRam.cs @@ -6,20 +6,20 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { public byte[] CloneSaveRam() { - LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_save(Context, SaveRamBuff, SaveRamBuff.Length)); + LibQuickNES.ThrowStringError(QN.qn_battery_ram_save(Context, SaveRamBuff, SaveRamBuff.Length)); return (byte[])SaveRamBuff.Clone(); } public void StoreSaveRam(byte[] data) { - LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_load(Context, data, data.Length)); + LibQuickNES.ThrowStringError(QN.qn_battery_ram_load(Context, data, data.Length)); } public bool SaveRamModified { get { - return LibQuickNES.qn_has_battery_ram(Context); + return QN.qn_has_battery_ram(Context); } } @@ -28,7 +28,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES private void InitSaveRamBuff() { int size = 0; - LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_size(Context, ref size)); + LibQuickNES.ThrowStringError(QN.qn_battery_ram_size(Context, ref size)); SaveRamBuff = new byte[size]; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISettable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISettable.cs index 110ae38c60..0d50e5ae0a 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISettable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.ISettable.cs @@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES public bool PutSettings(QuickNESSettings o) { _settings = o; - LibQuickNES.qn_set_sprite_limit(Context, _settings.NumSprites); + QN.qn_set_sprite_limit(Context, _settings.NumSprites); RecalculateCrops(); CalculatePalette(); @@ -146,7 +146,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES private static byte[] GetDefaultColors() { - IntPtr src = LibQuickNES.qn_get_default_colors(); + IntPtr src = QN.qn_get_default_colors(); byte[] ret = new byte[1536]; Marshal.Copy(src, ret, 0, 1536); return ret; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IStatable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IStatable.cs index 1a1fbbe0d6..2f834515b9 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IStatable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IStatable.cs @@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES public void SaveStateBinary(System.IO.BinaryWriter writer) { CheckDisposed(); - LibQuickNES.ThrowStringError(LibQuickNES.qn_state_save(Context, SaveStateBuff, SaveStateBuff.Length)); + LibQuickNES.ThrowStringError(QN.qn_state_save(Context, SaveStateBuff, SaveStateBuff.Length)); writer.Write(SaveStateBuff.Length); writer.Write(SaveStateBuff); // other variables @@ -47,7 +47,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES if (len != SaveStateBuff.Length) throw new InvalidOperationException("Unexpected savestate buffer length!"); reader.Read(SaveStateBuff, 0, SaveStateBuff.Length); - LibQuickNES.ThrowStringError(LibQuickNES.qn_state_load(Context, SaveStateBuff, SaveStateBuff.Length)); + LibQuickNES.ThrowStringError(QN.qn_state_load(Context, SaveStateBuff, SaveStateBuff.Length)); // other variables IsLagFrame = reader.ReadBoolean(); LagCount = reader.ReadInt32(); @@ -73,7 +73,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES private void InitSaveStateBuff() { int size = 0; - LibQuickNES.ThrowStringError(LibQuickNES.qn_state_size(Context, ref size)); + LibQuickNES.ThrowStringError(QN.qn_state_size(Context, ref size)); SaveStateBuff = new byte[size]; SaveStateBuff2 = new byte[size + 13]; } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IVideoProvider.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IVideoProvider.cs index ac87d84b9c..630ed11220 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IVideoProvider.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.IVideoProvider.cs @@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES private void Blit() { - LibQuickNES.qn_blit(Context, VideoOutput, VideoPalette, cropleft, croptop, cropright, cropbottom); + QN.qn_blit(Context, VideoOutput, VideoPalette, cropleft, croptop, cropright, cropbottom); } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index 0567aa96a1..c52fc7934b 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -11,6 +11,7 @@ using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; using BizHawk.Common; using BizHawk.Common.CollectionExtensions; +using BizHawk.Emulation.Common.BizInvoke; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { @@ -26,9 +27,14 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES public partial class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider, ISaveRam, IInputPollable, IStatable, IDebuggable, ISettable, Cores.Nintendo.NES.INESPPUViewable { + static readonly LibQuickNES QN; + static readonly Win32LibraryImportResolver Resolver; + + static QuickNES() { - LibQuickNES.qn_setup_mappers(); + Resolver = new Win32LibraryImportResolver(LibQuickNES.dllname); + QN = BizInvoker.GetInvoker(Resolver); } [CoreConstructor("NES")] @@ -39,12 +45,19 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES ServiceProvider = new BasicServiceProvider(this); CoreComm = comm; - Context = LibQuickNES.qn_new(); + Context = QN.qn_new(); if (Context == IntPtr.Zero) throw new InvalidOperationException("qn_new() returned NULL"); try { - LibQuickNES.ThrowStringError(LibQuickNES.qn_loadines(Context, file, file.Length)); + unsafe + { + fixed (byte* p = file) + { + Console.WriteLine((IntPtr)p); + LibQuickNES.ThrowStringError(QN.qn_loadines(Context, file, file.Length)); + } + } InitSaveRamBuff(); InitSaveStateBuff(); @@ -52,7 +65,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES InitMemoryDomains(); int mapper = 0; - string mappername = Marshal.PtrToStringAnsi(LibQuickNES.qn_get_mapper(Context, ref mapper)); + string mappername = Marshal.PtrToStringAnsi(QN.qn_get_mapper(Context, ref mapper)); Console.WriteLine("QuickNES: Booted with Mapper #{0} \"{1}\"", mapper, mappername); BoardName = mappername; CoreComm.VsyncNum = 39375000; @@ -185,21 +198,21 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES using (FP.Save()) { if (Controller["Power"]) - LibQuickNES.qn_reset(Context, true); + QN.qn_reset(Context, true); if (Controller["Reset"]) - LibQuickNES.qn_reset(Context, false); + QN.qn_reset(Context, false); int j1, j2; SetPads(out j1, out j2); if (Tracer.Enabled) - LibQuickNES.qn_set_tracecb(Context, _tracecb); + QN.qn_set_tracecb(Context, _tracecb); else - LibQuickNES.qn_set_tracecb(Context, null); + QN.qn_set_tracecb(Context, null); Frame++; - LibQuickNES.ThrowStringError(LibQuickNES.qn_emulate_frame(Context, j1, j2)); - IsLagFrame = LibQuickNES.qn_get_joypad_read_count(Context) == 0; + LibQuickNES.ThrowStringError(QN.qn_emulate_frame(Context, j1, j2)); + IsLagFrame = QN.qn_get_joypad_read_count(Context) == 0; if (IsLagFrame) LagCount++; @@ -214,7 +227,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES } IntPtr Context; - public int Frame { get; private set; } public string SystemId { get { return "NES"; } } @@ -304,7 +316,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES { if (Context != IntPtr.Zero) { - LibQuickNES.qn_delete(Context); + QN.qn_delete(Context); Context = IntPtr.Zero; } } @@ -324,12 +336,12 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES void InitAudio() { - LibQuickNES.ThrowStringError(LibQuickNES.qn_set_sample_rate(Context, 44100)); + LibQuickNES.ThrowStringError(QN.qn_set_sample_rate(Context, 44100)); } void DrainAudio() { - NumSamples = LibQuickNES.qn_read_audio(Context, MonoBuff, MonoBuff.Length); + NumSamples = QN.qn_read_audio(Context, MonoBuff, MonoBuff.Length); unsafe { fixed (short* _src = &MonoBuff[0], _dst = &StereoBuff[0]) diff --git a/quicknes/bizinterface.cpp b/quicknes/bizinterface.cpp index 74e9ab3ac1..5304124e87 100644 --- a/quicknes/bizinterface.cpp +++ b/quicknes/bizinterface.cpp @@ -31,7 +31,11 @@ void operator delete(void *p) std::free(p); } +#ifdef _MSC_VER +#define EXPORT extern "C" __declspec(dllexport) +#else #define EXPORT extern "C" __declspec(dllexport) __attribute__((force_align_arg_pointer)) +#endif EXPORT void qn_setup_mappers() {