Remove BizExvoker (unused for a long time, probably never going to use it)

Move MemoryBlock stuff and FPCtrl over from BizInvoke to Common
Move imports over to Common, remove unused imports
Do tons of cleanup here
This commit is contained in:
CasualPokePlayer 2023-10-26 03:22:28 -07:00
parent 00e0a5fb2a
commit 5c475ce898
20 changed files with 394 additions and 568 deletions

View File

@ -1,124 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using BizHawk.Common;
using BizHawk.Common.CollectionExtensions;
namespace BizHawk.BizInvoke
{
public static class BizExvoker
{
/// <summary>
/// the assembly that all delegate types are placed in
/// </summary>
private static readonly AssemblyBuilder ImplAssemblyBuilder;
/// <summary>
/// the module that all delegate types are placed in
/// </summary>
private static readonly ModuleBuilder ImplModuleBuilder;
static BizExvoker()
{
var aname = new AssemblyName("BizExvokeProxyAssembly");
ImplAssemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run);
ImplModuleBuilder = ImplAssemblyBuilder.DefineDynamicModule("BizExvokerModule");
}
/// <summary>
/// holds the delegate types for a type
/// </summary>
private class DelegateStorage
{
/// <summary>
/// the type that this storage was made for
/// </summary>
public Type OriginalType { get; }
/// <summary>
/// the type that the delegate types reside in
/// </summary>
public Type StorageType { get; }
public List<StoredDelegateInfo> DelegateTypes { get; } = new List<StoredDelegateInfo>();
public class StoredDelegateInfo
{
public MethodInfo Method { get; }
public Type DelegateType { get; }
public string EntryPointName { get; }
public StoredDelegateInfo(MethodInfo method, Type delegateType, string entryPointName)
{
Method = method;
DelegateType = delegateType;
EntryPointName = entryPointName;
}
}
public DelegateStorage(Type type)
{
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Select(static m => (Info: m, Attr: m.GetCustomAttributes(true).OfType<BizExportAttribute>().FirstOrDefault()))
.Where(static a => a.Attr is not null);
var typeBuilder = ImplModuleBuilder.DefineType($"Bizhawk.BizExvokeHolder{type.Name}", TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed);
foreach (var a in methods)
{
var delegateType = BizInvokeUtilities.CreateDelegateType(a.Info, a.Attr!.CallingConvention, typeBuilder, out _).CreateType()!;
DelegateTypes.Add(new StoredDelegateInfo(a.Info, delegateType, a.Attr.EntryPoint ?? a.Info.Name));
}
StorageType = typeBuilder.CreateType()!;
OriginalType = type;
}
}
private class ExvokerImpl : IImportResolver
{
private readonly Dictionary<string, IntPtr> EntryPoints = new Dictionary<string, IntPtr>();
private readonly List<Delegate> Delegates = new List<Delegate>();
public ExvokerImpl(object o, DelegateStorage d, ICallingConventionAdapter a)
{
foreach (var sdt in d.DelegateTypes)
{
var del = Delegate.CreateDelegate(sdt.DelegateType, o, sdt.Method);
Delegates.Add(del); // prevent garbage collection of the delegate, which would invalidate the pointer
EntryPoints.Add(sdt.EntryPointName, a.GetFunctionPointerForDelegate(del));
}
}
public IntPtr GetProcAddrOrZero(string entryPoint) => EntryPoints.TryGetValue(entryPoint, out var ret) ? ret : IntPtr.Zero;
public IntPtr GetProcAddrOrThrow(string entryPoint) => EntryPoints.TryGetValue(entryPoint, out var ret) ? ret : throw new InvalidOperationException($"could not find {entryPoint} in exports");
}
private static readonly Dictionary<Type, DelegateStorage> Impls = new Dictionary<Type, DelegateStorage>();
public static IImportResolver GetExvoker(object o, ICallingConventionAdapter a)
{
DelegateStorage ds;
lock (Impls) ds = Impls.GetValueOrPutNew1(o.GetType());
return new ExvokerImpl(o, ds, a);
}
}
/// <summary>Indicates that a method is to be exported by BizExvoker.</summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class BizExportAttribute : Attribute
{
public CallingConvention CallingConvention { get; }
/// <remarks>The annotated method's name is used iff <see langword="null"/>.</remarks>
public string? EntryPoint { get; set; } = null;
public BizExportAttribute(CallingConvention c)
{
CallingConvention = c;
}
}
}

View File

