using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace BizHawk.Common.BizInvoke { /// /// create interop delegates and function pointers for a particular calling convention /// public interface ICallingConventionAdapter { IntPtr GetFunctionPointerForDelegate(Delegate d); IntPtr GetArrivalFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime); Delegate GetDelegateForFunctionPointer(IntPtr p, Type delegateType); IntPtr GetDepartureFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime); } public static class CallingConventionAdapterExtensions { public static T GetDelegateForFunctionPointer(this ICallingConventionAdapter a, IntPtr p) where T : class { return (T)(object)a.GetDelegateForFunctionPointer(p, typeof(T)); } } public class ParameterInfo { public Type ReturnType { get; } public IReadOnlyList ParameterTypes { get; } public ParameterInfo(Type returnType, IEnumerable parameterTypes) { ReturnType = returnType; ParameterTypes = parameterTypes.ToList().AsReadOnly(); } 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(); } } public static class CallingConventionAdapters { private class NativeConvention : ICallingConventionAdapter { public IntPtr GetArrivalFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime) { return p; } public Delegate GetDelegateForFunctionPointer(IntPtr p, Type delegateType) { return Marshal.GetDelegateForFunctionPointer(p, delegateType); } public IntPtr GetDepartureFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime) { return p; } public IntPtr GetFunctionPointerForDelegate(Delegate d) { return Marshal.GetFunctionPointerForDelegate(d); } } /// /// native (pass-through) calling convention /// public static ICallingConventionAdapter Native { get; } = new NativeConvention(); /// /// convention appropriate for waterbox guests /// public static ICallingConventionAdapter Waterbox { get; } = #if true new NativeConvention(); #else new SysVHostMsGuest(); #endif private class SysVHostMsGuest : ICallingConventionAdapter { private const ulong Placeholder = 0xdeadbeeffeedface; private const byte Padding = 0x06; private const int BlockSize = 256; private static readonly byte[][] Depart = { new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x30, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x8b, 0x55, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x48, 0x89, 0xd1, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x30, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x40, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x40, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x89, 0x4d, 0xe0, 0x48, 0x8b, 0x7d, 0xe0, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf9, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x60, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x89, 0x4d, 0xe0, 0x4c, 0x89, 0x45, 0xd8, 0x48, 0x8b, 0x7d, 0xe0, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0x8b, 0x45, 0xd8, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf9, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x60, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x89, 0x4d, 0xe0, 0x4c, 0x89, 0x45, 0xd8, 0x4c, 0x89, 0x4d, 0xd0, 0x48, 0x8b, 0x7d, 0xe0, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0x8b, 0x45, 0xd0, 0x48, 0x89, 0x44, 0x24, 0x28, 0x48, 0x8b, 0x45, 0xd8, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf9, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, }; private static readonly byte[][] Arrive = { new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x20, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x30, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x8b, 0x55, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x48, 0x89, 0xd1, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x30, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x40, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x40, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x89, 0x4d, 0xe0, 0x48, 0x8b, 0x7d, 0xe0, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf9, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x60, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x89, 0x4d, 0xe0, 0x4c, 0x89, 0x45, 0xd8, 0x48, 0x8b, 0x7d, 0xe0, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0x8b, 0x45, 0xd8, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf9, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, new byte[] { 0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x60, 0x48, 0x89, 0x7d, 0xf8, 0x48, 0x89, 0x75, 0xf0, 0x48, 0x89, 0x55, 0xe8, 0x48, 0x89, 0x4d, 0xe0, 0x4c, 0x89, 0x45, 0xd8, 0x4c, 0x89, 0x4d, 0xd0, 0x48, 0x8b, 0x7d, 0xe0, 0x48, 0x8b, 0x75, 0xe8, 0x48, 0x8b, 0x55, 0xf0, 0x48, 0x8b, 0x4d, 0xf8, 0x48, 0x8b, 0x45, 0xd0, 0x48, 0x89, 0x44, 0x24, 0x28, 0x48, 0x8b, 0x45, 0xd8, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0xb8, 0xce, 0xfa, 0xed, 0xfe, 0xef, 0xbe, 0xad, 0xde, 0x49, 0x89, 0xf9, 0x49, 0x89, 0xf0, 0xff, 0xd0, 0xc9, 0xc3, }, }; private static readonly int[] DepartPlaceholderIndices; private static readonly int[] ArrivePlaceholderIndices; private static int FindPlaceholderIndex(byte[] data) { return Enumerable.Range(0, data.Length - 7) .Single(i => BitConverter.ToUInt64(data, i) == Placeholder); } static SysVHostMsGuest() { DepartPlaceholderIndices = Depart.Select(FindPlaceholderIndex).ToArray(); ArrivePlaceholderIndices = Arrive.Select(FindPlaceholderIndex).ToArray(); if (Depart.Any(b => b.Length > BlockSize) || Arrive.Any(b => b.Length > BlockSize)) throw new InvalidOperationException(); } private readonly MemoryBlock _memory; private readonly object _sync = new object(); private readonly WeakReference[] _refs; public SysVHostMsGuest() { int size = 4 * 1024 * 1024; _memory = new MemoryBlock((ulong)size); _memory.Activate(); _refs = new WeakReference[size / BlockSize]; } private int FindFreeIndex() { for (int i = 0; i < _refs.Length; i++) { if (_refs[i] == null || !_refs[i].IsAlive) return i; } throw new InvalidOperationException("Out of Thunk memory"); } private int FindUsedIndex(object o) { for (int i = 0; i < _refs.Length; i++) { if (_refs[i] == o) return i; } return -1; } private static void VerifyParameter(Type type) { if (type == typeof(float) || type == typeof(double)) throw new NotSupportedException("floating point not supported"); if (type == typeof(void) || type.IsPrimitive) return; if (type.IsByRef || type.IsClass) return; throw new NotSupportedException("Unknown type. Possibly supported?"); } private static int VerifyDelegateSignature(ParameterInfo pp) { VerifyParameter(pp.ReturnType); foreach (var ppp in pp.ParameterTypes) VerifyParameter(ppp); return pp.ParameterTypes.Count; } private void WriteThunk(byte[] data, int placeholderIndex, IntPtr p, int index) { _memory.Protect(_memory.Start, _memory.Size, MemoryBlock.Protection.RW); var ss = _memory.GetStream(_memory.Start + (ulong)index * BlockSize, BlockSize, true); ss.Write(data, 0, data.Length); for (int i = data.Length; i < BlockSize; i++) ss.WriteByte(Padding); ss.Position = placeholderIndex; var bw = new BinaryWriter(ss); bw.Write((long)p); _memory.Protect(_memory.Start, _memory.Size, MemoryBlock.Protection.RX); } private IntPtr GetThunkAddress(int index) { return Z.US(_memory.Start + (ulong)index * BlockSize); } private void SetLifetime(int index, object lifetime) { if (_refs[index] == null) _refs[index] = new WeakReference(lifetime); else _refs[index].Target = lifetime; } public IntPtr GetFunctionPointerForDelegate(Delegate d) { // for this call only, the expectation is that it can be called multiple times // on the same delegate and not leak extra memory, so the result has to be cached lock (_sync) { var index = FindUsedIndex(d); if (index != -1) { return GetThunkAddress(index); } else { return GetArrivalFunctionPointer( Marshal.GetFunctionPointerForDelegate(d), new ParameterInfo(d.GetType()), d); } } } public IntPtr GetArrivalFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime) { lock (_sync) { var index = FindFreeIndex(); var count = VerifyDelegateSignature(pp); WriteThunk(Arrive[count], ArrivePlaceholderIndices[count], p, index); SetLifetime(index, lifetime); return GetThunkAddress(index); } } public Delegate GetDelegateForFunctionPointer(IntPtr p, Type delegateType) { lock (_sync) { var index = FindFreeIndex(); var count = VerifyDelegateSignature(new ParameterInfo(delegateType)); WriteThunk(Depart[count], DepartPlaceholderIndices[count], p, index); var ret = Marshal.GetDelegateForFunctionPointer(GetThunkAddress(index), delegateType); SetLifetime(index, ret); return ret; } } public IntPtr GetDepartureFunctionPointer(IntPtr p, ParameterInfo pp, object lifetime) { lock (_sync) { var index = FindFreeIndex(); var count = VerifyDelegateSignature(pp); WriteThunk(Depart[count], DepartPlaceholderIndices[count], p, index); SetLifetime(index, lifetime); return GetThunkAddress(index); } } } } }