From 1292b27163a9fafe6905bad15dbdb427e023047d Mon Sep 17 00:00:00 2001 From: nattthebear Date: Sun, 18 Jun 2017 08:49:55 -0400 Subject: [PATCH] Add some draft work for supporting sysv <-> msabi interop. I wonder if we'll ever use this? I'd put it on a separate branch but it would be merge hell. --- BizHawk.Common/BizHawk.Common.csproj | 3 + BizHawk.Common/BizInvoke/BizExvoker.cs | 300 +++--- BizHawk.Common/BizInvoke/BizInvoker.cs | 55 +- .../BizInvoke/CallingConventionAdapter.cs | 248 +++++ .../BizInvoke}/MemoryBlock.cs | 104 +- BizHawk.Common/BizInvoke/WaterboxUtils.cs | 156 +++ .../BizHawk.Emulation.Cores.csproj | 1 - .../Consoles/Nintendo/Gameboy/Pizza.cs | 2 +- .../Consoles/Nintendo/QuickNES/QuickNES.cs | 2 +- .../Consoles/Nintendo/SNES/LibsnesApi.cs | 2 +- .../Consoles/SNK/DualNeoGeoPort.cs | 696 ++++++------- .../Consoles/Sega/gpgx64/GPGX.cs | 2 +- BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs | 975 +++++++++--------- BizHawk.Emulation.Cores/Waterbox/Heap.cs | 289 +++--- BizHawk.Emulation.Cores/Waterbox/MapHeap.cs | 3 +- BizHawk.Emulation.Cores/Waterbox/PeRunner.cs | 36 +- BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs | 13 +- BizHawk.Emulation.Cores/Waterbox/Swappable.cs | 1 + .../Waterbox/WaterboxCore.cs | 262 ++--- waterbox/thunk/build.sh | 2 + waterbox/thunk/gen.cs | 20 + waterbox/thunk/test.c | 103 ++ waterbox/thunk/test.exe | Bin 0 -> 131679 bytes waterbox/thunk/test.s | 682 ++++++++++++ 24 files changed, 2596 insertions(+), 1361 deletions(-) create mode 100644 BizHawk.Common/BizInvoke/CallingConventionAdapter.cs rename {BizHawk.Emulation.Cores/Waterbox => BizHawk.Common/BizInvoke}/MemoryBlock.cs (99%) create mode 100644 BizHawk.Common/BizInvoke/WaterboxUtils.cs create mode 100644 waterbox/thunk/build.sh create mode 100644 waterbox/thunk/gen.cs create mode 100644 waterbox/thunk/test.c create mode 100644 waterbox/thunk/test.exe create mode 100644 waterbox/thunk/test.s diff --git a/BizHawk.Common/BizHawk.Common.csproj b/BizHawk.Common/BizHawk.Common.csproj index 8e49195165..375d6039a1 100644 --- a/BizHawk.Common/BizHawk.Common.csproj +++ b/BizHawk.Common/BizHawk.Common.csproj @@ -73,7 +73,10 @@ + + + diff --git a/BizHawk.Common/BizInvoke/BizExvoker.cs b/BizHawk.Common/BizInvoke/BizExvoker.cs index 2487a0795d..120025f9cd 100644 --- a/BizHawk.Common/BizInvoke/BizExvoker.cs +++ b/BizHawk.Common/BizInvoke/BizExvoker.cs @@ -1,150 +1,150 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.InteropServices; -using System.Text; - -namespace BizHawk.Common.BizInvoke -{ - public static class BizExvoker - { - /// - /// the assembly that all delegate types are placed in - /// - private static readonly AssemblyBuilder ImplAssemblyBuilder; - - /// - /// the module that all delegate types are placed in - /// - private static readonly ModuleBuilder ImplModuleBuilder; - - static BizExvoker() - { - var aname = new AssemblyName("BizExvokeProxyAssembly"); - ImplAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run); - ImplModuleBuilder = ImplAssemblyBuilder.DefineDynamicModule("BizExvokerModule"); - } - - /// - /// holds the delegate types for a type - /// - private class DelegateStorage - { - /// - /// the type that this storage was made for - /// - public Type OriginalType { get; } - /// - /// the type that the delegate types reside in - /// - public Type StorageType { get; } - - public List DelegateTypes { get; } = new List(); - - 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(m => new - { - Info = m, - Attr = m.GetCustomAttributes(true).OfType().FirstOrDefault() - }) - .Where(a => a.Attr != null) - .ToList(); - - var typeBuilder = ImplModuleBuilder.DefineType( - "Bizhawk.BizExvokeHolder" + type.Name, TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed); - - foreach (var a in methods) - { - MethodBuilder unused; - var delegateType = BizInvokeUtilities.CreateDelegateType(a.Info, a.Attr.CallingConvention, typeBuilder, out unused).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 EntryPoints = new Dictionary(); - - private readonly List Delegates = new List(); - - public ExvokerImpl(object o, DelegateStorage d) - { - 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, Marshal.GetFunctionPointerForDelegate(del)); - } - } - - public IntPtr Resolve(string entryPoint) - { - IntPtr ret; - EntryPoints.TryGetValue(entryPoint, out ret); - return ret; - } - } - - static readonly Dictionary Impls = new Dictionary(); - - - public static IImportResolver GetExvoker(object o) - { - DelegateStorage ds; - lock (Impls) - { - var type = o.GetType(); - if (!Impls.TryGetValue(type, out ds)) - { - ds = new DelegateStorage(type); - Impls.Add(type, ds); - } - } - - return new ExvokerImpl(o, ds); - } - } - - /// - /// mark an instance method to be exported by BizExvoker - /// - [AttributeUsage(AttributeTargets.Method)] - public class BizExportAttribute : Attribute - { - public CallingConvention CallingConvention { get; } - - /// - /// Gets or sets the name of entry point; if not given, the method's name is used - /// - public string EntryPoint { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// unmanaged calling convention - public BizExportAttribute(CallingConvention c) - { - CallingConvention = c; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using System.Text; + +namespace BizHawk.Common.BizInvoke +{ + public static class BizExvoker + { + /// + /// the assembly that all delegate types are placed in + /// + private static readonly AssemblyBuilder ImplAssemblyBuilder; + + /// + /// the module that all delegate types are placed in + /// + private static readonly ModuleBuilder ImplModuleBuilder; + + static BizExvoker() + { + var aname = new AssemblyName("BizExvokeProxyAssembly"); + ImplAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run); + ImplModuleBuilder = ImplAssemblyBuilder.DefineDynamicModule("BizExvokerModule"); + } + + /// + /// holds the delegate types for a type + /// + private class DelegateStorage + { + /// + /// the type that this storage was made for + /// + public Type OriginalType { get; } + /// + /// the type that the delegate types reside in + /// + public Type StorageType { get; } + + public List DelegateTypes { get; } = new List(); + + 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(m => new + { + Info = m, + Attr = m.GetCustomAttributes(true).OfType().FirstOrDefault() + }) + .Where(a => a.Attr != null) + .ToList(); + + var typeBuilder = ImplModuleBuilder.DefineType( + "Bizhawk.BizExvokeHolder" + type.Name, TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed); + + foreach (var a in methods) + { + MethodBuilder unused; + var delegateType = BizInvokeUtilities.CreateDelegateType(a.Info, a.Attr.CallingConvention, typeBuilder, out unused).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 EntryPoints = new Dictionary(); + + private readonly List Delegates = new List(); + + 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 Resolve(string entryPoint) + { + IntPtr ret; + EntryPoints.TryGetValue(entryPoint, out ret); + return ret; + } + } + + static readonly Dictionary Impls = new Dictionary(); + + + public static IImportResolver GetExvoker(object o, ICallingConventionAdapter a) + { + DelegateStorage ds; + lock (Impls) + { + var type = o.GetType(); + if (!Impls.TryGetValue(type, out ds)) + { + ds = new DelegateStorage(type); + Impls.Add(type, ds); + } + } + + return new ExvokerImpl(o, ds, a); + } + } + + /// + /// mark an instance method to be exported by BizExvoker + /// + [AttributeUsage(AttributeTargets.Method)] + public class BizExportAttribute : Attribute + { + public CallingConvention CallingConvention { get; } + + /// + /// Gets or sets the name of entry point; if not given, the method's name is used + /// + public string EntryPoint { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// unmanaged calling convention + public BizExportAttribute(CallingConvention c) + { + CallingConvention = c; + } + } +} diff --git a/BizHawk.Common/BizInvoke/BizInvoker.cs b/BizHawk.Common/BizInvoke/BizInvoker.cs index d39f68c2f5..c00ad96da4 100644 --- a/BizHawk.Common/BizInvoke/BizInvoker.cs +++ b/BizHawk.Common/BizInvoke/BizInvoker.cs @@ -17,17 +17,18 @@ namespace BizHawk.Common.BizInvoke private class InvokerImpl { public Type ImplType; - public List> Hooks; + public List> Hooks; public Action ConnectMonitor; + public Action ConnectCallingConventionAdapter; - public object Create(IImportResolver dll, IMonitor monitor) + public object Create(IImportResolver dll, IMonitor monitor, ICallingConventionAdapter adapter) { var ret = Activator.CreateInstance(ImplType); + ConnectCallingConventionAdapter(ret, adapter); foreach (var f in Hooks) { - f(ret, dll); + f(ret, dll, adapter); } - ConnectMonitor?.Invoke(ret, monitor); return ret; } @@ -59,7 +60,7 @@ namespace BizHawk.Common.BizInvoke /// get an implementation proxy for an interop class /// /// The class type that represents the DLL - public static T GetInvoker(IImportResolver dll) + public static T GetInvoker(IImportResolver dll, ICallingConventionAdapter adapter) where T : class { InvokerImpl impl; @@ -78,10 +79,10 @@ namespace BizHawk.Common.BizInvoke throw new InvalidOperationException("Class was previously proxied with a monitor!"); } - return (T)impl.Create(dll, null); + return (T)impl.Create(dll, null, adapter); } - public static T GetInvoker(IImportResolver dll, IMonitor monitor) + public static T GetInvoker(IImportResolver dll, IMonitor monitor, ICallingConventionAdapter adapter) where T : class { InvokerImpl impl; @@ -100,7 +101,7 @@ namespace BizHawk.Common.BizInvoke throw new InvalidOperationException("Class was previously proxied without a monitor!"); } - return (T)impl.Create(dll, monitor); + return (T)impl.Create(dll, monitor, adapter); } private static InvokerImpl CreateProxy(Type baseType, bool monitor) @@ -153,19 +154,21 @@ namespace BizHawk.Common.BizInvoke } // hooks that will be run on the created proxy object - var postCreateHooks = new List>(); + var postCreateHooks = new List>(); var type = ImplModuleBuilder.DefineType("Bizhawk.BizInvokeProxy" + baseType.Name, TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed, baseType); var monitorField = monitor ? type.DefineField("MonitorField", typeof(IMonitor), FieldAttributes.Public) : null; + var adapterField = type.DefineField("CallingConvention", typeof(ICallingConventionAdapter), FieldAttributes.Public); + foreach (var mi in baseMethods) { var entryPointName = mi.Attr.EntryPoint ?? mi.Info.Name; var hook = mi.Attr.Compatibility ? ImplementMethodDelegate(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField) - : ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField); + : ImplementMethodCalli(type, mi.Info, mi.Attr.CallingConvention, entryPointName, monitorField, adapterField); postCreateHooks.Add(hook); } @@ -179,6 +182,7 @@ namespace BizHawk.Common.BizInvoke { ret.ConnectMonitor = (o, m) => o.GetType().GetField(monitorField.Name).SetValue(o, m); } + ret.ConnectCallingConventionAdapter = (o, a) => o.GetType().GetField(adapterField.Name).SetValue(o, a); return ret; } @@ -186,7 +190,8 @@ namespace BizHawk.Common.BizInvoke /// /// create a method implementation that uses GetDelegateForFunctionPointer internally /// - private static Action ImplementMethodDelegate(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName, FieldInfo monitorField) + private static Action ImplementMethodDelegate( + TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName, FieldInfo monitorField) { // create the delegate type MethodBuilder delegateInvoke; @@ -196,6 +201,13 @@ namespace BizHawk.Common.BizInvoke var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); var returnType = baseMethod.ReturnType; + if (paramTypes.Concat(new[] { returnType }).Any(typeof(Delegate).IsAssignableFrom)) + { + // this isn't a problem if CallingConventionAdapters.Waterbox is a no-op + if (CallingConventionAdapters.Waterbox.GetType() != CallingConventionAdapters.Native.GetType()) + throw new InvalidOperationException("Compatibility call mode cannot use ICallingConventionAdapters!"); + } + // define a field on the class to hold the delegate var field = type.DefineField( "DelegateField" + baseMethod.Name, @@ -255,10 +267,10 @@ namespace BizHawk.Common.BizInvoke type.DefineMethodOverride(method, baseMethod); - return (o, dll) => + return (o, dll, adapter) => { var entryPtr = dll.SafeResolve(entryPointName); - var interopDelegate = Marshal.GetDelegateForFunctionPointer(entryPtr, delegateType.CreateType()); + var interopDelegate = adapter.GetDelegateForFunctionPointer(entryPtr, delegateType.CreateType()); o.GetType().GetField(field.Name).SetValue(o, interopDelegate); }; } @@ -266,7 +278,9 @@ namespace BizHawk.Common.BizInvoke /// /// create a method implementation that uses calli internally /// - private static Action ImplementMethodCalli(TypeBuilder type, MethodInfo baseMethod, CallingConvention nativeCall, string entryPointName, FieldInfo monitorField) + private static Action ImplementMethodCalli( + TypeBuilder type, MethodInfo baseMethod, + CallingConvention nativeCall, string entryPointName, FieldInfo monitorField, FieldInfo adapterField) { var paramInfos = baseMethod.GetParameters(); var paramTypes = paramInfos.Select(p => p.ParameterType).ToArray(); @@ -304,7 +318,7 @@ namespace BizHawk.Common.BizInvoke for (int i = 0; i < paramTypes.Length; i++) { // arg 0 is this, so + 1 - nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i])); + nativeParamTypes.Add(EmitParamterLoad(il, i + 1, paramTypes[i], adapterField)); } il.Emit(OpCodes.Ldarg_0); @@ -342,10 +356,11 @@ namespace BizHawk.Common.BizInvoke type.DefineMethodOverride(method, baseMethod); - return (o, dll) => + return (o, dll, adapter) => { var entryPtr = dll.SafeResolve(entryPointName); - o.GetType().GetField(field.Name).SetValue(o, entryPtr); + o.GetType().GetField(field.Name).SetValue( + o, adapter.GetDepartureFunctionPointer(entryPtr, new ParameterInfo(returnType, paramTypes), o)); }; } @@ -394,7 +409,7 @@ namespace BizHawk.Common.BizInvoke /// /// emit a single parameter load with unmanaged conversions /// - private static Type EmitParamterLoad(ILGenerator il, int idx, Type type) + private static Type EmitParamterLoad(ILGenerator il, int idx, Type type, FieldInfo adapterField) { if (type.IsGenericType) { @@ -460,13 +475,15 @@ namespace BizHawk.Common.BizInvoke if (typeof(Delegate).IsAssignableFrom(type)) { - var mi = typeof(Marshal).GetMethod("GetFunctionPointerForDelegate", new[] { typeof(Delegate) }); + var mi = typeof(ICallingConventionAdapter).GetMethod("GetFunctionPointerForDelegate"); var end = il.DefineLabel(); var isNull = il.DefineLabel(); il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Brfalse, isNull); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, adapterField); il.Emit(OpCodes.Ldarg, (short)idx); il.Emit(OpCodes.Call, mi); il.Emit(OpCodes.Br, end); diff --git a/BizHawk.Common/BizInvoke/CallingConventionAdapter.cs b/BizHawk.Common/BizInvoke/CallingConventionAdapter.cs new file mode 100644 index 0000000000..11ab29d2d7 --- /dev/null +++ b/BizHawk.Common/BizInvoke/CallingConventionAdapter.cs @@ -0,0 +1,248 @@ +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 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) + { + 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); + } + } + + } + } +} diff --git a/BizHawk.Emulation.Cores/Waterbox/MemoryBlock.cs b/BizHawk.Common/BizInvoke/MemoryBlock.cs similarity index 99% rename from BizHawk.Emulation.Cores/Waterbox/MemoryBlock.cs rename to BizHawk.Common/BizInvoke/MemoryBlock.cs index 700c1922b0..e0437898fc 100644 --- a/BizHawk.Emulation.Cores/Waterbox/MemoryBlock.cs +++ b/BizHawk.Common/BizInvoke/MemoryBlock.cs @@ -5,45 +5,45 @@ using System.Text; using System.Runtime.InteropServices; using System.IO; -namespace BizHawk.Emulation.Cores.Waterbox +namespace BizHawk.Common.BizInvoke { public sealed class MemoryBlock : IDisposable - { + { /// /// starting address of the memory block /// - public ulong Start { get; private set; } + public ulong Start { get; private set; } /// /// total size of the memory block /// - public ulong Size { get; private set; } + public ulong Size { get; private set; } /// /// ending address of the memory block; equal to start + size /// - public ulong End { get; private set; } - + public ulong End { get; private set; } + /// /// handle returned by CreateFileMapping /// - private IntPtr _handle; - + private IntPtr _handle; + /// /// true if this is currently swapped in /// - public bool Active { get; private set; } - + public bool Active { get; private set; } + /// /// stores last set memory protection value for each page /// - private readonly Protection[] _pageData; - + private readonly Protection[] _pageData; + /// /// snapshot for XOR buffer /// private byte[] _snapshot; - public byte[] XorHash { get; private set; } - + public byte[] XorHash { get; private set; } + /// /// get a page index within the block /// @@ -53,16 +53,16 @@ namespace BizHawk.Emulation.Cores.Waterbox throw new ArgumentOutOfRangeException(); return (int)((addr - Start) >> WaterboxUtils.PageShift); - } - + } + /// /// get a start address for a page index within the block /// private ulong GetStartAddr(int page) { return ((ulong)page << WaterboxUtils.PageShift) + Start; - } - + } + /// /// allocate size bytes at any address /// @@ -70,8 +70,8 @@ namespace BizHawk.Emulation.Cores.Waterbox public MemoryBlock(ulong size) : this(0, size) { - } - + } + /// /// allocate size bytes starting at a particular address /// @@ -94,8 +94,8 @@ namespace BizHawk.Emulation.Cores.Waterbox End = start + size; Size = size; _pageData = new Protection[GetPage(End - 1) + 1]; - } - + } + /// /// activate the memory block, swapping it in at the specified address /// @@ -110,8 +110,8 @@ namespace BizHawk.Emulation.Cores.Waterbox } ProtectAll(); Active = true; - } - + } + /// /// deactivate the memory block, removing it from RAM but leaving it immediately available to swap back in /// @@ -122,16 +122,16 @@ namespace BizHawk.Emulation.Cores.Waterbox if (!Kernel32.UnmapViewOfFile(Z.US(Start))) throw new InvalidOperationException("UnmapViewOfFile() returned NULL"); Active = false; - } - + } + /// /// Memory protection constant /// public enum Protection : byte { None, R, RW, RX - } - + } + /// /// Get a stream that can be used to read or write from part of the block. Does not check for or change Protect()! /// @@ -143,8 +143,8 @@ namespace BizHawk.Emulation.Cores.Waterbox throw new ArgumentOutOfRangeException(nameof(length)); return new MemoryViewStream(!writer, writer, (long)start, (long)length, this); - } - + } + /// /// get a stream that can be used to read or write from part of the block. /// both reads and writes will be XORed against an earlier recorded snapshot @@ -159,8 +159,8 @@ namespace BizHawk.Emulation.Cores.Waterbox throw new InvalidOperationException("No snapshot taken!"); return new MemoryViewXorStream(!writer, writer, (long)start, (long)length, this, _snapshot, (long)(start - Start)); - } - + } + /// /// take a snapshot of the entire memory block's contents, for use in GetXorStream /// @@ -169,10 +169,10 @@ namespace BizHawk.Emulation.Cores.Waterbox if (_snapshot != null) throw new InvalidOperationException("Snapshot already taken"); if (!Active) - throw new InvalidOperationException("Not active"); - - // temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want - // that to complicate things + throw new InvalidOperationException("Not active"); + + // temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want + // that to complicate things Kernel32.MemoryProtection old; if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old)) throw new InvalidOperationException("VirtualProtect() returned FALSE!"); @@ -198,8 +198,8 @@ namespace BizHawk.Emulation.Cores.Waterbox default: throw new ArgumentOutOfRangeException(nameof(prot)); } return p; - } - + } + /// /// restore all recorded protections /// @@ -219,8 +219,8 @@ namespace BizHawk.Emulation.Cores.Waterbox ps = i + 1; } } - } - + } + /// /// set r/w/x protection on a portion of memory. rounded to encompassing pages /// @@ -233,9 +233,9 @@ namespace BizHawk.Emulation.Cores.Waterbox var p = GetKernelMemoryProtectionValue(prot); for (int i = pstart; i <= pend; i++) - _pageData[i] = prot; // also store the value for later use - - if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation + _pageData[i] = prot; // also store the value for later use + + if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation { var computedStart = WaterboxUtils.AlignDown(start); var computedEnd = WaterboxUtils.AlignUp(start + length); @@ -374,12 +374,12 @@ namespace BizHawk.Emulation.Cores.Waterbox { _initial = initial; _offset = (int)offset; - } - + } + /// /// the initial data to XOR against for both reading and writing /// - private readonly byte[] _initial; + private readonly byte[] _initial; /// /// offset into the XOR data that this stream is representing /// @@ -399,17 +399,17 @@ namespace BizHawk.Emulation.Cores.Waterbox if (count < 0 || count + offset > buffer.Length) throw new ArgumentOutOfRangeException(); if (count > Length - pos) - throw new ArgumentOutOfRangeException(); - // is mutating the buffer passed to Stream.Write kosher? + throw new ArgumentOutOfRangeException(); + // is mutating the buffer passed to Stream.Write kosher? XorTransform(_initial, _offset + pos, buffer, offset, count); base.Write(buffer, offset, count); } private static unsafe void XorTransform(byte[] source, int sourceOffset, byte[] dest, int destOffset, int length) - { - // we don't do any bounds check because MemoryViewStream.Read and MemoryViewXorStream.Write already did it - - // TODO: C compilers can make this pretty snappy, but can the C# jitter? Or do we need intrinsics + { + // we don't do any bounds check because MemoryViewStream.Read and MemoryViewXorStream.Write already did it + + // TODO: C compilers can make this pretty snappy, but can the C# jitter? Or do we need intrinsics fixed (byte* _s = source, _d = dest) { byte* s = _s + sourceOffset; diff --git a/BizHawk.Common/BizInvoke/WaterboxUtils.cs b/BizHawk.Common/BizInvoke/WaterboxUtils.cs new file mode 100644 index 0000000000..a85a437485 --- /dev/null +++ b/BizHawk.Common/BizInvoke/WaterboxUtils.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace BizHawk.Common.BizInvoke +{ + public static class WaterboxUtils + { + /// + /// copy `len` bytes from `src` to `dest` + /// + /// + /// + /// + public static void CopySome(Stream src, Stream dst, long len) + { + var buff = new byte[4096]; + while (len > 0) + { + int r = src.Read(buff, 0, (int)Math.Min(len, 4096)); + dst.Write(buff, 0, r); + len -= r; + } + } + + public static byte[] Hash(byte[] data) + { + using (var h = SHA1.Create()) + { + return h.ComputeHash(data); + } + } + + public static byte[] Hash(Stream s) + { + using (var h = SHA1.Create()) + { + return h.ComputeHash(s); + } + } + + public static unsafe void ZeroMemory(IntPtr mem, long length) + { + byte* p = (byte*)mem; + byte* end = p + length; + while (p < end) + { + *p++ = 0; + } + } + + public static long Timestamp() + { + return DateTime.UtcNow.Ticks; + } + + /// + /// system page size + /// + public static int PageSize { get; private set; } + + /// + /// bitshift corresponding to PageSize + /// + public static int PageShift { get; private set; } + /// + /// bitmask corresponding to PageSize + /// + public static ulong PageMask { get; private set; } + + static WaterboxUtils() + { + int p = PageSize = Environment.SystemPageSize; + while (p != 1) + { + p >>= 1; + PageShift++; + } + PageMask = (ulong)(PageSize - 1); + } + + /// + /// true if addr is aligned + /// + public static bool Aligned(ulong addr) + { + return (addr & PageMask) == 0; + } + + /// + /// align address down to previous page boundary + /// + public static ulong AlignDown(ulong addr) + { + return addr & ~PageMask; + } + + /// + /// align address up to next page boundary + /// + public static ulong AlignUp(ulong addr) + { + return ((addr - 1) | PageMask) + 1; + } + + /// + /// return the minimum number of pages needed to hold size + /// + public static int PagesNeeded(ulong size) + { + return (int)((size + PageMask) >> PageShift); + } + } + + // C# is annoying: arithmetic operators for native ints are not exposed. + // So we store them as long/ulong instead in many places, and use these helpers + // to convert to IntPtr when needed + + public static class Z + { + public static IntPtr US(ulong l) + { + if (IntPtr.Size == 8) + return (IntPtr)(long)l; + else + return (IntPtr)(int)l; + } + + public static UIntPtr UU(ulong l) + { + if (UIntPtr.Size == 8) + return (UIntPtr)l; + else + return (UIntPtr)(uint)l; + } + + public static IntPtr SS(long l) + { + if (IntPtr.Size == 8) + return (IntPtr)l; + else + return (IntPtr)(int)l; + } + + public static UIntPtr SU(long l) + { + if (UIntPtr.Size == 8) + return (UIntPtr)(ulong)l; + else + return (UIntPtr)(uint)l; + } + } +} diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index c5cdb63b4c..22306b5309 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1278,7 +1278,6 @@ - diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs index ff6b74b849..7b4d174b78 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs @@ -34,7 +34,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy PlainHeapSizeKB = 16 * 1024, MmapHeapSizeKB = 32 * 1024 }); - _pizza = BizInvoker.GetInvoker(_exe, _exe); + _pizza = BizInvoker.GetInvoker(_exe, _exe, CallingConventionAdapters.Waterbox); if (!_pizza.Init(rom, rom.Length)) { throw new InvalidOperationException("Core rejected the rom!"); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index 51dedb8f61..563f46f6ea 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -22,7 +22,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES static QuickNES() { Resolver = new DynamicLibraryImportResolver(LibQuickNES.dllname); - QN = BizInvoker.GetInvoker(Resolver); + QN = BizInvoker.GetInvoker(Resolver, CallingConventionAdapters.Native); } [CoreConstructor("NES")] diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesApi.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesApi.cs index 54a343ea22..be07d5030c 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesApi.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesApi.cs @@ -69,7 +69,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES // Marshal checks that function pointers passed to GetDelegateForFunctionPointer are // _currently_ valid when created, even though they don't need to be valid until // the delegate is later invoked. so GetInvoker needs to be acquired within a lock. - _core = BizInvoker.GetInvoker(_exe, _exe); + _core = BizInvoker.GetInvoker(_exe, _exe, CallingConventionAdapters.Waterbox); _comm = (CommStruct*)_core.DllInit().ToPointer(); } } diff --git a/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs b/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs index a531e83c39..5cf66cc8b3 100644 --- a/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs +++ b/BizHawk.Emulation.Cores/Consoles/SNK/DualNeoGeoPort.cs @@ -1,348 +1,348 @@ -using BizHawk.Common; -using BizHawk.Common.BizInvoke; -using BizHawk.Emulation.Common; -using BizHawk.Emulation.Cores.Sound; -using BizHawk.Emulation.Cores.Waterbox; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Consoles.SNK -{ - [CoreAttributes("Dual NeoPop", "Thomas Klausner and natt", true, false, "0.9.44.1", - "https://mednafen.github.io/releases/", false)] - public class DualNeoGeoPort : IEmulator - { - private NeoGeoPort _left; - private NeoGeoPort _right; - private readonly BasicServiceProvider _serviceProvider; - private bool _disposed = false; - private readonly DualSyncSound _soundProvider; - private readonly SideBySideVideo _videoProvider; - private readonly LinkInterop _leftEnd; - private readonly LinkInterop _rightEnd; - private readonly LinkCable _linkCable; - - [CoreConstructor("DNGP")] - public DualNeoGeoPort(CoreComm comm, byte[] rom, bool deterministic) - { - CoreComm = comm; - _left = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.CanonicalStart); - _right = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.AlternateStart); - _linkCable = new LinkCable(); - _leftEnd = new LinkInterop(_left, _linkCable.LeftIn, _linkCable.LeftOut); - _rightEnd = new LinkInterop(_right, _linkCable.RightIn, _linkCable.RightOut); - - - _serviceProvider = new BasicServiceProvider(this); - _soundProvider = new DualSyncSound(_left, _right); - _serviceProvider.Register(_soundProvider); - _videoProvider = new SideBySideVideo(_left, _right); - _serviceProvider.Register(_videoProvider); - } - - public void FrameAdvance(IController controller, bool render, bool rendersound = true) - { - var t1 = Task.Run(() => - { - _left.FrameAdvance(new PrefixController(controller, "P1 "), render, rendersound); - _leftEnd.SignalEndOfFrame(); - }); - var t2 = Task.Run(() => - { - _right.FrameAdvance(new PrefixController(controller, "P2 "), render, rendersound); - _rightEnd.SignalEndOfFrame(); - }); - var t3 = Task.Run(() => - { - _linkCable.RunFrame(); - }); - Task.WaitAll(t1, t2, t3); - Frame++; - _soundProvider.Fetch(); - _videoProvider.Fetch(); - } - - #region link cable - - private class LinkCable - { - public readonly BlockingCollection LeftIn = new BlockingCollection(); - public readonly BlockingCollection LeftOut = new BlockingCollection(); - public readonly BlockingCollection RightIn = new BlockingCollection(); - public readonly BlockingCollection RightOut = new BlockingCollection(); - - private readonly Queue _leftData = new Queue(); - private readonly Queue _rightData = new Queue(); - - public void RunFrame() - { - LinkRequest l = LeftIn.Take(); - LinkRequest r = RightIn.Take(); - while (true) - { - switch (l.RequestType) - { - case LinkRequest.RequestTypes.EndOfFrame: - if (r.RequestType == LinkRequest.RequestTypes.EndOfFrame) - { - Console.WriteLine("\nEnd of Frame {0} {1}", _leftData.Count, _rightData.Count); - return; - } - break; - case LinkRequest.RequestTypes.Write: - Console.Write("LW "); - _leftData.Enqueue(l.Data); - l = LeftIn.Take(); - continue; - case LinkRequest.RequestTypes.Read: - case LinkRequest.RequestTypes.Poll: - if (_rightData.Count > 0) - { - if (l.RequestType == LinkRequest.RequestTypes.Read) - Console.Write("LR "); - LeftOut.Add(new LinkResult - { - Data = l.RequestType == LinkRequest.RequestTypes.Read ? _rightData.Dequeue() : _rightData.Peek(), - Return = true - }); - l = LeftIn.Take(); - continue; - } - else if (r.RequestType != LinkRequest.RequestTypes.Write) - { - if (l.RequestType == LinkRequest.RequestTypes.Read) - Console.Write("L! "); - LeftOut.Add(new LinkResult - { - Data = l.Data, - Return = false - }); - l = LeftIn.Take(); - continue; - } - else - { - break; - } - } - switch (r.RequestType) - { - case LinkRequest.RequestTypes.Write: - Console.Write("RW "); - _rightData.Enqueue(r.Data); - r = RightIn.Take(); - continue; - case LinkRequest.RequestTypes.Read: - case LinkRequest.RequestTypes.Poll: - if (_leftData.Count > 0) - { - if (r.RequestType == LinkRequest.RequestTypes.Read) - Console.Write("RR "); - RightOut.Add(new LinkResult - { - Data = r.RequestType == LinkRequest.RequestTypes.Read ? _leftData.Dequeue() : _leftData.Peek(), - Return = true - }); - r = RightIn.Take(); - continue; - } - else if (l.RequestType != LinkRequest.RequestTypes.Write) - { - if (r.RequestType == LinkRequest.RequestTypes.Read) - Console.Write("R! "); - RightOut.Add(new LinkResult - { - Data = r.Data, - Return = false - }); - r = RightIn.Take(); - continue; - } - else - { - break; - } - } - } - } - } - - public struct LinkRequest - { - public enum RequestTypes : byte - { - Read, - Poll, - Write, - EndOfFrame - } - public RequestTypes RequestType; - public byte Data; - } - public struct LinkResult - { - public byte Data; - public bool Return; - } - - private unsafe class LinkInterop - { - private readonly BlockingCollection _push; - private readonly BlockingCollection _pull; - private NeoGeoPort _core; - private readonly IntPtr _readcb; - private readonly IntPtr _pollcb; - private readonly IntPtr _writecb; - private readonly IImportResolver _exporter; - - public LinkInterop(NeoGeoPort core, BlockingCollection push, BlockingCollection pull) - { - _core = core; - _push = push; - _pull = pull; - _exporter = BizExvoker.GetExvoker(this); - _readcb = _exporter.SafeResolve("CommsReadCallback"); - _pollcb = _exporter.SafeResolve("CommsPollCallback"); - _writecb = _exporter.SafeResolve("CommsWriteCallback"); - ConnectPointers(); - } - - private void ConnectPointers() - { - _core._neopop.SetCommsCallbacks(_readcb, _pollcb, _writecb); - } - - private bool CommsPollNoBuffer() - { - _push.Add(new LinkRequest - { - RequestType = LinkRequest.RequestTypes.Poll - }); - return _pull.Take().Return; - } - - [BizExport(CallingConvention.Cdecl)] - public bool CommsReadCallback(byte* buffer) - { - if (buffer == null) - return CommsPollNoBuffer(); - _push.Add(new LinkRequest - { - RequestType = LinkRequest.RequestTypes.Read, - Data = *buffer - }); - var r = _pull.Take(); - *buffer = r.Data; - return r.Return; - } - [BizExport(CallingConvention.Cdecl)] - public bool CommsPollCallback(byte* buffer) - { - if (buffer == null) - return CommsPollNoBuffer(); - _push.Add(new LinkRequest - { - RequestType = LinkRequest.RequestTypes.Poll, - Data = *buffer - }); - var r = _pull.Take(); - *buffer = r.Data; - return r.Return; - } - [BizExport(CallingConvention.Cdecl)] - public void CommsWriteCallback(byte data) - { - _push.Add(new LinkRequest - { - RequestType = LinkRequest.RequestTypes.Write, - Data = data - }); - } - - public void SignalEndOfFrame() - { - _push.Add(new LinkRequest - { - RequestType = LinkRequest.RequestTypes.EndOfFrame - }); - } - - public void PostLoadState() - { - ConnectPointers(); - } - } - - #endregion - - private class PrefixController : IController - { - public PrefixController(IController controller, string prefix) - { - _controller = controller; - _prefix = prefix; - } - - private readonly IController _controller; - private readonly string _prefix; - - public ControllerDefinition Definition => null; - - public float GetFloat(string name) - { - return _controller.GetFloat(_prefix + name); - } - - public bool IsPressed(string button) - { - return _controller.IsPressed(_prefix + button); - } - } - - public ControllerDefinition ControllerDefinition => DualNeoGeoPortController; - - private static readonly ControllerDefinition DualNeoGeoPortController = new ControllerDefinition - { - BoolButtons = - { - "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 A", "P1 B", "P1 Option", "P1 Power", - "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 A", "P2 B", "P2 Option", "P2 Power" - }, - Name = "Dual NeoGeo Portable Controller" - }; - - public void ResetCounters() - { - Frame = 0; - } - - public int Frame { get; private set; } - - public IEmulatorServiceProvider ServiceProvider => _serviceProvider; - - public CoreComm CoreComm { get; } - - public bool DeterministicEmulation => _left.DeterministicEmulation && _right.DeterministicEmulation; - - public string SystemId => "DNGP"; - - public void Dispose() - { - if (!_disposed) - { - _left.Dispose(); - _right.Dispose(); - _left = null; - _right = null; - _disposed = true; - } - } - } -} +using BizHawk.Common; +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Sound; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.SNK +{ + [CoreAttributes("Dual NeoPop", "Thomas Klausner and natt", true, false, "0.9.44.1", + "https://mednafen.github.io/releases/", false)] + public class DualNeoGeoPort : IEmulator + { + private NeoGeoPort _left; + private NeoGeoPort _right; + private readonly BasicServiceProvider _serviceProvider; + private bool _disposed = false; + private readonly DualSyncSound _soundProvider; + private readonly SideBySideVideo _videoProvider; + private readonly LinkInterop _leftEnd; + private readonly LinkInterop _rightEnd; + private readonly LinkCable _linkCable; + + [CoreConstructor("DNGP")] + public DualNeoGeoPort(CoreComm comm, byte[] rom, bool deterministic) + { + CoreComm = comm; + _left = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.CanonicalStart); + _right = new NeoGeoPort(comm, rom, new NeoGeoPort.SyncSettings { Language = LibNeoGeoPort.Language.English }, deterministic, PeRunner.AlternateStart); + _linkCable = new LinkCable(); + _leftEnd = new LinkInterop(_left, _linkCable.LeftIn, _linkCable.LeftOut); + _rightEnd = new LinkInterop(_right, _linkCable.RightIn, _linkCable.RightOut); + + + _serviceProvider = new BasicServiceProvider(this); + _soundProvider = new DualSyncSound(_left, _right); + _serviceProvider.Register(_soundProvider); + _videoProvider = new SideBySideVideo(_left, _right); + _serviceProvider.Register(_videoProvider); + } + + public void FrameAdvance(IController controller, bool render, bool rendersound = true) + { + var t1 = Task.Run(() => + { + _left.FrameAdvance(new PrefixController(controller, "P1 "), render, rendersound); + _leftEnd.SignalEndOfFrame(); + }); + var t2 = Task.Run(() => + { + _right.FrameAdvance(new PrefixController(controller, "P2 "), render, rendersound); + _rightEnd.SignalEndOfFrame(); + }); + var t3 = Task.Run(() => + { + _linkCable.RunFrame(); + }); + Task.WaitAll(t1, t2, t3); + Frame++; + _soundProvider.Fetch(); + _videoProvider.Fetch(); + } + + #region link cable + + private class LinkCable + { + public readonly BlockingCollection LeftIn = new BlockingCollection(); + public readonly BlockingCollection LeftOut = new BlockingCollection(); + public readonly BlockingCollection RightIn = new BlockingCollection(); + public readonly BlockingCollection RightOut = new BlockingCollection(); + + private readonly Queue _leftData = new Queue(); + private readonly Queue _rightData = new Queue(); + + public void RunFrame() + { + LinkRequest l = LeftIn.Take(); + LinkRequest r = RightIn.Take(); + while (true) + { + switch (l.RequestType) + { + case LinkRequest.RequestTypes.EndOfFrame: + if (r.RequestType == LinkRequest.RequestTypes.EndOfFrame) + { + Console.WriteLine("\nEnd of Frame {0} {1}", _leftData.Count, _rightData.Count); + return; + } + break; + case LinkRequest.RequestTypes.Write: + Console.Write("LW "); + _leftData.Enqueue(l.Data); + l = LeftIn.Take(); + continue; + case LinkRequest.RequestTypes.Read: + case LinkRequest.RequestTypes.Poll: + if (_rightData.Count > 0) + { + if (l.RequestType == LinkRequest.RequestTypes.Read) + Console.Write("LR "); + LeftOut.Add(new LinkResult + { + Data = l.RequestType == LinkRequest.RequestTypes.Read ? _rightData.Dequeue() : _rightData.Peek(), + Return = true + }); + l = LeftIn.Take(); + continue; + } + else if (r.RequestType != LinkRequest.RequestTypes.Write) + { + if (l.RequestType == LinkRequest.RequestTypes.Read) + Console.Write("L! "); + LeftOut.Add(new LinkResult + { + Data = l.Data, + Return = false + }); + l = LeftIn.Take(); + continue; + } + else + { + break; + } + } + switch (r.RequestType) + { + case LinkRequest.RequestTypes.Write: + Console.Write("RW "); + _rightData.Enqueue(r.Data); + r = RightIn.Take(); + continue; + case LinkRequest.RequestTypes.Read: + case LinkRequest.RequestTypes.Poll: + if (_leftData.Count > 0) + { + if (r.RequestType == LinkRequest.RequestTypes.Read) + Console.Write("RR "); + RightOut.Add(new LinkResult + { + Data = r.RequestType == LinkRequest.RequestTypes.Read ? _leftData.Dequeue() : _leftData.Peek(), + Return = true + }); + r = RightIn.Take(); + continue; + } + else if (l.RequestType != LinkRequest.RequestTypes.Write) + { + if (r.RequestType == LinkRequest.RequestTypes.Read) + Console.Write("R! "); + RightOut.Add(new LinkResult + { + Data = r.Data, + Return = false + }); + r = RightIn.Take(); + continue; + } + else + { + break; + } + } + } + } + } + + public struct LinkRequest + { + public enum RequestTypes : byte + { + Read, + Poll, + Write, + EndOfFrame + } + public RequestTypes RequestType; + public byte Data; + } + public struct LinkResult + { + public byte Data; + public bool Return; + } + + private unsafe class LinkInterop + { + private readonly BlockingCollection _push; + private readonly BlockingCollection _pull; + private NeoGeoPort _core; + private readonly IntPtr _readcb; + private readonly IntPtr _pollcb; + private readonly IntPtr _writecb; + private readonly IImportResolver _exporter; + + public LinkInterop(NeoGeoPort core, BlockingCollection push, BlockingCollection pull) + { + _core = core; + _push = push; + _pull = pull; + _exporter = BizExvoker.GetExvoker(this, CallingConventionAdapters.Waterbox); + _readcb = _exporter.SafeResolve("CommsReadCallback"); + _pollcb = _exporter.SafeResolve("CommsPollCallback"); + _writecb = _exporter.SafeResolve("CommsWriteCallback"); + ConnectPointers(); + } + + private void ConnectPointers() + { + _core._neopop.SetCommsCallbacks(_readcb, _pollcb, _writecb); + } + + private bool CommsPollNoBuffer() + { + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.Poll + }); + return _pull.Take().Return; + } + + [BizExport(CallingConvention.Cdecl)] + public bool CommsReadCallback(byte* buffer) + { + if (buffer == null) + return CommsPollNoBuffer(); + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.Read, + Data = *buffer + }); + var r = _pull.Take(); + *buffer = r.Data; + return r.Return; + } + [BizExport(CallingConvention.Cdecl)] + public bool CommsPollCallback(byte* buffer) + { + if (buffer == null) + return CommsPollNoBuffer(); + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.Poll, + Data = *buffer + }); + var r = _pull.Take(); + *buffer = r.Data; + return r.Return; + } + [BizExport(CallingConvention.Cdecl)] + public void CommsWriteCallback(byte data) + { + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.Write, + Data = data + }); + } + + public void SignalEndOfFrame() + { + _push.Add(new LinkRequest + { + RequestType = LinkRequest.RequestTypes.EndOfFrame + }); + } + + public void PostLoadState() + { + ConnectPointers(); + } + } + + #endregion + + private class PrefixController : IController + { + public PrefixController(IController controller, string prefix) + { + _controller = controller; + _prefix = prefix; + } + + private readonly IController _controller; + private readonly string _prefix; + + public ControllerDefinition Definition => null; + + public float GetFloat(string name) + { + return _controller.GetFloat(_prefix + name); + } + + public bool IsPressed(string button) + { + return _controller.IsPressed(_prefix + button); + } + } + + public ControllerDefinition ControllerDefinition => DualNeoGeoPortController; + + private static readonly ControllerDefinition DualNeoGeoPortController = new ControllerDefinition + { + BoolButtons = + { + "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 A", "P1 B", "P1 Option", "P1 Power", + "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 A", "P2 B", "P2 Option", "P2 Power" + }, + Name = "Dual NeoGeo Portable Controller" + }; + + public void ResetCounters() + { + Frame = 0; + } + + public int Frame { get; private set; } + + public IEmulatorServiceProvider ServiceProvider => _serviceProvider; + + public CoreComm CoreComm { get; } + + public bool DeterministicEmulation => _left.DeterministicEmulation && _right.DeterministicEmulation; + + public string SystemId => "DNGP"; + + public void Dispose() + { + if (!_disposed) + { + _left.Dispose(); + _right.Dispose(); + _left = null; + _right = null; + _disposed = true; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs index 8df435dd0d..b10437c208 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.cs @@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx64 using (_elf.EnterExit()) { - Core = BizInvoker.GetInvoker(_elf, _elf); + Core = BizInvoker.GetInvoker(_elf, _elf, CallingConventionAdapters.Waterbox); _syncSettings = (GPGXSyncSettings)syncSettings ?? new GPGXSyncSettings(); _settings = (GPGXSettings)settings ?? new GPGXSettings(); diff --git a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs index dcca1d1d30..5498331740 100644 --- a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs +++ b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs @@ -1,487 +1,488 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Runtime.InteropServices; -using ELFSharp.ELF; -using ELFSharp.ELF.Sections; -using ELFSharp.ELF.Segments; -using System.Reflection; -using BizHawk.Common; -using System.Security.Cryptography; -using System.IO; -using System.Collections.Concurrent; -using System.Threading; -using BizHawk.Emulation.Common; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - public sealed class ElfRunner : Swappable, IImportResolver, IBinaryStateable - { - // TODO: a lot of things only work with our elves and aren't fully generalized - - private ELF _elf; - private byte[] _elfhash; - - /// - /// executable is loaded here - /// - private MemoryBlock _base; - /// - /// standard malloc() heap - /// - private Heap _heap; - - /// - /// sealed heap (writable only during init) - /// - private Heap _sealedheap; - - /// - /// invisible heap (not savestated, use with care) - /// - private Heap _invisibleheap; - - private long _loadoffset; - private Dictionary> _symdict; - private List> _symlist; - - /// - /// everything to clean up at dispose time - /// - private List _disposeList = new List(); - - private ulong GetHeapStart(ulong prevend) - { - // if relocatable, we won't have constant pointers, so put the heap anywhere - // otherwise, put the heap at a canonical location aligned 1MB from the end of the elf, then incremented 16MB - ulong heapstart = HasRelocations() ? 0 : ((prevend - 1) | 0xfffff) + 0x1000001; - return heapstart; - } - - public ElfRunner(string filename, long heapsize, long sealedheapsize, long invisibleheapsize) - { - using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) - { - _elfhash = WaterboxUtils.Hash(fs); - } - - // todo: hack up this baby to take Streams - _elf = ELFReader.Load(filename); - - var loadsegs = _elf.Segments.Where(s => s.Type == SegmentType.Load); - - long orig_start = loadsegs.Min(s => s.Address); - orig_start &= ~(Environment.SystemPageSize - 1); - long orig_end = loadsegs.Max(s => s.Address + s.Size); - if (HasRelocations()) - { - _base = new MemoryBlock((ulong)(orig_end - orig_start)); - _loadoffset = (long)_base.Start - orig_start; - Initialize(0); - } - else - { - Initialize((ulong)orig_start); - _base = new MemoryBlock((ulong)orig_start, (ulong)(orig_end - orig_start)); - _loadoffset = 0; - Enter(); - } - - try - { - _disposeList.Add(_base); - AddMemoryBlock(_base); - _base.Activate(); - _base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.RW); - - foreach (var seg in loadsegs) - { - var data = seg.GetContents(); - Marshal.Copy(data, 0, Z.SS(seg.Address + _loadoffset), data.Length); - } - RegisterSymbols(); - ProcessRelocations(); - - _base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.R); - - foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Allocatable) != 0)) - { - if ((sec.Flags & SectionFlags.Executable) != 0) - _base.Protect((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, MemoryBlock.Protection.RX); - else if ((sec.Flags & SectionFlags.Writable) != 0) - _base.Protect((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, MemoryBlock.Protection.RW); - } - - ulong end = _base.End; - - if (heapsize > 0) - { - _heap = new Heap(GetHeapStart(end), (ulong)heapsize, "sbrk-heap"); - _heap.Memory.Activate(); - end = _heap.Memory.End; - _disposeList.Add(_heap); - AddMemoryBlock(_heap.Memory); - } - - if (sealedheapsize > 0) - { - _sealedheap = new Heap(GetHeapStart(end), (ulong)sealedheapsize, "sealed-heap"); - _sealedheap.Memory.Activate(); - end = _sealedheap.Memory.End; - _disposeList.Add(_sealedheap); - AddMemoryBlock(_sealedheap.Memory); - } - - if (invisibleheapsize > 0) - { - _invisibleheap = new Heap(GetHeapStart(end), (ulong)invisibleheapsize, "invisible-heap"); - _invisibleheap.Memory.Activate(); - end = _invisibleheap.Memory.End; - _disposeList.Add(_invisibleheap); - AddMemoryBlock(_invisibleheap.Memory); - } - - ConnectAllClibPatches(); - Console.WriteLine("Loaded {0}@{1:X16}", filename, _base.Start); - foreach (var sec in _elf.Sections.Where(s => s.LoadAddress != 0)) - { - Console.WriteLine(" {0}@{1:X16}, size {2}", sec.Name.PadLeft(20), sec.LoadAddress + _loadoffset, sec.Size.ToString().PadLeft(12)); - } - - PrintTopSavableSymbols(); - } - catch - { - Dispose(); - throw; - } - finally - { - Exit(); - } - } - - private void PrintTopSavableSymbols() - { - Console.WriteLine("Top savestate symbols:"); - foreach (var text in _symlist - .Where(s => s.PointedSection != null && (s.PointedSection.Flags & SectionFlags.Writable) != 0) - .OrderByDescending(s => s.Size) - .Take(30) - .Select(s => string.Format("{0} size {1}", s.Name, s.Size))) - { - Console.WriteLine(text); - } - } - - private class Elf32_Rel - { - public long Address; - public byte Type; - public int SymbolIdx; - public long Addend; - - public Elf32_Rel(byte[] data, int start, int len) - { - if (len == 8 || len == 12) - { - Address = BitConverter.ToInt32(data, start); - Type = data[start + 4]; - SymbolIdx = (int)(BitConverter.ToUInt32(data, start + 4) >> 8); - Addend = data.Length == 12 ? BitConverter.ToInt32(data, start + 8) : 0; - } - else - { - throw new InvalidOperationException(); - } - } - } - - private bool HasRelocations() - { - return _elf.Sections.Any(s => s.Name.StartsWith(".rel")); - } - - // elfsharp does not read relocation tables, so there - private void ProcessRelocations() - { - // todo: amd64 - foreach (var rel in _elf.Sections.Where(s => s.Name.StartsWith(".rel"))) - { - byte[] data = rel.GetContents(); - var symbols = Enumerable.Range(0, data.Length / 8) - .Select(i => new Elf32_Rel(data, i * 8, 8)); - foreach (var symbol in symbols) - { - ApplyRelocation(symbol); - } - } - } - private void ApplyRelocation(Elf32_Rel rel) - { - // http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf - // this is probably mostly wrong - - long val = 0; - long A = rel.Addend; - // since all symbols were moved by the same amount, just add _loadoffset here - long S = _symlist[rel.SymbolIdx].Value + _loadoffset; - long B = _loadoffset; - switch (rel.Type) - { - case 0: val = 0; break; - case 1: val = S + A; break; - case 2: throw new NotImplementedException(); - case 3: throw new NotImplementedException(); - case 4: throw new NotImplementedException(); - case 5: val = 0; break; - case 6: val = S; break; - case 7: val = S; break; - case 8: val = B + A; break; - case 9: throw new NotImplementedException(); - case 10: throw new NotImplementedException(); - default: throw new InvalidOperationException(); - } - byte[] tmp = new byte[4]; - Marshal.Copy((IntPtr)(rel.Address + _loadoffset), tmp, 0, 4); - long currentVal = BitConverter.ToUInt32(tmp, 0); - tmp = BitConverter.GetBytes((uint)(currentVal + val)); - Marshal.Copy(tmp, 0, (IntPtr)(rel.Address + _loadoffset), 4); - } - - private void RegisterSymbols() - { - var symbols = ((ISymbolTable)_elf.GetSection(".symtab")) - .Entries - .Cast>(); - - // when there are duplicate names, don't register either in the dictionary - _symdict = symbols - .GroupBy(e => e.Name) - .Where(g => g.Count() == 1) - .ToDictionary(g => g.Key, g => g.First()); - - _symlist = symbols.ToList(); - } - - public void Seal() - { - Enter(); - try - { - _sealedheap.Seal(); - } - finally - { - Exit(); - } - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - foreach (var d in _disposeList) - d.Dispose(); - _disposeList.Clear(); - PurgeMemoryBlocks(); - _base = null; - _heap = null; - _sealedheap = null; - _invisibleheap = null; - } - } - - #region clib monkeypatches - - // our clib expects a few function pointers to be defined for it - - /// - /// abort() / other abnormal situation - /// - /// desired exit code - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void Trap_D(); - - /// - /// expand heap - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate IntPtr Sbrk_D(UIntPtr n); - - /// - /// output a string - /// - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void DebugPuts_D(string s); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate IntPtr SbrkSealed_D(UIntPtr n); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate IntPtr SbrkInvisible_D(UIntPtr n); - - [CLibPatch("_ecl_trap")] - private void Trap() - { - throw new InvalidOperationException("Waterbox code trapped!"); - } - - [CLibPatch("_ecl_sbrk")] - private IntPtr Sbrk(UIntPtr n) - { - return Z.US(_heap.Allocate((ulong)n, 1)); - } - - [CLibPatch("_ecl_debug_puts")] - private void DebugPuts(string s) - { - Console.WriteLine("Waterbox debug puts: {0}", s); - } - - [CLibPatch("_ecl_sbrk_sealed")] - private IntPtr SbrkSealed(UIntPtr n) - { - return Z.US(_sealedheap.Allocate((ulong)n, 16)); - } - - [CLibPatch("_ecl_sbrk_invisible")] - private IntPtr SbrkInvisible(UIntPtr n) - { - return Z.US(_invisibleheap.Allocate((ulong)n, 16)); - } - - /// - /// list of delegates that need to not be GCed - /// - private List _delegates = new List(); - - private void ConnectAllClibPatches() - { - _delegates.Clear(); // in case we're reconnecting - - var methods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) - .Where(mi => mi.GetCustomAttributes(typeof(CLibPatchAttribute), false).Length > 0); - foreach (var mi in methods) - { - var delegateType = GetType().GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic) - .Single(t => t.Name == mi.Name + "_D"); - var del = Delegate.CreateDelegate(delegateType, this, mi); - IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); - _delegates.Add(del); - var sym = _symdict[((CLibPatchAttribute)mi.GetCustomAttributes(typeof(CLibPatchAttribute), false)[0]).NativeName]; - if (sym.Size != IntPtr.Size) - throw new InvalidOperationException("Unexpected function pointer size patching clib!"); - IntPtr dest = Z.SS(sym.Value + _loadoffset); - Marshal.Copy(new[] { ptr }, 0, dest, 1); - } - } - - [AttributeUsage(AttributeTargets.Method)] - private class CLibPatchAttribute : Attribute - { - public string NativeName { get; private set; } - public CLibPatchAttribute(string nativeName) - { - NativeName = nativeName; - } - } - - #endregion - - public IntPtr Resolve(string entryPoint) - { - SymbolEntry sym; - if (_symdict.TryGetValue(entryPoint, out sym)) - { - return Z.SS(sym.Value + _loadoffset); - } - else - { - return IntPtr.Zero; - } - } - - #region state - - const ulong MAGIC = 0xb00b1e5b00b1e569; - - public void SaveStateBinary(BinaryWriter bw) - { - Enter(); - try - { - bw.Write(MAGIC); - bw.Write(_elfhash); - bw.Write(_loadoffset); - foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Writable) != 0)) - { - var ms = _base.GetStream((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, false); - bw.Write(sec.Size); - ms.CopyTo(bw.BaseStream); - } - - if (_heap != null) _heap.SaveStateBinary(bw); - if (_sealedheap != null) _sealedheap.SaveStateBinary(bw); - bw.Write(MAGIC); - } - finally - { - Exit(); - } - } - - public void LoadStateBinary(BinaryReader br) - { - Enter(); - try - { - if (br.ReadUInt64() != MAGIC) - throw new InvalidOperationException("Magic not magic enough!"); - if (!br.ReadBytes(_elfhash.Length).SequenceEqual(_elfhash)) - throw new InvalidOperationException("Elf changed disguise!"); - if (br.ReadInt64() != _loadoffset) - throw new InvalidOperationException("Trickys elves moved on you!"); - - foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Writable) != 0)) - { - var len = br.ReadInt64(); - if (sec.Size != len) - throw new InvalidOperationException("Unexpected section size for " + sec.Name); - var ms = _base.GetStream((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, true); - WaterboxUtils.CopySome(br.BaseStream, ms, len); - } - - if (_heap != null) _heap.LoadStateBinary(br); - if (_sealedheap != null) _sealedheap.LoadStateBinary(br); - if (br.ReadUInt64() != MAGIC) - throw new InvalidOperationException("Magic not magic enough!"); - - // the syscall trampolines were overwritten in loadstate (they're in .bss), and if we're cross-session, - // are no longer valid. cores must similiarly resend any external pointers they gave the core. - ConnectAllClibPatches(); - } - finally - { - Exit(); - } - } - - #endregion - - #region utils - - private byte[] HashSection(ulong ptr, ulong len) - { - using (var h = SHA1.Create()) - { - var ms = _base.GetStream(ptr, len, false); - return h.ComputeHash(ms); - } - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using ELFSharp.ELF; +using ELFSharp.ELF.Sections; +using ELFSharp.ELF.Segments; +using System.Reflection; +using BizHawk.Common; +using System.Security.Cryptography; +using System.IO; +using System.Collections.Concurrent; +using System.Threading; +using BizHawk.Emulation.Common; +using BizHawk.Common.BizInvoke; + +namespace BizHawk.Emulation.Cores.Waterbox +{ + public sealed class ElfRunner : Swappable, IImportResolver, IBinaryStateable + { + // TODO: a lot of things only work with our elves and aren't fully generalized + + private ELF _elf; + private byte[] _elfhash; + + /// + /// executable is loaded here + /// + private MemoryBlock _base; + /// + /// standard malloc() heap + /// + private Heap _heap; + + /// + /// sealed heap (writable only during init) + /// + private Heap _sealedheap; + + /// + /// invisible heap (not savestated, use with care) + /// + private Heap _invisibleheap; + + private long _loadoffset; + private Dictionary> _symdict; + private List> _symlist; + + /// + /// everything to clean up at dispose time + /// + private List _disposeList = new List(); + + private ulong GetHeapStart(ulong prevend) + { + // if relocatable, we won't have constant pointers, so put the heap anywhere + // otherwise, put the heap at a canonical location aligned 1MB from the end of the elf, then incremented 16MB + ulong heapstart = HasRelocations() ? 0 : ((prevend - 1) | 0xfffff) + 0x1000001; + return heapstart; + } + + public ElfRunner(string filename, long heapsize, long sealedheapsize, long invisibleheapsize) + { + using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read)) + { + _elfhash = WaterboxUtils.Hash(fs); + } + + // todo: hack up this baby to take Streams + _elf = ELFReader.Load(filename); + + var loadsegs = _elf.Segments.Where(s => s.Type == SegmentType.Load); + + long orig_start = loadsegs.Min(s => s.Address); + orig_start &= ~(Environment.SystemPageSize - 1); + long orig_end = loadsegs.Max(s => s.Address + s.Size); + if (HasRelocations()) + { + _base = new MemoryBlock((ulong)(orig_end - orig_start)); + _loadoffset = (long)_base.Start - orig_start; + Initialize(0); + } + else + { + Initialize((ulong)orig_start); + _base = new MemoryBlock((ulong)orig_start, (ulong)(orig_end - orig_start)); + _loadoffset = 0; + Enter(); + } + + try + { + _disposeList.Add(_base); + AddMemoryBlock(_base); + _base.Activate(); + _base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.RW); + + foreach (var seg in loadsegs) + { + var data = seg.GetContents(); + Marshal.Copy(data, 0, Z.SS(seg.Address + _loadoffset), data.Length); + } + RegisterSymbols(); + ProcessRelocations(); + + _base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.R); + + foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Allocatable) != 0)) + { + if ((sec.Flags & SectionFlags.Executable) != 0) + _base.Protect((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, MemoryBlock.Protection.RX); + else if ((sec.Flags & SectionFlags.Writable) != 0) + _base.Protect((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, MemoryBlock.Protection.RW); + } + + ulong end = _base.End; + + if (heapsize > 0) + { + _heap = new Heap(GetHeapStart(end), (ulong)heapsize, "sbrk-heap"); + _heap.Memory.Activate(); + end = _heap.Memory.End; + _disposeList.Add(_heap); + AddMemoryBlock(_heap.Memory); + } + + if (sealedheapsize > 0) + { + _sealedheap = new Heap(GetHeapStart(end), (ulong)sealedheapsize, "sealed-heap"); + _sealedheap.Memory.Activate(); + end = _sealedheap.Memory.End; + _disposeList.Add(_sealedheap); + AddMemoryBlock(_sealedheap.Memory); + } + + if (invisibleheapsize > 0) + { + _invisibleheap = new Heap(GetHeapStart(end), (ulong)invisibleheapsize, "invisible-heap"); + _invisibleheap.Memory.Activate(); + end = _invisibleheap.Memory.End; + _disposeList.Add(_invisibleheap); + AddMemoryBlock(_invisibleheap.Memory); + } + + ConnectAllClibPatches(); + Console.WriteLine("Loaded {0}@{1:X16}", filename, _base.Start); + foreach (var sec in _elf.Sections.Where(s => s.LoadAddress != 0)) + { + Console.WriteLine(" {0}@{1:X16}, size {2}", sec.Name.PadLeft(20), sec.LoadAddress + _loadoffset, sec.Size.ToString().PadLeft(12)); + } + + PrintTopSavableSymbols(); + } + catch + { + Dispose(); + throw; + } + finally + { + Exit(); + } + } + + private void PrintTopSavableSymbols() + { + Console.WriteLine("Top savestate symbols:"); + foreach (var text in _symlist + .Where(s => s.PointedSection != null && (s.PointedSection.Flags & SectionFlags.Writable) != 0) + .OrderByDescending(s => s.Size) + .Take(30) + .Select(s => string.Format("{0} size {1}", s.Name, s.Size))) + { + Console.WriteLine(text); + } + } + + private class Elf32_Rel + { + public long Address; + public byte Type; + public int SymbolIdx; + public long Addend; + + public Elf32_Rel(byte[] data, int start, int len) + { + if (len == 8 || len == 12) + { + Address = BitConverter.ToInt32(data, start); + Type = data[start + 4]; + SymbolIdx = (int)(BitConverter.ToUInt32(data, start + 4) >> 8); + Addend = data.Length == 12 ? BitConverter.ToInt32(data, start + 8) : 0; + } + else + { + throw new InvalidOperationException(); + } + } + } + + private bool HasRelocations() + { + return _elf.Sections.Any(s => s.Name.StartsWith(".rel")); + } + + // elfsharp does not read relocation tables, so there + private void ProcessRelocations() + { + // todo: amd64 + foreach (var rel in _elf.Sections.Where(s => s.Name.StartsWith(".rel"))) + { + byte[] data = rel.GetContents(); + var symbols = Enumerable.Range(0, data.Length / 8) + .Select(i => new Elf32_Rel(data, i * 8, 8)); + foreach (var symbol in symbols) + { + ApplyRelocation(symbol); + } + } + } + private void ApplyRelocation(Elf32_Rel rel) + { + // http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf + // this is probably mostly wrong + + long val = 0; + long A = rel.Addend; + // since all symbols were moved by the same amount, just add _loadoffset here + long S = _symlist[rel.SymbolIdx].Value + _loadoffset; + long B = _loadoffset; + switch (rel.Type) + { + case 0: val = 0; break; + case 1: val = S + A; break; + case 2: throw new NotImplementedException(); + case 3: throw new NotImplementedException(); + case 4: throw new NotImplementedException(); + case 5: val = 0; break; + case 6: val = S; break; + case 7: val = S; break; + case 8: val = B + A; break; + case 9: throw new NotImplementedException(); + case 10: throw new NotImplementedException(); + default: throw new InvalidOperationException(); + } + byte[] tmp = new byte[4]; + Marshal.Copy((IntPtr)(rel.Address + _loadoffset), tmp, 0, 4); + long currentVal = BitConverter.ToUInt32(tmp, 0); + tmp = BitConverter.GetBytes((uint)(currentVal + val)); + Marshal.Copy(tmp, 0, (IntPtr)(rel.Address + _loadoffset), 4); + } + + private void RegisterSymbols() + { + var symbols = ((ISymbolTable)_elf.GetSection(".symtab")) + .Entries + .Cast>(); + + // when there are duplicate names, don't register either in the dictionary + _symdict = symbols + .GroupBy(e => e.Name) + .Where(g => g.Count() == 1) + .ToDictionary(g => g.Key, g => g.First()); + + _symlist = symbols.ToList(); + } + + public void Seal() + { + Enter(); + try + { + _sealedheap.Seal(); + } + finally + { + Exit(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + foreach (var d in _disposeList) + d.Dispose(); + _disposeList.Clear(); + PurgeMemoryBlocks(); + _base = null; + _heap = null; + _sealedheap = null; + _invisibleheap = null; + } + } + + #region clib monkeypatches + + // our clib expects a few function pointers to be defined for it + + /// + /// abort() / other abnormal situation + /// + /// desired exit code + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void Trap_D(); + + /// + /// expand heap + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate IntPtr Sbrk_D(UIntPtr n); + + /// + /// output a string + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void DebugPuts_D(string s); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate IntPtr SbrkSealed_D(UIntPtr n); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate IntPtr SbrkInvisible_D(UIntPtr n); + + [CLibPatch("_ecl_trap")] + private void Trap() + { + throw new InvalidOperationException("Waterbox code trapped!"); + } + + [CLibPatch("_ecl_sbrk")] + private IntPtr Sbrk(UIntPtr n) + { + return Z.US(_heap.Allocate((ulong)n, 1)); + } + + [CLibPatch("_ecl_debug_puts")] + private void DebugPuts(string s) + { + Console.WriteLine("Waterbox debug puts: {0}", s); + } + + [CLibPatch("_ecl_sbrk_sealed")] + private IntPtr SbrkSealed(UIntPtr n) + { + return Z.US(_sealedheap.Allocate((ulong)n, 16)); + } + + [CLibPatch("_ecl_sbrk_invisible")] + private IntPtr SbrkInvisible(UIntPtr n) + { + return Z.US(_invisibleheap.Allocate((ulong)n, 16)); + } + + /// + /// list of delegates that need to not be GCed + /// + private List _delegates = new List(); + + private void ConnectAllClibPatches() + { + _delegates.Clear(); // in case we're reconnecting + + var methods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(mi => mi.GetCustomAttributes(typeof(CLibPatchAttribute), false).Length > 0); + foreach (var mi in methods) + { + var delegateType = GetType().GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic) + .Single(t => t.Name == mi.Name + "_D"); + var del = Delegate.CreateDelegate(delegateType, this, mi); + IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); + _delegates.Add(del); + var sym = _symdict[((CLibPatchAttribute)mi.GetCustomAttributes(typeof(CLibPatchAttribute), false)[0]).NativeName]; + if (sym.Size != IntPtr.Size) + throw new InvalidOperationException("Unexpected function pointer size patching clib!"); + IntPtr dest = Z.SS(sym.Value + _loadoffset); + Marshal.Copy(new[] { ptr }, 0, dest, 1); + } + } + + [AttributeUsage(AttributeTargets.Method)] + private class CLibPatchAttribute : Attribute + { + public string NativeName { get; private set; } + public CLibPatchAttribute(string nativeName) + { + NativeName = nativeName; + } + } + + #endregion + + public IntPtr Resolve(string entryPoint) + { + SymbolEntry sym; + if (_symdict.TryGetValue(entryPoint, out sym)) + { + return Z.SS(sym.Value + _loadoffset); + } + else + { + return IntPtr.Zero; + } + } + + #region state + + const ulong MAGIC = 0xb00b1e5b00b1e569; + + public void SaveStateBinary(BinaryWriter bw) + { + Enter(); + try + { + bw.Write(MAGIC); + bw.Write(_elfhash); + bw.Write(_loadoffset); + foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Writable) != 0)) + { + var ms = _base.GetStream((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, false); + bw.Write(sec.Size); + ms.CopyTo(bw.BaseStream); + } + + if (_heap != null) _heap.SaveStateBinary(bw); + if (_sealedheap != null) _sealedheap.SaveStateBinary(bw); + bw.Write(MAGIC); + } + finally + { + Exit(); + } + } + + public void LoadStateBinary(BinaryReader br) + { + Enter(); + try + { + if (br.ReadUInt64() != MAGIC) + throw new InvalidOperationException("Magic not magic enough!"); + if (!br.ReadBytes(_elfhash.Length).SequenceEqual(_elfhash)) + throw new InvalidOperationException("Elf changed disguise!"); + if (br.ReadInt64() != _loadoffset) + throw new InvalidOperationException("Trickys elves moved on you!"); + + foreach (var sec in _elf.Sections.Where(s => (s.Flags & SectionFlags.Writable) != 0)) + { + var len = br.ReadInt64(); + if (sec.Size != len) + throw new InvalidOperationException("Unexpected section size for " + sec.Name); + var ms = _base.GetStream((ulong)(sec.LoadAddress + _loadoffset), (ulong)sec.Size, true); + WaterboxUtils.CopySome(br.BaseStream, ms, len); + } + + if (_heap != null) _heap.LoadStateBinary(br); + if (_sealedheap != null) _sealedheap.LoadStateBinary(br); + if (br.ReadUInt64() != MAGIC) + throw new InvalidOperationException("Magic not magic enough!"); + + // the syscall trampolines were overwritten in loadstate (they're in .bss), and if we're cross-session, + // are no longer valid. cores must similiarly resend any external pointers they gave the core. + ConnectAllClibPatches(); + } + finally + { + Exit(); + } + } + + #endregion + + #region utils + + private byte[] HashSection(ulong ptr, ulong len) + { + using (var h = SHA1.Create()) + { + var ms = _base.GetStream(ptr, len, false); + return h.ComputeHash(ms); + } + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Waterbox/Heap.cs b/BizHawk.Emulation.Cores/Waterbox/Heap.cs index 1c5a876621..8d2d58b35d 100644 --- a/BizHawk.Emulation.Cores/Waterbox/Heap.cs +++ b/BizHawk.Emulation.Cores/Waterbox/Heap.cs @@ -1,144 +1,145 @@ -using BizHawk.Emulation.Common; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace BizHawk.Emulation.Cores.Waterbox -{ - /// - /// a simple grow-only fixed max size heap - /// - internal sealed class Heap : IBinaryStateable, IDisposable - { - public MemoryBlock Memory { get; private set; } - /// - /// name, used in identifying errors - /// - public string Name { get; private set; } - /// - /// total number of bytes used - /// - public ulong Used { get; private set; } - - /// - /// true if the heap has been sealed, preventing further changes - /// - public bool Sealed { get; private set; } - - private byte[] _hash; - - public Heap(ulong start, ulong size, string name) - { - Memory = new MemoryBlock(start, size); - Used = 0; - Name = name; - Console.WriteLine("Created heap `{1}` at {0:x16}:{2:x16}", start, name, start + size); - } - - private void EnsureAlignment(int align) - { - if (align > 1) - { - ulong newused = ((Used - 1) | (ulong)(align - 1)) + 1; - if (newused > Memory.Size) - { - throw new InvalidOperationException(string.Format("Failed to meet alignment {0} on heap {1}", align, Name)); - } - Used = newused; - } - } - - public ulong Allocate(ulong size, int align) - { - if (Sealed) - throw new InvalidOperationException(string.Format("Attempt made to allocate from sealed heap {0}", Name)); - - EnsureAlignment(align); - - ulong newused = Used + size; - if (newused > Memory.Size) - { - throw new InvalidOperationException(string.Format("Failed to allocate {0} bytes from heap {1}", size, Name)); - } - ulong ret = Memory.Start + Used; - Memory.Protect(ret, newused - Used, MemoryBlock.Protection.RW); - Used = newused; - Console.WriteLine($"Allocated {size} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)"); - return ret; - } - - public void Seal() - { - if (!Sealed) - { - Memory.Protect(Memory.Start, Used, MemoryBlock.Protection.R); - _hash = WaterboxUtils.Hash(Memory.GetStream(Memory.Start, Used, false)); - Sealed = true; - } - else - { - throw new InvalidOperationException(string.Format("Attempt to reseal heap {0}", Name)); - } - } - - public void SaveStateBinary(BinaryWriter bw) - { - bw.Write(Name); - bw.Write(Used); - if (!Sealed) - { - bw.Write(Memory.XorHash); - var ms = Memory.GetXorStream(Memory.Start, Used, false); - ms.CopyTo(bw.BaseStream); - } - else - { - bw.Write(_hash); - } - } - - public void LoadStateBinary(BinaryReader br) - { - var name = br.ReadString(); - if (name != Name) - // probable cause: internal error - throw new InvalidOperationException(string.Format("Name did not match for heap {0}", Name)); - var used = br.ReadUInt64(); - if (used > Memory.Size) - throw new InvalidOperationException(string.Format("Heap {0} used {1} larger than available {2}", Name, used, Memory.Size)); - if (!Sealed) - { - var hash = br.ReadBytes(Memory.XorHash.Length); - if (!hash.SequenceEqual(Memory.XorHash)) - { - throw new InvalidOperationException(string.Format("Hash did not match for heap {0}. Is this the same rom with the same SyncSettings?", Name)); - } - - Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.None); - Memory.Protect(Memory.Start, used, MemoryBlock.Protection.RW); - var ms = Memory.GetXorStream(Memory.Start, used, true); - WaterboxUtils.CopySome(br.BaseStream, ms, (long)used); - Used = used; - } - else - { - var hash = br.ReadBytes(_hash.Length); - if (!hash.SequenceEqual(_hash)) - { - throw new InvalidOperationException(string.Format("Hash did not match for heap {0}. Is this the same rom with the same SyncSettings?", Name)); - } - } - } - - public void Dispose() - { - if (Memory != null) - { - Memory.Dispose(); - Memory = null; - } - } - } -} +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace BizHawk.Emulation.Cores.Waterbox +{ + /// + /// a simple grow-only fixed max size heap + /// + internal sealed class Heap : IBinaryStateable, IDisposable + { + public MemoryBlock Memory { get; private set; } + /// + /// name, used in identifying errors + /// + public string Name { get; private set; } + /// + /// total number of bytes used + /// + public ulong Used { get; private set; } + + /// + /// true if the heap has been sealed, preventing further changes + /// + public bool Sealed { get; private set; } + + private byte[] _hash; + + public Heap(ulong start, ulong size, string name) + { + Memory = new MemoryBlock(start, size); + Used = 0; + Name = name; + Console.WriteLine("Created heap `{1}` at {0:x16}:{2:x16}", start, name, start + size); + } + + private void EnsureAlignment(int align) + { + if (align > 1) + { + ulong newused = ((Used - 1) | (ulong)(align - 1)) + 1; + if (newused > Memory.Size) + { + throw new InvalidOperationException(string.Format("Failed to meet alignment {0} on heap {1}", align, Name)); + } + Used = newused; + } + } + + public ulong Allocate(ulong size, int align) + { + if (Sealed) + throw new InvalidOperationException(string.Format("Attempt made to allocate from sealed heap {0}", Name)); + + EnsureAlignment(align); + + ulong newused = Used + size; + if (newused > Memory.Size) + { + throw new InvalidOperationException(string.Format("Failed to allocate {0} bytes from heap {1}", size, Name)); + } + ulong ret = Memory.Start + Used; + Memory.Protect(ret, newused - Used, MemoryBlock.Protection.RW); + Used = newused; + Console.WriteLine($"Allocated {size} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)"); + return ret; + } + + public void Seal() + { + if (!Sealed) + { + Memory.Protect(Memory.Start, Used, MemoryBlock.Protection.R); + _hash = WaterboxUtils.Hash(Memory.GetStream(Memory.Start, Used, false)); + Sealed = true; + } + else + { + throw new InvalidOperationException(string.Format("Attempt to reseal heap {0}", Name)); + } + } + + public void SaveStateBinary(BinaryWriter bw) + { + bw.Write(Name); + bw.Write(Used); + if (!Sealed) + { + bw.Write(Memory.XorHash); + var ms = Memory.GetXorStream(Memory.Start, Used, false); + ms.CopyTo(bw.BaseStream); + } + else + { + bw.Write(_hash); + } + } + + public void LoadStateBinary(BinaryReader br) + { + var name = br.ReadString(); + if (name != Name) + // probable cause: internal error + throw new InvalidOperationException(string.Format("Name did not match for heap {0}", Name)); + var used = br.ReadUInt64(); + if (used > Memory.Size) + throw new InvalidOperationException(string.Format("Heap {0} used {1} larger than available {2}", Name, used, Memory.Size)); + if (!Sealed) + { + var hash = br.ReadBytes(Memory.XorHash.Length); + if (!hash.SequenceEqual(Memory.XorHash)) + { + throw new InvalidOperationException(string.Format("Hash did not match for heap {0}. Is this the same rom with the same SyncSettings?", Name)); + } + + Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.None); + Memory.Protect(Memory.Start, used, MemoryBlock.Protection.RW); + var ms = Memory.GetXorStream(Memory.Start, used, true); + WaterboxUtils.CopySome(br.BaseStream, ms, (long)used); + Used = used; + } + else + { + var hash = br.ReadBytes(_hash.Length); + if (!hash.SequenceEqual(_hash)) + { + throw new InvalidOperationException(string.Format("Hash did not match for heap {0}. Is this the same rom with the same SyncSettings?", Name)); + } + } + } + + public void Dispose() + { + if (Memory != null) + { + Memory.Dispose(); + Memory = null; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs b/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs index 81a90396d5..3ae6e57a7c 100644 --- a/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs +++ b/BizHawk.Emulation.Cores/Waterbox/MapHeap.cs @@ -6,7 +6,8 @@ using System.Text; using System.Threading.Tasks; using System.IO; using System.Runtime.InteropServices; - +using BizHawk.Common.BizInvoke; + namespace BizHawk.Emulation.Cores.Waterbox { /// diff --git a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs index ce1792d011..90d78b15ea 100644 --- a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs +++ b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs @@ -107,7 +107,7 @@ namespace BizHawk.Emulation.Cores.Waterbox throw new InvalidOperationException(s); }; _traps.Add(del); - ptr = Marshal.GetFunctionPointerForDelegate(del); + ptr = CallingConventionAdapters.Waterbox.GetFunctionPointerForDelegate(del); } return ptr; }).ToArray(); @@ -354,17 +354,17 @@ namespace BizHawk.Emulation.Cores.Waterbox // in midipix, this just sets up a SEH frame and then calls musl's start_main [BizExport(CallingConvention.Cdecl, EntryPoint = "start_main")] public unsafe int StartMain(IntPtr main, int argc, IntPtr argv, IntPtr libc_start_main) - { - //var del = (LibcStartMain)Marshal.GetDelegateForFunctionPointer(libc_start_main, typeof(LibcStartMain)); - // this will init, and then call user main, and then call exit() - //del(main, argc, argv); - //int* foobar = stackalloc int[128]; - - - // if we return from this, psx will then halt, so break out - //if (_firstTime) - //{ - //_firstTime = false; + { + //var del = (LibcStartMain)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libc_start_main, typeof(LibcStartMain)); + // this will init, and then call user main, and then call exit() + //del(main, argc, argv); + //int* foobar = stackalloc int[128]; + + + // if we return from this, psx will then halt, so break out + //if (_firstTime) + //{ + //_firstTime = false; throw new InvalidOperationException("This shouldn't be called"); //} //else @@ -676,11 +676,11 @@ namespace BizHawk.Emulation.Cores.Waterbox { // load any predefined exports _psx = new Psx(this); - _exports.Add("libpsxscl.so", BizExvoker.GetExvoker(_psx)); + _exports.Add("libpsxscl.so", BizExvoker.GetExvoker(_psx, CallingConventionAdapters.Waterbox)); _emu = new Emu(this); - _exports.Add("libemuhost.so", BizExvoker.GetExvoker(_emu)); + _exports.Add("libemuhost.so", BizExvoker.GetExvoker(_emu, CallingConventionAdapters.Waterbox)); _syscalls = new Syscalls(this); - _exports.Add("__syscalls", BizExvoker.GetExvoker(_syscalls)); + _exports.Add("__syscalls", BizExvoker.GetExvoker(_syscalls, CallingConventionAdapters.Waterbox)); // load and connect all modules, starting with the executable var todoModules = new Queue(); @@ -730,7 +730,7 @@ namespace BizHawk.Emulation.Cores.Waterbox } _libcpatch = new LibcPatch(this); - _exports["libc.so"] = new PatchImportResolver(_exports["libc.so"], BizExvoker.GetExvoker(_libcpatch)); + _exports["libc.so"] = new PatchImportResolver(_exports["libc.so"], BizExvoker.GetExvoker(_libcpatch, CallingConventionAdapters.Waterbox)); ConnectAllImports(); @@ -764,7 +764,7 @@ namespace BizHawk.Emulation.Cores.Waterbox var libcEnter = _exports["libc.so"].SafeResolve("__libc_entry_routine"); var psxInit = _exports["libpsxscl.so"].SafeResolve("__psx_init"); - var del = (LibcEntryRoutineD)Marshal.GetDelegateForFunctionPointer(libcEnter, typeof(LibcEntryRoutineD)); + var del = (LibcEntryRoutineD)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libcEnter, typeof(LibcEntryRoutineD)); // the current mmglue code doesn't use the main pointer at all, and this just returns del(IntPtr.Zero, psxInit, 0); @@ -811,7 +811,7 @@ namespace BizHawk.Emulation.Cores.Waterbox if (_exports.TryGetValue("libco.so", out libco)) { Console.WriteLine("Calling co_clean()..."); - Marshal.GetDelegateForFunctionPointer(libco.SafeResolve("co_clean"))(); + CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libco.SafeResolve("co_clean"))(); } _sealedheap.Seal(); diff --git a/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs b/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs index 5968b4582d..07c057f2cd 100644 --- a/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs +++ b/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs @@ -1,4 +1,5 @@ using BizHawk.Common; +using BizHawk.Common.BizInvoke; using BizHawk.Emulation.Common; using PeNet; using System; @@ -73,18 +74,18 @@ namespace BizHawk.Emulation.Cores.Waterbox [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate void ExeEntry();*/ [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void GlobalCtor(); - + private delegate void GlobalCtor(); + /*public bool RunDllEntry() { - var entryThunk = (DllEntry)Marshal.GetDelegateForFunctionPointer(EntryPoint, typeof(DllEntry)); + var entryThunk = (DllEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(DllEntry)); return entryThunk(Z.US(Start), 1, IntPtr.Zero); // DLL_PROCESS_ATTACH } public void RunExeEntry() { - var entryThunk = (ExeEntry)Marshal.GetDelegateForFunctionPointer(EntryPoint, typeof(ExeEntry)); + var entryThunk = (ExeEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(ExeEntry)); entryThunk(); - }*/ + }*/ public unsafe void RunGlobalCtors() { int did = 0; @@ -94,7 +95,7 @@ namespace BizHawk.Emulation.Cores.Waterbox IntPtr f; while ((f = *++p) != IntPtr.Zero) // skip 0th dummy pointer { - var ctorThunk = (GlobalCtor)Marshal.GetDelegateForFunctionPointer(f, typeof(GlobalCtor)); + var ctorThunk = (GlobalCtor)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(f, typeof(GlobalCtor)); //Console.WriteLine(f); //System.Diagnostics.Debugger.Break(); ctorThunk(); diff --git a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs index 2eff85f9a7..bee1f6a0ab 100644 --- a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs +++ b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs @@ -1,4 +1,5 @@ using BizHawk.Common; +using BizHawk.Common.BizInvoke; using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs b/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs index a8ee4561d8..892708d6d0 100644 --- a/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs +++ b/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Waterbox { - public abstract class WaterboxCore : IEmulator, IVideoProvider, ISoundProvider, IStatable, + public abstract class WaterboxCore : IEmulator, IVideoProvider, ISoundProvider, IStatable, IInputPollable, ISaveRam { private LibWaterboxCore _core; @@ -54,7 +54,7 @@ namespace BizHawk.Emulation.Cores.Waterbox _exe = new PeRunner(options); using (_exe.EnterExit()) { - var ret = BizInvoker.GetInvoker(_exe, _exe); + var ret = BizInvoker.GetInvoker(_exe, _exe, CallingConventionAdapters.Waterbox); _core = ret; return ret; } @@ -87,15 +87,15 @@ namespace BizHawk.Emulation.Cores.Waterbox private LibWaterboxCore.MemoryArea[] _saveramAreas; private int _saveramSize; - - public unsafe bool SaveRamModified - { - get - { - if (_saveramSize == 0) - return false; - using (_exe.EnterExit()) - { + + public unsafe bool SaveRamModified + { + get + { + if (_saveramSize == 0) + return false; + using (_exe.EnterExit()) + { foreach (var area in _saveramAreas) { int* p = (int*)area.Data; @@ -107,35 +107,35 @@ namespace BizHawk.Emulation.Cores.Waterbox if (*p++ != cmp) return true; } - } - } - return false; - } - } - - public byte[] CloneSaveRam() - { - if (_saveramSize == 0) - return null; - using (_exe.EnterExit()) - { - var ret = new byte[_saveramSize]; - var offs = 0; + } + } + return false; + } + } + + public byte[] CloneSaveRam() + { + if (_saveramSize == 0) + return null; + using (_exe.EnterExit()) + { + var ret = new byte[_saveramSize]; + var offs = 0; foreach (var area in _saveramAreas) { Marshal.Copy(area.Data, ret, offs, (int)area.Size); offs += (int)area.Size; - } - return ret; - } - } - - public void StoreSaveRam(byte[] data) - { - using (_exe.EnterExit()) - { - if (data.Length != _saveramSize) - throw new InvalidOperationException("Saveram size mismatch"); + } + return ret; + } + } + + public void StoreSaveRam(byte[] data) + { + using (_exe.EnterExit()) + { + if (data.Length != _saveramSize) + throw new InvalidOperationException("Saveram size mismatch"); using (_exe.EnterExit()) { var offs = 0; @@ -144,11 +144,11 @@ namespace BizHawk.Emulation.Cores.Waterbox Marshal.Copy(data, offs, area.Data, (int)area.Size); offs += (int)area.Size; } - } - } + } + } } - #endregion ISaveRam + #endregion ISaveRam #region IEmulator @@ -196,14 +196,14 @@ namespace BizHawk.Emulation.Cores.Waterbox } public CoreComm CoreComm { get; } - public int Frame { get; private set; } - public int LagCount { get; set; } - public bool IsLagFrame { get; set; } - - public void ResetCounters() - { - Frame = 0; - } + public int Frame { get; private set; } + public int LagCount { get; set; } + public bool IsLagFrame { get; set; } + + public void ResetCounters() + { + Frame = 0; + } protected readonly BasicServiceProvider _serviceProvider; public IEmulatorServiceProvider ServiceProvider => _serviceProvider; @@ -214,56 +214,56 @@ namespace BizHawk.Emulation.Cores.Waterbox #endregion - #region IStatable + #region IStatable - public bool BinarySaveStatesPreferred => true; - - public void SaveStateText(TextWriter writer) - { - var temp = SaveStateBinary(); - temp.SaveAsHexFast(writer); - } - - public void LoadStateText(TextReader reader) - { - string hex = reader.ReadLine(); - byte[] state = new byte[hex.Length / 2]; - state.ReadFromHexFast(hex); - LoadStateBinary(new BinaryReader(new MemoryStream(state))); - } - - public void LoadStateBinary(BinaryReader reader) - { + public bool BinarySaveStatesPreferred => true; + + public void SaveStateText(TextWriter writer) + { + var temp = SaveStateBinary(); + temp.SaveAsHexFast(writer); + } + + public void LoadStateText(TextReader reader) + { + string hex = reader.ReadLine(); + byte[] state = new byte[hex.Length / 2]; + state.ReadFromHexFast(hex); + LoadStateBinary(new BinaryReader(new MemoryStream(state))); + } + + public void LoadStateBinary(BinaryReader reader) + { _exe.LoadStateBinary(reader); // other variables - Frame = reader.ReadInt32(); - LagCount = reader.ReadInt32(); - IsLagFrame = reader.ReadBoolean(); - BufferWidth = reader.ReadInt32(); + Frame = reader.ReadInt32(); + LagCount = reader.ReadInt32(); + IsLagFrame = reader.ReadBoolean(); + BufferWidth = reader.ReadInt32(); BufferHeight = reader.ReadInt32(); // reset pointers here! - _core.SetInputCallback(null); - } - - public void SaveStateBinary(BinaryWriter writer) - { + _core.SetInputCallback(null); + } + + public void SaveStateBinary(BinaryWriter writer) + { _exe.SaveStateBinary(writer); // other variables - writer.Write(Frame); - writer.Write(LagCount); - writer.Write(IsLagFrame); - writer.Write(BufferWidth); - writer.Write(BufferHeight); - } - - public byte[] SaveStateBinary() - { - var ms = new MemoryStream(); - var bw = new BinaryWriter(ms); - SaveStateBinary(bw); - bw.Flush(); - ms.Close(); - return ms.ToArray(); + writer.Write(Frame); + writer.Write(LagCount); + writer.Write(IsLagFrame); + writer.Write(BufferWidth); + writer.Write(BufferHeight); + } + + public byte[] SaveStateBinary() + { + var ms = new MemoryStream(); + var bw = new BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + ms.Close(); + return ms.ToArray(); } /// @@ -288,54 +288,54 @@ namespace BizHawk.Emulation.Cores.Waterbox } - #endregion + #endregion - #region ISoundProvider + #region ISoundProvider - public void SetSyncMode(SyncSoundMode mode) - { - if (mode == SyncSoundMode.Async) - { - throw new NotSupportedException("Async mode is not supported."); - } - } - - public void GetSamplesSync(out short[] samples, out int nsamp) - { - samples = _soundBuffer; - nsamp = _numSamples; - } - - public void GetSamplesAsync(short[] samples) - { - throw new InvalidOperationException("Async mode is not supported."); - } - - public void DiscardSamples() - { - } - - protected readonly short[] _soundBuffer; - protected int _numSamples; - public bool CanProvideAsync => false; + public void SetSyncMode(SyncSoundMode mode) + { + if (mode == SyncSoundMode.Async) + { + throw new NotSupportedException("Async mode is not supported."); + } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = _soundBuffer; + nsamp = _numSamples; + } + + public void GetSamplesAsync(short[] samples) + { + throw new InvalidOperationException("Async mode is not supported."); + } + + public void DiscardSamples() + { + } + + protected readonly short[] _soundBuffer; + protected int _numSamples; + public bool CanProvideAsync => false; public SyncSoundMode SyncMode => SyncSoundMode.Sync; - #endregion + #endregion - #region IVideoProvider + #region IVideoProvider + + public int[] GetVideoBuffer() + { + return _videoBuffer; + } - public int[] GetVideoBuffer() - { - return _videoBuffer; - } - protected readonly int[] _videoBuffer; - public virtual int VirtualWidth => BufferWidth; - public virtual int VirtualHeight => BufferWidth; - public int BufferWidth { get; private set; } - public int BufferHeight { get; private set; } - public virtual int VsyncNumerator { get; protected set; } - public virtual int VsyncDenominator { get; protected set; } + public virtual int VirtualWidth => BufferWidth; + public virtual int VirtualHeight => BufferWidth; + public int BufferWidth { get; private set; } + public int BufferHeight { get; private set; } + public virtual int VsyncNumerator { get; protected set; } + public virtual int VsyncDenominator { get; protected set; } public int BackgroundColor => unchecked((int)0xff000000); #endregion diff --git a/waterbox/thunk/build.sh b/waterbox/thunk/build.sh new file mode 100644 index 0000000000..e9d1bc1614 --- /dev/null +++ b/waterbox/thunk/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +gcc test.c -o test.exe -Wall diff --git a/waterbox/thunk/gen.cs b/waterbox/thunk/gen.cs new file mode 100644 index 0000000000..857d049513 --- /dev/null +++ b/waterbox/thunk/gen.cs @@ -0,0 +1,20 @@ +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, }, +}; diff --git a/waterbox/thunk/test.c b/waterbox/thunk/test.c new file mode 100644 index 0000000000..de5fd11c38 --- /dev/null +++ b/waterbox/thunk/test.c @@ -0,0 +1,103 @@ +#include + +typedef int64_t ll; + +__attribute__((sysv_abi)) ll Depart0(void) +{ + return ((__attribute__((ms_abi)) ll (*)(void))0xdeadbeeffeedface)(); +} + +__attribute__((sysv_abi)) ll Depart1(ll a) +{ + return ((__attribute__((ms_abi)) ll (*)(ll))0xdeadbeeffeedface)(a); +} + +__attribute__((sysv_abi)) ll Depart2(ll a, ll b) +{ + return ((__attribute__((ms_abi)) ll (*)(ll, ll))0xdeadbeeffeedface)(a, b); +} + +__attribute__((sysv_abi)) ll Depart3(ll a, ll b, ll c) +{ + return ((__attribute__((ms_abi)) ll (*)(ll, ll, ll))0xdeadbeeffeedface)(a, b, c); +} + +__attribute__((sysv_abi)) ll Depart4(ll a, ll b, ll c, ll d) +{ + return ((__attribute__((ms_abi)) ll (*)(ll, ll, ll, ll))0xdeadbeeffeedface)(a, b, c, d); +} + +__attribute__((sysv_abi)) ll Depart5(ll a, ll b, ll c, ll d, ll e) +{ + return ((__attribute__((ms_abi)) ll (*)(ll, ll, ll, ll, ll))0xdeadbeeffeedface)(a, b, c, d, e); +} + +__attribute__((sysv_abi)) ll Depart6(ll a, ll b, ll c, ll d, ll e, ll f) +{ + return ((__attribute__((ms_abi)) ll (*)(ll, ll, ll, ll, ll, ll))0xdeadbeeffeedface)(a, b, c, d, e, f); +} + +__attribute__((ms_abi)) ll Arrive0(void) +{ + return ((__attribute__((sysv_abi)) ll (*)(void))0xdeadbeeffeedface)(); +} + +__attribute__((ms_abi)) ll Arrive1(ll a) +{ + return ((__attribute__((sysv_abi)) ll (*)(ll))0xdeadbeeffeedface)(a); +} + +__attribute__((ms_abi)) ll Arrive2(ll a, ll b) +{ + return ((__attribute__((sysv_abi)) ll (*)(ll, ll))0xdeadbeeffeedface)(a, b); +} + +__attribute__((ms_abi)) ll Arrive3(ll a, ll b, ll c) +{ + return ((__attribute__((sysv_abi)) ll (*)(ll, ll, ll))0xdeadbeeffeedface)(a, b, c); +} + +__attribute__((ms_abi)) ll Arrive4(ll a, ll b, ll c, ll d) +{ + return ((__attribute__((sysv_abi)) ll (*)(ll, ll, ll, ll))0xdeadbeeffeedface)(a, b, c, d); +} + +__attribute__((ms_abi)) ll Arrive5(ll a, ll b, ll c, ll d, ll e) +{ + return ((__attribute__((sysv_abi)) ll (*)(ll, ll, ll, ll, ll))0xdeadbeeffeedface)(a, b, c, d, e); +} + +__attribute__((ms_abi)) ll Arrive6(ll a, ll b, ll c, ll d, ll e, ll f) +{ + return ((__attribute__((sysv_abi)) ll (*)(ll, ll, ll, ll, ll, ll))0xdeadbeeffeedface)(a, b, c, d, e, f); +} + +void End(void) +{ +} + +#include +const void* ptrs[] = { Depart0, Depart1, Depart2, Depart3, Depart4, Depart5, Depart6, + Arrive0, Arrive1, Arrive2, Arrive3, Arrive4, Arrive5, Arrive6, End }; + +void print(const char* name, int offs) +{ + printf("private static readonly byte[][] %s =\n{\n", name); + for (int i = offs; i < offs + 7; i++) + { + printf("\tnew byte[] { "); + const uint8_t* start = ptrs[i]; + const uint8_t* end = ptrs[i + 1]; + while (start < end) + printf("0x%02x, ", *start++); + printf("},\n"); + } + printf("};\n"); +} + +int main(void) +{ + print("Depart", 0); + print("Arrive", 0); + return 0; +} diff --git a/waterbox/thunk/test.exe b/waterbox/thunk/test.exe new file mode 100644 index 0000000000000000000000000000000000000000..0a0fba4826917428fb845859ab9ba350f044416b GIT binary patch literal 131679 zcmeEv3wTu3wf{b6&P-;=V-iV1fB+)`1|^V$0D*#?kPJ*@@*oey=VX#hLTX-4COmvX z0qZ!%Mq683(NeWl>!a8zp!M1SD%jdKms%gSwY2s2hN8FBT5GAd`Ty41`^-$j{z1BYa?0rt|^8ig2AQ3L&lKi4iH@=hUT+%Ci+?Gzr0 zH8&jG7zTY4RSad3%8qg*{u)J1A^*EOA~}ZvRqSdFL4Jb-OWUu z{M$_VYnSnHT@OHp^5-5ci}(iTmetl1cOCuh#6{)35Ld#_E!=~1!yTM1%k6?E$@vn5 zK+1aQ4K;)2wjBgPbxeIM;YaDtE0&BQD(?_3Dlg|m^6B3#(wEOGT+EEBN8ud7^%${4>BQ^+k?#B#4uJrBX3>Tk(dIrs8; ziwaooAm#rE{HeUF)yh48+u^>9W0T_T1<@OlRWSG+M|z8qUU@VLZ5+;i zzkJ4J+FsJN&h5X)f5iG7D9rNc%$GZa@bq`g&?=kXso3`~7GLD(uy-1G+u!m=o=2lR z3<2k@mue{g8{TNjFNSsadi#4cZ{+vGQ;Ck0{~V>s{6$3ssqXV`J)jN$bt)QU@y~Af zVa3*W>=zy^&qtCZnSVKA8+IcfsFsI5Nlw;su%f9x{8N=g(tT0uJO)_>B-~~v?<|lJ z>v;U5b8PzwB8z7I`;|b4`;v!`BekgYeL(M)=cspGdWHY;(#uLOKM=1U=brKzjYqBM z=(7IMJt9c|pHMDz3+f@;-$c7Tg#wA;2Pi_x90n7ayqoiydLx|SP4M0R&OsaX3Fr17 zK>+QCG4>hD(ft4_{>VR%WZiY05JwjhZvb&@l5sS_Imi}tcphRpdJX(Nh3_2w$zMlC zBKwAqO~J^4ywqo3BVq2R!xzG04nO(f$jATRWmw#~y_44PhMa7~M&4UK`95`s?k*vLn>44Zy1Q;0t zfk7dVH`nDy?@j{MZ~X%5``FJZr!u{G7Gmk`{o#ps{`$LTAANIV=*0tyTi}gc_klOs z@PRkx>qUKz6^NjlM;mZeCI}`-xj7n=iZl#+qkXu>N~6!7HbA8Eh&Os2u2Tp6^XRaR zqY^_{U&Qm;N#Ud5v8s#}tRnKaMC6N94xJ*hA&Ly`ex_J2T0>2{Wlo{)v7OA=^dTn#D41g zAL*wU%mWzJ(r7@>R;EW9a?q6NumZQ?7IgSM%!Q(!oQUVcqg$Z^z`l>zKWFxb-aEbh zna_hyMq>30I8j6QMm)!(o)0ng8RcCR_Z~jxg04I6?XSV?qv5!7w{Y$rjO<6`4HWri zM9%)7@C=7u0oP01CUCcUKGcw#t%FIQ@OD~Il2y6TB`lm395yOi( zgQ>en;7SsRral6iWri;y2F-_k{iM?)ud{9+Ud3U2G)+D*yn+My$lLlUie&*995)yN zVmycSCU4|56sX|R1DFtm7ZQGbBwcbaIicGJ@G!p^UP<+UDe>nAQICJ3KzS$%|J3|r z!8s6>Gu0_D@lopxV)WP8y-~N_8_CKdi0SsPW!U(9ONDl#2*U{nw;R)V9{n#6s@+e85lgOFn0nE?((Ig{T`>=T9i?$GXBgn`><-RbI^%yWo z6-1sN$$E<9=C=U6>)ngu;s-|Izj_N_^+x{cE&Mm{*)Mn(zZpiu9{2tUW_`i=lw!nM zJSO2-`|W+{a@XP3mr;e!&Q}>fb|_x|4Y<|f8C{PT8VY?qKvTKpl&9#F2~C*bW5(Lw zfZ53FBU$r_y?wAEvd_8gKI$C(o+DHXB~%L)=uOdv*9u>x{#XF7VeXh{%GVkxi>R!T ztUre+V(YU=g#;HY198y$xu6gZLku3Y_Jg9vL5Xe$3DH$&6T)DQT5BZv!AREA6it8X zH*wQ9lJx_Ck*ss^|1?Fy)6R#0hF?32V({c3vPz_bETJn}e}&u}-H3|i2B!YE^@Jrn z0o`u=UX7Bb-W}`z!{154qH&Brfa;Pn?r#^-ZMq(|9pt6Btta&G_^l_jusbGv=@s1m zQ(nJ;(q3?woW*|#|H2!!&@@@cIN5r@4p!egiD8B-km9kY)cx?-{)p7qqAF%mf`B&De}=M`-9%XY)qtXXL%>%&4DHfd9*8l;FR7hI?d)wAb2`)Uy|#XC=kcgvmW zr7myuDm!wtDF=U7r4K)VNa2Cx(I32xxG%^;P~_*@8F>|v-=oKX4$p86XG3tnnTJFR z2WjevWb=?LGQ1JBh$IXoaqKSKKY^Y|^k-H50B-_5dMz51@()}}xFe1ogfENZ8wmSU z2YBPxqsKe62wE*ZJ_wt-fn2Vi5-(KmTGHaE>$N^~?M5H!f1T9CkEf%0`cp$l#)ndu z?wyQ^dz!k?@Ko}OhG*mr-@-zulgLkTe=z*K6TW%i4UExWvGovY_vZZ7&4_@~eZ?0! zh;@it|A1)GAC`e%co_HfP`Kz)-w$Q_pm;sFeA+%>=cji;aO-{zA~}nH+XtiZ`tb2& zgr(v9oO9c+DW3k0PT|GPeMRIQpC$h&Mt?=blU{nz15f0y0825Y(fS!FrF#oVT!VIo z9fAx}wLT4TZ`7A(AIVzP%@K#g(efGP{WXJIfAw9gS_~UokNhP(qat$DXGy&U(xrRd z6b)j;?2gssI0Qtm&f98jgTGc0`M?WB2or~%Yu>CMeK8GXedp*BwBSgVm)Dk3ubYbz zuz&ap+#4e+^C)seYezGj43T> z29Sw2!dc$P+enkv2THHF++3eJXng_R+?cDW`Y=H;+_;{{Ejm=kk>kTpqn5C0@NJ|E z75WT0v!8>GLooVL*TERIMuAC*&xSeuAnE$s@t=JCtn%n&&-OQ*+dW93JX&XXwjXwG zFJZ9Cjs>s2$ z`XaycMFzbVt#mKVR=g3<&~O75CsdN5uSc!lXryJJ!uOHMTc1QdBLj;%DFb~k`J(wV zKpvYY*FUztfG&9;=MxJyAc(X7CTb%X5{vap;AlFu^3T0duU+-H$O&4$qs6(ikR7zN z2U3-h-^1_>W8OVf9yM}GM}Bhbp_u;WI-sUT7HX*F`>z|~l9%o^1ZDmNl1CN%(xOp0 z`mqjzAfcZfU4$yydcxkIs7DhjvS7~ z=@!fGK)7&l*%v6d8^NhwhHq8Do!f2#BtyB0Jt#YD8e;up*%S(YaM>;7OpV#$1FWxQ zUP=+!@XLS3+;foU24)^-$Mfjiz7{}NEokp7&(cd+ON|6iibB$sRQ zRmuH6^&mC>j?F(^!;jNiS>*YswdZoul6^D1+usSh(5N}*(IjdOxTtolJEDoG$eCto zTxvF|>|@0h@bAxB4*91@kNf&(zK`Ks_`Wyt3oNid<)SK!yjn5)ZCY-Ryn1Zz3rUXh zNSoosV2#$K&^V9Oy=TGX9{PFquy^Z0d!+2cNZkkCt$%fei)g{O50A}|hrZ{%+uwI? z>qRw0-et>H5gA5~UqIR;Sud}HA-;$zj*>j~7UhqoS1vE68IkWt^~k<0zl67I%b&~2 zR=S+qegrY@s?N`BBa*tSb1Qn3;4T|3+TiA{8lKJFw6EOkqSvES$u8=WT{Jvfbx)pT zqgh>-%9#=O2j{jZ#mRl*O=y`vUCN~iV-8NH$aSfA_KhugVN2k;ScwtrNd!zZ8ZNysE51)Z(^2krTkrzf@ zMQ2GvX9+u?wa~GCPj``*rXxt-&k)xF3XU;EJsG!bHzTODA7r@EW1`l-T!tCIk1#Pp zMB})TuJB_f_jC@jEFYomlYJ8R$Kj=s5^to3rUySmu#v1^1t~|?ZvudKh@8ljud-8? zK9V&V$?@Q)k#vB&%&{FZeUn8z8ZOJi+`{mRMa7MDeLV?~G7=9S4>FCAOXIXy?-$l3uSavvXb19yO z?kgPJvY4KThV?%jC>(6vvMC48K|has`PMCq@wnE3+b9|S*bk{z;{9KRckU_8#LP^~ zaqg+Gp@_4$9@V`4DG^`4?@(TO)SXhXc*qyox4~}lVtW0(5IsW6^DREq_v^yJKOmLZ z^JUc^aWg%v8C0qd>>G0NQ0L_td@J3$WeKt~iRMqFN6?#af1Q8^@cd#p52Zs5{nXpM z9A(?@+_M~cyG%#kmgB)>c>mU6>|Tlt`DX8{&<0DLdunX> zb7x{E(qAre!Y^(8Ei#gMJ1S`?Pw7LdE8pUMzR3QNoh-!Se-7ow>dI62qM3|o->{Yt zXO&Zh(ewGNubo3(U)B~34)SH*@1cI|GJC^RKX_i3Zes!2z7!ti3nDyHJ5Bixq7Rwn zlO^-2lD#NPc33q(Zm19Cz3)EP1+|2!=yWfY7%lHb%MT}%Cf@$1Di8AWlj$hWVO7`3 z-Ux2}xlEYC?HexP>{K{CL#9VOK2@GODEweTaT3FSfElD&oHI06;ppbt#^dx9?S&=4 zSl{MkqV5@g8rnBJO{Ixe8k@f|J(8_%Fo#!sM&C-kS+hcAj{aMe`y-J zwZ8GkeuK^+%U4Q+W$R#O)Qj7sz9dXv(2fUj_jJR*lt;ZOmD=lO_mlc$b&Yo!?N6hn zl$_8+@0O#CwmO72pDFg;@UPz4`@CSTy?E5>z`(%B%oPDzP8^xJP`R^|o2^`%a^G*1 za=%gTE6ROAxx1D7b>)6Rxi>0zy>eTXyGFSyl)F&5Q7QPa$iyI3(9>= zx%VjdR(8$$gq8ergAf>wvVO^Mea8A5VsQnI%&b!3E>P}V>@t;)S#xn0V=Lb+ASy+FBhl{;0rY0CX8rWBmMa^F<$57{-#M~^Np zKWf7?RQ3<3=21U)v0EhmMtK4x`h86Q|E~euhn?{^nB`{wSCsoFT6CQa@r0E|2HQNOYBHS^u< z->6*k;e{FK2kPm7iT|&;9qmnR&7aCITsXI-qeJ*FT<@<9wzc<$gQ2pHKyPmlPc%mT zdP42%1L2^nHyjAJH@iZ?KudR5$0k?Prf~4m%P+m$HM7^XY}_^Dgrh6CL2+HzxJ1Fm znFU1~=eWdmbHsI@5aq$1KqxFqLx?Vzn1Fk`n_=T->DIWU%S`;0hT8f%gI!@)OLu1g zd&+~MPF^e)$4EocU> zUgN1<;j6xAwx~h2+5;V~j_%%GS9hzcx4o^ay|o>C*_(r|yc%D1T^+|%-yJ@!RDDbe zTZY$R`+KmruOsZj*7$IDx2v}^(9r>kH^v8h;ECb{TU>b!Rpn+b8oI9R>fWGA$NBbm z2Eyx5*u2?GsESeYnZ0>4+vd1tw#|0A@+0%uwRZ;Eg899{ z=5Tv=mvFh(w1>idfsWOE!O$iSjJ&s^4Q94*bN9LeVOJm)i>!4K4_6cF4kIozaR0}YibXp z9j^{1%A5Hz_rII?h}&(F>>eM32QMD&KNjz-8SO8`X#Do=qjBcvM&mhSaQPVQzHzkt zO_9-f^BCMS25%gL#m>>;)5qYPF}Pq19(0cmUoa+ryJnB^KYKJjqQ<*Wzw|NT-J3_t ze=tV=y)pRs7`(EqY>6wcHn_fMj%#^eJBDG?CRb(M#dSs0oj!%0INP;g?)xoq?lHpc~;ngewK~F1T-I3Xuu=2wXi2 zXWT&3nY1`2Q-#RI_<++)MD3H4k(n+z)XTgBJOa!L^iR;Ev!bCqAB=pGD8`LA&4varr>^z)hQn zJc3S#`!KG{K<|S4TU;%mkHfvDKnOayw;AqkTw&0Ia90KKe?KrTMdg=quDK1dl69yybC5-FZjSA77yD+@oPz9 z-XWVfZ?9F%e9#cCJ1t`JO}fb5sELeqLZoV$!e+5F>OF|-<8c48^^({|A=O5S?Pb-4 zwN@(_)nCkhLoW3t^BQLp<3hFqYopO)snG|uaaQZN!)Unzy-~Kk>T@=Wur1dMv_=({ z`q{mT&UT3Gn`|O|qg5oYGlcC|336sS6uwFEQ<6l=y@|aR@nkrx8Q)5^j4N<7+Ix}) ztwWY0qtd5*7A549%2 zkaD*v_a5awrrhr-_gBjOP`PK_q2gDrTe$-KgA8EB6-V-l^Q3%Ke6NpHS{I%6&n(KUD5($~~go z-zxV#<^EN zh?#^dyIcA?g5E$^OGmI&h>M6vk*kG8+JXR1T^Q@EeLWRjt=**}%A8GDbndLjiezc; ziuR5mQR3_5Q{UcvWmz{?D}^|rS9GClfex(ZjV+GP;tK}WpTri=T6n3l2J61vp-x)I z<;a2|VKr*Q9mv;;zAj$etPeDG1ci;bWr3bB7DdatQE_-LM=J4r-Q8FA^_aq_($FT{ zYmkpx5^d<((B6f5xzDHzh8w!paUHaHHZ}))X#EnULMw<}MqNiR*dv}b>Vu)q_AacZ zo0Tt~H|jfjQRFp&j=rEcY@9X{aokXeA}p<^V%xaM;o{2Au{|tS1GNRiw3Z$Swe^Zx;C9q;uxq`jBf7iE--`OdT>(%>b9dKz_Qwny zxHlN~2YPz^;Y~eQ#cz_FzFw3|u2$2nhCk5U+0xM^t_IcF+vewF#Vx|$EUCW;e=EHe zhF&+pQKOPN#U9O%b>AyRv+h?pop15?burIo1e&_>^k9O4P7aO`GYlEmBBK=#D7wO} z;sT>J6hxdAM(YL)2T+>~T6ibvEk3tMeXYcyvP}s}`75?Mj z|D_se%y6HwIO*w<@0xmQfAjgA%XMo1Q%3qR^B<=McFA^aRPKlB`HFj|6?r;`#Hk z=?(QO@=K=ECW5Y(08K@fP2Uvko&L!S#x12y2c1pWQGoDWz00Qeg}RpXHm?hI272>5 z+nYn(z1^+h{O0b?C4t_~x$6t3yD+C~Zw>Z_*T|>BGO-KBxm-)bp}yWQ%`yMR$mUO{ zUumKteD=!K?a^M6jg>8x}A-Z436Aeu=)GjR;6{JzsEr5Ss|-f7$dvZ$;Po z?kj_#>GRA$OXr<7#-;NTQ(rnSRtO5a6b0|W)9|MD4%*r9AJ)+*ajGqufu)$Vb>J3( zvKq_M+4=djTcM&HI}>P!1m?0N62uezxU{Rcucrr-FMi~I5k$H-^kPc@TGExDAB#!} z1zYgOzlxS+)2|5@6%-a0FIbdcxS)7pe)0U~1^Iymt-<_)f|8bCFgU+uerv&X3Dxso z&S=41YX!ucgliv#%!S{5T8txYZYCBdSW{6JB0aY0ehg4V_J1OGGmC^=(3iUUQh zMJ2)c`3vSZEzU14E-uV(3M_8UZ&|pwr8rO=SQwmN@;{W1xrJxUNnz{4MGKo3EzB<| z2o~lS7tLRszj#4YQ+~<(mV(xWEd?!sMVu2Cj|}v{|9aCD7Mw9BCBcH$MS;Si{HE5X z`DmM9Nj_R?L4I*bYs-RyK;eSGVs0Cmlcn<#dL<8yd80ZzRLMM3mt0`(5;3*Y$F5le zc)TNY26o9(^SvUgDKXlW^3Ti^Ldp;1x>?}G(PA+qj5&&Sxme9`IrWqMkP>2JSiCy8Rc)hz?63@`8R0c`xHN! z-?U0oeyoU0|4_`|oJZd`R#0IX-*xq8j;~~m=|80A<-XJ8Pp1FsWv0Aqx|>rvbNTPN z+>|d+@|hg!{zN~brT?_sPvdY4RuI%l*13 z&%z0RGQQ(6e{;Vo9(n(d{Ptob%dFY8l~oO{vc-$#ekfOdCpHP@cZT~gJ8KJe1w+{G znco=*HLqK?v1DOBdA7OotBYLuz2TN+ZC!nf7mJj#C79UsbkD=OmfU8Tk5z;Gw)XHm zymmFtjZ@@5PhG){!MRwTIfVw&HCyw#KuB0<8=pTcEayCf((Cpl%pKdXR}+7IU9@n6 zy@*);fsZxrLkvqF{(Hh93cHHJS~gIG1e=mDG;}?wcO6Y@Mn_{MN?N{I#C4>e_N~r}0I{Ty#-e zZ1u2=mdK1d9T$>fjyBxa^Jl$5+*Vz;d zVXteT6YIqxM6iP+$ccxcX)`V3VTLG-yB*0ep)O&!8}@}r!r?EguB!K}trtUvaf9s~ zL~5%k^mb#(*4S+ODrd2#Na8Jw?Y5fv5`UU;x~-;I;;oExY&8oc-p1Hvt63=VcE))& zwHB)ie4~vjqQF+Uv8*>FjL$GEw$%mO%21k{Y!|?{gdjF47!ig`2zmf-X1J8#3cx7C z3ka?RyoI5gU>#sT!{s)QO88bzsoYi`DiFqJId5KoLSYOrr~xPv#%&DN0?fxVH3p3U z3x)AH2I~M83FGq&dTh0Ujl#Ht!A4tcGr$)ZY_`?50DO_bO}5%5fIArs*lK%w(C`fI zgb%=%8SI2lyD;uz@E{@p*uh|zt+oV>$@E@Z?P6iv!}Or7wh*QH3WGxcD9ug=LjWkv zR~ftpfYRK{;0QpmFz#b846r~L_cJ(dt8GVSzQ*8#aeE!;uv+pOEK+xe%+2M(c-Hn? z@~|imYTZzGhcLd&!FC$S<-w-DwitVm*$*1Y^t@p?Hg7kttMBG?_OZ{dM4uI*?oRgG z&whKQUu-C;fCt!faExax>~rjW$Pku`Dyo)OSNnwZ1{(F25V3L<%eyzPi5pdFy^Oi=5r6lO3>W2D?Dg^$HzQ~4c7*rDtAks+Hq>)xh zBi+N4NU1qRz-RRy$U}4D-|DX{EGj7#>BdUyy}&kK_Xc5jt;wK;&D-9#4w<%|K&*C= zB1Mf0t?S|IB=JF9=`^EBl6c6(PT{2LK4M}&()$fS2CHkk(bC;Vk9hQSgO#&_hJ75S zhi4h~3^?OIKo%VSwUw3ry3#cse`&3!bUvn|y74JnA8b17A@tOXxH}?((P^Vh8Maz%^$)ENwqSI1G0SS>Cuh|?v;kfi-5k)Sn60gNx`MrU ztZ$&q=^0xcp6}5x62?_5n-Yr?gEYjfG+Q~|5MQlA_j2fT=EVbtSu!&g7=yZxS=pvO zUC-fiB=lDXdao452Il43R#3+_2QoC|M%yk_vCFo$vonFa$wsZTGubRObj8&+YN(x> zNEutZ#x=HV)P3eo&3MT6AoPOGi)V~@SY~{KAv#E~EzrD47!Su2gT{KqM%`g2C5=jb zl+i9isE)@N4H826zR75a5OT7M(GfyWTaPn3o(u&2{4GY%Sb)&b-)5N35X$ojhAxKC zpHDI@U`Q%-H)FTN$kHChH4-CJdl@%MjBGu{xJP1S>}kfEB}Uf1!+1cG{hU-O_j^rb zp+kP1RcQ8#j&L0wd^cX~ODDt5 za}sHzM!%eu82316zA|cfE1#W?Dugub6h3i2Q&U}mi9&51hWe9^FThhyQFc3uLCe8} z2duThW*$y^9F##j;;Dysj@@`XZ4g9u6+MJCzLoYz0yr|W9__+lR9dy3l@-u;rHK=% zbYm9_PTWE1P5cY~DSC<((Lb3sGZ`_;r`70fyVFRgWt-1RjXjJ>@=zMR&M13DJLXL^ zz=ZK4>$+^U>L`rwrSa6v>aVKz*HbA@sHBdQmFlY2O+D!qOx?Q$B*xI_`RM|oJtaI zV^3ClDhd7xv2x@{wc;fVW6iO8H7R|kp2#18~X-A>spY_XP9LN ziyngWlUd`Rm^Y4ifsKmyEb~&p!}bK@yNuEZA(=r&=|IiBeVxMC$M`H@nt|+Rl*zur zIKU_ysG~K&nR<>nIpB1!@9IV~FrEaABT#4oE^#h-2RqxL><_a448_3wSQv+xHA@D; z_VEn9J)o;_fStug_Ej_Jb83kB#xNaAyQ7NbL0D2nP`)gnp62agOG-u z{LR){QQ`BL&6R1)%KmH`TBkD5yso_qUF_U!*<5nwU)`$qhM<^v>;rR!4HxLyLG(*J z={9C(Q+GCOs$11}*KO)->h6$xB#iUe*RF0P5FL9Ww)5E|#a4$Kq3YIZ-uMvcFtzy{ zPAlD3hI_9-GkPmVM0+!W<+I0GW-TO^#h9DDAB8oGSzcOS>MyUreNugO?Zv`wG-dye zN~*R)#BOz(IU}&l$cQjnWNFO!$3$WTvmZhnW)jtWD^}_=Yx9WFp8aAv*Cu!UD`j0K z{S5uz=*TAHt|zrYF&do=EJ^=NK3y`+q#u$`Hv@apEd+WPq$J%>;3@{`Ned}Vh(V@} z^g?y3R5ZQJ%SqZx0$~QZNi78W7`T$spuLUt3}z&~OObBiEaoL$M?M=FoR@Tvz$ONB zlCC6hHG{cH^sW)(8U_VPeH8Vz42qJTB(du_>f)sH$meU=@O(NxLY-Zr* zr8Z*=M^K*RrU*XGU`0|Hh1tqt-lVGuY-8X{`W;2Eok2~~8uGc3L4DFg6v1a0tWC-# za1(>el0HviBHT2M!b!d0b+i(XFK2AnjA=|v<&2xNPe9JFr#MgB=9q6EH+Ez{d{(^G z@8&WH+3NQsw)$5RTYV>I&XBGCRR*fn?`5D`{XPb&)$eDJDO>n!*)$8zkuCgn_E9bT z00Y&+4{~-?3qQm_weU9>s1|;hfokDL7^oJ0l!0pD$2e-$!rx?|T6h-&)xwW6P%Zo| zjzG2Ww;8Aweu9gjTKGu@HL`_wGgvEIcn^a{*}{7{>UFY(pJLD_IHsBQ=i8rgz+z&w&_#vtMiwY`h3ev%U0p; zZqkDs_A&fVI)Fbm?>yOH3emm@ZYr6X*T|b@Sz@xcqN=XGw5rVG$4!uR=tKDA_H|)z zR*SF>(d`iKN@Fr$rD6rwuu}wP*zt6MA<4ahWf=b3bP9$Ye7H~g%CYiIwBeKbU zq+eU#P*YL0qPkS9vrnV5K)KCL!tUakQf^0&d2=A_Xa5}8w}Vw4?7cGF-6QPNiIWaa zeR~-9Z1!^q=j66*SS#!^C;}H~7=W2X&mcaldiyNGdE_VYxrFDCKkxe^)6*;LdBiOs zsYLE<;ueF8ChsT>hePd6eb_r@Kacn&#OLKU`}u^I=5DB4*S$eL1QqrK=_~;vLlQ;eEJ+lL@hnmzG9{x#WHAHLW=jsJ2}JSQN)Az| zjylwfm`H$DVaeinB@%B#sKa73LaO8{Toe*O=48Z4cnwd% zFh&W;n!xcl61=$9vjSs*XjG%8b`ABb%>*laKF>-t8eUo$N0p^@s~{m`t-rX&LkvJ+ z@z<A6pS?Ea4u5u$Wrc&UF&Y-nCiv>p_XF_#`X+JS7 z*~5c*o{dm=2tW*yoJkP{v3`o2N)B{|1EFV=@2DhCO6v-lRs*F~kKMvlnk!^7a>FtZ z#UjpY>66<_g8rT!?43oG_6qGDO?#4p2AYYc=9(E;Pb6_?1=e>6ZKw7sl-I0wp?#GF zW_1beUgn)Y3*HZCk0Ex_ZX`CxPj`-O8_;xk_eFb4>~V&9JFi`3K1)Jc{L^-)n;BS^&v9;@|GR>WurK+&nj;*;caa7>UbT=2D z{P z)*T~a;WrikP4J6fN(g(E3R@@^&B<_c+(#i(qSc}uxhU90PD4;=cbQvJD_9IU-ti7X zFS=iadylfB)jBM$%`&0|+G>X-CkEC!Ea@iDH@F-Yp%5y~@8_NF_@*T8a!E@F?Man-BBhMwo;*Or)t1({CqAfmd{v*V`1NmB_!qZbSq5bkXbVti=L)R~E?<(gd z=GA@Jl`phk(UY+wkpF}mf7vdI6T{ihd(*W)&|qmm@{3z(SqV!F+Iz&>M652}ZYH!p zQvSY)Yv~Qp32&*4qFeA!dv64XmX|F*g%^~`1($x5dH_`N#I{sluL$dWRxkJ zWd`BFN}qN9Mbgi1`r$oF{$_q^qpg)*hUvv~6`rYR7t?emuB^N`mebMQ#A~2|4(I}U zz9dq86aoC&JA3@<*o>CHka^gN+1}C89KakzE0$uh$x&BgFxInlz9)G-Sza?XbxbGu z8ma*>X(F*6%dApSk_(NN{M7|$jM+4cI$4BNn@GO*B1jna%kf{qal;z=o3NgR8&U0C zTJbbf#C|=VyJ#5cD4143JJC$L0U3+)qxoOEyUy_i@t zi7n;N&>&E=A7aZCYT4A~$2Wqx@P0Zs)P&`Cp}DQ+;F;eo<2hn^(x?e9K*Pjf`84<# zc1i$gO0OWMO zl4wUyS*J2HUNSQdOXKN8yXd|X()a=d-H4*sxMifssLWS)M@w*Hb5A&S>`S|3A?_4c zN#CAkX=+=kk})uLsw|nlJ(xX{DQ=^}J1lnP@AX$!RIR)Sw=K8{_4#lIw!C4bzp}L6 zi(5CLwNnlFWE>Q}Bi!49!)bo(BcbwN%u}H;{%92CeaR`jd;7Xg?@PyeV;+p{DI0}$ z8T(Z37edgL{6OWGbRpp>=t}!mr3)FQEBT96>Wr6ioQtZ}Au;8o z4{9~)u$XcxOA!ZM+GUmZ!l6q$P^lq0y3!++QE=$eW>=v(&(cCiXWF0I0_i&YqM zX%&WCxe7y+T!mp?Y!!x(T7_W|UWFklwhBWawhBWawhBXlR$*9zR$(N8RTxQN6^2Ey z3L_a7-G;W47+j_hRBmwVa8F& zgjE>Au~irX<|+&es#O@G6INje##dnyP;3>3c&Au}u};{Dj+m_Mi#$E7oqZ*E=9qr~ zgMoh9j$2`sQux@gl(+|dH+7VCJ%>c}+Zq*@4eJcaTbG6@7VhYUHj`o>4?*Ysxo(cy z1C^KllmayXY4FX!O=*d6KA#}td?f*-zoUR30emum-iu(Xk%0`{INY{QI8P{NV00ZY4xFPxRAv`~oqkswtrW zmjP#1D6udYh|lr|x-ZT$XB}!nl0((wu$XUjjCph2O_ZMI>m9SH=FUTgClDcW)QnL! zfzWb42`_Bc!2iVKFeY?acTJK1x^Hd1TBG zo%sKV4N+TjGZ|Odp|R%-E=JA%7wnJ)dxl%uTU$x*uYD19@b_kz&R~4LjFz|B#uy*^ z4win$KjS~?493SEGd?5_<8vvhF9~`<86P60@ga(g54$ivd?W$m!y+&~k^$qx3^G2FL&k?F zX?%!^86N^{e27ReJ_JqUL-Z))LkwknBq@y#(WdbsI%a$b#ElQ3xbYz*jSo?#@sYGL zK5>t@r71Th;w%rbMj0Psknv$x8XqE0Ha-ptnP7Yf$BYjF)A+EUGCo8n7$1Uhao zl4drbHO(3SQZZGgOp6@!({_9k;u*P$y$iV!XHlN2!5%*Y@gn1Xb)!XFT>L)lX`@@` zGz#djtSxqvOkAiuQRplww9yoz`!MqXEcCu?XMg-p=U zgku_-fT^KbP-$qQ6ErlzxQ0$ZF%3<;L=Byki+Q0zz0kOOBXz=)p00uu(?k4pHIczj zSM40fSEvs=PP6i8Nqv}nE#?c($5C1*3-cPJCGAy~HD8XNZ2viMN?aQ6KcaaeJ-F?~ zLqJP%66@9{l$jVurHM1Rx^vNCDovKq zTu)M=EQ2c77J0T!yn&iA1a0a1h*iE?&_mwGp4t5ol|`KZ{6}6xviFcuKaOTN?X2B8 z#u!)-LcilV{M*i843c8TfaEbKTJurVMiW()|#i~)g|F(5$3fF;NnNCL({5-$z zNh@Oz_lRr$a#JGC@(^p3F(3vR19qh`Ao65mz^hCN#z3wp(Toz)Qv#+jU_oUJh)ysD z1mnga0mX~~@e+-JLvGSBaz2HQIIeS3;v3_%bn5zJ2e-CEEh&+Sh%M@;kf_6Ae*GcY zAM;6*K0`{4VXmy6T2OeQWl`66Sn_td5dnVpA|Hl$<)c?`Fgf(x5$bL1D+Ydp%>5rw zZ>QC17J5yfeCB!6#?4p$(5f9DMq ze-vRJKpk_|(ISmd!p2^u-tN@D=$NEq#VB@6mQS4PcRJ3++$e!9x4`OOV!mN3#_Ui3 zvV+FEJs#FPhN0i(_$-2_TTeQ1uJ7Ooa^hhUPn_#_JLm+MOC;GX_UXu$J>kT;euK@4 zNUiF`xxU%9nX{-)oa)RN+tm?$MzMXNNb<~M- z{YD#AM1fVFIM+YJu-J-Y;y7`x-(;H%-x7j2ajr)gE+L2$=labImlDK@b3MxN0)i_6 zZ(-;rSO?h8aJf~TIM;9Gl*+Ai;#~hM=gkXHDD(jaH2^T_w=q}?Fkk4mGiU@@DD=-U zSO>63=$~iMW2F=4`W*~5TIs~O{sjh`t#smC|008%taRdBzmvg$l}?=NUt(}4d;q@8 zU?+Uqg?<-<2N40l4hFldIB~Au&GcR?PMquaFg<9c6X*I@7#spXX?8Lg0zhfL%HTBs zl;&OrM*xb2ejkHjfCWOopTTh}o%Yth#^3|T1_wHp9v9M8trPi0i-BULr>P(767VMuAi9`X99SXnf*OH}+&=ljwUI z>kwNx>54yUJK#y$UjLJpi>ZBF?t?_RKP$O3zpu1*rH7_oo|QO(VAtQ(=#-}OB}9|n zC-f7XTKej~0Br$9avy3B!3%jE6;A(mPR%fnp6e^EUsS0J-D`cCf>`(wE0y_0pGVKq+|V8C=qHE2ydM6P#pK$~L9W8gS>PYnS5Jn9g+eih56 z#Nx!b3o$FrnsA6*@8!_x%!?f&*TXEC84HYY+Q+PHQ$?=la5)mnL*)7f=H*()93s~@ z+P;RGb6IidI(~>;-(;iu-Z{=JG=~4xHmY(wiahBMxqgj}YJdDrO@GMNhM{g%du#P? zFhoa_hsgDZ|%6;5H!)_jE;{3 zg3A3CBPbz2DBW)}OlJsv_yj{2L#V_j85S@kt+<=9TViBs591n%k*U3m8zn}zo?_f1 zF*5cvhM^E^y~z?Hf=j%&1nt$d$H{eFHzFYCN<3ryjv2l4%enrx`?V?Xy;r4aw5IiTfPBP z?U%!=&)czwtH_)ZzXEysX*+Up1bI@k@-MDIlJtaoaxc!T(;M8i1(dqwwp}+&#<7Fe zW_pmbumRmrzs;Q+RTJ!F#4&|-n(n$mO#QZEf&m#EN22o;^vX$pTd(?7C3dE{rojup zzwke0WG8kOx}ZXv@v?ZC+cxR}P}E|wnZNd35Ylg}0fS#RQ&T3iE3bl-RyVo5vyD&V zONQmPy*EszZI?|Q?Y;Q$<_daMk`hL?CZQzq4e(kA_2q?>%DS!=?xI>J&3X0PytzwM zF5i^}bXXcUyJd!RS1Yd!+`>%?1wPZOg12two{-V5YXRp<^jL1!!H(x8x7A`5V0k>u% zgW8qqPAQJ*RN0eP*7;*I9Q6hVZ6=Kv+_-O-p-DzfqfLofq(0&`F)9{ytOd{8^3F!O z@qC^`V+|*5V%^eg(K(be?D#L0>76X%$1c;Jjv#$AR%rQB+NGpt%J<02f2&&)yHQp{ z{DlVXNBcH3zuLNsyU+-7qnjAtm!ZMyu$bS?G?#H7$B;6_Ow`gxoOA54#T_)Q;;IgG zAM#7Tf1`5z=T~tP-rT0IqT@e*^MgJ4s>Uf5*4Rbv>RYFppplTfE!a>1J+ zV0)8n>eUgW4isCnCl$ieurI>D-G@KNK_uo_Pk%(=@wj~B1O>YuaU4$Wse!ccIpCHr%j^rp2}BPi@0p+$e}hV z4T34yY>=>R9H#?lnL~RBb&$FSe_HL!z^Ro?QkGH!Owvmp(t70nw|bIJ>w!vZcpa64 zdiR3ijK@ry7=(5!#cU24d>4;@+Kz0jW~Osew^CBK!_1^}a<@`)-zUgP-pcQlzzi+J z<8pJ@hoBI|Sq(_bd_{jd^T|{`g;>Y;z-t*ojK2oNAtG)BQHN2Qc^imKI;E4fS;aO> zg{5b39P&-bamWc!IUMp6AhO5GPoSv6@R>l-Jpuw*+>@9^Y)uPygo16d8c_l0gy;ObJ9ybXa{Bg)mXdYrXrW%kk!#vXT%+Pv}XCw9sBq#y6GE08|stMDfy2OX4yqAZWwYf{iR8{f8K(Gg0LEX5NmwP-0-{gsdp0=p#NYw}OKPygGLxWi(8XnFEz!T5&#C#0PD0p`(i2|M?nlyc^WnWuK3 zsB~%5@R1iXT6-`-{D_SfpN9zY>qp;3AN?TVZR$O zjiae_a%%(LidTsbkzv&n3U4<&9QJ4MpF$~oA4Dqsfk~qv&3up?3bf;_qWJ+cW0$E} zN+C)=ZO1y8Uneq)J((2KF<1;IXID$3?3O_FI0J?7y&t|;F)C8%QK0ltZwKZXPV&Wj z12Skrl0jL(y24p0XS>11luE6B#Il8FsF*s$KCEVZ%cUftg88TI_zvZ(RcKSGsPyW= z%sEOX3~h}V&2t5@!cEnv00m0w?O05-K;!I@EhMy=R87;7PByoYb}p5&3wXj774ini z=RB;y&72s{}#WqS`#rn|Wenfx!r zt5lZ1Ya?s1|7~VA{seu@OM=H3TnGBAe#buiThFjF%NE<2Me;DXtI(oW=t5<1iIh9D zh@x$}>|$pYxwJEjUF^&vmv&~6D|coQC3j{qFSav_klLBWBD^z;sMyXd0AF7Xl# zuHDa{-@+St)O%vJiL1~hwO&%Oe7=IQ7)L$St%f&9{%JeD05X}*tKrr=MQVfi+4pJh zLkf$qH@cy}`bg?)(tGXVna?RWn^?(ERwwLr|H8(-pc&-P@U??*>%Q^2Rq z<1f3{(l!n(pr6_X^r(WGqHBISwp_;>8%YnJnUR#KO#C#tcV2eH+(yE~7j#?zEY;S~dX5FS<`mA%TAmtM%S83|aw=!2A=HdQdu7K9z9c}bk z5%n79JX`4m8p)0-nqAwLPgqKm5l3ZvZ!=zVIf0V3JI1_8*S3G@daI*m)Yr# z*b9QqbzoZs`(!5g(D>t;?iIER**7QdORrYy;t!I*cNP2QI?VSJ*Cqz_O?UzYcEvnL zg{)-H>5j3B!nP`oa)ubER>Q~CiLH9V6Q~lKohsQm#>%%)8{Bam{^@71@)n+4+?0s3Jj5Dh<%vO7 zo?U6>i9FfLpG6@PtUTe^VH^Ucm1jX^<%v$P@&w~nJ^{t7Jn<5({CKSW zc+Gk#sR)rt`)}zcV;MmM+kEgjbv&N;O9P1q>=>Yad?vfozrIjt7f}BlpQ5e!6Ghd# zsS|In86A(8C0*P&_^gwXBJ5G!tgFMM9c>Z`@WhD?5qJkqL^5i^8Pm5g}3{FbNC zs?QYldIYGMjM%as!Pw2w9(tT&#=CF)J?g9I+QTw>JU5naGQ#VqUZKop;;l^Ug!U+D z!*r2N;kC!ELhuQGKYuXXkN3TF;|uSaRnty@pMx3=5h7!hgRfZ;Z6ymp-v|dy$Nj|w z98OKFQ=!e*vMlMpK#X zxljtNFY?nn^|eKu)T55ah;ILqn5DQsg-SZ>Na?a zp!Ql|Rh1q~)tOVK0F@3uZAUkjJ966?ZgA(Fg zOaxuGPj?e7iU1ZO#MyLK_PAObI2-R#D?A%}9#)BIJEpryPKJ7Tx|?WGk5B;)wHo^$ zAW5%zoA&s0H+hOi_#*Fp5SUK$vvcr)vMXtq0KLkQ8xk8hP`giP^u>o6vJId36DoFQ zlhAh48M?svASCF5-8^o7E zxLoJBAt6K$N>z=Js40|LOsi4+YrvngNr4S4u$rQxTCo?1Ip0!VQT77QEnM#^9__mL zhKv(0p{kV$372z*q{#b4)4Ra*&Yzax&Fz-1ywGlxH6iA{suH*nnS%(eF~JkgYGez} zTC@t5IPjDct7`cEZ)-bVj;UG*kNbY1)uURe?te*jPtEcxZWat<8pT5UHFwu}{+?j_ z{E~%!Q^~N-!kdTCONTU<%IBXkAnB*=xDgdvumP`LljlMXqn}}>V{zT+W`V-ax*$G0 zpuNoee}Qlwjq>#>9v6hQBibL)YDMEwHAQFBUq1e3lh^r6K-p2?^Hzc2*1QzNqzriV zfLL%Ah#Np`29Za-DP^Wimo}Vf`?O2VjJ zMOLFCql(cEX%Cu_xyGx=%88)J+Cb!AX^M}EtOV~{6e^izTqo#hDDU|DSS>LZkqquNIFtJy@KUj)zbc(LAjp8!Gid0sga zx7;x2$12Q92*=u*$MYYR&!y~hv3@=Bo-#HIvB8Sw^Q-QA2fC)18eWIj_i-=ZwG%Pm zRMd?!`!+in%BgsL8U_JrN(_X@<@lJPL>BmC8#im}JPqa5n3wpf%VKXMC?IWnMj=mC zIlZ=^Pzkof!pGkOSVY`~uR#6M<)~lO%0?G&N;B`e6=R%GM9Gs7q|0%gwm5O$Rqopmu`#?cOz%L%;>7c4A}j~X zHzL}0A%}QXgK7XYV2SkFWEGgpJ-pX}U+BK>_H z-^JT1p-HXl@MqKUhsB02I7tZ`e-lngpcZbn5nM`8o{}PX0YQ06ilCdIJS9bNxjlYLic%`Kt5Z^x zH!lFT&kt}xYXETLeH(+d0N6NxJA+05J|#tA)&cM-DFQuqbxMlBM!PyCMPRdCosuGO zlU<#XA~0ZAr=$qn2_GCx`ZA}p6Fz)OihLeK1bj-0z%IKyB}MdJyF4XD^q^gxk|J;j z0HxW5kA+Pd+6z2VF{7 zln1pgeU}rbq$t=?8-^r(3==1k z4j@y)Zk|Z`7LZ+}$&X>;MAA=yog_XDSNcpOg^ytpo@Ziw43kPpuXAZn#8D+0b;3@Ip6Yv-II2WE-JWn%iPAOf=BXX@+AbThtad(tq`r5F zqe>LerkJs#N;Hn4B*z|ABH5H!oap=^4xVPG&8Oyv7J6Czq%$w^s1gOvj0MJE>EpoJ zrk+~Q;c_I59aW;7_IRbRay(L zjGc5;>9-h&_V`gHg6IRWqe`*F_^1-K@J>n^mHMcS&@Mu#j>i}c5<>aD$!LfWa&PhgrErk`KS`XbcTFXiJ*%iA5|h)z)&7lBJ7qJS=z%1)kus??Pc63 zF|zd(;~t5Tv8NetmY9z!QG7V6#9f(G6n9-soW)0#NHvX5II2V)6gc6i5^*wrgg+U> zQUu@_CSv_O;j9uJY)p#Ha7P_YqSYGv*rQ3*6JXs(9Ze#hVIO@oiTkcdJJrdgO2q5j z`2X7b7Wk^FEAMmf$%RV-AwYnDpqGG(3Lz+e+MRxNF9>nLMsMT_jNyvj&P3nA$I#=oDy7ty;@d-ruTrTP)<{18CPMrYl{$k5 zV%Y8QXRVt`ZX{(A136c##PVc#`yswYk(`4njnql}(y2dyN(z#sWPGg{;VGX0o8KZZ z>MGLDkl*7PHQ^`_hyEVdsEI^uYG`X?GIbgE<}eE_8)9o?n%UYQe5~=lj#+UTR-Jik zgMxZ%gF>{mp?>Wx3?;6aOr7_n0**`2|2 zw@Wvf;j8)VP)hX-j_a2#Hmd}p&6Goi>z1#~s3mrpMLV%;Xyz$1>H83-CwBh>nCzcS z)5q8~G<&=nYe=Uo$7VwCy~GR+Jl`<|sO0Wr&D6h5oeI>N&i=j@+^dt#@jT6mwGi&B zNKD}qtm+#`t;G5taM%B=GRoaXsy&P=i)Xz-%OzP#egAZ6BmZOQU5KqRm9gKk7@nQ}~iR3!T$G=SwqM zSm>)@T6NB3d5Oe{okKc2z?o>0LJz*sM2o((NR9fzF2!QP8so0KJ+0DH04@0ff|E`{ zW%XgL4m&*5e_liJOf9Xb=~q_IduPIN?g8}F7nsLSsN}!r$S)9C4OWm8rzzKI>L~mf zI`ms4XLT}duFT!baE0D{hen!>J%%liWhNb$@ec2L_WL!O?(E~NpF^0Q*!>rP_$m4% zDQ(HG}UXY@tcl8~-})6tBICNV>J0XY-uT4bHEpgB$;-|IfX)s0b^hQ>O) zTvyvzi_hw29=#u%d&OS*OV6jFa3ohK}4o;e1EDjBV=3sB;F5_Ys&$x~mjE7&&kA0WOnn&f6tw$JRa2#W3W1^4@4i%;E3q<41Y|d`S zNHR8qz6~Jv2r?XpNlu}+9L562iKVEvoU!^9)mfE*C&%Ig7|2^rNF|VvUOO2_Y~+Ov zB807K#f0xE-x&J|5XOTf&PueYoa1pKV*uTJ*EZtdOrRGYj6)#^p4k0a^zIX%M$+vk zV=%Cn9nL8_SzX#a@i0<9h1Aj~IdwA54&;F`o8PZgSccmaR-p|;ggvj5gJrYpo&>g^Ic5rF-z7d z2RP{kD{01$Iq6d&gE1&&HUFEO0WDRUiNO@ZDHV zJDGx~kO|Ish7-w_8NZT2X&}XJ`zqm6P~(3ufIq30qZd;arEw+n;~j(v8hqj9cE1K~&tl zaz1t+N{Ircb)jX$ISI2UaJKyU<@TTC$G3RAAah1DZ&-QYLftN&{gI zF;4Yu=@l#_6=@#&E|#x+8@WfRfoq~HRzMMN+YXnnXq6VUsc-$_G$eM^};Vc|19V5(}^!nz6&0*kX|%x?`(Avk5BFeG67mQcJ(=lzfjL(ZDn3f3w&dvOXJWqsB>_Tbp}kS!?-i_&U@hAi_LPE+1*{)8tb9eqiYW#* z2UM&CvDW^9ZYS|o6`zQBCsMzrVx=n8Nj$A$QxNOg)S`qss32v4^ls_tbrR30_;kc^ z$iYcGt4chBlC#~NZBF9rDt!(E=tIhhLke`7&T_Vscy98?pryHpg9!!?PU88=$DpUB z&S+8>B`PJmI{t{n$H*NoKAk7NF_|hw?!*bE97WDVlG3HNXw6lst2A-i6!l*5>Xz0H z`Ve&D^eMW!_-q_pKs1Btj&Qz(%LPJC) zLRDv}gt3Ks%j42Mnjo}v@fKlXp5jnabPsWuuObtrynd+@6;pnOVo8Cp=3eDp=Yz{2 zH%>VY3PqnKGOdcEqc}Q=HZ8Ug4dqN^r+gWCMpN!nFk(|RFy8RFw4UBL!8}X9bAmnc zV~P8xtVW+&xLVu#$5?r*TdwWRVH0I@Hpff0PGa{IYCZA7)tx<7+8&kWvRc%^o5Wr% zD#L2M&)v|ju#O2z1}#uJiO(wFc?{H63?tvCz)K6OJF=~t*O9yV zoPwka4NE8SdBv-y5Chuq##-VFDs2VR#fgO0&Aj>UBpy>$veKVMA9BYH@rf_0qXfQko0vdRy9X8K9b-CmvVwc9{0tu%!k21k`9zyuVY4U4>+3HJv$h z*z`6*;)yAAOKhJsExWNKo}5CBO59W=!Uxcp63HRZ;7O-+18FRGJ!`OswY;ikdBZw1 z$7C$=ohcK>pgpdmrMUb6ChCcQRN^|yQpM4HS2?|c!mtDSo@&iyM#cd3#E~gO(%9e3rr2?0De%hqes+3Y+if>0au}Yb%tjj{;YLVsRv1!#6Zr~DzRL<_%(_QE;WnZKu%mbbvMeMNRl?8){CZp57F_e zcSuiHs=PGUiJMkix>j`N`tyw)16W;4RH-DR(c0DZm*T2|n&s=Tl2T7!i6~86IDL5$ zRyXK{1xNR1)hbg#;fmheR(xn2n@Sz@edEM(1xHUtuN5X%sMuK8lE?011uGG|x7j6D zD!HYQ5eT)@Z-+QILxjH*GVnN^Sc=YiemGXv2#v;Q2Yup$=$m3?cOyQj4?R6wbZq*w z=nzZaM~Nq$19eU6gKbOVjC)|QpuD29r>%autn9_+lV!!u$0D|-I-jUc&TVONV)K83 zpeDC~$d%H|)Uis+Q2G=F7nT6QnX(fi$yi-hZDRNLgXGlDAgKbg+_EnrFzpcj#2Tjk zBYxam*&dzKsYrte*sH%lxc_m<*^ZpkDd%1yLAlE)?@1Fm3rX!vA&sOMvfL1PlZeFd z=ljSzV=Mx(wbQ5KXX0rfO4og?r@(v!Za@GVKLN!o+pa1*R!ybtt4J%lyAAdKz&i;i z_c01Z_JT&Ks+Q%Xz*AHutRSsjcsim=a~Bh>7K$qEtt9Pz7||3d@HXbrhboL+`gb5X zi`gJc)v61+kr z3Q*N&~0nD)x2muq8vG%wr7wBMnSLI3{N@lu)`l}0y$eyYwhsIng847BnW+oH=s z;cz)#43r~mW6QVEmgmi+++&RRej`C?36z^%gBRRxeiXoupdebkw~|0-D*L*P1d26j z>$;uj{3p<~b=^VMwcrdE_*JIgM*_c(Eh|FcX_aXufmS>OhQjKX`<&2T;=Y5imKbU8 zgtS_`j}wAY(yffOk9a=Fl^wzJp)k+d{x!Wl^uh$qBWio92XZ-FZ>xmB`;FuU4{(;d zjR5bNK(^S(?}0u{LaamKwM>svlq^!xKdbFhJG6bIIAx*-nd~u=_zM?+q?VfE;i`Q( zAhj~lR~S^8K=v<9C7<&}$e@jsfXYZ;1W5UxKT?2<^yD*DF^rMY>*Dm1BwY&K)wwa( zlG~o?LDz!?z61X;&T*O-rd5XLP3h@1tCKsCOxC;%fPKa8N1_D3WurCm6WmDYj)o(lT(v;Wnbi8M=I(4WuI zd#n62>RbZ!wBo%}`5m>|IgIQD&Ko!>@@HJM?8YE&fnNXSYpbyE^=_PWiStKNYuDhy z^SU~{Wt73q4=ZtY%!#S5Q{X+*begn)&t4|z)(mIEC@RxjoOStgm#MP{dq16cRk?+( z7SCsMa+Ct)t3O9ejS~{;d6~*9%a()QE)oylF5W1BI>jw1$?I+DJrKODI7TMNB}`BZfFh)}G~ML??bt0qxhhzlL=d zbC(U6it3dZ#z2esEpWyYc8PYP?b$r`xdw4PPOSKGpo;rRQlr9yPhsoKxTtQZ%%doC zGy5)dW$M9n1s&f++WIxe;S^%5VG=bj!AH-k@sp)T0lp30SLr_>@Q~`qCY!tk#AJ+H z|K7AhpF+{2;4qp3bbSN*Z~9QckdhWnK|e6YO;%|liR3>}=S&oYz%Y86bcG_|7y|Sq zqyGn=*o4@r$w@_Sazbq4=n{1G@i|UW(S-oOzNX+FtalW>v#53woacDJDUg^HbCX3y zfG1*zjvA+t619`);>*#97E_vzCMg#*t@0!!PvJNR$#6YzJ!4zkj z5)GuM6MJHQk|}QyMQ1Pt=z_{Svp{L1b`t4{(odcXeat}0DZUh?sgz1VF=s(MVyN)T zfmTeK6~JsLfi$R;J*Oge(KJ;F2`XqxWx?@c4I=4JL+3-4q;z)rBt(`%5pye?U_MMa z7OT>pSpk6w|MY2^+F3=g`gsg3I(-@$Lb~k3%T(d#Gb;!a$C#yE%q;2<~16ZcC8h zP=e!}D>&F$(-@9(K9`(i1DSXDqR0&GUN)5~WGRV3f{7Rx_yDMl3ll8IMMaQe;%L0i zL=eZR^2x6%aG6FRqSbyH1gOyhQgFErI?f7Hc~EOeITw4>pcKijKBp*vm2$G9WEUw zm*ClOQX0V+Uy9OUp)`V`A(S)LXLYFAah?uH1I4~5QpbB%QX*DD)iA+lNfV_?CK;}( zJq7HwRB~f^&tq}XG~zkMb4F9OGb;0=P@PQ+*TZyA%eo#$s%bb`pBhzD8Z8P=`cjmF z$B2RpO;Jsw{*Z3eMV{zXwz0lwP}|V8*2%rgrcsL+?^9`sO`d(m1mr=p>&$d`a>mW!s}6~KcwM+um2yd`~PIH{~sg$KL!HQd8y%mzo@~%0C$;+W%1O& z2xPqO#!Ea}X#8Sz6UFK#`NssM{xQL1Qy^6(GDLtoqXJFk(Sb@UV;)GO1}}YcxPeXg zDujBblYCLMw;8%kk&l|`YfvX^Fa{2Ox_895L*}-d3cAKwx~);;EZ3GiTMvQecs#YC zpE^vd3*(=#k^Ae+8zky_AH|7ka57qV7D*XcjnR&@(5PQku75}f^^f%{L&hBApae5S z@^uIBP=k&}O1wN&C+wt1aT6)EM-9BDdQGrQH(@fWVMYUiUeBo7@0m^UhN~3a(h92eV(FFAxW~x>iG+ydAFw&s09r^Mpkjprwsoq3MtF?^PsyyK|wjdRmPV=vmJilc; zK5Kl2S0UuMW>PCX*=K@?pW>NdN=%SMn&q?aa^1padyKSTb3F3c1oh}*X6aQv`$c?m zR8QIFhNnhn=&9YA$|*vkv&1{j)AEos^F=hwBXy7)8>W!nWYyR`WvTQOMGY4h+lVEQ zD$dsPv~xV~yvQ#X?kDq#qvuLX!jAOZK(AQTgH@070yt{TxZ; z|6(^PedQ?a07n~ds?1cpDYp!2D88{*p>A76DvwzX87pJ2abDY?u~)IbSw*b0_=isu z{llk89v!UM>9H8CxtR9rpF8;)njTO|6P)HtQDvJhO>mg?7h^lVAu+d{lVRH2BFS8E zUP?S0MN?i?QFIofKIC#SRx-oczIHUnGw4$hTQp&+?2)&lcwe>=2GG!ohluRJm8UR=>+l_^WhO&~#{>j+c$dsj784^37w!;tI9J zY3ngg4%Q=LQ?_w>%!z(kLuE~AyjMDO#^m58Sk7dkxQIz!`{hm;6L6o#r)b5I)~K(= zas_mVWxFA2-aqa_-TKE}h@PaW=mjSljdLZ=^mz|uohNIrb_z)vG#FIutU!gK1$p&z z;-28dG0h;sSLgo9Rzq*B&Pq=j9C}MPnUro)ugID<>Ys?AUTF~S^CV+~E@X%<*2{-_ zSDpscQ1|{Br|x$qdW{q7wtjoTpj%oZ48d!(=wdx1;wFy87~LabIYte;lu@B}P~|#- zD%)F)KxYat(q{%`3iLYUH)Qk(b3yfsJj!o`tz?$&hRU_E%=YYwU4^!@Q+*cg_ZGTa zp6e-qq&vgULpuxcO-M!O>X&aGAyWZpffL=V%up&kG~N3x2v{soM`E#W*$%~_jMfe% zsSE617*uUdFAS3Hg?oar(i4m`b>>?P$KHTah8jza*Z!!)UL}FOC_@xK6X< zDV}rog3g}Su7S4fg1-K?&DdRR9a(nDH3D&&x#K`?{Q*!E9VbO{ojvohk=Rwy0rgN~ zTXs`FB2d@X&YqsU4x$K%AxTH+Cii@Qn~vam7qw>zCA9Q)Y8WoGrnkfKZZKY&?l)k3 zqMWjt_*VLg3v+`k*&NR9f&ex{ag$3+PSa{7BpsxKF*=0PJK!hbHXM%9VP>o&=;h;1 zNm^N@`&%dGjyWE4UyiN1-rZi&{@iolaPPVCNPm0#1IeEy>o>S#o^ZSJ?klk)15YKJ zpLaiaV{+G5-J`MmUiafS#%ISa*`8c}NAgkkolm^%j!UKr;_mm|WChyrXKpx-$k9=& z5dQS`3U^z6;k^JH8Fe!NAK!knTiE0dy(%XV4O~BA#*T&Eg?$5Ki zAnY?b7(Cg_t{H4ExQD!I|H=)Od!FoHzv0;p8!o?a{S^cB{{`;ACb#H`moNE^Qla`F zsq+jn|JhaOw70f){(4^4XYfs%L}8h8_Wh&%r|h&JB3vmBvWuyg(|`ANU8f=L+@ zLKrWT;ASfIcCl?lElLGf4x)k)h(v{fGwy0@#j6OqZt;;z((`L?FOv~vUBe&%V8UM^+t9dw-^XppVWBYk{R3ho$tRvT1kqa|6G5=LH?^r1+AUJ2vkQR9wHu-MZT? z%s=u-`*X<)+-%b$@e-)&|4#GB;Mf)4FORA~^^+Yu{TERmxIqc^!S-#&SHskXuhPgR zr&4JumE(RO7OQus?Mlwu;C|w!hc`djvB52Fa;$A9#z1_1-05O>~gpYQvoF6o-=pgc_h z#~l+ZQip@%Jx4{}_?u?4$X1?l2WWUk91r*>fRj$3J2vt<{&O3GBH4 zh&jzgo7}1<_o8HKL-MD|JKdi<$yM$bZ+gQ0hLgwdu4LUU$@4#d%R4XFoNT{kZ`*~J z*Co3zc{M(#%zYjRV|KY)pKw2PQ2(Ce4@vlvZ+0Ji(9yBwL2LJW1pyVXC;@q8+TW^V=COOtIl5#i9dF093 z3l_WSrS1WzY2ko7p5VLOvLjD^;>rQ{s^i~*kv6#=9X5t%bVP# zP41eepSqj#KXt2L`Z&#fW21JDy0U+dd(!UpZpTXl{ms2+wZGThzVyxu>R0T!;Op*n zOBK?lQEthh-?*_n`jzt9! z1#bRkDEq37XS>r^xt%Y4W6K7&2<^0gm0QyEygP9G){Hywl6z)y>3X;GxV!j>d&wa; zb>y@AFS+b}xkvW6y+=@)ZhMnEZMVBAA3qbiZC0)L?H;5TKkFZVW3i>ew>>&NDve zzN@0l9g|PqId#k3wZD7vrj^YLzMZdj8y|M(=iPNn-3#(=-@|UnQnx?3bTv?ts+@K!KLoEotuB}D5hWjjFf=GZD^fgJJjDJ007 zPSc_*Zb??V|BNi3L6)sc5nSqi9a&nBuinz`{_Mt8DBBuBz87BroUzn>*KxP-_$3fR zd8z!Ut?RFgCt-o@&}7_cdHd{xUKUh-0Rt}YZ9y_cb#19L1Hre+@D-~O+2Vh7p&P4n zgS@_@`@*4DuLC_Ct!Deyn$;kRU*B;C##C$ed|4djOK5u&kMKWQnpW|0 z^;gMzHQ1Z`JN!`fDV42IEKhb|IfELaSryfFWNW<(tFldztQ9MOCG@qdw@)6!j2&qT`&LR&mkK zV0;OlM3ii(;{F&l1)O!i)Jz1@6FEVMBl%MRE_@z!sD^7o8eyF$GiQlcn@0Nn< zar`>pVC5uzr(mnH0=0nYpgtO)?^Q)>s8*nV#;Z@IDe82pzj(Q*xa3yicZ&;S$rhpz zPs2%JQ_rLr^`0 zPw;ekTE+eFC_gp_SciZlYXdDRS5W`;fL+CS2g=h3d0%}V+7Hzf?~29W7{U#icGn1+ zBu8pan#eoyp<-os@N*wJf*MWRiMcOtDpdx)lamTNJ-U`Gxou_^B$|p#E-Ce+jb= z{|+F3_?s0&w9ESI2Wgk2ghfm*{DBCRpZsNIJkS1eMG;zq@|>#84pzPz(A$ePDw8*w z*wCZ&iFS1xwc}gKZ`_ioDXx!S0eA5<+{Hmy>ocJLtZtsFoJ1#lpEc;*=JN+=r0WWi z{b^)Bi0scOZ*yq~1pB9Q620)<(o#-12UWP0Me!9Jtfi`yd=6jfDmOYyJ~(le4?gbQ z99%~E2GbA055QR;WqPL|PLIHyinNMPRDV5qis8!{PVIu|gl~AV^|Q%R#;OoxSy6mL zP=|Q&l<9{rPl8EYrHnBSy0F$&Twm0lNuKbHM8IzZ;>x1+8d2zm?+xwUIKj>KZVy>pk zJq>5Mu`;s>SFIzD6TC7}#0h>;N6A5A;FHnFlWFPe%g{`Q#Z-57M%*u|3xONo%%zXR z+!T<>;35DzJb;q)JDHa5{&sx)rZey0q*7-dM{~NFW8nTtk4?3{byAfWSN!>|I@LHT z8zX6meMxE5-K45)mI^h6@GYoD8eG`d?+kV6Xz-p=xtG>|uci6g6+R2mn`?6-F6PW+ zvN#CPiTn1|VSoXAwF_Sh%{od4s@|l=6=aaBO+lm;-{Z>k>F@M8{+lEp3q7M>fnRlfkaGJ~9K#iY`c42lOG1dK`Oj+O{_6JNA7S z@OLSrR=4bnCHwF3SJvxqX1KqicVlMLKu@a^zFVvx4M}~jV+&deX8D^SVYk{`!X;$f z$n>pkej*l%@8UYu-q7v_EMEAIkF2me!HfUYrEL5$F*brA0^bZ6){<%7^dT$t-Lb~b z?kw7R7Ht3*PxY;7O5D)&hEe4aT#(05=w zP0zRam(VoC`zxfr_y-0hoNdm+)AyVR)0bF&`kE0vQG&h3 zICBlhMj?GMmnY^l>iphc$#tfWvV=Do&0KyOQxJ+V=W+?w`IIH$j~IrRKgvPTDRO$; zbx2D0D=DRuwDn%f0@4-$Jy9wM<3yCVj?45U{}$rAqC3)x;vNz42#f4bQy9KKVLCDA zs&ICId>K@*JH$QaTxRFGY_MDdoSTX+VY;!No|v;POeso3Ql*j|MlV6p2r4K{30Rto z_xEPz`x(REGOb;#Mlpscd0t|iKk~2ZB;VX45uEr?PXv1$pjXdX{HyXw1h4T8mQN?( z)lCkdrAjbsK-m>zN5VIIXdE&>LaE81TqVI~290$FoUycYGW`bU z^gRLWwghDvaXc7qaZWFx^1b=yMboYR;V9>Q)zD~=;1AiqMYyG@oHWaz)Ju@QJ3>#) zS$}|o2MwrRf*$Sl8r}Xohe5D;2`*qzwABa+!Hzj=CFsw%*3yBUu_-EJla;ZOb9#1) zn>Y1y4gYFUCDzYSlu3dg3?NGELmr|8|2lxk5%Xhz))<%i4c8@K}+9*+M zb=smdMTycBAxcw}C`}QfG)0MGSCC!ULG*6)$N|YHN{BmWTeUAZB6G|cMEo#u$?zUd zKxZgHaR9EZO%Pn!rR#Qmuw_Fg<<2Ie&?P78Bv!1iouf$bQaXE1ZOZP zltU)3tGpZ%TpOL^N(T3MIeIyGmj{v{eHn$G5L*rO{;p$9Jnv~llr-IFq~UKlc+i0A zC3u8E#ZG^LPa%5*{(Ud~27kK1|HY7O5GAHeX&OXJmw6yj!$uDzLD5WD7E#lEUY7kF zq}@t-B8=rWR>{LgHR~iO+Cm4O-y@`+Go-9WCHdx#U$CL0PBOtR?mi(>XOr@ID4YxF!ku3un&?Spr8&gry1z`U|%{qHyb@3b%fbUAXm8 zgz zBz(+=Ct)cK;Ys*vt_|WV;fLn<{e?pV+%_eK*9tqP2YUm+635x=vhI$ukbf=kr z(R+WKgAaQk2|i{(YvsVnuMMbPYUVUg{1qJB;DMw>?H;I$gV%W=DUUjP2o@2e3aPoM zTKZos6%|>+4;t7)Q%fQ;;YnC#gQP-fGefEH(=7Fm_?KwC!r@bS#6vI%?_^j+2OubE zgHnGb2RC~lE{FN~JTrTof1xW0{@j2r7m+;nmm3Nk*u^~d7+wt$Eac7&qX!8VF^IfN z!ybt(@en0=o&hyTPPtCT?t$iVaES-f6uiIpvM@5up<0%3f|GLI%Bn(91(u30_lYbOdZiC0;ddF>!aY)4 z;Y~ax?8y-^L}a;|`8Q%rdoCxPXHe=TDC-Up4X}@~Z#TIbBzTtrT_(W~Fi2am#H7o^ z5K4mo;^md#zXtM3>=zy)hs=*H+FB89r^(eQK@o;TTf0YM_Zq~t68zr=)F?p_M(357 zCz|Nbi1rB%{hgszFTtY*v`z@UY(VuA^k`cXKIuDU3k6!0;1$NXt&?CAgLI8ZY^R6F zyv@%{PFl#nWPuW{HreWhQilhUwJ;$b)n4X=sKfaPaTzqDJQ9=v3$P^UiO?V-bQuvE z!XhA}DHsQ&|NMfNOM*`^Nb5>sveE>(BsiVjoJN$G93udt1TP98O6+2W0#SmO1`s9I z=pjmQLjX}?Z62ZouMHp$uZ6WyT3EZs*23B-Ev(&RYhi7a7S`?&Ez}vA*Gf>zt=pNz zr0AHWNKneH5hW%?2ciW1TCgW6wklU4EmjEB{}k91YZasN^G7njz$Sq zFetiqqzjCjjBA;&`FVvyXBm`r5)|4IstFU0QcWdOtTmJyB)FDAT55?&j{^rI!EXc* zjnEvpmQ&wrI5dS(WbP`C2W5Gjv4wU-@xp|oidVu8_6*}_8y0eqUNEBv1(D!o4ALc* z*hf7?ox}SpDDN>S^|y1-%Oyr^veht3EB{6bew5SX03jNX1pmZIT1ttHb~SwzM}jh_ zjyAg~oT1HRw4B*APLc^4R*_60IB+JTWDFInxM3<$#ofW#|Jsmgkl+Ih(xs5tgC3#; zA2OgO3H~XNH_5Y|sr;)cN=&wgA`1B!llg|>&>%s{RqR}ET-o)G?a74qB<4NJxgOhI z<$EN)oZ%66{seQC!T?2rLbW8wefF-HqzHXvlAwreX9`#DQ#R+pP(&ZFQwfTY!>LB* z<$@0j9*oNmS)eErD&b7i`4^Ie$%Ij`Z7*Mdw7Q;f-v5aYu7Ofc67O)B<0*FH=kTxe zAz?~#7!%6wXO z>}C60HsZtiPh)u&@$YK>73aB+%{sgrBwLAn>~x0d z$@0`FmSZgc(!I{~NLL~Imm2E*J;-?9;ol?tD_e&irVc$9!k0b5!SH{}_^a7f+sV# zFup9UJj@B7<=-#yue84x!x=fgi21#ff93v#@W^jHr-#YNmP8Au%c}h&9GCJuJxKoV zGW>`9`(yqUKX8oW(yqh&rG1~o`kW>0fPZC(cm5#yevsjx;onF2_lx{1?dlN6!~9=j z_%HeQ82`!$P1;kE?KPZV+E)puOZ&Qd5d2#i{(k5qf~twH%8%DRsGJAdw^Q!!PyI3P-_cQIdK9Dx4cjsx*~=KT<)o z!FFI6oPPn%;bCx+usrH@hT?SwaGHTI6wbxKA@4F2P8K*+no;;icz8b=5ZaO$D%bmg zb95M-hk#Q*40l@8+BFQF9{?vc49+N2K{N2Y`kDmDjv=^bd$#mZX{nZV+0_fah?V&v zt%x`&1EJViT-~0=76XzVf*0i?@4GJ`;YL6X3{$Qe!g_{8kgmOKD zT+PGC^Go3L4S`bu$Cf8zc!-x9qgRhstGN8BXlu#0aD>X_#y~LiRBY_;7m%{&*c^$P z-->M>Aus`*4*V@N=@h8w^KgiZ8(WFL6DcY1xJV>#L;u2cDv0BNlNts`rpshiZfyUl zjw7QFp@L6cksx@+;h!5L&i1pAp%WAijl6X2`!Sa?4&6uK#wzjG#<_%XsKjpUDE`{d zdd4Y6k{hGmmTE5eWEqFNk{gpAJ%~egLADA4z&SIR$g%60&Ul5B0wdCt_efuPJwV)7 zZf~a-u<64m8?)`|3j;{I6Le^CPT7yjelH;P%AV0u>;fp9PXlM4;5cHdX$2vfw*ZSQ zz{;u% z+?VPFsV>L^3|1Ev*S6*e+tZ&tb4;i!eI}2B35@Ra)TXs(by;Wh}CrgtgViCog6?C zt0P|8qAm-hON6YBTGOCb$0OmVkU`s+@7pOt1ixPeon4|aOJa>ks}g&`Nb^EfLcj8m z&T#1g-67(n;bw!H*OW;c^ol5XJ?|0C?*-`XXz%ktL~EWrNbnKOzXUJ}19@bG7sR7J zg?bY5ly2<3uohXw^YExop`JwdDYEwOrBjId6zWNIpCX6pf;iNtP%RJDr%-=Ey@c*B zXuI9hyilY5f_e!mwHuo!;J`0gOWzRILdeu#q(ENx7kz*L;CsYty#$!HDDOiwJ~Zc- z$kr^DV2g{pm(D+X;dx7(y~w2MU1k0ga_yB|3WARyJM`uzk#Om55Z%6^3TDY z14I-e`<=FYFD|z6N}*<&9t{drv}zoqp9mbnBYR#BlhF439CQr&+>I1%&t(xc zrtSG&;AneZMcJ%}?711x;p~}gTHA9mc-id8R`-IJ_BMl$ZFV86wpH??WC7kIw)z@C z+E#rZ^Ip7}qjzfN929#U^3fdnc5PGOC4i8ZmqUS=0 zp!Mwgf)UetD|$YFJX+6S9D3f0o@;QvPTTz7g9=$v@F6`G@jJ zm)3JO$b@60Lo$rP>{)b3=aRJ!>8p79V@ZC3)u;z;tEp&P<@umz&nRHXHpHF}Bjv>P z98niMAEXrxhy{5faCknLLb)i_jj0+z$a^aA@sl}CwbgS0qSi3je305s>^!N{pCCqM z3qDk*X;Y{2!buQ^>XgbpoR50J)G3|j!~da&>a=o*Iwf@KPm|D(_ED99kjA}7>OF1h z-TMNSV9BL(mC8q@B4=CDzBCT$(6)D}_oE=J>s>f_ztXCZq%w^Qmb?7~rXZ&*@)*hgoh~EzS|~3)M4b|12XIP2N7q-duOJvbJAjiK2Ip@G2LgMerY`}~ zjNGc0iH!4gK+^c<^b$Elb~p}I zp!upT4Pj+>1}wo2;&!`bRJ;H7PqPFLAj~m0N#>X8>yH@b57)mpL*U{1_xFO1u8XXp?+=ik4-BDa(ILGDqjhM$73U}8{2Jxf zI!s0CkdWU1PQB7|iY=LL_AY?}NC)BGlWaYc+18e6!AuHa)x&DYb=m`H2$GyRpmV@b zw(In7fCJ{f=dJW9$B!CxkipY7B8cWpGtVFshS7?Wh zi0bi1U3GdW9A5X^3!Ese0?c%3HZ&vb@uC#((Y$J^jOHWeXcaI8%8|l9nw7e-x%eBj zdzN`aJyM3|RabT1P&nWd%opo8I18nwYgrVV}(ReKhJ0}r*q zQ?Y`l+u#RLbM`|%^MmM@wa;7+b=zp=0^&UMhQ0pW>&^~i7$dF}wkF@u1LIfVq)8)E>U1(`vhgFLsdA$4*dMoXVL?9_5 z>8N&5y@AR-wEa|95$~aKYBy94k5ff0crZG|gY4(sjpD>* z2awik8rV>A=pjou53hRa+4>%6VrmFOTS5sCv?Xkg)GK+CU!s<^Cpnv?50WBlIgH%H zd6FxDpsi&zux)naNp_%6)S{wxmxwEQlKLSmig3u28~`2dNp=B3HsL*Db%iL6wmRRp zLK#i&Ax>f2QQ40zt|K>*8yqv9*AOa&wnUFN`nb0+W-Ecmc zgY=XdwN^k`$XfpCDzTQY17g!6zi~ZGMO#a>4<}2Y0n~7oa1E?ZTf%l^uvJU7yyFCF zxgL1BmdSg9vG0*urnjrvw}d{is*yKC!IeYQGGSd0oV4JWTGqKps}U>GBdTRmU+|Gp z+~cN}BYX?52t8mn;z1HYu_5?F+`XPcMkkj@2a9pb!>eBHovBu+KzO6zd&vFO#n zIRGc@`GkHzR*J@yTsH&KjDJdp3bKwXO>)JwRrR1g6%Y28PMXx-vTNbOqlC{n~j*77Lk?0UQ!n+$DPSY$0-##+*r%#?vV zz}3jDJ-`{Zs{syK)IQ`M$^(!^orz|vE$Tl}Lfh)7b-#f^>DE19$x8sTI`Tuh?6%br zjvB8*$+~qP1%zzEd&Dy7G`qG;EGOhUa&11VB5KhKnhd|#XNP+s zskXxuh)|V!kJ#bgLuO-#{Ml&1l6pU?EP?baDc-ML?+`BZ$c{!@Y&C)oWuf|NzuVLo zAXLKOL-kb$pDXJ(cv-V2B~O0 z^Z7bYhlFWAq{CA_C^{SqO#={mNQYZaK!-HS9ZrY8HahGHanVuI;o%d|;TuMW=Cj=a zdM%)#?JyOoA40yKG>>-b_0%5#Bo%;s43NG6*0wDVWkbeW@SOD?|Kz5}3 zxe8(32Lh1O0coBkIJPCwyS+g1J<^&^#1iU~x>(LwQ|^qwQxh#T96 zzfeu+A&WZeuf(GM1HS29Aq+j9-)k)Dnn=?mFIsV`c+o4U@3J29qV>k=$gA6=$cy#? zM|;u1$MaWX9;aJ(3PjKlLJ#%${rFb`xEsSlYs5p=OH~ARz!{>)M`^UDrM0u*3WC&o zWx19D?;u#5-nM$@6RmWAtM=;pYhm7((NPC{2By!*V~JzdwLCi{MFb!5>OpT@17#{zB$N*V^pV=L)59IAEOO2BCAl*OHHLkRF%^oqrO zj>Ub)h`7(NxYOG~LGJX{XaKTgy2bs@5pgfGxIbud|B=PL#NxhdMBGa)?zC1ADCr`4 zlQsZZlC-$rHzMvd7YMQ_SynCOWwfvkRfb!dq>25ti^q&#r>eg{Z)(mLnGpz zw7AooY=M$4qBW}kWXUfq?)yf>y};srgT?(Zi~CWF`{N_xPJ{YDxfbUw?hjksU$nR% z7!mi^!8*vj%i>P!*a4g+^nM9L*y;|Ai2G|6_cn|B{TBCUE$)X$#Qjx^`&AbAdo1p> zz8oz3izDKG%;Mf?ao=fif6U^3bVS@=vAEY++;6qGf7;^y@`$*oUE^H}sPjV^v# zZ?m}H{5Ela(c(VG;(nFIeVfHSIij+E*Wy0I;@)U+r*rSfNTWfvx{?ubKWuR?wYb+= z+?(Gf?%%YykF~hhSlsI@?qwq?`yq>afyI5P#r+)?_tc2EAGEl?9yfK$-e!@-{Y;Dd z+!1j6`;H`fPq=+#k2N(@ic+nKeJd;_g`7 z&l^$MAG5gs$l_jVaX*g5mSEY_BjQdw>4B0i{;tJ+ti_$qqjN4^2Z+5Wk;Mvx7Qpn01iE}_O;WMwXX;qK4tMR#D|I?4>tJ7bV3iUT^#$QtX*6K1X_Iv zKD2f*7uogNh1}8<#G$pX+pv+LcMvW`A}TNR(Aw8t$gI~lqSwA?ci?5SzCkxY*>q@q zqZ!%_b^%APS*-*nGWi}^*)9QC+s~$!%~>A;f*n0A-30RNa8tfxpa+XCK3nBp zuTR+q$rGeOF{$?wq>(j6;_&J>m5XXA5=Z-lvuX8BwE}gLhqT~5ye6~AlUY`2d@^&} z`PL9+m)`upv0RH@hkkUqBJ5e0eeppQcNm;==OVktv2EEZmt8w%?aS1KtaP1wKgcS| z0$F^}@>=v(Zvbb>^PbmYYuP5&@|L*YXK~+eaevj~{O+EuiN`FIn6_U~%6yBJOn7A;4?#gBJH6S=_I)xZgJ-?sr<;@3*+qs&hcX zB{_@x-Vt%X)#84S#r>NW_eP8RLnGq8!{WZv;(pNLp0>E}8xi*#Ebg~j-1l4DPqVl` zJ|gbhEbcd0+#j>JPqern7!mip#XWCvf7s&wI{Fl3MBgl)_Rxs9_gUP#Ebb3l-2cVm zet1OOyDaW)7Wext?$2A?UmOwl4vYI$7WaEB?vGmBkB*3Yo5j7+;=a@3PWPN3qp#jy z9ufCui+i2L{Z@q%HmG<`v$p} zjfne7i~9_Vd!xmjW-dYQsS$CnvACC7-0Ljvue)LHb4SEIZE+uKaj&tszhrT*91-`W z7WV>+`%;VhH$Cn=llo!7@Uuvdj?5x`ym(%amQwsT%py@NZ9hZIB0ZU97U`3j%l`8r z$}VGo|Hg7HDmZ~zOl-+Exmfs>fM@%`$6TX>n|~z zuasH8BJq1bv^G-FbNv5KO9(8|^Rg#wO4cpe5!Q1Y15{hU- z2s|}NJg`3PyovQg4QU0WhHXPbwgVzNj~a3tAYvOD@&F+8P)tMi0n*2%(2%bHB6~?1 z@=ZWwXAR$+&vo|XH#v?z9g$)>F9K&L&|!)U`86OikJEIDu*h*0RH1P)UA?VXpmOxy zpU^4eg%Bu>>6{M8VdNqmYC0j4peh*FS#+VqGjvujm}Y~sww{hICsw0-ao5Cb zfE)t^|FeD$0di|Vo}U7;Ex`R%K;%tZ&CA6icN1uM)-oB8wg8<6C=CuQmeMaV%jlDUVk?1oXQeO`Mr_$gc+4%+_o`jhU?zqZiD8bjheh8c( zoU&N^2`S07_w1JL(+iU5AL3gT>?ldP%d>1f|K@f?*vXsK<4d$$hwWD z{C+@!^-k~pJ!ZJKvi0ioHlmHc15OG!UVDEUkR7xAw)!JL(!lX7^WOoHai-?}2S9cP z$~79BEnfeH_q$aWqE9*r_fvtB2LY{(bzRwPANbrC;C>Nsb_DcO4+vS4XHgkI=$<8# zMax6)Hr)`AXFDMEbNuDH1CW;tcVu-w3J5)3?R=3Sfn3i5LjAke2K;I#_1%>~^W^ys zaP|dcJ`TvyK)K!oq%^>5Dvng|1H>!E0zf=Z+n1+1H+6k^I2QruK!EZ(f}rg9pSAHW zK*k12x)YGs(S_(z^mn%Rv~hk>)hl$1C>3J5)3Ne=+h7r^;8ATmRYJN@}w zPiuFdvn@dRW#GIXkmrwpJQ#pX#G4IE1CY}J3D)2ef&@x>IUs423&y}@zYUNFf#cQb z2LV|Wp!_L7=<)i~{eW}@aDD*D!vQ+~2FR@e$g4~T8JYVyRB;M1Ph-;oX%5)N`D6yj z;&HD7N^fcsHE zjsge&Gq2wfod8Y&-anWVpfdrG#|(}dfuN<-8*OB0u)&@d14pJ@iCp)XTmU-_fb0rr ztP_x$0I#ipyf)jfpW6Wmj&kn-{ghj2EJ}}8@4p1jSdjJXFpiyMa@k(Jp9F~46H)4XKu8viliAeW+m=PWquF}i z_?^H>1|(boND55=LUJiu0eLte^8n$@@oVf3KuQ91?gOMNAoC{y*>C8mI&F2FH28QW zeG)hwfOvh%AwaeTyu^0_Iiz@rgs%W{&`?Hp=S@Hs1-OsLI|{EFuSGbg0dfGp$jBu< zn{WbjssY)HbdX}4D*H=pNn-?|=LX)H3kk^c!GklHg zVbCG!TEauXxhIh8dw`JJ0uVUpQ@XM}Xr}?0Ujk00!2zQ48$eRXqH__X4DWwe8vPKY z0+1WP)@zk30P*xgI30lKCxsi6(2Pknq2~tRoHtu+BQwyWX1e-(FLEjDJAqRg;I$W! zl7P&g1*8esUTgXaAaZ6P?rhSmbEo-Hk1oo)4C z*PYF0b1V5IQ(rEdY0c1OV~&2EeNII#y9pzk3dz@*@5=YL<$Cj(O})8Ra8-BR4T844 z=LE3&2YQFYVmk~o%VavcTiUakjV=9IbX1X|wDk35^4t5ePIpTWaN7_MBWAcIqPrU` zNqx-3O4RN?#Fm8W%Wzi^PFr2O=He?dwQJTjR@K#IYL+j%?BdMos>YSe8yc{R1KmT~ zDD8_6MB~CYJ+xH>6`yJAm5Z~nnvkcf?lIS0N9Y3)CB|ju_!x)&)X+emUib+Mn+YL- zH+*bIOMj-7E|0g=k!eGH(;@|xAVU+HjV)MDvdC&v>g{UFZfor`_fFV&N{=nA-ECbx zgCr_W_y(7qtd{I7&VaT=;L;Sq#_a^|6`eh+Lz?98Gi`-I4XNVxo`DPsnC;7V_V&o- z8WtOV^^mWFtgfo!P%VYDOBlijcgyl4PJg6(XJy}g?|$wSgh zh`z+uA>o`R6QNc)n%%nTE^OG}f+Oo~d8A{IZ%$ zGY!k@F0Izaw|q^F?anhpWFTwR#a*|2Wk!i^TAZXPr<9c=yChr(%oxH!Iw#vbkkN-; zDWtEP%S5+eriV*OsLYwo+3nhpMLE9o)_zBlGhJv#kTSD%$s#Q(5ol>^GX$gv1aH-v zv+#4M26$~DDp@j(b?c0~^$)8lHUY@#bWrG7I2H|1a z2YF$tE+M!2o2e(=vyh;4|x1qQm_*c2T=KGWB) z+#8F;!m1T%<{*5v6)_R=_Ij|aP}Quer4^+`hL_F3H7HNma&2!e(?77WUzIPj1)UC!K`1RlH+^BRNpz~I z)?~Xd3#dl3>xAk}%hw!-Hg>MswFBAQc0<3@&=-ZU^m@cBMd|Ku#}El~4Uwypmz+Ym zV1=07W0>ce1F~ox!=h*)mg(xN?ry77lOmq6n2yn-Hh7n#^rogp*0@wl3v*#-)Yy^B zwzSpKTp?m=$8R*pO3GAc`uno2 zotrvaJr_1qTi3n?jjj%Zn}NO+upR2Dm-pmV1&N_`WLx?IcBb8f*FL0l!HEUgpqkQ} zWZ&dTi#!NxHWU;#kFf(cGc!9A3+M}#zb@OdCBiUu-S?4QKy48!67jr-D04_tosFHX zn{~$|$YN8L33v#DTaebfR}Q^+sz{9GIfNDl^Pa4lKI2Qy7|WoYv_a`QaC%>4U2#$n2S4 zh(~w-7IM;5d93v%BH%%`fLu}Jl2_7(VQ^gHNzQR z5!I-COhJ5&^j|&eI(oOF3uDLF*Q!j{lyR`R2%Vz~HC2+Q7Mjr0Wy_HS=gEFsA1V_O z<*vG04KE@bnk;^@PEcI#woNIxuMj&SKM)*<@M#{csX={;P9AC)DI9hC^Yt=R4q14I zi3ZKfKd42c3XWQZq-LJ+#$lKXgc#_Z@{bvD=;lL13=X;`$uzn#FqCd${X3i zE4KIsQE=UQ(L=9m1lKQ!uYdSqHBeNWB^70ovjMu`p>7<`fE3lxx?0-x03M_I&Hee!nQof@h89kFGNg@B8hl4>4{RU4Ld`_{vofz{%uq8p z`ylO_W#4mGYBYFZLCbQyMVX7>fqBgQm5d(-lJ>gS%zFFTzCl@2g(%-+nLrSC?a=y`Lf HNjm=rIylOD literal 0 HcmV?d00001 diff --git a/waterbox/thunk/test.s b/waterbox/thunk/test.s new file mode 100644 index 0000000000..988fcf43e1 --- /dev/null +++ b/waterbox/thunk/test.s @@ -0,0 +1,682 @@ + .file "test.c" + .text + .globl Depart0 + .def Depart0; .scl 2; .type 32; .endef +Depart0: + pushq %rbp + movq %rsp, %rbp + subq $32, %rsp + movabsq $-2401053088335136050, %rax + call *%rax + leave + ret + .globl Depart1 + .def Depart1; .scl 2; .type 32; .endef +Depart1: + pushq %rbp + movq %rsp, %rbp + subq $48, %rsp + movq %rdi, -8(%rbp) + movq -8(%rbp), %rdx + movabsq $-2401053088335136050, %rax + movq %rdx, %rcx + call *%rax + leave + ret + .globl Depart2 + .def Depart2; .scl 2; .type 32; .endef +Depart2: + pushq %rbp + movq %rsp, %rbp + subq $48, %rsp + movq %rdi, -8(%rbp) + movq %rsi, -16(%rbp) + movq -16(%rbp), %rdx + movq -8(%rbp), %rcx + movabsq $-2401053088335136050, %rax + call *%rax + leave + ret + .globl Depart3 + .def Depart3; .scl 2; .type 32; .endef +Depart3: + pushq %rbp + movq %rsp, %rbp + subq $64, %rsp + movq %rdi, -8(%rbp) + movq %rsi, -16(%rbp) + movq %rdx, -24(%rbp) + movq -24(%rbp), %rsi + movq -16(%rbp), %rdx + movq -8(%rbp), %rcx + movabsq $-2401053088335136050, %rax + movq %rsi, %r8 + call *%rax + leave + ret + .globl Depart4 + .def Depart4; .scl 2; .type 32; .endef +Depart4: + pushq %rbp + movq %rsp, %rbp + subq $64, %rsp + movq %rdi, -8(%rbp) + movq %rsi, -16(%rbp) + movq %rdx, -24(%rbp) + movq %rcx, -32(%rbp) + movq -32(%rbp), %rdi + movq -24(%rbp), %rsi + movq -16(%rbp), %rdx + movq -8(%rbp), %rcx + movabsq $-2401053088335136050, %rax + movq %rdi, %r9 + movq %rsi, %r8 + call *%rax + leave + ret + .globl Depart5 + .def Depart5; .scl 2; .type 32; .endef +Depart5: + pushq %rbp + movq %rsp, %rbp + subq $96, %rsp + movq %rdi, -8(%rbp) + movq %rsi, -16(%rbp) + movq %rdx, -24(%rbp) + movq %rcx, -32(%rbp) + movq %r8, -40(%rbp) + movq -32(%rbp), %rdi + movq -24(%rbp), %rsi + movq -16(%rbp), %rdx + movq -8(%rbp), %rcx + movq -40(%rbp), %rax + movq %rax, 32(%rsp) + movabsq $-2401053088335136050, %rax + movq %rdi, %r9 + movq %rsi, %r8 + call *%rax + leave + ret + .globl Depart6 + .def Depart6; .scl 2; .type 32; .endef +Depart6: + pushq %rbp + movq %rsp, %rbp + subq $96, %rsp + movq %rdi, -8(%rbp) + movq %rsi, -16(%rbp) + movq %rdx, -24(%rbp) + movq %rcx, -32(%rbp) + movq %r8, -40(%rbp) + movq %r9, -48(%rbp) + movq -32(%rbp), %rdi + movq -24(%rbp), %rsi + movq -16(%rbp), %rdx + movq -8(%rbp), %rcx + movq -48(%rbp), %rax + movq %rax, 40(%rsp) + movq -40(%rbp), %rax + movq %rax, 32(%rsp) + movabsq $-2401053088335136050, %rax + movq %rdi, %r9 + movq %rsi, %r8 + call *%rax + leave + ret + .globl Arrive0 + .def Arrive0; .scl 2; .type 32; .endef + .seh_proc Arrive0 +Arrive0: + pushq %rbp + .seh_pushreg %rbp + pushq %rdi + .seh_pushreg %rdi + pushq %rsi + .seh_pushreg %rsi + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $160, %rsp + .seh_stackalloc 160 + movaps %xmm6, (%rsp) + .seh_savexmm %xmm6, 0 + movaps %xmm7, 16(%rsp) + .seh_savexmm %xmm7, 16 + movaps %xmm8, -128(%rbp) + .seh_savexmm %xmm8, 32 + movaps %xmm9, -112(%rbp) + .seh_savexmm %xmm9, 48 + movaps %xmm10, -96(%rbp) + .seh_savexmm %xmm10, 64 + movaps %xmm11, -80(%rbp) + .seh_savexmm %xmm11, 80 + movaps %xmm12, -64(%rbp) + .seh_savexmm %xmm12, 96 + movaps %xmm13, -48(%rbp) + .seh_savexmm %xmm13, 112 + movaps %xmm14, -32(%rbp) + .seh_savexmm %xmm14, 128 + movaps %xmm15, -16(%rbp) + .seh_savexmm %xmm15, 144 + .seh_endprologue + movabsq $-2401053088335136050, %rax + call *%rax + movaps (%rsp), %xmm6 + movaps 16(%rsp), %xmm7 + movaps -128(%rbp), %xmm8 + movaps -112(%rbp), %xmm9 + movaps -96(%rbp), %xmm10 + movaps -80(%rbp), %xmm11 + movaps -64(%rbp), %xmm12 + movaps -48(%rbp), %xmm13 + movaps -32(%rbp), %xmm14 + movaps -16(%rbp), %xmm15 + addq $160, %rsp + popq %rsi + popq %rdi + popq %rbp + ret + .seh_endproc + .globl Arrive1 + .def Arrive1; .scl 2; .type 32; .endef + .seh_proc Arrive1 +Arrive1: + pushq %rbp + .seh_pushreg %rbp + pushq %rdi + .seh_pushreg %rdi + pushq %rsi + .seh_pushreg %rsi + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $160, %rsp + .seh_stackalloc 160 + movaps %xmm6, (%rsp) + .seh_savexmm %xmm6, 0 + movaps %xmm7, 16(%rsp) + .seh_savexmm %xmm7, 16 + movaps %xmm8, -128(%rbp) + .seh_savexmm %xmm8, 32 + movaps %xmm9, -112(%rbp) + .seh_savexmm %xmm9, 48 + movaps %xmm10, -96(%rbp) + .seh_savexmm %xmm10, 64 + movaps %xmm11, -80(%rbp) + .seh_savexmm %xmm11, 80 + movaps %xmm12, -64(%rbp) + .seh_savexmm %xmm12, 96 + movaps %xmm13, -48(%rbp) + .seh_savexmm %xmm13, 112 + movaps %xmm14, -32(%rbp) + .seh_savexmm %xmm14, 128 + movaps %xmm15, -16(%rbp) + .seh_savexmm %xmm15, 144 + .seh_endprologue + movq %rcx, 32(%rbp) + movabsq $-2401053088335136050, %rax + movq 32(%rbp), %rdi + call *%rax + movaps (%rsp), %xmm6 + movaps 16(%rsp), %xmm7 + movaps -128(%rbp), %xmm8 + movaps -112(%rbp), %xmm9 + movaps -96(%rbp), %xmm10 + movaps -80(%rbp), %xmm11 + movaps -64(%rbp), %xmm12 + movaps -48(%rbp), %xmm13 + movaps -32(%rbp), %xmm14 + movaps -16(%rbp), %xmm15 + addq $160, %rsp + popq %rsi + popq %rdi + popq %rbp + ret + .seh_endproc + .globl Arrive2 + .def Arrive2; .scl 2; .type 32; .endef + .seh_proc Arrive2 +Arrive2: + pushq %rbp + .seh_pushreg %rbp + pushq %rdi + .seh_pushreg %rdi + pushq %rsi + .seh_pushreg %rsi + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $160, %rsp + .seh_stackalloc 160 + movaps %xmm6, (%rsp) + .seh_savexmm %xmm6, 0 + movaps %xmm7, 16(%rsp) + .seh_savexmm %xmm7, 16 + movaps %xmm8, -128(%rbp) + .seh_savexmm %xmm8, 32 + movaps %xmm9, -112(%rbp) + .seh_savexmm %xmm9, 48 + movaps %xmm10, -96(%rbp) + .seh_savexmm %xmm10, 64 + movaps %xmm11, -80(%rbp) + .seh_savexmm %xmm11, 80 + movaps %xmm12, -64(%rbp) + .seh_savexmm %xmm12, 96 + movaps %xmm13, -48(%rbp) + .seh_savexmm %xmm13, 112 + movaps %xmm14, -32(%rbp) + .seh_savexmm %xmm14, 128 + movaps %xmm15, -16(%rbp) + .seh_savexmm %xmm15, 144 + .seh_endprologue + movq %rcx, 32(%rbp) + movq %rdx, 40(%rbp) + movq 40(%rbp), %rdx + movabsq $-2401053088335136050, %rax + movq %rdx, %rsi + movq 32(%rbp), %rdi + call *%rax + movaps (%rsp), %xmm6 + movaps 16(%rsp), %xmm7 + movaps -128(%rbp), %xmm8 + movaps -112(%rbp), %xmm9 + movaps -96(%rbp), %xmm10 + movaps -80(%rbp), %xmm11 + movaps -64(%rbp), %xmm12 + movaps -48(%rbp), %xmm13 + movaps -32(%rbp), %xmm14 + movaps -16(%rbp), %xmm15 + addq $160, %rsp + popq %rsi + popq %rdi + popq %rbp + ret + .seh_endproc + .globl Arrive3 + .def Arrive3; .scl 2; .type 32; .endef + .seh_proc Arrive3 +Arrive3: + pushq %rbp + .seh_pushreg %rbp + pushq %rdi + .seh_pushreg %rdi + pushq %rsi + .seh_pushreg %rsi + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $160, %rsp + .seh_stackalloc 160 + movaps %xmm6, (%rsp) + .seh_savexmm %xmm6, 0 + movaps %xmm7, 16(%rsp) + .seh_savexmm %xmm7, 16 + movaps %xmm8, -128(%rbp) + .seh_savexmm %xmm8, 32 + movaps %xmm9, -112(%rbp) + .seh_savexmm %xmm9, 48 + movaps %xmm10, -96(%rbp) + .seh_savexmm %xmm10, 64 + movaps %xmm11, -80(%rbp) + .seh_savexmm %xmm11, 80 + movaps %xmm12, -64(%rbp) + .seh_savexmm %xmm12, 96 + movaps %xmm13, -48(%rbp) + .seh_savexmm %xmm13, 112 + movaps %xmm14, -32(%rbp) + .seh_savexmm %xmm14, 128 + movaps %xmm15, -16(%rbp) + .seh_savexmm %xmm15, 144 + .seh_endprologue + movq %rcx, 32(%rbp) + movq %rdx, 40(%rbp) + movq %r8, 48(%rbp) + movq 48(%rbp), %rdx + movq 40(%rbp), %rcx + movabsq $-2401053088335136050, %rax + movq %rcx, %rsi + movq 32(%rbp), %rdi + call *%rax + movaps (%rsp), %xmm6 + movaps 16(%rsp), %xmm7 + movaps -128(%rbp), %xmm8 + movaps -112(%rbp), %xmm9 + movaps -96(%rbp), %xmm10 + movaps -80(%rbp), %xmm11 + movaps -64(%rbp), %xmm12 + movaps -48(%rbp), %xmm13 + movaps -32(%rbp), %xmm14 + movaps -16(%rbp), %xmm15 + addq $160, %rsp + popq %rsi + popq %rdi + popq %rbp + ret + .seh_endproc + .globl Arrive4 + .def Arrive4; .scl 2; .type 32; .endef + .seh_proc Arrive4 +Arrive4: + pushq %rbp + .seh_pushreg %rbp + pushq %rdi + .seh_pushreg %rdi + pushq %rsi + .seh_pushreg %rsi + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $160, %rsp + .seh_stackalloc 160 + movaps %xmm6, (%rsp) + .seh_savexmm %xmm6, 0 + movaps %xmm7, 16(%rsp) + .seh_savexmm %xmm7, 16 + movaps %xmm8, -128(%rbp) + .seh_savexmm %xmm8, 32 + movaps %xmm9, -112(%rbp) + .seh_savexmm %xmm9, 48 + movaps %xmm10, -96(%rbp) + .seh_savexmm %xmm10, 64 + movaps %xmm11, -80(%rbp) + .seh_savexmm %xmm11, 80 + movaps %xmm12, -64(%rbp) + .seh_savexmm %xmm12, 96 + movaps %xmm13, -48(%rbp) + .seh_savexmm %xmm13, 112 + movaps %xmm14, -32(%rbp) + .seh_savexmm %xmm14, 128 + movaps %xmm15, -16(%rbp) + .seh_savexmm %xmm15, 144 + .seh_endprologue + movq %rcx, 32(%rbp) + movq %rdx, 40(%rbp) + movq %r8, 48(%rbp) + movq %r9, 56(%rbp) + movq 56(%rbp), %rcx + movq 48(%rbp), %rdx + movq 40(%rbp), %r8 + movabsq $-2401053088335136050, %rax + movq %r8, %rsi + movq 32(%rbp), %rdi + call *%rax + movaps (%rsp), %xmm6 + movaps 16(%rsp), %xmm7 + movaps -128(%rbp), %xmm8 + movaps -112(%rbp), %xmm9 + movaps -96(%rbp), %xmm10 + movaps -80(%rbp), %xmm11 + movaps -64(%rbp), %xmm12 + movaps -48(%rbp), %xmm13 + movaps -32(%rbp), %xmm14 + movaps -16(%rbp), %xmm15 + addq $160, %rsp + popq %rsi + popq %rdi + popq %rbp + ret + .seh_endproc + .globl Arrive5 + .def Arrive5; .scl 2; .type 32; .endef + .seh_proc Arrive5 +Arrive5: + pushq %rbp + .seh_pushreg %rbp + pushq %rdi + .seh_pushreg %rdi + pushq %rsi + .seh_pushreg %rsi + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $160, %rsp + .seh_stackalloc 160 + movaps %xmm6, (%rsp) + .seh_savexmm %xmm6, 0 + movaps %xmm7, 16(%rsp) + .seh_savexmm %xmm7, 16 + movaps %xmm8, -128(%rbp) + .seh_savexmm %xmm8, 32 + movaps %xmm9, -112(%rbp) + .seh_savexmm %xmm9, 48 + movaps %xmm10, -96(%rbp) + .seh_savexmm %xmm10, 64 + movaps %xmm11, -80(%rbp) + .seh_savexmm %xmm11, 80 + movaps %xmm12, -64(%rbp) + .seh_savexmm %xmm12, 96 + movaps %xmm13, -48(%rbp) + .seh_savexmm %xmm13, 112 + movaps %xmm14, -32(%rbp) + .seh_savexmm %xmm14, 128 + movaps %xmm15, -16(%rbp) + .seh_savexmm %xmm15, 144 + .seh_endprologue + movq %rcx, 32(%rbp) + movq %rdx, 40(%rbp) + movq %r8, 48(%rbp) + movq %r9, 56(%rbp) + movq 64(%rbp), %r8 + movq 56(%rbp), %rcx + movq 48(%rbp), %rdx + movq 40(%rbp), %r9 + movabsq $-2401053088335136050, %rax + movq %r9, %rsi + movq 32(%rbp), %rdi + call *%rax + movaps (%rsp), %xmm6 + movaps 16(%rsp), %xmm7 + movaps -128(%rbp), %xmm8 + movaps -112(%rbp), %xmm9 + movaps -96(%rbp), %xmm10 + movaps -80(%rbp), %xmm11 + movaps -64(%rbp), %xmm12 + movaps -48(%rbp), %xmm13 + movaps -32(%rbp), %xmm14 + movaps -16(%rbp), %xmm15 + addq $160, %rsp + popq %rsi + popq %rdi + popq %rbp + ret + .seh_endproc + .globl Arrive6 + .def Arrive6; .scl 2; .type 32; .endef + .seh_proc Arrive6 +Arrive6: + pushq %rbp + .seh_pushreg %rbp + pushq %rdi + .seh_pushreg %rdi + pushq %rsi + .seh_pushreg %rsi + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $160, %rsp + .seh_stackalloc 160 + movaps %xmm6, (%rsp) + .seh_savexmm %xmm6, 0 + movaps %xmm7, 16(%rsp) + .seh_savexmm %xmm7, 16 + movaps %xmm8, -128(%rbp) + .seh_savexmm %xmm8, 32 + movaps %xmm9, -112(%rbp) + .seh_savexmm %xmm9, 48 + movaps %xmm10, -96(%rbp) + .seh_savexmm %xmm10, 64 + movaps %xmm11, -80(%rbp) + .seh_savexmm %xmm11, 80 + movaps %xmm12, -64(%rbp) + .seh_savexmm %xmm12, 96 + movaps %xmm13, -48(%rbp) + .seh_savexmm %xmm13, 112 + movaps %xmm14, -32(%rbp) + .seh_savexmm %xmm14, 128 + movaps %xmm15, -16(%rbp) + .seh_savexmm %xmm15, 144 + .seh_endprologue + movq %rcx, 32(%rbp) + movq %rdx, 40(%rbp) + movq %r8, 48(%rbp) + movq %r9, 56(%rbp) + movq 72(%rbp), %r9 + movq 64(%rbp), %r8 + movq 56(%rbp), %rcx + movq 48(%rbp), %rdx + movq 40(%rbp), %r10 + movabsq $-2401053088335136050, %rax + movq %r10, %rsi + movq 32(%rbp), %rdi + call *%rax + movaps (%rsp), %xmm6 + movaps 16(%rsp), %xmm7 + movaps -128(%rbp), %xmm8 + movaps -112(%rbp), %xmm9 + movaps -96(%rbp), %xmm10 + movaps -80(%rbp), %xmm11 + movaps -64(%rbp), %xmm12 + movaps -48(%rbp), %xmm13 + movaps -32(%rbp), %xmm14 + movaps -16(%rbp), %xmm15 + addq $160, %rsp + popq %rsi + popq %rdi + popq %rbp + ret + .seh_endproc + .globl End + .def End; .scl 2; .type 32; .endef + .seh_proc End +End: + pushq %rbp + .seh_pushreg %rbp + movq %rsp, %rbp + .seh_setframe %rbp, 0 + .seh_endprologue + nop + popq %rbp + ret + .seh_endproc + .globl ptrs + .data + .align 32 +ptrs: + .quad Depart0 + .quad Depart1 + .quad Depart2 + .quad Depart3 + .quad Depart4 + .quad Depart5 + .quad Depart6 + .quad Arrive0 + .quad Arrive1 + .quad Arrive2 + .quad Arrive3 + .quad Arrive4 + .quad Arrive5 + .quad Arrive6 + .quad End + .section .rdata,"dr" + .align 8 +.LC0: + .ascii "private static readonly byte[][] %s =\12{\12\0" +.LC1: + .ascii "\11new byte[] { \0" +.LC2: + .ascii "0x%02x, \0" +.LC3: + .ascii "},\0" +.LC4: + .ascii "};\0" + .text + .globl print + .def print; .scl 2; .type 32; .endef + .seh_proc print +print: + pushq %rbp + .seh_pushreg %rbp + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $64, %rsp + .seh_stackalloc 64 + .seh_endprologue + movq %rcx, 16(%rbp) + movl %edx, 24(%rbp) + movq 16(%rbp), %rdx + leaq .LC0(%rip), %rcx + call printf + movl 24(%rbp), %eax + movl %eax, -4(%rbp) + jmp .L31 +.L34: + leaq .LC1(%rip), %rcx + call printf + movl -4(%rbp), %eax + cltq + leaq 0(,%rax,8), %rdx + leaq ptrs(%rip), %rax + movq (%rdx,%rax), %rax + movq %rax, -16(%rbp) + movl -4(%rbp), %eax + addl $1, %eax + cltq + leaq 0(,%rax,8), %rdx + leaq ptrs(%rip), %rax + movq (%rdx,%rax), %rax + movq %rax, -24(%rbp) + jmp .L32 +.L33: + movq -16(%rbp), %rax + leaq 1(%rax), %rdx + movq %rdx, -16(%rbp) + movzbl (%rax), %eax + movzbl %al, %eax + movl %eax, %edx + leaq .LC2(%rip), %rcx + call printf +.L32: + movq -16(%rbp), %rax + cmpq -24(%rbp), %rax + jb .L33 + leaq .LC3(%rip), %rcx + call puts + addl $1, -4(%rbp) +.L31: + movl 24(%rbp), %eax + addl $7, %eax + cmpl -4(%rbp), %eax + jg .L34 + leaq .LC4(%rip), %rcx + call puts + nop + addq $64, %rsp + popq %rbp + ret + .seh_endproc + .def __main; .scl 2; .type 32; .endef + .section .rdata,"dr" +.LC5: + .ascii "Depart\0" +.LC6: + .ascii "Arrive\0" + .text + .globl main + .def main; .scl 2; .type 32; .endef + .seh_proc main +main: + pushq %rbp + .seh_pushreg %rbp + movq %rsp, %rbp + .seh_setframe %rbp, 0 + subq $32, %rsp + .seh_stackalloc 32 + .seh_endprologue + call __main + movl $0, %edx + leaq .LC5(%rip), %rcx + call print + movl $0, %edx + leaq .LC6(%rip), %rcx + call print + movl $0, %eax + addq $32, %rsp + popq %rbp + ret + .seh_endproc + .ident "GCC: (Rev2, Built by MSYS2 project) 5.3.0" + .def printf; .scl 2; .type 32; .endef + .def puts; .scl 2; .type 32; .endef