@ -34,6 +34,7 @@ namespace BizHawk.BizInvoke
CallingConventions.Standard,
new[] { typeof(object), typeof(IntPtr) });
// ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
delegateCtor.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
var delegateInvoke = delegateType.DefineMethod(
@ -44,7 +45,7 @@ namespace BizHawk.BizInvoke
// we have to project all of the attributes from the baseMethod to the delegateInvoke
// so for something like [Out], the interop engine will see it and use it
for (int i = 0; i < paramInfos.Length; i++)
for (var i = 0; i < paramInfos.Length; i++)
{
var p = delegateInvoke.DefineParameter(i + 1, ParameterAttributes.None, paramInfos[i].Name);
foreach (var a in paramInfos[i].GetCustomAttributes(false))
@ -61,6 +62,7 @@ namespace BizHawk.BizInvoke
}
}
// ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
delegateInvoke.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
// add the [UnmanagedFunctionPointer] to the delegate so interop will know how to call it
@ -80,13 +82,15 @@ namespace BizHawk.BizInvoke
{
// anything more clever we can do here?
var t = o.GetType();
if (t == typeof(OutAttribute) || t == typeof(InAttribute))
{
return new CustomAttributeBuilder(t.GetConstructor(Type.EmptyTypes)!, Array.Empty<object>());
return new(t.GetConstructor(Type.EmptyTypes)!, Array.Empty<object>());
}
else if (t == typeof(MarshalAsAttribute))
{
return new CustomAttributeBuilder(t.GetConstructor(new[] { typeof(UnmanagedType) })!, new object[] { ((MarshalAsAttribute)o).Value });
if (t == typeof(MarshalAsAttribute))
{
return new(t.GetConstructor(new[] { typeof(UnmanagedType) })!, new object[] { ((MarshalAsAttribute)o).Value });
}
throw new InvalidOperationException($"Unknown parameter attribute {t.Name}");

View File

@ -5,6 +5,7 @@ using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using BizHawk.Common;
using BizHawk.Common.CollectionExtensions;
@ -152,11 +153,8 @@ namespace BizHawk.BizInvoke
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");
}
_ = baseType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null)
?? throw new InvalidOperationException("Base type must have a zero arg constructor");
var baseMethods = baseType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Select(static m => (Info: m, Attr: m.GetCustomAttributes(true).OfType<BizImportAttribute>().FirstOrDefault()))
@ -187,13 +185,13 @@ namespace BizHawk.BizInvoke
var adapterField = type.DefineField("CallingConvention", typeof(ICallingConventionAdapter), FieldAttributes.Public);
foreach (var mi in baseMethods)
foreach (var (info, attr) in baseMethods)
{
var entryPointName = mi.Attr!.EntryPoint ?? mi.Info.Name;
var entryPointName = attr!.EntryPoint ?? info.Name;
var hook = mi.Attr.Compatibility
? ImplementMethodDelegate(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField, nonTrivialAdapter)
: ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField, adapterField);
var hook = attr.Compatibility
? ImplementMethodDelegate(type, info, attr.CallingConvention, entryPointName, monitorField, nonTrivialAdapter)
: ImplementMethodCalli(type, info, attr.CallingConvention, entryPointName, monitorField, adapterField);
postCreateHooks.Add(hook);
}
@ -262,7 +260,7 @@ namespace BizHawk.BizInvoke
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
for (int i = 0; i < paramTypes.Length; i++)
for (var i = 0; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg, (short)(i + 1));
}
@ -336,9 +334,8 @@ namespace BizHawk.BizInvoke
{
var paramInfos = baseMethod.GetParameters();
var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray();
var paramLoadInfos = new List<ParameterLoadInfo>();
var returnType = baseMethod.ReturnType;
if (returnType != typeof(void) && !returnType.IsPrimitive && !returnType.IsPointer && !returnType.IsEnum)
if (returnType != typeof(void) && returnType is { IsPrimitive: false, IsPointer: false, IsEnum: false })
{
throw new InvalidOperationException("Only primitive return types are supported");
}
@ -368,11 +365,12 @@ namespace BizHawk.BizInvoke
}
// phase 1: empty eval stack and each parameter load thunk does any prep work it needs to do
for (int i = 0; i < paramTypes.Length; i++)
{
// arg 0 is this, so + 1
paramLoadInfos.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField));
}
var paramLoadInfos = paramTypes
.Select(
// arg 0 is this, so + 1
(t, i) => EmitParamterLoad(il, i + 1, t, adapterField))
.ToArray();
// phase 2: actually load the individual params, leaving each one on the stack
foreach (var pli in paramLoadInfos)
{
@ -427,7 +425,7 @@ namespace BizHawk.BizInvoke
{
var entryPtr = dll.GetProcAddrOrThrow(entryPointName);
o.GetType().GetField(field.Name).SetValue(
o, adapter.GetDepartureFunctionPointer(entryPtr, new ParameterInfo(returnType, paramTypes), o));
o, adapter.GetDepartureFunctionPointer(entryPtr, new(returnType, paramTypes), o));
};
}
@ -452,6 +450,7 @@ namespace BizHawk.BizInvoke
il.Emit(OpCodes.Conv_I);
}
#if false
/// <summary>
/// load a UIntPtr constant in an IL stream
/// </summary>
@ -472,6 +471,7 @@ namespace BizHawk.BizInvoke
il.Emit(OpCodes.Conv_U);
}
#endif
/// <summary>
/// emit a single parameter load with unmanaged conversions. The evaluation stack will be empty when the IL generated here runs,
@ -693,7 +693,7 @@ namespace BizHawk.BizInvoke
public CallingConvention CallingConvention { get; }
/// <remarks>The annotated method's name is used iff <see langword="null"/>.</remarks>
public string? EntryPoint { get; set; } = null;
public string? EntryPoint { get; set; }
/// <summary><see langword="true"/> iff a compatibility interop should be used, which is slower but supports more argument types.</summary>
public bool Compatibility { get; set; }

View File

@ -4,6 +4,13 @@ using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
// ReSharper disable ClassNeverInstantiated.Local
// ReSharper disable MemberCanBePrivate.Local
// ReSharper disable NotAccessedField.Local
#pragma warning disable CS0414
#pragma warning disable CS0649
namespace BizHawk.BizInvoke
{
public static unsafe class BizInvokerUtilities
@ -51,9 +58,7 @@ namespace BizHawk.BizInvoke
/// </summary>
/// <returns></returns>
public static int ComputeClassFirstFieldOffset()
{
return ComputeFieldOffset(typeof(CF).GetField("FirstField"));
}
=> ComputeFieldOffset(typeof(CF).GetField("FirstField"));
/// <summary>
/// Compute the byte offset of the first byte of string data (UTF16) relative to a pointer to the string.
@ -65,7 +70,7 @@ namespace BizHawk.BizInvoke
int ret;
fixed(char* fx = s)
{
U u = new(new U2(s));
U u = new(new(s));
ret = (int) ((ulong) (UIntPtr) fx - (ulong) u.First!.P);
}
return ret;
@ -81,12 +86,13 @@ namespace BizHawk.BizInvoke
int ret;
fixed (int* p = arr)
{
U u = new(new U2(arr));
U u = new(new(arr));
ret = (int)((ulong)(UIntPtr) p - (ulong) u.First!.P);
}
return ret;
}
#if false
/// <summary>
/// Compute the offset to the 0th element of an array of object types
/// Slow, so cache it if you need it.
@ -112,6 +118,7 @@ namespace BizHawk.BizInvoke
var del = (Func<object[], int>)method.CreateDelegate(typeof(Func<object[], int>));
return del(obj);
}
#endif
/// <summary>
/// Compute the byte offset of a field relative to a pointer to the class instance.
@ -119,7 +126,7 @@ namespace BizHawk.BizInvoke
/// </summary>
public static int ComputeFieldOffset(FieldInfo fi)
{
if (fi.DeclaringType.IsValueType)
if (fi.DeclaringType!.IsValueType)
{
throw new NotImplementedException("Only supported for class fields right now");
}

View File

@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BizHawk.Common;
namespace BizHawk.BizInvoke
@ -17,6 +19,7 @@ namespace BizHawk.BizInvoke
/// to adjust the calling convention appropriately
/// </summary>
IntPtr GetFunctionPointerForDelegate(Delegate d);
/// <summary>
/// Like Marshal.GetFunctionPointerForDelegate, but only the unmanaged thunk-to-thunk part, with no
/// managed wrapper involved. Called "arrival" because it is to be used when the foreign code is calling
@ -67,7 +70,10 @@ namespace BizHawk.BizInvoke
public ParameterInfo(Type delegateType)
{
if (!typeof(Delegate).IsAssignableFrom(delegateType))
{
throw new InvalidOperationException("Must be a delegate type!");
}
var invoke = delegateType.GetMethod("Invoke")!;
ReturnType = invoke.ReturnType;
ParameterTypes = invoke.GetParameters().Select(p => p.ParameterType).ToList().AsReadOnly();
@ -96,24 +102,16 @@ namespace BizHawk.BizInvoke
private class NativeConvention : ICallingConventionAdapter
{
public IntPtr GetArrivalFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime)
{
return p;
}
=> p;
public Delegate GetDelegateForFunctionPointer(IntPtr p, Type delegateType)
{
return Marshal.GetDelegateForFunctionPointer(p, delegateType);
}
=> Marshal.GetDelegateForFunctionPointer(p, delegateType);
public IntPtr GetDepartureFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime)
{
return p;
}
=> p;
public IntPtr GetFunctionPointerForDelegate(Delegate d)
{
return Marshal.GetFunctionPointerForDelegate(d);
}
=> Marshal.GetFunctionPointerForDelegate(d);
}
/// <summary>
@ -125,16 +123,13 @@ namespace BizHawk.BizInvoke
/// waterbox calling convention, including thunk handling for stack marshalling
/// </summary>
public static ICallingConventionAdapter MakeWaterbox(IEnumerable<Delegate> slots, ICallbackAdjuster waterboxHost)
{
return new WaterboxAdapter(slots, waterboxHost);
}
=> new WaterboxAdapter(slots, waterboxHost);
/// <summary>
/// waterbox calling convention, including thunk handling for stack marshalling. Can only do callins, not callouts
/// </summary>
public static ICallingConventionAdapter MakeWaterboxDepartureOnly(ICallbackAdjuster waterboxHost)
{
return new WaterboxAdapter(null, waterboxHost);
}
=> new WaterboxAdapter(null, waterboxHost);
/// <summary>
/// Get a waterbox calling convention adapater, except no wrapping is done for stack marshalling and callback support.
@ -143,26 +138,21 @@ namespace BizHawk.BizInvoke
/// </summary>
/// <returns></returns>
public static ICallingConventionAdapter GetWaterboxUnsafeUnwrapped()
{
return WaterboxAdapter.WaterboxWrapper;
}
=> WaterboxAdapter.WaterboxWrapper;
private class WaterboxAdapter : ICallingConventionAdapter
{
private class ReferenceEqualityComparer : IEqualityComparer<Delegate>
{
public bool Equals(Delegate x, Delegate y)
{
return x == y;
}
=> x == y;
public int GetHashCode(Delegate obj)
{
return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
}
=> RuntimeHelpers.GetHashCode(obj);
}
internal static readonly ICallingConventionAdapter WaterboxWrapper;
static WaterboxAdapter()
{
WaterboxWrapper = OSTailoredCode.IsUnixHost
@ -180,6 +170,7 @@ namespace BizHawk.BizInvoke
_slots = slots.Select(static (cb, i) => (cb, i))
.ToDictionary(a => a.cb, a => a.i, new ReferenceEqualityComparer());
}
_waterboxHost = waterboxHost;
}
@ -189,11 +180,17 @@ namespace BizHawk.BizInvoke
{
throw new InvalidOperationException("This calling convention adapter was created for departure only! Pass known delegate slots when constructing to enable arrival");
}
if (lifetime is not Delegate d) throw new ArgumentException(message: "For this calling convention adapter, lifetimes must be delegate so guest slot can be inferred", paramName: nameof(lifetime));
if (lifetime is not Delegate d)
{
throw new ArgumentException(message: "For this calling convention adapter, lifetimes must be delegate so guest slot can be inferred", paramName: nameof(lifetime));
}
if (!_slots.TryGetValue(d, out var slot))
{
throw new InvalidOperationException("All callback delegates must be registered at load");
}
return _waterboxHost.GetCallbackProcAddr(WaterboxWrapper.GetArrivalFunctionPointer(p, pp, lifetime), slot);
}
@ -215,10 +212,12 @@ namespace BizHawk.BizInvoke
{
throw new InvalidOperationException("This calling convention adapter was created for departure only! Pass known delegate slots when constructing to enable arrival");
}
if (!_slots.TryGetValue(d, out var slot))
{
throw new InvalidOperationException("All callback delegates must be registered at load");
}
return _waterboxHost.GetCallbackProcAddr(WaterboxWrapper.GetFunctionPointerForDelegate(d), slot);
}
}
@ -231,6 +230,7 @@ namespace BizHawk.BizInvoke
{
// This is implemented by using thunks defined in a small dll, and putting stubs on top of them that close over the
// function pointer parameter. A dll is used here to easily set unwind information (allowing SEH exceptions to work).
// TODO: Another dll might be required for ARM64? Investigate
private const int BlockSize = 32;
private static readonly IImportResolver ThunkDll;
@ -253,33 +253,40 @@ namespace BizHawk.BizInvoke
private int FindFreeIndex()
{
for (int i = 0; i < _refs.Length; i++)
for (var i = 0; i < _refs.Length; i++)
{
if (_refs[i] is not { IsAlive: true }) return i; // return index of first null or dead
if (_refs[i] is not { IsAlive: true })
{
return i; // return index of first null or dead
}
}
throw new InvalidOperationException("Out of Thunk memory");
}
private int FindUsedIndex(object lifetime)
{
for (int i = 0; i < _refs.Length; i++)
for (var i = 0; i < _refs.Length; i++)
{
if (_refs[i]?.Target == lifetime)
{
return i;
}
}
return -1;
}
private static void VerifyParameter(Type type)
{
if (type == typeof(float) || type == typeof(double))
return;
if (type == typeof(void) || type.IsPrimitive || type.IsEnum)
return;
if (type.IsPointer || typeof(Delegate).IsAssignableFrom(type))
return;
if (type.IsByRef || type.IsClass)
if (type == typeof(float) || type == typeof(double) ||
type == typeof(void) || type.IsPrimitive || type.IsEnum ||
type.IsPointer || typeof(Delegate).IsAssignableFrom(type) ||
type.IsByRef || type.IsClass)
{
return;
}
throw new NotSupportedException($"Unknown type {type}. Possibly supported?");
}
@ -287,16 +294,25 @@ namespace BizHawk.BizInvoke
{
VerifyParameter(pp.ReturnType);
foreach (var ppp in pp.ParameterTypes)
{
VerifyParameter(ppp);
}
var ret = pp.ParameterTypes.Count(t => t != typeof(float) && t != typeof(double));
var fargs = pp.ParameterTypes.Count - ret;
if (ret > 6 || fargs > 4)
{
throw new InvalidOperationException("Too many parameters to marshal!");
}
// a function may only use exclusively floating point args or integer/pointer args
// mixing these is not supported, due to complex differences with how msabi and sysv
// decide which register to use when dealing with this mixing
if (ret > 0 && fargs > 0)
{
throw new NotSupportedException("Mixed integer/pointer and floating point parameters are not supported!");
}
return ret;
}
@ -326,9 +342,7 @@ namespace BizHawk.BizInvoke
}
private IntPtr GetThunkAddress(int index)
{
return Z.US(_memory.Start + (ulong)index * BlockSize);
}
=> Z.US(_memory.Start + (ulong)index * BlockSize);
private void SetLifetime(int index, object lifetime)
{
@ -347,11 +361,9 @@ namespace BizHawk.BizInvoke
{
return GetThunkAddress(index);
}
else
{
return GetArrivalFunctionPointer(
Marshal.GetFunctionPointerForDelegate(d), new(d.GetType()), d);
}
return GetArrivalFunctionPointer(
Marshal.GetFunctionPointerForDelegate(d), new(d.GetType()), d);
}
}
@ -391,7 +403,6 @@ namespace BizHawk.BizInvoke
return GetThunkAddress(index);
}
}
}
}
}

View File

@ -1,188 +0,0 @@
using System;
using System.Runtime.InteropServices;
using static BizHawk.BizInvoke.MemoryBlock;
namespace BizHawk.BizInvoke
{
internal sealed unsafe class MemoryBlockWindowsPal : IMemoryBlockPal
{
public ulong Start { get; }
private readonly ulong _size;
private bool _disposed;
public MemoryBlockWindowsPal(ulong size)
{
var ptr = (ulong)Kernel32.VirtualAlloc(
UIntPtr.Zero, Z.UU(size), Kernel32.AllocationType.MEM_RESERVE | Kernel32.AllocationType.MEM_COMMIT, Kernel32.MemoryProtection.NOACCESS);
if (ptr == 0)
throw new InvalidOperationException($"{nameof(Kernel32.VirtualAlloc)}() returned NULL");
Start = ptr;
_size = size;
}
public void Protect(ulong start, ulong size, Protection prot)
{
if (!Kernel32.VirtualProtect(Z.UU(start), Z.UU(size), GetKernelMemoryProtectionValue(prot), out var old))
throw new InvalidOperationException($"{nameof(Kernel32.VirtualProtect)}() returned FALSE!");
}
private static Kernel32.MemoryProtection GetKernelMemoryProtectionValue(Protection prot)
{
Kernel32.MemoryProtection p;
switch (prot)
{
case Protection.None: p = Kernel32.MemoryProtection.NOACCESS; break;
case Protection.R: p = Kernel32.MemoryProtection.READONLY; break;
case Protection.RW: p = Kernel32.MemoryProtection.READWRITE; break;
case Protection.RX: p = Kernel32.MemoryProtection.EXECUTE_READ; break;
default: throw new ArgumentOutOfRangeException(nameof(prot));
}
return p;
}
public void Dispose()
{
if (_disposed)
return;
Kernel32.VirtualFree(Z.UU(Start), UIntPtr.Zero, Kernel32.FreeType.Release);
_disposed = true;
GC.SuppressFinalize(this);
}
~MemoryBlockWindowsPal()
{
Dispose();
}
private static class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern UIntPtr VirtualAlloc(UIntPtr lpAddress, UIntPtr dwSize,
AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualProtect(
UIntPtr lpAddress,
UIntPtr dwSize,
MemoryProtection flNewProtect,
out MemoryProtection lpflOldProtect);
[Flags]
public enum AllocationType : uint
{
MEM_COMMIT = 0x00001000,
MEM_RESERVE = 0x00002000,
MEM_RESET = 0x00080000,
MEM_RESET_UNDO = 0x1000000,
MEM_LARGE_PAGES = 0x20000000,
MEM_PHYSICAL = 0x00400000,
MEM_TOP_DOWN = 0x00100000,
MEM_WRITE_WATCH = 0x00200000
}
[Flags]
public enum MemoryProtection : uint
{
EXECUTE = 0x10,
EXECUTE_READ = 0x20,
EXECUTE_READWRITE = 0x40,
EXECUTE_WRITECOPY = 0x80,
NOACCESS = 0x01,
READONLY = 0x02,
READWRITE = 0x04,
WRITECOPY = 0x08,
GUARD_Modifierflag = 0x100,
NOCACHE_Modifierflag = 0x200,
WRITECOMBINE_Modifierflag = 0x400
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFileMapping(
IntPtr hFile,
IntPtr lpFileMappingAttributes,
FileMapProtection flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);
[Flags]
public enum FileMapProtection : uint
{
PageReadonly = 0x02,
PageReadWrite = 0x04,
PageWriteCopy = 0x08,
PageExecuteRead = 0x20,
PageExecuteReadWrite = 0x40,
SectionCommit = 0x8000000,
SectionImage = 0x1000000,
SectionNoCache = 0x10000000,
SectionReserve = 0x4000000,
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32.dll")]
public static extern IntPtr MapViewOfFileEx(
IntPtr hFileMappingObject,
FileMapAccessType dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
UIntPtr dwNumberOfBytesToMap,
IntPtr lpBaseAddress);
[Flags]
public enum FileMapAccessType : uint
{
Copy = 0x01,
Write = 0x02,
Read = 0x04,
AllAccess = 0x08,
Execute = 0x20,
}
public static readonly IntPtr INVALID_HANDLE_VALUE = Z.US(0xffffffffffffffff);
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public MemoryProtection AllocationProtect;
public UIntPtr RegionSize;
public StateEnum State;
public MemoryProtection Protect;
public TypeEnum Type;
}
public enum StateEnum : uint
{
MEM_COMMIT = 0x1000,
MEM_FREE = 0x10000,
MEM_RESERVE = 0x2000
}
public enum TypeEnum : uint
{
MEM_IMAGE = 0x1000000,
MEM_MAPPED = 0x40000,
MEM_PRIVATE = 0x20000
}
[DllImport("kernel32.dll")]
public static extern UIntPtr VirtualQuery(UIntPtr lpAddress, MEMORY_BASIC_INFORMATION* lpBuffer, UIntPtr dwLength);
[Flags]
public enum FreeType
{
Decommit = 0x4000,
Release = 0x8000,
}
[DllImport("kernel32.dll")]
public static extern bool VirtualFree(UIntPtr lpAddress, UIntPtr dwSize, FreeType dwFreeType);
}
}
}

View File

@ -1,33 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace BizHawk.BizInvoke
{
public static class POSIXLibC
{
[DllImport("libc.so.6")]
public static extern int close(int fd);
[DllImport("libc.so.6")]
public static extern int memfd_create(string name, uint flags);
[DllImport("libc.so.6")]
private static extern IntPtr mmap(IntPtr addr, UIntPtr length, int prot, int flags, int fd, IntPtr offset);
public static IntPtr mmap(IntPtr addr, UIntPtr length, MemoryProtection prot, int flags, int fd, IntPtr offset) => mmap(addr, length, (int) prot, flags, fd, offset);
[DllImport("libc.so.6")]
private static extern int mprotect(IntPtr addr, UIntPtr len, int prot);
public static int mprotect(IntPtr addr, UIntPtr len, MemoryProtection prot) => mprotect(addr, len, (int) prot);
[DllImport("libc.so.6")]
public static extern int munmap(IntPtr addr, UIntPtr length);
[DllImport("libc.so.6")]
public static extern int ftruncate(int fd, IntPtr length);
/// <remarks>32-bit signed int</remarks>
[Flags]
public enum MemoryProtection : int { None = 0x0, Read = 0x1, Write = 0x2, Execute = 0x4 }
}
}

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using BizHawk.BizInvoke;
using BizHawk.Common;
using BizHawk.Emulation.Common;
@ -324,16 +323,15 @@ namespace BizHawk.Client.Common
{
var startByte = _states[_firstStateIndex].Start;
var endByte = (_states[HeadStateIndex].Start + _states[HeadStateIndex].Size) & _sizeMask;
var destStream = SpanStream.GetOrBuild(writer.BaseStream);
if (startByte > endByte)
{
_backingStore.Position = startByte;
WaterboxUtils.CopySome(_backingStore, writer.BaseStream, Size - startByte);
MemoryBlockUtils.CopySome(_backingStore, writer.BaseStream, Size - startByte);
startByte = 0;
}
{
_backingStore.Position = startByte;
WaterboxUtils.CopySome(_backingStore, writer.BaseStream, endByte - startByte);
MemoryBlockUtils.CopySome(_backingStore, writer.BaseStream, endByte - startByte);
}
}
}
@ -351,7 +349,7 @@ namespace BizHawk.Client.Common
nextByte += _states[i].Size;
}
_backingStore.Position = 0;
WaterboxUtils.CopySome(reader.BaseStream, _backingStore, nextByte);
MemoryBlockUtils.CopySome(reader.BaseStream, _backingStore, nextByte);
}
public static ZwinderBuffer Create(BinaryReader reader, RewindConfig rewindConfig, bool hackyV0 = false)

View File

@ -6,7 +6,6 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using BizHawk.BizInvoke;
using BizHawk.Bizware.BizwareGL;
using BizHawk.Bizware.Graphics;
using BizHawk.Common;

View File

@ -2,7 +2,7 @@
using System.IO;
using System.Runtime.InteropServices;
namespace BizHawk.BizInvoke
namespace BizHawk.Common
{
// .NET is weird and sets the x87 control register to double precision
// It does this even on Linux which normally expects it set to extended x87 precision
@ -17,9 +17,7 @@ namespace BizHawk.BizInvoke
// This can extend to issues in games: https://github.com/TASEmulators/BizHawk/issues/3726
public static class FPCtrl
{
private const CallingConvention cc = CallingConvention.Cdecl;
[UnmanagedFunctionPointer(cc)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void FixFPCtrlDelegate();
private static readonly MemoryBlock? _memory;

View File

@ -1,6 +1,6 @@
using System;
namespace BizHawk.BizInvoke
namespace BizHawk.Common
{
/// <summary>
/// Platform abstraction layer over mmap like functionality
@ -8,6 +8,7 @@ namespace BizHawk.BizInvoke
public interface IMemoryBlockPal : IDisposable
{
public ulong Start { get; }
/// <summary>
/// Change protection on [start, start + size), guaranteed to be page aligned and in the allocated area
/// </summary>

View File

@ -1,25 +1,29 @@
using System;
using System.IO;
using BizHawk.Common;
namespace BizHawk.BizInvoke
namespace BizHawk.Common
{
public class MemoryBlock : IDisposable /*, IBinaryStateable */
public class MemoryBlock : IDisposable
{
/// <summary>allocate <paramref name="size"/> bytes</summary>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="size"/> is not aligned or is <c>0</c></exception>
public MemoryBlock(ulong size)
{
if (!WaterboxUtils.Aligned(size))
if (!MemoryBlockUtils.Aligned(size))
{
throw new ArgumentOutOfRangeException(nameof(size), size, "size must be aligned");
if (size == 0)
throw new ArgumentOutOfRangeException(nameof(size), size, "cannot create 0-length block");
Size = WaterboxUtils.AlignUp(size);
}
if (size == 0)
{
throw new ArgumentOutOfRangeException(nameof(size), size, "cannot create 0-length block");
}
Size = MemoryBlockUtils.AlignUp(size);
_pal = OSTailoredCode.IsUnixHost
? new MemoryBlockLinuxPal(Size)
: new MemoryBlockWindowsPal(Size);
Start = _pal!.Start;
Start = _pal.Start;
EndExclusive = Start + Size;
}
@ -46,27 +50,52 @@ namespace BizHawk.BizInvoke
/// <exception cref="ObjectDisposedException">disposed</exception>
public Stream GetStream(ulong start, ulong length, bool writer)
{
if (_pal == null) throw new ObjectDisposedException(nameof(MemoryBlock));
if (_pal == null)
{
throw new ObjectDisposedException(nameof(MemoryBlock));
}
if (start < Start)
{
throw new ArgumentOutOfRangeException(nameof(start), start, "invalid address");
}
if (EndExclusive < start + length)
{
throw new ArgumentOutOfRangeException(nameof(length), length, "requested length implies invalid end address");
}
return new MemoryViewStream(!writer, writer, (long)start, (long)length);
}
/// <summary>Memory protection constant</summary>
public enum Protection : byte
{
None,
R,
RW,
RX,
}
/// <summary>set r/w/x protection on a portion of memory. rounded to encompassing pages</summary>
/// <exception cref="InvalidOperationException">failed to protect memory</exception>
/// <exception cref="ObjectDisposedException">disposed</exception>
public void Protect(ulong start, ulong length, Protection prot)
{
if (_pal == null) throw new ObjectDisposedException(nameof(MemoryBlock));
if (_pal == null)
{
throw new ObjectDisposedException(nameof(MemoryBlock));
}
if (length == 0)
{
return;
}
// Note: asking for prot.none on memory that was not previously committed, commits it
var computedStart = WaterboxUtils.AlignDown(start);
var computedEnd = WaterboxUtils.AlignUp(start + length);
var computedStart = MemoryBlockUtils.AlignDown(start);
var computedEnd = MemoryBlockUtils.AlignUp(start + length);
var computedLength = computedEnd - computedStart;
_pal.Protect(computedStart, computedLength, prot);
@ -80,14 +109,5 @@ namespace BizHawk.BizInvoke
_pal = null;
}
}
/// <summary>Memory protection constant</summary>
public enum Protection : byte
{
None,
R,
RW,
RX,
}
}
}

View File

@ -1,9 +1,10 @@
using System;
using System.Runtime.InteropServices;
using static BizHawk.BizInvoke.MemoryBlock;
using static BizHawk.BizInvoke.POSIXLibC;
namespace BizHawk.BizInvoke
using static BizHawk.Common.MemoryBlock;
using static BizHawk.Common.MmanImports;
namespace BizHawk.Common
{
internal sealed class MemoryBlockLinuxPal : IMemoryBlockPal
{
@ -20,43 +21,35 @@ namespace BizHawk.BizInvoke
/// </exception>
public MemoryBlockLinuxPal(ulong size)
{
var ptr = (ulong)mmap(IntPtr.Zero, Z.UU(size), MemoryProtection.None, 0x22 /* MAP_PRIVATE | MAP_ANON */, -1, IntPtr.Zero);
if (ptr == ulong.MaxValue)
var ptr = mmap(IntPtr.Zero, Z.UU(size), MemoryProtection.None, 0x22 /* MAP_PRIVATE | MAP_ANON */, -1, IntPtr.Zero);
if (ptr == new IntPtr(-1))
{
throw new InvalidOperationException($"{nameof(mmap)}() failed with error {Marshal.GetLastWin32Error()}");
}
_size = size;
Start = ptr;
Start = (ulong)ptr;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_ = munmap(Z.US(Start), Z.UU(_size));
_disposed = true;
GC.SuppressFinalize(this);
}
~MemoryBlockLinuxPal()
private static MemoryProtection ToMemoryProtection(Protection prot) => prot switch
{
Dispose();
}
private static MemoryProtection ToMemoryProtection(Protection prot)
{
switch (prot)
{
case Protection.None:
return MemoryProtection.None;
case Protection.R:
return MemoryProtection.Read;
case Protection.RW:
return MemoryProtection.Read | MemoryProtection.Write;
case Protection.RX:
return MemoryProtection.Read | MemoryProtection.Execute;
default:
throw new ArgumentOutOfRangeException(nameof(prot));
}
}
Protection.None => MemoryProtection.None,
Protection.R => MemoryProtection.Read,
Protection.RW => MemoryProtection.Read | MemoryProtection.Write,
Protection.RX => MemoryProtection.Read | MemoryProtection.Execute,
_ => throw new InvalidOperationException(nameof(prot))
};
public void Protect(ulong start, ulong size, Protection prot)
{
@ -65,8 +58,11 @@ namespace BizHawk.BizInvoke
Z.UU(size),
ToMemoryProtection(prot)
);
if (errorCode != 0)
{
throw new InvalidOperationException($"{nameof(mprotect)}() failed with error {Marshal.GetLastWin32Error()}!");
}
}
}
}

View File

@ -1,56 +1,54 @@
using System;
using System.Buffers;
using System.IO;
namespace BizHawk.BizInvoke
namespace BizHawk.Common
{
public static class WaterboxUtils
public static class MemoryBlockUtils
{
/// <summary>
/// copy `len` bytes from `src` to `dest`
/// </summary>
public static void CopySome(Stream src, Stream dst, long len)
{
var buff = new byte[65536];
while (len > 0)
const int TEMP_BUFFER_LENGTH = 65536;
var tmpBuf = ArrayPool<byte>.Shared.Rent(TEMP_BUFFER_LENGTH);
try
{
int r = src.Read(buff, 0, (int)Math.Min(len, 65536));
if (r == 0)
throw new InvalidOperationException($"End of source stream was reached with {len} bytes left to copy!");
dst.Write(buff, 0, r);
len -= r;
}
}
while (len > 0)
{
var r = src.Read(tmpBuf, 0, (int)Math.Min(len, TEMP_BUFFER_LENGTH));
if (r == 0)
{
throw new InvalidOperationException($"End of source stream was reached with {len} bytes left to copy!");
}
public static unsafe void ZeroMemory(IntPtr mem, long length)
{
byte* p = (byte*)mem;
byte* end = p + length;
while (p < end)
dst.Write(tmpBuf, 0, r);
len -= r;
}
}
finally
{
*p++ = 0;
ArrayPool<byte>.Shared.Return(tmpBuf);
}
}
public static long Timestamp()
{
return DateTime.UtcNow.Ticks;
}
/// <summary>
/// system page size
/// System page size, currently hardcoded/assumed to be 4096
/// </summary>
public const int PageSize = 4096;
public const int PageSize = 1 << PageShift;
/// <summary>
/// bitshift corresponding to PageSize
/// </summary>
public const int PageShift = 12;
/// <summary>
/// bitmask corresponding to PageSize
/// </summary>
public const ulong PageMask = 4095;
public const ulong PageMask = PageSize - 1;
static WaterboxUtils()
static MemoryBlockUtils()
{
if (PageSize != Environment.SystemPageSize)
{
@ -63,33 +61,25 @@ namespace BizHawk.BizInvoke
/// true if addr is aligned
/// </summary>
public static bool Aligned(ulong addr)
{
return (addr & PageMask) == 0;
}
=> (addr & PageMask) == 0;
/// <summary>
/// align address down to previous page boundary
/// </summary>
public static ulong AlignDown(ulong addr)
{
return addr & ~PageMask;
}
=> addr & ~PageMask;
/// <summary>
/// align address up to next page boundary
/// </summary>
public static ulong AlignUp(ulong addr)
{
return ((addr - 1) | PageMask) + 1;
}
=> ((addr - 1) | PageMask) + 1;
/// <summary>
/// return the minimum number of pages needed to hold size
/// </summary>
public static int PagesNeeded(ulong size)
{
return (int)((size + PageMask) >> PageShift);
}
=> (int)((size + PageMask) >> PageShift);
}
// C# is annoying: arithmetic operators for native ints are not exposed.

View File

@ -0,0 +1,54 @@
using System;
using static BizHawk.Common.Kernel32Imports;
using static BizHawk.Common.MemoryBlock;
namespace BizHawk.Common
{
internal sealed class MemoryBlockWindowsPal : IMemoryBlockPal
{
public ulong Start { get; }
private bool _disposed;
public MemoryBlockWindowsPal(ulong size)
{
var ptr = (ulong)VirtualAlloc(
UIntPtr.Zero, Z.UU(size), AllocationType.MEM_RESERVE | AllocationType.MEM_COMMIT, MemoryProtection.NOACCESS);
if (ptr == 0)
{
throw new InvalidOperationException($"{nameof(VirtualAlloc)}() returned NULL");
}
Start = ptr;
}
public void Protect(ulong start, ulong size, Protection prot)
{
if (!VirtualProtect(Z.UU(start), Z.UU(size), GetKernelMemoryProtectionValue(prot), out _))
{
throw new InvalidOperationException($"{nameof(VirtualProtect)}() returned FALSE!");
}
}
private static MemoryProtection GetKernelMemoryProtectionValue(Protection prot) => prot switch
{
Protection.None => MemoryProtection.NOACCESS,
Protection.R => MemoryProtection.READONLY,
Protection.RW => MemoryProtection.READWRITE,
Protection.RX => MemoryProtection.EXECUTE_READ,
_ => throw new InvalidOperationException(nameof(prot))
};
public void Dispose()
{
if (_disposed)
{
return;
}
VirtualFree(Z.UU(Start), UIntPtr.Zero, FreeType.Release);
_disposed = true;
}
}
}

View File

@ -1,8 +1,7 @@
using System;
using System.IO;
using BizHawk.Common;
namespace BizHawk.BizInvoke
namespace BizHawk.Common
{
/// <summary>
/// Create a stream that allows read/write over a set of unmanaged memory pointers
@ -23,21 +22,25 @@ namespace BizHawk.BizInvoke
private readonly long _ptr;
private readonly bool _readable;
private readonly bool _writable;
private bool _closed;
private long _pos;
private bool _closed;
public override bool CanRead => _readable;
public override bool CanSeek => true;
public override bool CanWrite => _writable;
public override long Length => _length;
public override long Position
{
get => _pos;
set
{
if (value < 0 || value > _length)
{
throw new ArgumentOutOfRangeException(paramName: nameof(value), value, message: "index out of range");
}
_pos = value;
}
}
@ -45,17 +48,25 @@ namespace BizHawk.BizInvoke
private void EnsureNotDisposed()
{
if (_closed)
{
throw new ObjectDisposedException(nameof(MemoryViewStream));
}
}
public override void Flush() {}
public override void Flush()
{
}
private byte* CurrentPointer() => (byte*)Z.SS(_ptr + _pos);
private byte* CurrentPointer()
=> (byte*)Z.SS(_ptr + _pos);
public int Read(Span<byte> buffer)
{
if (!_readable)
{
throw new IOException();
}
EnsureNotDisposed();
var count = (int)Math.Min(buffer.Length, _length - _pos);
new ReadOnlySpan<byte>(CurrentPointer(), count).CopyTo(buffer);
@ -64,76 +75,66 @@ namespace BizHawk.BizInvoke
}
public override int Read(byte[] buffer, int offset, int count)
{
return Read(new Span<byte>(buffer, offset, count));
}
=> Read(new(buffer, offset, count));
public override int ReadByte()
{
if (_pos < _length)
{
var ret = *CurrentPointer();
_pos++;
return ret;
}
else
if (_pos >= _length)
{
return -1;
}
var ret = *CurrentPointer();
_pos++;
return ret;
}
public override long Seek(long offset, SeekOrigin origin)
{
long newpos;
switch (origin)
var newpos = origin switch
{
default:
case SeekOrigin.Begin:
newpos = offset;
break;
case SeekOrigin.Current:
newpos = _pos + offset;
break;
case SeekOrigin.End:
newpos = _length + offset;
break;
}
SeekOrigin.Begin => offset,
SeekOrigin.Current => _pos + offset,
SeekOrigin.End => _length + offset,
_ => offset
};
Position = newpos;
return newpos;
}
public override void SetLength(long value)
{
throw new IOException();
}
=> throw new IOException();
public void Write(ReadOnlySpan<byte> buffer)
{
if (!_writable)
{
throw new IOException();
}
EnsureNotDisposed();
if (_pos + buffer.Length > _length)
{
throw new IOException("End of non-resizable stream");
buffer.CopyTo(new Span<byte>(CurrentPointer(), buffer.Length));
}
buffer.CopyTo(new(CurrentPointer(), buffer.Length));
_pos += buffer.Length;
}
public override void Write(byte[] buffer, int offset, int count)
{
Write(new ReadOnlySpan<byte>(buffer, offset, count));
}
=> Write(new(buffer, offset, count));
public override void WriteByte(byte value)
{
if (_pos < _length)
{
*CurrentPointer() = value;
_pos++;
}
else
if (_pos >= _length)
{
throw new IOException("End of non-resizable stream");
}
*CurrentPointer() = value;
_pos++;
}
protected override void Dispose(bool disposing)

View File

@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
namespace BizHawk.Common
{
public static class MmanImports
{
[Flags]
public enum MemoryProtection : int
{
None = 0x0,
Read = 0x1,
Write = 0x2,
Execute = 0x4
}
[DllImport("libc.so.6")]
public static extern IntPtr mmap(IntPtr addr, UIntPtr length, MemoryProtection prot, int flags, int fd, IntPtr offset);
[DllImport("libc.so.6")]
public static extern int mprotect(IntPtr addr, UIntPtr len, MemoryProtection prot);
[DllImport("libc.so.6")]
public static extern int munmap(IntPtr addr, UIntPtr length);
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Runtime.InteropServices;
// ReSharper disable FieldCanBeMadeReadOnly.Global
namespace BizHawk.Common
{
public static class Kernel32Imports
{
[Flags]
public enum AllocationType : uint
{
MEM_COMMIT = 0x00001000,
MEM_RESERVE = 0x00002000,
MEM_RESET = 0x00080000,
MEM_RESET_UNDO = 0x1000000,
MEM_LARGE_PAGES = 0x20000000,
MEM_PHYSICAL = 0x00400000,
MEM_TOP_DOWN = 0x00100000,
MEM_WRITE_WATCH = 0x00200000
}
[Flags]
public enum MemoryProtection : uint
{
EXECUTE = 0x10,
EXECUTE_READ = 0x20,
EXECUTE_READWRITE = 0x40,
EXECUTE_WRITECOPY = 0x80,
NOACCESS = 0x01,
READONLY = 0x02,
READWRITE = 0x04,
WRITECOPY = 0x08,
GUARD_Modifierflag = 0x100,
NOCACHE_Modifierflag = 0x200,
WRITECOMBINE_Modifierflag = 0x400
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern UIntPtr VirtualAlloc(UIntPtr lpAddress, UIntPtr dwSize,
AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool VirtualProtect(
UIntPtr lpAddress,
UIntPtr dwSize,
MemoryProtection flNewProtect,
out MemoryProtection lpflOldProtect);
[Flags]
public enum FreeType : uint
{
Decommit = 0x4000,
Release = 0x8000,
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool VirtualFree(UIntPtr lpAddress, UIntPtr dwSize, FreeType dwFreeType);
}
}

View File

@ -216,13 +216,16 @@ namespace BizHawk.Emulation.Cores.Waterbox
return val;
}
private void SettingsQuery(string name, IntPtr dest)
private unsafe void SettingsQuery(string name, IntPtr dest)
{
var val = SettingsQuery(name);
var bytes = Encoding.UTF8.GetBytes(val);
if (bytes.Length > 255)
{
throw new InvalidOperationException($"Value {val} for setting {name} was too long");
WaterboxUtils.ZeroMemory(dest, 256);
}
new Span<byte>((void*)dest, 256).Clear();
Marshal.Copy(bytes, 0, dest, bytes.Length);
}

View File

@ -1,11 +1,12 @@
using BizHawk.Common;
using BizHawk.BizInvoke;
using BizHawk.Emulation.Common;
using System;
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using BizHawk.Common;
using BizHawk.BizInvoke;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Waterbox
{
public abstract class WaterboxCore : IEmulator, IVideoProvider, ISoundProvider, IStatable,
@ -182,7 +183,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
var source = new MemoryStream(data, false);
foreach (var area in _saveramAreas)
{
WaterboxUtils.CopySome(source, new MemoryDomainStream(area), area.Size);
MemoryBlockUtils.CopySome(source, new MemoryDomainStream(area), area.Size);
}
}
}