├── .gitignore ├── LICENSE.txt ├── LICENSE.txt.meta ├── README.md ├── README.md.meta ├── UnityNativeTool.sln.meta ├── package.json ├── package.json.meta ├── scripts.meta ├── scripts ├── Attributes.cs ├── Attributes.cs.meta ├── Detour.cs ├── Detour.cs.meta ├── DllLoadException.cs ├── DllLoadException.cs.meta ├── DllManipulator.ReflectionData.cs ├── DllManipulator.ReflectionData.cs.meta ├── DllManipulator.cs ├── DllManipulator.cs.meta ├── DllManipulatorScript.cs ├── DllManipulatorScript.cs.meta ├── Editor.meta ├── Editor │ ├── DllManipulatorEditor.cs │ ├── DllManipulatorEditor.cs.meta │ ├── DllManipulatorWindowEditor.cs │ ├── DllManipulatorWindowEditor.cs.meta │ ├── mcpiroman.UnityNativeTool.Editor.asmdef │ └── mcpiroman.UnityNativeTool.Editor.asmdef.meta ├── IlGeneratorExtensions.cs ├── IlGeneratorExtensions.cs.meta ├── LowLevelPluginManager.cs ├── LowLevelPluginManager.cs.meta ├── MethodInvoker.cs ├── MethodInvoker.cs.meta ├── NativeDll.cs ├── NativeDll.cs.meta ├── NativeFunction.cs ├── NativeFunction.cs.meta ├── NativeFunctionSignature.cs ├── NativeFunctionSignature.cs.meta ├── PInvokes.cs ├── PInvokes.cs.meta ├── PathUtils.cs ├── PathUtils.cs.meta ├── mcpiroman.UnityNativeTool.asmdef └── mcpiroman.UnityNativeTool.asmdef.meta ├── stubLluiPlugin.c ├── stubLluiPlugin.c.meta └── tests.meta /.gitignore: -------------------------------------------------------------------------------- 1 | scripts/LICENSE.txt 2 | scripts/LICENSE.txt.meta 3 | scripts/plugins 4 | scripts/plugins.meta 5 | *.sln 6 | .vs 7 | stubLluiPlugin 8 | tests -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 MCpiroman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f4a13bbeab717a4abf582c2cdd455d9 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tool created mainly to solve the old problem with reloading [native plugins](https://docs.unity3d.com/Manual/NativePlugins.html) without the need to reopen Unity Editor. 2 | 3 | ## Overview 4 | 5 | - Automatically unloads native plugins after stopping the game and loads them when needed. 6 | - You can unload/reload them manually, even when the game is running. 7 | - No code change is required - use usual `[DllImport]`. 8 | - [Low level interface](https://docs.unity3d.com/Manual/NativePluginInterface.html) callbacks `UnityPluginLoad` and `UnityPluginUnload` do fire - to enable them see [this section](#low-level-interface-callbacks-support). 9 | - Works on Windows, Linux and Mac, but only on x86/x86_64 processors. 10 | - Ability to log native calls to file in order to diagnose crashes caused by them. 11 | 12 | ## Installation 13 | 14 | 1. Either download and add unity package from [releases](https://github.com/MCpiroman/UnityNativeTool/releases), or clone this repo into the `assets` of your project. 15 | 16 | - Clone it into the `/Packages` folder to use it as a local [embedded package](https://docs.unity3d.com/Manual/upm-embed.html) with [upm](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@1.8/manual/index.html). 17 | 18 | 2. In project settings, set _Api Compatibility Level_ to .NET 4.x or above. 19 | Edit > Project Settings > Player > Other Settings > Api Compatibility Level 20 | 21 | 3. Check _Allow 'unsafe' code_. 22 | Edit > Project Settings > Player > Other Settings > Allow 'unsafe' code 23 | 24 | 4. One of the gameobjects in the scene needs to have `DllManipulatorScript` on it. (This script calls `DontDestroayOnLoad(gameObject)` and deletes itself when a duplicate is found in order to behave nicely when switching scenes). 25 | 26 | ## Usage 27 | - Your plugin files must be at path specified in options. By default, just add `__` (two underscores) at the beginning of your dll files in the Assets/Plugins folder (e.g. on Windows, plugin named `FastCalcs` should be at path `Assets\Plugins\__FastCalcs.dll`). 28 | - By default, all `extern` methods in the main scripts assembly will be mocked (i.e. handled by this tool instead of Unity, allowing them to be unloaded). You can change this in options and use provided attributes to specify that yourself (they are in `UnityNativeTool` namespace, file `Attributes.cs`). 29 | - Options are accessible via `DllManipulatorScript` editor or window. 30 | - You can also unload and load all DLLs via shortcut, `Alt+D` and `Alt+Shfit+D` respectively. Editable in the Shortcut Manager for 2019.1+ 31 | - You can get callbacks in your C# code when the load state of a DLL has changed with attributes like `[NativeDllLoadedTrigger]`. See `Attributes.cs`. 32 | - Although this tool presumably works in the built game, it's intended to be used only during development. 33 | - If something doesn't work, first check out available options (and read their descriptions), then [report an issue](https://github.com/mcpiroman/UnityNativeTool/issues/new). 34 | 35 | ## Low level interface callbacks support 36 | For that, you'll need a `StubLluiPlugin` DLL. I only embed it into .unitypackage for x64 Windows platform, so for other cases you'll need to compile it manually. 37 | 38 | This is, compile the file `./stubLluiPlugin.c` into the dynamic library (name it `StubLluiPlugin`, no underscores) and put into Unity like you would do with other plugins. 39 | 40 | ## Limitations 41 | - Native callbacks `UnityRenderingExtEvent` and `UnityRenderingExtQuery` do not fire. 42 | - Only some basic attributes on parameters of `extern` methods (such as `[MarshalAs]` or `[In]`) are supported. 43 | - Properties `MarshalCookie`, `MarshalType`, `MarshalTypeRef` and `SafeArrayUserDefinedSubType` on `[MarshalAs]` attribute are not supported (due to [Mono bug](https://github.com/mono/mono/issues/12747)). 44 | - Explicitly specifying `UnmanagedType.LPArray` in `[MarshalAs]` is not supported (due to [another Mono bug](https://github.com/mono/mono/issues/16570)). Note that this should be the default for array types, so in trivial situations you don't need to use it anyway. 45 | - Properties `ExactSpelling` and `PreserveSig` of `[DllImport]` attribute are not supported. 46 | - Calling native functions from static constructors generally won't work. Although the rules are more relaxed, you usually shouldn't even attempt to do that in the first place. Note that in C# _[static constructors don't fire on their own](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors#remarks)_. 47 | - Additional threads that execute past `OnApplicationQuit` event are not-very-well handled (usually not something to worry about). 48 | 49 | ## Troubleshooting & advanced usage 50 | - The path in the `DLL path pattern` option cannot be simply set to `{assets}/Plugins/{name}.dll` as it would interfere with Unity's plugin loading - hence the underscores. 51 | - In version `2019.3.x` Unity changed behaviour of building. If you want to use this tool in the built game (although preferably just for development) you should store your plugins in architecture-specific subfolders and update the `DLL path pattern` option accordingly, e.g. `{assets}/Plugins/x86_64/__{name}.dll`. 52 | - The `UnityNativeTool.DllManipulatorScript` script by default has an execution order of -10000 to make it run first. If you have a script that has even lower execution order and that scripts calls a DLL, then you should make sure that `UnityNativeTool.DllManipulatorScript` runs before it, e.g. by further lowering its execution order. 53 | 54 | 55 | ## Performance 56 | 57 | | Configuration | Relative call time | 58 | | --- |:---:| 59 | | Vanilla Unity | 100% | 60 | | Preloaded mode | ~150% | 61 | | Lazy mode | ~190% | 62 | | With thread safety | ~430% | 63 | 64 | ## References 65 | - https://github.com/pardeike/Harmony 66 | - https://stackoverflow.com/a/9507589/7249108 67 | - http://runningdimensions.com/blog/?p=5 68 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8d1b3b29c21705c44a9b6b3700c72715 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UnityNativeTool.sln.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b5ec40a733ee7a4c9e522ac4a557ade 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.mcpiroman.unity-native-tool", 3 | "displayName": "Unity Native Tool", 4 | "version": "8.0.0", 5 | "description": "Tool for unloading native DLLs in the editor. \nhttps://github.com/mcpiroman/UnityNativeTool", 6 | "keywords": [ 7 | "native", 8 | "c++", 9 | "dll" 10 | ], 11 | "category": "Unity", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mcpiroman/UnityNativeTool.git" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 246fd71d40afbc74687dbcf5c273b8a9 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0de865a5ea16c5142993a91313dbea05 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /scripts/Attributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityNativeTool 4 | { 5 | /// 6 | /// Member native functions in types with this attributes will be mocked. This attribute is redundant if "Mock all native functions" option is true. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 9 | public class MockNativeDeclarationsAttribute : Attribute 10 | { 11 | 12 | } 13 | 14 | /// 15 | /// Native functions with this attribute will be mocked. This attribute is redundant if "Mock all native functions" option is true. 16 | /// 17 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 18 | public class MockNativeDeclarationAttribute : Attribute 19 | { 20 | 21 | } 22 | 23 | /// 24 | /// Applied to native function, prevents it from being mocked. 25 | /// Applied to class, prevents all member native functions from being mocked. 26 | /// 27 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 28 | public class DisableMockingAttribute : Attribute 29 | { 30 | 31 | } 32 | 33 | /// 34 | /// Such a method must be static and have one of the following signatures: 35 | /// 36 | /// public static void Func() 37 | /// public static void Func(NativeDll dll) 38 | /// public static void Func(NativeDll dll, int mainThreadId) 39 | /// 40 | /// 41 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] 42 | public class TriggerAttribute : Attribute 43 | { 44 | /// 45 | /// Should the method always be executed on the main thread, to allow use of the Unity API. 46 | /// Note: this means the method is not immediately executed but put in a queue, if it is not triggered from the main thread. 47 | /// 48 | public bool UseMainThreadQueue = false; 49 | } 50 | 51 | /// 52 | /// Methods with this attribute are called directly after a native DLL has been loaded. Native functions can be used within such a method. 53 | /// This is called after UnityPluginLoad.
54 | /// Such method must be and either have no parameters or one parameter of type 55 | /// which indicates the state of the dll being loaded. Please treat this parameter as readonly.
56 | /// Preloaded: only called once all native methods have been loaded. 57 | ///
58 | ///
59 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 60 | public class NativeDllLoadedTriggerAttribute : TriggerAttribute 61 | { 62 | 63 | } 64 | 65 | /// 66 | /// Methods with this attribute are called directly before a native DLL is going to be unloaded. 67 | /// Such method must be and either have no parameters or one parameter of type 68 | /// which indicates the state of the dll being unloaded. Please treat this parameter as readonly. 69 | ///
70 | ///
71 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 72 | public class NativeDllBeforeUnloadTriggerAttribute : TriggerAttribute 73 | { 74 | 75 | } 76 | 77 | /// 78 | /// Methods with this attribute are called directly after a native DLL has been unloaded. 79 | /// Such method must be and either have no parameters or one parameter of type 80 | /// which indicates the state of the dll being unloaded. Please treat this parameter as readonly. 81 | ///
82 | ///
83 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 84 | public class NativeDllAfterUnloadTriggerAttribute : TriggerAttribute 85 | { 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /scripts/Attributes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9bbc6aaa5a6ecef45a82fc6043d21c4b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/Detour.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Following file is essentially copy of Memory.cs file from https://github.com/pardeike/Harmony 3 | // 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using System.Reflection.Emit; 9 | using System.Runtime.CompilerServices; 10 | 11 | namespace UnityNativeTool.Internal 12 | { 13 | /// A low level memory helper 14 | internal static class Detour 15 | { 16 | static readonly HashSet WindowsPlatformIDSet = new HashSet 17 | { 18 | PlatformID.Win32NT, PlatformID.Win32S, PlatformID.Win32Windows, PlatformID.WinCE 19 | }; 20 | 21 | /// Is current environment Windows? 22 | /// True if it is Windows 23 | /// 24 | internal static bool IsWindows => WindowsPlatformIDSet.Contains(Environment.OSVersion.Platform); 25 | 26 | /// Unprotect memory pages 27 | /// The starting memory address 28 | /// The length of memory block 29 | /// 30 | internal static void UnprotectMemory(long memory, ulong size) 31 | { 32 | if (IsWindows) 33 | { 34 | var success = PInvokes_Windows.VirtualProtect(new IntPtr(memory), new UIntPtr(size), PInvokes_Windows.Protection.PAGE_EXECUTE_READWRITE, out var _); 35 | if (!success) 36 | throw new System.ComponentModel.Win32Exception(); 37 | } 38 | } 39 | 40 | internal static void FlushCode(long memory, ulong size) 41 | { 42 | if (IsWindows) 43 | { 44 | var processHandle = PInvokes_Windows.GetCurrentProcess(); 45 | var success = PInvokes_Windows.FlushInstructionCache(processHandle, new IntPtr(memory), new UIntPtr(size)); 46 | if (!success) 47 | throw new System.ComponentModel.Win32Exception(); 48 | } 49 | } 50 | 51 | /// Mark method for no inlining 52 | /// The method to change 53 | unsafe public static void MarkForNoInlining(MethodBase method) 54 | { 55 | // TODO for now, this only works on mono 56 | if (Type.GetType("Mono.Runtime") != null) 57 | { 58 | var iflags = (ushort*)(method.MethodHandle.Value) + 1; 59 | *iflags |= (ushort)MethodImplOptions.NoInlining; 60 | } 61 | } 62 | 63 | /// Detours a method 64 | /// The original method 65 | /// The replacement method 66 | /// An error string 67 | /// 68 | public unsafe static string DetourMethod(MethodBase original, MethodBase replacement) 69 | { 70 | var originalCodeStart = GetMethodStart(original, out var exception); 71 | if (originalCodeStart == 0) 72 | return exception.Message; 73 | var patchCodeStart = GetMethodStart(replacement, out exception); 74 | if (patchCodeStart == 0) 75 | return exception.Message; 76 | 77 | return WriteJump(originalCodeStart, patchCodeStart); 78 | } 79 | 80 | /// Writes a jump to memory 81 | /// The memory address 82 | /// Jump destination 83 | /// An error string 84 | /// 85 | public static string WriteJump(long memory, long destination) 86 | { 87 | UnprotectMemory(memory, IntPtr.Size == sizeof(long) ? 12u : 6u); 88 | 89 | long writtenMemoryAddress; 90 | if (IntPtr.Size == sizeof(long)) 91 | { 92 | if (CompareBytes(memory, new byte[] { 0xe9 })) 93 | { 94 | var offset = ReadInt(memory + 1); 95 | memory += 5 + offset; 96 | UnprotectMemory(memory, 12); 97 | } 98 | 99 | writtenMemoryAddress = memory; 100 | memory = WriteBytes(memory, new byte[] { 0x48, 0xB8 }); 101 | memory = WriteLong(memory, destination); 102 | memory = WriteBytes(memory, new byte[] { 0xFF, 0xE0 }); 103 | } 104 | else 105 | { 106 | writtenMemoryAddress = memory; 107 | memory = WriteByte(memory, 0x68); 108 | memory = WriteInt(memory, (int)destination); 109 | memory = WriteByte(memory, 0xc3); 110 | } 111 | 112 | FlushCode(writtenMemoryAddress, IntPtr.Size == sizeof(long) ? 12u : 6u); 113 | return null; 114 | } 115 | 116 | static RuntimeMethodHandle GetRuntimeMethodHandle(MethodBase method) 117 | { 118 | if (method is DynamicMethod) 119 | { 120 | var noninternalInstance = BindingFlags.NonPublic | BindingFlags.Instance; 121 | 122 | // DynamicMethod actually generates its m_methodHandle on-the-fly and therefore 123 | // we should call GetMethodDescriptor to force it to be created 124 | // 125 | var m_GetMethodDescriptor = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", noninternalInstance); 126 | if (m_GetMethodDescriptor != null) 127 | return (RuntimeMethodHandle)m_GetMethodDescriptor.Invoke(method, new object[0]); 128 | 129 | // .Net Core 130 | var f_m_method = typeof(DynamicMethod).GetField("m_method", noninternalInstance); 131 | if (f_m_method != null) 132 | return (RuntimeMethodHandle)f_m_method.GetValue(method); 133 | 134 | // Mono 135 | var f_mhandle = typeof(DynamicMethod).GetField("mhandle", noninternalInstance); 136 | return (RuntimeMethodHandle)f_mhandle.GetValue(method); 137 | } 138 | 139 | return method.MethodHandle; 140 | } 141 | 142 | /// Gets the start of a method in memory 143 | /// The method 144 | /// [out] Details of the exception 145 | /// The method start address 146 | /// 147 | public static long GetMethodStart(MethodBase method, out Exception exception) 148 | { 149 | // required in .NET Core so that the method is JITed and the method start does not change 150 | // 151 | var handle = GetRuntimeMethodHandle(method); 152 | try 153 | { 154 | RuntimeHelpers.PrepareMethod(handle); 155 | } 156 | catch (Exception) 157 | { 158 | } 159 | 160 | try 161 | { 162 | exception = null; 163 | return handle.GetFunctionPointer().ToInt64(); 164 | } 165 | catch (Exception ex) 166 | { 167 | exception = ex; 168 | return 0; 169 | } 170 | } 171 | 172 | /// Compare bytes 173 | /// The memory address 174 | /// The bytes to compare to 175 | /// True if memory address contains the bytes 176 | /// 177 | internal static unsafe bool CompareBytes(long memory, byte[] values) 178 | { 179 | var p = (byte*)memory; 180 | foreach (var value in values) 181 | { 182 | if (value != *p) return false; 183 | p++; 184 | } 185 | return true; 186 | } 187 | 188 | /// Reads a byte 189 | /// The memory address 190 | /// The byte 191 | /// 192 | internal static unsafe byte ReadByte(long memory) 193 | { 194 | var p = (byte*)memory; 195 | return *p; 196 | } 197 | 198 | /// Reads an int 199 | /// The memory address 200 | /// The int 201 | /// 202 | internal static unsafe int ReadInt(long memory) 203 | { 204 | var p = (int*)memory; 205 | return *p; 206 | } 207 | 208 | /// Reads a long 209 | /// The memory address 210 | /// The long 211 | /// 212 | internal static unsafe long ReadLong(long memory) 213 | { 214 | var p = (long*)memory; 215 | return *p; 216 | } 217 | 218 | /// Writes a byte 219 | /// The memory address 220 | /// The byte 221 | /// Advanced memory address 222 | /// 223 | internal static unsafe long WriteByte(long memory, byte value) 224 | { 225 | var p = (byte*)memory; 226 | *p = value; 227 | return memory + sizeof(byte); 228 | } 229 | 230 | /// Writes some bytes 231 | /// The memory address 232 | /// The bytes to write 233 | /// Advanced memory address 234 | /// 235 | internal static unsafe long WriteBytes(long memory, byte[] values) 236 | { 237 | foreach (var value in values) 238 | memory = WriteByte(memory, value); 239 | return memory; 240 | } 241 | 242 | /// Writes an int 243 | /// The memory address 244 | /// The int 245 | /// Advanced memory address 246 | /// 247 | internal static unsafe long WriteInt(long memory, int value) 248 | { 249 | var p = (int*)memory; 250 | *p = value; 251 | return memory + sizeof(int); 252 | } 253 | 254 | /// Writes a long 255 | /// The memory address 256 | /// The long 257 | /// Advanced memory address 258 | /// 259 | internal static unsafe long WriteLong(long memory, long value) 260 | { 261 | var p = (long*)memory; 262 | *p = value; 263 | return memory + sizeof(long); 264 | } 265 | } 266 | } -------------------------------------------------------------------------------- /scripts/Detour.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 97f036a7a52b17248b49530ecf5a9c79 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/DllLoadException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace UnityNativeTool 5 | { 6 | public class NativeDllException : Exception 7 | { 8 | public NativeDllException() 9 | { 10 | } 11 | 12 | public NativeDllException(string message) : base(message) 13 | { 14 | } 15 | 16 | public NativeDllException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | 20 | protected NativeDllException(SerializationInfo info, StreamingContext context) : base(info, context) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/DllLoadException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2fdbe6a397ce4d499cfe2d89a93cf38 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/DllManipulator.ReflectionData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | using System.Threading; 9 | 10 | namespace UnityNativeTool.Internal 11 | { 12 | public partial class DllManipulator 13 | { 14 | private static readonly Type[] DELEGATE_CTOR_PARAMETERS = { typeof(object), typeof(IntPtr) }; 15 | private static readonly Type[] MARSHAL_AS_ATTRIBUTE_CTOR_PARAMETERS = { typeof(UnmanagedType) }; 16 | 17 | 18 | private static readonly Lazy Field_MockedNativeFunctions = new Lazy( 19 | () => typeof(DllManipulator).GetField(nameof(_mockedNativeFunctions), BindingFlags.NonPublic | BindingFlags.Static)); 20 | 21 | private static readonly Lazy Field_NativeFunctionDelegate = new Lazy( 22 | () => typeof(NativeFunction).GetField(nameof(NativeFunction.@delegate), BindingFlags.Public | BindingFlags.Instance)); 23 | 24 | private static readonly Lazy Method_LoadTargetFunction = new Lazy( 25 | () => typeof(DllManipulator).GetMethod(nameof(LoadTargetFunction), BindingFlags.NonPublic | BindingFlags.Static)); 26 | 27 | private static readonly Lazy Field_NativeFunctionLoadLock = new Lazy( 28 | () => typeof(DllManipulator).GetField(nameof(_nativeFunctionLoadLock), BindingFlags.NonPublic | BindingFlags.Static)); 29 | 30 | private static readonly Lazy Method_WriteNativeCrashLog = new Lazy( 31 | () => typeof(DllManipulator).GetMethod(nameof(WriteNativeCrashLog), BindingFlags.NonPublic | BindingFlags.Static)); 32 | 33 | 34 | private static readonly Lazy Method_List_NativeFunction_get_Item = new Lazy( 35 | () => typeof(List).GetMethod("get_Item", BindingFlags.Public | BindingFlags.Instance)); 36 | 37 | /// 38 | /// 39 | /// 40 | private static readonly Lazy Method_Rwls_EnterReadLock = new Lazy( 41 | () => typeof(ReaderWriterLockSlim).GetMethod(nameof(ReaderWriterLockSlim.EnterReadLock), BindingFlags.Public | BindingFlags.Instance)); 42 | 43 | /// 44 | /// 45 | /// 46 | private static readonly Lazy Method_Rwls_ExitReadLock = new Lazy( 47 | () => typeof(ReaderWriterLockSlim).GetMethod(nameof(ReaderWriterLockSlim.ExitReadLock), BindingFlags.Public | BindingFlags.Instance)); 48 | 49 | /// 50 | /// 51 | /// 52 | private static readonly Lazy Ctor_Ufp = new Lazy( 53 | () => typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new[] { typeof(CallingConvention) } )); 54 | 55 | /// 56 | /// 57 | /// 58 | private static readonly Lazy Field_Ufpa_BestFitMapping = new Lazy( 59 | () => typeof(UnmanagedFunctionPointerAttribute).GetField(nameof(UnmanagedFunctionPointerAttribute.BestFitMapping), BindingFlags.Public | BindingFlags.Instance)); 60 | 61 | /// 62 | /// 63 | /// 64 | private static readonly Lazy Field_Ufpa_CharSet = new Lazy( 65 | () => typeof(UnmanagedFunctionPointerAttribute).GetField(nameof(UnmanagedFunctionPointerAttribute.CharSet), BindingFlags.Public | BindingFlags.Instance)); 66 | 67 | /// 68 | /// 69 | /// 70 | private static readonly Lazy Field_Ufpa_SetLastError = new Lazy( 71 | () => typeof(UnmanagedFunctionPointerAttribute).GetField(nameof(UnmanagedFunctionPointerAttribute.SetLastError), BindingFlags.Public | BindingFlags.Instance)); 72 | 73 | /// 74 | /// 75 | /// 76 | private static readonly Lazy Field_Ufpa_ThrowOnUnmappableChar = new Lazy( 77 | () => typeof(UnmanagedFunctionPointerAttribute).GetField(nameof(UnmanagedFunctionPointerAttribute.ThrowOnUnmappableChar), BindingFlags.Public | BindingFlags.Instance)); 78 | 79 | /// 80 | /// 81 | /// 82 | private static readonly Lazy Ctor_MarshalAsAttribute = new Lazy( 83 | () => typeof(MarshalAsAttribute).GetConstructor(MARSHAL_AS_ATTRIBUTE_CTOR_PARAMETERS)); 84 | 85 | #region Mono specific 86 | 87 | /// 88 | /// DynamicMethod.CreateDynMethod() 89 | /// 90 | private static readonly Lazy Method_DynamicMethod_CreateDynMethod = new Lazy( 91 | () => typeof(DynamicMethod).GetMethod("CreateDynMethod", BindingFlags.NonPublic | BindingFlags.Instance)); 92 | 93 | #endregion 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /scripts/DllManipulator.ReflectionData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 107ce30347a400f45ae4b92c36560c28 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/DllManipulator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using System.IO; 9 | using UnityEngine; 10 | #if UNITY_EDITOR 11 | using UnityEditor; 12 | #endif 13 | 14 | namespace UnityNativeTool.Internal 15 | { 16 | //Note "DLL" used in this code refers to Dynamically Loaded Library, and not to the .dll file extension on Windows. 17 | public partial class DllManipulator 18 | { 19 | public const string DLL_PATH_PATTERN_DLL_NAME_MACRO = "{name}"; 20 | public const string DLL_PATH_PATTERN_ASSETS_MACRO = "{assets}"; 21 | public const string DLL_PATH_PATTERN_PROJECT_MACRO = "{proj}"; 22 | private const string CRASH_FILE_NAME_PREFIX = "unityNativeCrash_"; 23 | public static readonly string[] DEFAULT_ASSEMBLY_NAMES = {"Assembly-CSharp" 24 | #if UNITY_EDITOR 25 | , "Assembly-CSharp-Editor" 26 | #endif 27 | }; 28 | public static readonly string[] INTERNAL_ASSEMBLY_NAMES = {"mcpiroman.UnityNativeTool" 29 | #if UNITY_EDITOR 30 | , "mcpiroman.UnityNativeTool.Editor" 31 | #endif 32 | }; 33 | public static readonly string[] IGNORED_ASSEMBLY_PREFIXES = { "UnityEngine.", "UnityEditor.", "Unity.", "com.unity.", "Mono." , "nunit."}; 34 | 35 | 36 | public static DllManipulatorOptions Options { get; private set; } 37 | private static int _unityMainThreadId; 38 | private static string _assetsPath; 39 | private static readonly LinkedList _antiGcRefHolder = new LinkedList(); 40 | private static readonly ReaderWriterLockSlim _nativeFunctionLoadLock = new ReaderWriterLockSlim(); 41 | private static ModuleBuilder _customDelegateTypesModule = null; 42 | private static readonly Dictionary _dlls = new Dictionary(); 43 | private static readonly Dictionary _nativeFunctionMocks = new Dictionary(); 44 | private static readonly Dictionary _delegateTypesForNativeFunctionSignatures = new Dictionary(); 45 | private static List _mockedNativeFunctions = new List(); 46 | private static int _createdDelegateTypes = 0; 47 | private static int _lastNativeCallIndex = 0; //Use with synchronization 48 | 49 | private static List> _customLoadedTriggers = null; //List of callbacks to run, whether to run them on the main thread. 50 | private static List> _customBeforeUnloadTriggers = null; 51 | private static List> _customAfterUnloadTriggers = null; 52 | 53 | /// 54 | /// Initialization. 55 | /// Finds and mocks relevant native function declarations. 56 | /// If option is specified, loads all DLLs specified by these functions. 57 | /// Options have to be configured before calling this method. 58 | /// 59 | internal static void Initialize(DllManipulatorOptions options, int unityMainThreadId, string assetsPath) 60 | { 61 | // Make a deep copy of the options so we can edit them in DllManipulatorScript independently 62 | Options = new DllManipulatorOptions(); 63 | options.CloneTo(Options); 64 | _unityMainThreadId = unityMainThreadId; 65 | _assetsPath = assetsPath; 66 | 67 | LowLevelPluginManager.ResetStubPlugin(); 68 | 69 | IEnumerable assemblyPathsTemp = Options.assemblyNames; 70 | if (!assemblyPathsTemp.Any()) 71 | assemblyPathsTemp = DEFAULT_ASSEMBLY_NAMES; 72 | 73 | assemblyPathsTemp = assemblyPathsTemp.Concat(INTERNAL_ASSEMBLY_NAMES); 74 | 75 | var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); 76 | var assemblies = allAssemblies.Where(a => !a.IsDynamic && assemblyPathsTemp.Any(p => p == Path.GetFileNameWithoutExtension(a.Location))).ToArray(); 77 | var missingAssemblies = assemblyPathsTemp.Except(assemblies.Select(a => Path.GetFileNameWithoutExtension(a.Location))); 78 | foreach (var assembly in missingAssemblies.Except(DEFAULT_ASSEMBLY_NAMES)) 79 | { 80 | Debug.LogError($"Could not find assembly: {assembly}"); 81 | } 82 | 83 | foreach (var assembly in assemblies) 84 | { 85 | var allTypes = assembly.GetTypes(); 86 | foreach (var type in allTypes) 87 | { 88 | foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) 89 | { 90 | if (method.IsDefined(typeof(DllImportAttribute))) 91 | { 92 | if (method.IsDefined(typeof(DisableMockingAttribute))) 93 | continue; 94 | 95 | if (method.DeclaringType.IsDefined(typeof(DisableMockingAttribute))) 96 | continue; 97 | 98 | if (Options.mockAllNativeFunctions || method.IsDefined(typeof(MockNativeDeclarationAttribute)) || method.DeclaringType.IsDefined(typeof(MockNativeDeclarationsAttribute))) 99 | MockNativeFunction(method); 100 | } 101 | else 102 | { 103 | if (method.IsDefined(typeof(NativeDllLoadedTriggerAttribute))) 104 | RegisterTriggerMethod(method, ref _customLoadedTriggers, method.GetCustomAttribute()); 105 | 106 | if (method.IsDefined(typeof(NativeDllBeforeUnloadTriggerAttribute))) 107 | RegisterTriggerMethod(method, ref _customBeforeUnloadTriggers, method.GetCustomAttribute()); 108 | 109 | if (method.IsDefined(typeof(NativeDllAfterUnloadTriggerAttribute))) 110 | RegisterTriggerMethod(method, ref _customAfterUnloadTriggers, method.GetCustomAttribute()); 111 | } 112 | } 113 | } 114 | } 115 | 116 | if (Options.loadingMode == DllLoadingMode.Preload) 117 | LoadAll(); 118 | } 119 | 120 | /// 121 | /// Will unload/forget all dll's and reset the state 122 | /// 123 | public static void Reset() 124 | { 125 | UnloadAll(); 126 | ForgetAllDlls(); 127 | ClearCrashLogs(); 128 | 129 | _customLoadedTriggers?.Clear(); 130 | _customAfterUnloadTriggers?.Clear(); 131 | _customBeforeUnloadTriggers?.Clear(); 132 | 133 | Options = null; 134 | } 135 | 136 | private static void RegisterTriggerMethod(MethodInfo method, ref List> triggersList, TriggerAttribute attribute) 137 | { 138 | var parameters = method.GetParameters(); 139 | if (parameters.Length == 0 || parameters.Length == 1 && parameters[0].ParameterType == typeof(NativeDll) 140 | || parameters.Length == 2 && parameters[0].ParameterType == typeof(NativeDll) && parameters[1].ParameterType == typeof(int)) 141 | { 142 | if (triggersList == null) 143 | triggersList = new List>(); 144 | triggersList.Add(new Tuple(method, attribute.UseMainThreadQueue)); 145 | } 146 | else 147 | { 148 | Debug.LogError($"Trigger method must either take no parameters, one parameter of type {nameof(NativeDll)} or one of type {nameof(NativeDll)} and one int. " + 149 | $"See the TriggerAttribute for more details. Violation on method {method.Name} in {method.DeclaringType.FullName}"); 150 | } 151 | } 152 | 153 | /// 154 | /// Loads all DLLs and functions for mocked methods 155 | /// 156 | public static void LoadAll() 157 | { 158 | _nativeFunctionLoadLock.EnterWriteLock(); //Locking with no thread safety option is not required but is ok (this function isn't performance critical) 159 | try 160 | { 161 | foreach (var dll in _dlls.Values) 162 | { 163 | if (dll.handle == IntPtr.Zero) 164 | { 165 | foreach (var nativeFunction in dll.functions) 166 | { 167 | LoadTargetFunction(nativeFunction, false); 168 | } 169 | 170 | // Notify that the dll and its functions have been loaded in preload mode 171 | // This here allows use of native functions in the triggers 172 | if(Options.loadingMode == DllLoadingMode.Preload) 173 | InvokeCustomTriggers(_customLoadedTriggers, dll); 174 | } 175 | } 176 | } 177 | finally 178 | { 179 | _nativeFunctionLoadLock.ExitWriteLock(); 180 | } 181 | } 182 | 183 | /// 184 | /// Unloads all DLLs and functions currently loaded 185 | /// 186 | public static void UnloadAll() 187 | { 188 | _nativeFunctionLoadLock.EnterWriteLock(); //Locking with no thread safety option is not required but is ok (this function isn't performance critical) 189 | try 190 | { 191 | foreach (var dll in _dlls.Values) 192 | { 193 | if (dll.handle != IntPtr.Zero) 194 | { 195 | LowLevelPluginManager.OnBeforeDllUnload(dll); 196 | InvokeCustomTriggers(_customBeforeUnloadTriggers, dll); 197 | 198 | bool success = SysUnloadDll(dll.handle); 199 | if (!success) 200 | Debug.LogWarning($"Error while unloading DLL \"{dll.name}\" at path \"{dll.path}\""); 201 | 202 | dll.ResetAsUnloaded(); 203 | InvokeCustomTriggers(_customAfterUnloadTriggers, dll); 204 | } 205 | } 206 | } 207 | finally 208 | { 209 | _nativeFunctionLoadLock.ExitWriteLock(); 210 | } 211 | } 212 | 213 | internal static void ForgetAllDlls() 214 | { 215 | _dlls.Clear(); 216 | _mockedNativeFunctions.Clear(); 217 | } 218 | 219 | internal static void ClearCrashLogs() 220 | { 221 | if (Options.enableCrashLogs) 222 | { 223 | if (Options.crashLogsDir == null) 224 | return; 225 | var dir = ApplyDirectoryPathMacros(Options.crashLogsDir); 226 | foreach (var filePath in Directory.GetFiles(dir)) 227 | { 228 | if (Path.GetFileName(filePath).StartsWith(CRASH_FILE_NAME_PREFIX)) 229 | File.Delete(filePath); 230 | } 231 | } 232 | } 233 | 234 | /// 235 | /// Creates information snapshot of all known DLLs. 236 | /// 237 | public static IList GetUsedDllsInfos() 238 | { 239 | var dllInfos = new NativeDllInfo[_dlls.Count]; 240 | int i = 0; 241 | foreach (var dll in _dlls.Values) 242 | { 243 | var loadedFunctions = dll.functions.Select(f => f.identity.symbol).ToList(); 244 | dllInfos[i] = new NativeDllInfo(dll.name, dll.path, dll.handle != IntPtr.Zero, dll.loadingError, dll.symbolError, loadedFunctions); 245 | i++; 246 | } 247 | 248 | return dllInfos; 249 | } 250 | 251 | private static string ApplyDirectoryPathMacros(string path) 252 | { 253 | return path 254 | .Replace(DLL_PATH_PATTERN_ASSETS_MACRO, _assetsPath) 255 | .Replace(DLL_PATH_PATTERN_PROJECT_MACRO, _assetsPath + "/../"); 256 | } 257 | 258 | private static void MockNativeFunction(MethodInfo function) 259 | { 260 | var methodMock = GetNativeFunctionMockMethod(function); 261 | Detour.MarkForNoInlining(function); 262 | PrepareDynamicMethod(methodMock); 263 | Detour.DetourMethod(function, methodMock); 264 | } 265 | 266 | /// 267 | /// Creates and registers new DynamicMethod that mocks and itself calls dynamically loaded function from DLL. 268 | /// 269 | private static DynamicMethod GetNativeFunctionMockMethod(MethodInfo nativeMethod) 270 | { 271 | if (!_nativeFunctionMocks.TryGetValue(nativeMethod, out var mockedDynamicMethod)) 272 | { 273 | var dllImportAttr = nativeMethod.GetCustomAttribute(); 274 | var dllName = dllImportAttr.Value; 275 | string dllPath; 276 | var nativeFunctionSymbol = dllImportAttr.EntryPoint; 277 | 278 | if (_dlls.TryGetValue(dllName, out var dll)) 279 | { 280 | dllPath = dll.path; 281 | } 282 | else 283 | { 284 | dllPath = ApplyDirectoryPathMacros(Options.dllPathPattern).Replace(DLL_PATH_PATTERN_DLL_NAME_MACRO, dllName); 285 | dll = new NativeDll(dllName, dllPath); 286 | _dlls.Add(dllName, dll); 287 | } 288 | 289 | var nativeFunction = new NativeFunction(nativeFunctionSymbol, dll); 290 | dll.functions.Add(nativeFunction); 291 | var nativeFunctionIndex = _mockedNativeFunctions.Count; 292 | _mockedNativeFunctions.Add(nativeFunction); 293 | 294 | var parameters = nativeMethod.GetParameters(); 295 | var parameterTypes = parameters.Select(x => x.ParameterType).ToArray(); 296 | var nativeMethodSignature = new NativeFunctionSignature(nativeMethod, dllImportAttr.CallingConvention, 297 | dllImportAttr.BestFitMapping, dllImportAttr.CharSet, dllImportAttr.SetLastError, dllImportAttr.ThrowOnUnmappableChar); 298 | if (!_delegateTypesForNativeFunctionSignatures.TryGetValue(nativeMethodSignature, out nativeFunction.delegateType)) 299 | { 300 | nativeFunction.delegateType = CreateDelegateTypeForNativeFunctionSignature(nativeMethodSignature, nativeMethod.Name); 301 | _delegateTypesForNativeFunctionSignatures.Add(nativeMethodSignature, nativeFunction.delegateType); 302 | } 303 | var targetDelegateInvokeMethod = nativeFunction.delegateType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); 304 | 305 | mockedDynamicMethod = new DynamicMethod(dllName + ":::" + nativeFunctionSymbol, nativeMethod.ReturnType, parameterTypes, typeof(DllManipulator)); 306 | mockedDynamicMethod.DefineParameter(0, nativeMethod.ReturnParameter.Attributes, null); 307 | for (int i = 0; i < parameters.Length; i++) 308 | { 309 | mockedDynamicMethod.DefineParameter(i + 1, parameters[i].Attributes, null); 310 | } 311 | 312 | GenerateNativeFunctionMockBody(mockedDynamicMethod.GetILGenerator(), parameters, targetDelegateInvokeMethod, nativeFunctionIndex); 313 | 314 | _antiGcRefHolder.AddLast(nativeFunction); 315 | _antiGcRefHolder.AddLast(mockedDynamicMethod); 316 | } 317 | 318 | return mockedDynamicMethod; 319 | } 320 | 321 | private static void GenerateNativeFunctionMockBody(ILGenerator il, ParameterInfo[] parameters, MethodInfo delegateInvokeMethod, int nativeFunctionIndex) 322 | { 323 | var returnsVoid = delegateInvokeMethod.ReturnType == typeof(void); 324 | 325 | if (Options.threadSafe) 326 | { 327 | if (!returnsVoid) 328 | il.DeclareLocal(delegateInvokeMethod.ReturnType); //Local 0: return value 329 | 330 | il.Emit(OpCodes.Ldsfld, Field_NativeFunctionLoadLock.Value); 331 | il.Emit(OpCodes.Call, Method_Rwls_EnterReadLock.Value); 332 | il.BeginExceptionBlock(); //Start lock clause: lock, try { ... }, finally { release } 333 | } 334 | 335 | il.Emit(OpCodes.Ldsfld, Field_MockedNativeFunctions.Value); //Load NativeFunction object 336 | il.EmitFastI4Load(nativeFunctionIndex); 337 | il.Emit(OpCodes.Call, Method_List_NativeFunction_get_Item.Value); 338 | 339 | if (Options.loadingMode == DllLoadingMode.Lazy) //If lazy mode, load the function. Otherwise we assume it's already loaded 340 | { 341 | if (Options.threadSafe) 342 | throw new InvalidOperationException("Thread safety with Lazy mode is not supported"); 343 | 344 | il.Emit(OpCodes.Dup); 345 | il.Emit(OpCodes.Ldc_I4_0); //ignoreLoadErrors -> false 346 | il.Emit(OpCodes.Call, Method_LoadTargetFunction.Value); 347 | } 348 | 349 | if (Options.enableCrashLogs) //Log function invocation 350 | { 351 | il.EmitFastI4Load(parameters.Length); //Generate array of arguments 352 | il.Emit(OpCodes.Newarr, typeof(object)); 353 | for (int i = 0; i < parameters.Length; i++) 354 | { 355 | il.Emit(OpCodes.Dup); 356 | il.EmitFastI4Load(i); 357 | il.EmitFastArgLoad(i); 358 | if (parameters[i].ParameterType.IsValueType) 359 | il.Emit(OpCodes.Box, parameters[i].ParameterType); 360 | il.Emit(OpCodes.Stelem_Ref); 361 | } 362 | il.Emit(OpCodes.Call, Method_WriteNativeCrashLog.Value); 363 | 364 | il.Emit(OpCodes.Ldsfld, Field_MockedNativeFunctions.Value); //Once again load native function, previous one was consumed by log method 365 | il.EmitFastI4Load(nativeFunctionIndex); 366 | il.Emit(OpCodes.Call, Method_List_NativeFunction_get_Item.Value); 367 | } 368 | 369 | il.Emit(OpCodes.Ldfld, Field_NativeFunctionDelegate.Value); 370 | //Seems like cast to concrete delegate type is not required here 371 | for (int i = 0; i < parameters.Length; i++) 372 | { 373 | il.EmitFastArgLoad(i); 374 | } 375 | il.Emit(OpCodes.Callvirt, delegateInvokeMethod); //Call native function 376 | 377 | if (Options.threadSafe) //End lock clause. Lock is being held during execution of native function, which is necessary since the DLL could be otherwise unloaded between acquire of delegate and call to delegate 378 | { 379 | var retLabel = il.DefineLabel(); 380 | if (!returnsVoid) 381 | il.Emit(OpCodes.Stloc_0); 382 | il.Emit(OpCodes.Leave_S, retLabel); 383 | il.BeginFinallyBlock(); 384 | il.Emit(OpCodes.Ldsfld, Field_NativeFunctionLoadLock.Value); 385 | il.Emit(OpCodes.Call, Method_Rwls_ExitReadLock.Value); 386 | il.EndExceptionBlock(); 387 | il.MarkLabel(retLabel); 388 | if (!returnsVoid) 389 | il.Emit(OpCodes.Ldloc_0); 390 | } 391 | il.Emit(OpCodes.Ret); 392 | } 393 | 394 | /// 395 | /// Prepares to be injected (aka. patched) into other method 396 | /// 397 | private static void PrepareDynamicMethod(DynamicMethod method) 398 | { 399 | // 400 | // From https://github.com/pardeike/Harmony 401 | // 402 | 403 | if (Method_DynamicMethod_CreateDynMethod.Value != null) 404 | { 405 | var h_CreateDynMethod = MethodInvoker.GetHandler(Method_DynamicMethod_CreateDynMethod.Value); 406 | h_CreateDynMethod(method, new object[0]); 407 | } 408 | else 409 | { 410 | throw new Exception("DynamicMethod.CreateDynMethod() not found"); 411 | } 412 | } 413 | 414 | private static Type CreateDelegateTypeForNativeFunctionSignature(NativeFunctionSignature functionSignature, string functionName) 415 | { 416 | if (_customDelegateTypesModule == null) 417 | { 418 | var aName = new AssemblyName("HelperRuntimeDelegates"); 419 | var delegateTypesAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave); 420 | _customDelegateTypesModule = delegateTypesAssembly.DefineDynamicModule(aName.Name, aName.Name + ".dll"); 421 | } 422 | 423 | var delBuilder = _customDelegateTypesModule.DefineType("HelperNativeDelegate" + _createdDelegateTypes.ToString(), 424 | TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AnsiClass | TypeAttributes.AutoClass, typeof(MulticastDelegate)); 425 | 426 | //ufp = UnmanagedFunctionPointer 427 | object[] ufpAttrCtorArgValues = { functionSignature.callingConvention }; 428 | FieldInfo[] ufpAttrNamedFields = { Field_Ufpa_BestFitMapping.Value, Field_Ufpa_CharSet.Value, Field_Ufpa_SetLastError.Value, Field_Ufpa_ThrowOnUnmappableChar.Value }; 429 | object[] ufpAttrFieldValues = { functionSignature.bestFitMapping, functionSignature.charSet, functionSignature.setLastError, functionSignature.throwOnUnmappableChar }; 430 | var ufpAttrBuilder = new CustomAttributeBuilder(Ctor_Ufp.Value, ufpAttrCtorArgValues, ufpAttrNamedFields, ufpAttrFieldValues); 431 | delBuilder.SetCustomAttribute(ufpAttrBuilder); 432 | 433 | var ctorBuilder = delBuilder.DefineConstructor(MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, 434 | CallingConventions.Standard, DELEGATE_CTOR_PARAMETERS); 435 | ctorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); 436 | 437 | var invokeBuilder = delBuilder.DefineMethod("Invoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, 438 | CallingConventions.Standard | CallingConventions.HasThis, functionSignature.returnParameter.type, functionSignature.parameters.Select(p => p.type).ToArray()); 439 | invokeBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); 440 | var invokeReturnParam = invokeBuilder.DefineParameter(0, functionSignature.returnParameter.parameterAttributes, null); 441 | foreach (var attr in functionSignature.returnParameter.customAttributes) 442 | { 443 | var attrBuilder = CreateAttributeBuilderFromAttributeInstance(attr, functionName); 444 | if(attrBuilder != null) 445 | invokeReturnParam.SetCustomAttribute(attrBuilder); 446 | } 447 | for (int i = 0; i < functionSignature.parameters.Length; i++) 448 | { 449 | var param = functionSignature.parameters[i]; 450 | var paramBuilder = invokeBuilder.DefineParameter(i + 1, param.parameterAttributes, null); 451 | foreach(var attr in param.customAttributes) 452 | { 453 | var attrBuilder = CreateAttributeBuilderFromAttributeInstance(attr, functionName); 454 | if (attrBuilder != null) 455 | paramBuilder.SetCustomAttribute(attrBuilder); 456 | } 457 | } 458 | 459 | _createdDelegateTypes++; 460 | return delBuilder.CreateType(); 461 | } 462 | 463 | private static CustomAttributeBuilder CreateAttributeBuilderFromAttributeInstance(Attribute attribute, string nativeFunctionName) 464 | { 465 | var attrType = attribute.GetType(); 466 | switch (attribute) 467 | { 468 | case MarshalAsAttribute marshalAsAttribute: 469 | { 470 | if(marshalAsAttribute.Value == UnmanagedType.LPArray) // Used to bypass Mono bug, see https://github.com/mono/mono/issues/16570 471 | throw new Exception("UnmanagedType.LPArray in [MarshalAs] attribute is not supported. See Limitations section."); 472 | 473 | object[] ctorArgs = { marshalAsAttribute.Value }; 474 | 475 | var fields = attrType.GetFields(BindingFlags.Public | BindingFlags.Instance) 476 | .Where(f => f.FieldType.IsValueType).ToArray(); // Used to bypass Mono bug, see https://github.com/mono/mono/issues/12747 477 | var fieldValues = fields.Select(f => f.GetValue(attribute)).ToArray(); 478 | 479 | //MarshalAsAttribute has no properties other than Value, which is passed in constructor, hence empty properties array 480 | return new CustomAttributeBuilder(Ctor_MarshalAsAttribute.Value, ctorArgs, Array.Empty(), 481 | Array.Empty(), fields, fieldValues); 482 | } 483 | case InAttribute _: 484 | case OutAttribute _: 485 | case OptionalAttribute _: 486 | { 487 | var ctor = attrType.GetConstructor(Type.EmptyTypes); 488 | return new CustomAttributeBuilder(ctor, Array.Empty(), Array.Empty(), Array.Empty(), 489 | Array.Empty(), Array.Empty()); 490 | } 491 | default: 492 | { 493 | Debug.LogWarning($"Skipping copy of attribute [{attrType.Name}] in function {nativeFunctionName} as it is not supported. However, if it is desirable to include it, adding such support should be easy. See the method that throws this exception."); 494 | return null; 495 | } 496 | } 497 | } 498 | 499 | /// 500 | /// Loads DLL and function delegate of if not yet loaded. 501 | /// To achieve thread safety calls to this method must be synchronized. 502 | /// Note: This method is being called by dynamically generated code. Be careful when changing its signature. 503 | /// 504 | internal static void LoadTargetFunction(NativeFunction nativeFunction, bool ignoreLoadError) 505 | { 506 | var dll = nativeFunction.containingDll; 507 | if (dll.handle == IntPtr.Zero) 508 | { 509 | dll.handle = SysLoadDll(dll.path); 510 | if (dll.handle == IntPtr.Zero) 511 | { 512 | if (!ignoreLoadError) 513 | { 514 | dll.loadingError = true; 515 | #if UNITY_EDITOR 516 | DispatchOnMainThread(() => { EditorApplication.isPaused = true; }); 517 | #endif 518 | throw new NativeDllException($"Could not load DLL \"{dll.name}\" at path \"{dll.path}\"."); 519 | } 520 | 521 | return; 522 | } 523 | else 524 | { 525 | dll.loadingError = false; 526 | LowLevelPluginManager.OnDllLoaded(dll); 527 | 528 | // Call the custom triggers once UnityPluginLoad has been called 529 | // For Lazy mode call the triggers immediately, preload waits until all functions are loaded (in LoadAll) 530 | if(Options.loadingMode == DllLoadingMode.Lazy) 531 | InvokeCustomTriggers(_customLoadedTriggers, dll); 532 | } 533 | } 534 | 535 | if (nativeFunction.@delegate == null) 536 | { 537 | IntPtr funcPtr = SysGetDllProcAddress(dll.handle, nativeFunction.identity.symbol); 538 | if (funcPtr == IntPtr.Zero) 539 | { 540 | if (!ignoreLoadError) 541 | { 542 | dll.symbolError = true; 543 | #if UNITY_EDITOR 544 | DispatchOnMainThread(() => { EditorApplication.isPaused = true; }); 545 | #endif 546 | throw new NativeDllException($"Could not get address of symbol \"{nativeFunction.identity.symbol}\" in DLL \"{dll.name}\" at path \"{dll.path}\"."); 547 | } 548 | 549 | return; 550 | } 551 | else 552 | { 553 | dll.symbolError = false; 554 | } 555 | 556 | nativeFunction.@delegate = Marshal.GetDelegateForFunctionPointer(funcPtr, nativeFunction.delegateType); 557 | } 558 | } 559 | 560 | private static void InvokeCustomTriggers(List> triggers, NativeDll dll) 561 | { 562 | if (triggers == null) 563 | return; 564 | 565 | foreach(var (methodInfo, useMainThreadQueue) in triggers) 566 | { 567 | object[] args; 568 | 569 | // Determine args for method 570 | if (methodInfo.GetParameters().Length == 2) 571 | args = new object[] { dll, _unityMainThreadId }; 572 | else if (methodInfo.GetParameters().Length == 1) 573 | args = new object[] { dll }; 574 | else 575 | args = Array.Empty(); 576 | 577 | // Execute now or queue to the main thread 578 | if (useMainThreadQueue && Thread.CurrentThread.ManagedThreadId != _unityMainThreadId) 579 | DllManipulatorScript.MainThreadTriggerQueue.Enqueue(() => methodInfo.Invoke(null, args)); 580 | else 581 | methodInfo.Invoke(null, args); 582 | } 583 | } 584 | 585 | /// 586 | /// Ensure the action is executed on the main thread. Executes immediately if on the main thread already, 587 | /// otherwise the action is added to a queue 588 | /// 589 | private static void DispatchOnMainThread(Action action) 590 | { 591 | if(Thread.CurrentThread.ManagedThreadId == _unityMainThreadId) 592 | action(); 593 | else 594 | DllManipulatorScript.MainThreadTriggerQueue.Enqueue(action); 595 | } 596 | 597 | /// 598 | /// Logs native function's call to file. If that file exists, it is overwritten. One file is maintained for each thread. 599 | /// Note: This method is being called by dynamically generated code. Be careful when changing its signature. 600 | /// 601 | private static void WriteNativeCrashLog(NativeFunction nativeFunction, object[] arguments) 602 | { 603 | var threadId = Thread.CurrentThread.ManagedThreadId; 604 | var filePath = Path.Combine(ApplyDirectoryPathMacros(Options.crashLogsDir), $"{CRASH_FILE_NAME_PREFIX}tid{threadId}.log"); 605 | using (var file = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.Read)) //Truncates file if exists 606 | { 607 | using(var writer = new StreamWriter(file)) 608 | { 609 | writer.Write("function: "); 610 | writer.WriteLine(nativeFunction.identity.symbol); 611 | 612 | writer.Write($"from DLL: "); 613 | writer.WriteLine(nativeFunction.containingDll.name); 614 | 615 | writer.Write($" at path: "); 616 | writer.WriteLine(nativeFunction.containingDll.path); 617 | 618 | writer.Write("arguments: "); 619 | if (arguments.Length == 0) 620 | { 621 | writer.WriteLine("no arguments"); 622 | } 623 | else 624 | { 625 | writer.WriteLine(); 626 | for (int i = 0; i < arguments.Length; i++) 627 | { 628 | writer.Write($" {i}:".PadRight(5)); 629 | var param = arguments[i]; 630 | if (param == null) 631 | { 632 | writer.Write("null"); 633 | } 634 | else 635 | { 636 | switch (param) 637 | { 638 | case string _: 639 | writer.Write($"\"{param}\""); 640 | break; 641 | //For float types use InvariantCulture, as so to use dot decimal separator over comma 642 | case float f: 643 | writer.Write(f.ToString(System.Globalization.CultureInfo.InvariantCulture)); 644 | break; 645 | case double f: 646 | writer.Write(f.ToString(System.Globalization.CultureInfo.InvariantCulture)); 647 | break; 648 | case decimal f: 649 | writer.Write(f.ToString(System.Globalization.CultureInfo.InvariantCulture)); 650 | break; 651 | default: 652 | writer.Write(param); 653 | break; 654 | } 655 | } 656 | writer.WriteLine(); 657 | } 658 | } 659 | 660 | writer.Write("thread: "); 661 | if(threadId == _unityMainThreadId) 662 | writer.WriteLine("unity main thread"); 663 | else 664 | writer.WriteLine($"{Thread.CurrentThread.Name}({threadId})"); 665 | 666 | var nativeCallIndex = Interlocked.Increment(ref _lastNativeCallIndex) - 1; 667 | writer.Write("call index: "); 668 | writer.WriteLine(nativeCallIndex); 669 | 670 | if (Options.crashLogsStackTrace) 671 | { 672 | var stackTrace = new System.Diagnostics.StackTrace(1); //Skip this frame 673 | writer.WriteLine("stack trace:"); 674 | writer.Write(stackTrace.ToString()); 675 | } 676 | } 677 | } 678 | } 679 | 680 | private static IntPtr SysLoadDll(string filepath) 681 | { 682 | #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN 683 | return PInvokes_Windows.LoadLibrary(filepath); 684 | #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX 685 | return PInvokes_Osx.dlopen(filepath, (int)Options.posixDlopenFlags); 686 | #elif UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX 687 | return PInvokes_Linux.dlopen(filepath, (int)Options.posixDlopenFlags); 688 | #else 689 | throw GetUnsupportedPlatformExcpetion(); 690 | #endif 691 | } 692 | 693 | private static bool SysUnloadDll(IntPtr libHandle) 694 | { 695 | #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN 696 | return PInvokes_Windows.FreeLibrary(libHandle); 697 | #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX 698 | return PInvokes_Osx.dlclose(libHandle) == 0; 699 | #elif UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX 700 | return PInvokes_Linux.dlclose(libHandle) == 0; 701 | #else 702 | throw GetUnsupportedPlatformExcpetion(); 703 | #endif 704 | } 705 | 706 | private static IntPtr SysGetDllProcAddress(IntPtr libHandle, string symbol) 707 | { 708 | #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN 709 | return PInvokes_Windows.GetProcAddress(libHandle, symbol); 710 | #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX 711 | return PInvokes_Osx.dlsym(libHandle, symbol); 712 | #elif UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX 713 | return PInvokes_Linux.dlsym(libHandle, symbol); 714 | #else 715 | throw GetUnsupportedPlatformExcpetion(); 716 | #endif 717 | } 718 | 719 | private static Exception GetUnsupportedPlatformExcpetion() 720 | { 721 | return new PlatformNotSupportedException("This tool is intended to run only on x86 based desktop systems. If you want to use it on other platform, please file an issue. We'll see what can be done:)."); 722 | } 723 | } 724 | 725 | [Serializable] 726 | public class DllManipulatorOptions 727 | { 728 | public string dllPathPattern; 729 | public List assemblyNames; // empty means only default assemblies 730 | public DllLoadingMode loadingMode; 731 | public PosixDlopenFlags posixDlopenFlags; 732 | public bool threadSafe; 733 | public bool enableCrashLogs; 734 | public string crashLogsDir; 735 | public bool crashLogsStackTrace; 736 | public bool mockAllNativeFunctions; 737 | public bool onlyInEditor; 738 | public bool enableInEditMode; 739 | 740 | public DllManipulatorOptions CloneTo(DllManipulatorOptions other) 741 | { 742 | other.dllPathPattern = dllPathPattern; 743 | other.assemblyNames = assemblyNames.Select(item => (string)item.Clone()).ToList(); 744 | other.loadingMode = loadingMode; 745 | other.posixDlopenFlags = posixDlopenFlags; 746 | other.threadSafe = threadSafe; 747 | other.enableCrashLogs = enableCrashLogs; 748 | other.crashLogsDir = crashLogsDir; 749 | other.crashLogsStackTrace = crashLogsStackTrace; 750 | other.mockAllNativeFunctions = mockAllNativeFunctions; 751 | other.onlyInEditor = onlyInEditor; 752 | other.enableInEditMode = enableInEditMode; 753 | 754 | return other; 755 | } 756 | 757 | public bool Equals(DllManipulatorOptions other) 758 | { 759 | return other.dllPathPattern == dllPathPattern && other.assemblyNames.SequenceEqual(assemblyNames) && 760 | other.loadingMode == loadingMode && other.posixDlopenFlags == posixDlopenFlags && 761 | other.threadSafe == threadSafe && other.enableCrashLogs == enableCrashLogs && 762 | other.crashLogsDir == crashLogsDir && other.crashLogsStackTrace == crashLogsStackTrace && 763 | other.mockAllNativeFunctions == mockAllNativeFunctions && other.onlyInEditor == onlyInEditor && 764 | other.enableInEditMode == enableInEditMode; 765 | } 766 | } 767 | 768 | public enum DllLoadingMode 769 | { 770 | Lazy, 771 | Preload 772 | } 773 | } -------------------------------------------------------------------------------- /scripts/DllManipulator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3e2b3e99b7e1c8a478240fd9c2c03e60 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/DllManipulatorScript.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using UnityEngine; 6 | using UnityNativeTool.Internal; 7 | #if UNITY_EDITOR 8 | using UnityEditor; 9 | #endif 10 | 11 | namespace UnityNativeTool 12 | { 13 | #if UNITY_EDITOR 14 | [ExecuteInEditMode] 15 | #endif 16 | public class DllManipulatorScript : MonoBehaviour 17 | { 18 | private static DllManipulatorScript _singletonInstance = null; 19 | public TimeSpan? InitializationTime { get; private set; } = null; 20 | public DllManipulatorOptions Options = new DllManipulatorOptions() 21 | { 22 | dllPathPattern = 23 | #if UNITY_STANDALONE_LINUX || UNITY_EDITOR_LINUX 24 | "{assets}/Plugins/__{name}.so", 25 | #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX 26 | "{assets}/Plugins/__{name}.dylib", 27 | #elif UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN 28 | "{assets}/Plugins/__{name}.dll", 29 | #else 30 | "", 31 | #endif 32 | assemblyNames = new List(), 33 | loadingMode = DllLoadingMode.Lazy, 34 | posixDlopenFlags = PosixDlopenFlags.Lazy, 35 | threadSafe = false, 36 | enableCrashLogs = false, 37 | crashLogsDir = "{assets}/", 38 | crashLogsStackTrace = false, 39 | mockAllNativeFunctions = true, 40 | onlyInEditor = true, 41 | enableInEditMode = false 42 | }; 43 | 44 | public static ConcurrentQueue MainThreadTriggerQueue = new ConcurrentQueue(); 45 | 46 | private void OnEnable() 47 | { 48 | #if UNITY_EDITOR 49 | if (_singletonInstance != null) 50 | { 51 | if (EditorApplication.isPlaying) 52 | Destroy(gameObject); 53 | else if(_singletonInstance != this) 54 | enabled = false; //Don't destroy as the user may be editing a Prefab 55 | return; 56 | } 57 | _singletonInstance = this; 58 | 59 | if(EditorApplication.isPlaying) 60 | DontDestroyOnLoad(gameObject); 61 | 62 | if(EditorApplication.isPlaying || Options.enableInEditMode) 63 | { 64 | Initialize(); 65 | AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; 66 | } 67 | 68 | // Ensure update is called every frame in edit mode, ExecuteInEditMode only calls Update when the scene changes 69 | if(!EditorApplication.isPlaying && Options.enableInEditMode) 70 | EditorApplication.update += Update; 71 | 72 | #else 73 | if (Options.onlyInEditor) 74 | return; 75 | 76 | if (_singletonInstance != null) 77 | { 78 | Destroy(gameObject); 79 | return; 80 | } 81 | _singletonInstance = this; 82 | 83 | DontDestroyOnLoad(gameObject); 84 | Initialize(); 85 | #endif 86 | } 87 | 88 | public void Initialize() 89 | { 90 | var initTimer = System.Diagnostics.Stopwatch.StartNew(); 91 | 92 | DllManipulator.Initialize(Options, Thread.CurrentThread.ManagedThreadId, Application.dataPath); 93 | 94 | initTimer.Stop(); 95 | InitializationTime = initTimer.Elapsed; 96 | } 97 | 98 | /// 99 | /// Will reset the DllManipulator and Initialize it again. 100 | /// Note: Unloads all Dlls, may be a dangerous operation if using preloaded 101 | /// 102 | public void Reinitialize() 103 | { 104 | DllManipulator.Reset(); 105 | 106 | #if UNITY_EDITOR 107 | if(EditorApplication.isPlaying || Options.enableInEditMode) 108 | #endif 109 | Initialize(); 110 | } 111 | 112 | /// 113 | /// Note: also called in edit mode if Options.enableInEditMode is set. 114 | /// 115 | private void Update() 116 | { 117 | InvokeMainThreadQueue(); 118 | } 119 | 120 | /// 121 | /// Executes queued methods. 122 | /// Should be called from the main thread in Update. 123 | /// 124 | public static void InvokeMainThreadQueue() 125 | { 126 | while (MainThreadTriggerQueue.TryDequeue(out var action)) 127 | action(); 128 | } 129 | 130 | #if UNITY_EDITOR 131 | private bool _isRecompiling; 132 | /// 133 | /// Called when Assemblies are reloaded due to recompilation. 134 | /// Called before OnDisable. 135 | /// 136 | private void OnBeforeAssemblyReload() 137 | { 138 | _isRecompiling = true; 139 | } 140 | 141 | private void OnDisable() 142 | { 143 | if(_singletonInstance == this && !EditorApplication.isPlaying && Options.enableInEditMode) 144 | { 145 | EditorApplication.update -= Update; 146 | AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; 147 | 148 | // When recompiling OnDestroy is not called by default (the object is not really destroyed) 149 | // Manually trigger OnDestroy to clean up if we are disabled because of recompilation 150 | if (_isRecompiling) 151 | { 152 | _isRecompiling = false; 153 | Reset(); 154 | } 155 | } 156 | } 157 | #endif 158 | 159 | private void OnDestroy() 160 | { 161 | if (_singletonInstance == this) 162 | Reset(); 163 | } 164 | 165 | private void Reset() 166 | { 167 | //Note on threading: Because we don't wait for other threads to finish, we might be stealing function delegates from under their nose if Unity doesn't happen to close them yet. 168 | //On Preloaded mode this leads to NullReferenceException, but on Lazy mode the DLL and function would be just reloaded so we would up with loaded DLL after game exit. 169 | //Thankfully thread safety with Lazy mode is not implemented yet. 170 | 171 | if (DllManipulator.Options != null) // Check that we have initialized 172 | DllManipulator.Reset(); 173 | _singletonInstance = null; 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /scripts/DllManipulatorScript.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: de05c130c77e69a408c0f25cd6b50d1d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: -10000 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fbc6fd093eba8524a9eccf9b4b1385a3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /scripts/Editor/DllManipulatorEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using UnityEditor.Compilation; 6 | #if UNITY_2019_1_OR_NEWER 7 | using UnityEditor.ShortcutManagement; 8 | #endif 9 | using System.IO; 10 | using System; 11 | 12 | namespace UnityNativeTool.Internal 13 | { 14 | [CustomEditor(typeof(DllManipulatorScript))] 15 | public class DllManipulatorEditor : Editor 16 | { 17 | private static readonly string INFO_BOX_GUI_CONTENT = 18 | "Mocks native functions to allow manually un/loading native DLLs. DLLs are always unloaded at OnDestroy. Configuration changes below are always applied at OnEnable."; 19 | private static readonly GUIContent TARGET_ALL_NATIVE_FUNCTIONS_GUI_CONTENT = new GUIContent("All native functions", 20 | "If true, all found native functions will be mocked.\n\n" + 21 | $"If false, you have to select them by using [{nameof(MockNativeDeclarationsAttribute)}] or [{nameof(MockNativeDeclarationAttribute)}]."); 22 | private static readonly GUIContent ONLY_ASSEMBLY_CSHARP_GUI_CONTENT = new GUIContent("Only Assembly-CSharp(-Editor)", 23 | "If true, native functions will be mocked only in Assembly-CSharp and Assembly-CSharp-Editor. Alternatively enter a list of assembly names."); 24 | private static readonly GUIContent ONLY_IN_EDITOR = new GUIContent("Only in Editor", 25 | "Whether to run only inside editor (which is recommended)."); 26 | private static readonly GUIContent ENABLE_IN_EDIT_MODE = new GUIContent("Enable in Edit Mode", 27 | "Should the DLLs also be mocked in edit mode (i.e. even if you don't hit 'play' in editor). " + 28 | "Turning this off when not needed improves performance when entering edit mode. " + 29 | "Changes are currently only visible on the next time edit mode is entered (i.e. when OnEnable is called so hit 'play' then 'stop' to apply)."); 30 | private static readonly GUIContent TARGET_ASSEMBLIES_GUI_CONTENT = new GUIContent("Target assemblies", 31 | "List of assembly names to mock native functions in (no file extension)."); 32 | private static readonly GUIContent DLL_PATH_PATTERN_GUI_CONTENT = new GUIContent("DLL path pattern", 33 | "Available macros:\n\n" + 34 | $"{DllManipulator.DLL_PATH_PATTERN_DLL_NAME_MACRO} - name of DLL as specified in [DllImport] attribute.\n\n" + 35 | $"{DllManipulator.DLL_PATH_PATTERN_ASSETS_MACRO} - assets folder of current project.\n\n" + 36 | $"{DllManipulator.DLL_PATH_PATTERN_PROJECT_MACRO} - project folder i.e. one above Assets."); 37 | private static readonly GUIContent DLL_LOADING_MODE_GUI_CONTENT = new GUIContent("DLL loading mode", 38 | "Specifies how DLLs and functions will be loaded.\n\n" + 39 | "Lazy - All DLLs and functions are loaded each time they are called, if not loaded yet. This allows them to be easily unloaded and loaded within game execution.\n\n" + 40 | "Preloaded - Slight performance benefit over Lazy mode. All declared DLLs and functions are loaded at startup (OnEnable()) and not reloaded later. Mid-execution it's not safe to unload them unless game is paused."); 41 | private static readonly GUIContent POSIX_DLOPEN_FLAGS_GUI_CONTENT = new GUIContent("dlopen flags", 42 | "Flags used in dlopen() P/Invoke on Linux and OSX systems. Has minor meaning unless library is large."); 43 | private static readonly GUIContent THREAD_SAFE_GUI_CONTENT = new GUIContent("Thread safe", 44 | "Ensures synchronization required for native calls from any other than Unity main thread. Overhead might be few times higher, with uncontended locks.\n\n" + 45 | "Only in Preloaded mode."); 46 | private static readonly GUIContent CRASH_LOGS_GUI_CONTENT = new GUIContent("Crash logs", 47 | "Logs each native call to file. In case of crash or hang caused by native function, you can than see what function was that, along with arguments and, optionally, stack trace.\n\n" + 48 | "In multi-threaded scenario there will be one file for each thread and you'll have to guess the right one (call index will be a hint).\n\n" + 49 | "Note that existence of log files doesn't mean the crash was caused by any tracked native function.\n\n" + 50 | "Overhead is HIGH (on poor PC there might be just few native calls per update to disturb 60 fps.)"); 51 | private static readonly GUIContent CRASH_LOGS_DIR_GUI_CONTENT = new GUIContent("Logs directory", 52 | "Path to directory in which crash logs will be stored. You can use macros as in DLL path. Note that this file(s) will usually exist during majority of game execution."); 53 | private static readonly GUIContent CRASH_LOGS_STACK_TRACE_GUI_CONTENT = new GUIContent("Stack trace", 54 | "Whether to include stack trace in crash log.\n\n" + 55 | "Overhead is about 4 times higher."); 56 | private static readonly GUIContent UNLOAD_ALL_DLLS_IN_PLAY_PRELOADED_GUI_CONTENT = new GUIContent("Unload all DLLs [dangerous]", 57 | "Use only if you are sure no mocked native calls will be made while DLL is unloaded."); 58 | private static readonly GUIContent UNLOAD_ALL_DLLS_WITH_THREAD_SAFETY_GUI_CONTENT = new GUIContent("Unload all DLLs [dangerous]", 59 | "Use only if you are sure no other thread will call mocked natives."); 60 | private static readonly GUIContent UNLOAD_ALL_DLLS_AND_PAUSE_WITH_THREAD_SAFETY_GUI_CONTENT = new GUIContent("Unload all DLLs & Pause [dangerous]", 61 | "Use only if you are sure no other thread will call mocked natives."); 62 | private static readonly TimeSpan ASSEMBLIES_REFRESH_INTERVAL = TimeSpan.FromSeconds(5); 63 | 64 | private static readonly GUIContent INITIALIZE_ENABLED_EDIT_MODE_GUI_CONTENT = new GUIContent( 65 | "Apply Changes Now & Initialize", 66 | "Start mocking native functions in edit mode immediately without waiting for OnEnable."); 67 | private static readonly GUIContent REINITIALIZE_WITH_CHANGES_LAZY_GUI_CONTENT = new GUIContent( 68 | "Unload, Apply Changes Now & Reinitialize", 69 | "Changes made to the options above are only applied when play(/edit) mode is entered." + 70 | " Use this to unload all Dlls and initialize with the new changes immediately."); 71 | private static readonly GUIContent REINITIALIZE_WITH_CHANGES_PRELOADED_GUI_CONTENT = new GUIContent( 72 | "Unload, Apply Changes Now & Reinitialize [Dangerous]", 73 | "Changes made to the options above are only applied when play(/edit) mode is entered. " + 74 | "Use this to unload all Dlls and initialize with the new changes immediately. " + 75 | "Use only if you are sure no mocked native calls will be made while DLL is unloaded."); 76 | 77 | private bool _showLoadedLibraries = true; 78 | private bool _showTargetAssemblies = true; 79 | private string[] _possibleTargetAssemblies = null; 80 | private DateTime _lastKnownAssembliesRefreshTime; 81 | 82 | /// 83 | /// Used to check if the options have changed, in order to set the object as dirty so changes are saved 84 | /// 85 | private DllManipulatorOptions _prevOptions = new DllManipulatorOptions(); 86 | 87 | public static event Action RepaintAllEditors = delegate {}; 88 | 89 | public DllManipulatorEditor() 90 | { 91 | EditorApplication.pauseStateChanged += _ => Repaint(); 92 | EditorApplication.playModeStateChanged += _ => Repaint(); 93 | RepaintAllEditors += Repaint; 94 | } 95 | 96 | private void Awake() 97 | { 98 | // Immediately copy the Options to the previous so we don't need to check for null later 99 | ((DllManipulatorScript)target).Options.CloneTo(_prevOptions); 100 | } 101 | 102 | public override void OnInspectorGUI() 103 | { 104 | var t = (DllManipulatorScript)this.target; 105 | 106 | EditorGUILayout.HelpBox(INFO_BOX_GUI_CONTENT, MessageType.Info); 107 | 108 | DrawOptions(t.Options); 109 | 110 | DetectOptionChanges(t); 111 | 112 | EditorGUILayout.Space(); 113 | 114 | DrawCurrentState(t); 115 | } 116 | 117 | /// 118 | /// Detects whether the have changed, both relative to the previous 119 | /// options and the if we are currently initialized. 120 | /// 121 | /// The OnInspectorGUI target 122 | private void DetectOptionChanges(DllManipulatorScript t) 123 | { 124 | // Set the target as dirty so changes can be saved, if there are changes 125 | if (GUI.changed) 126 | { 127 | if (!t.Options.Equals(_prevOptions)) 128 | { 129 | // If the options have changed then update the _prevOptions and notify there are changes to be saved 130 | // CloneTo is used to ensure a deep copy is made 131 | t.Options.CloneTo(_prevOptions); 132 | EditorUtility.SetDirty(target); 133 | } 134 | } 135 | 136 | // Allow Reinitializing DllManipulator if there are changes 137 | if (DllManipulator.Options != null && !t.Options.Equals(DllManipulator.Options)) 138 | { 139 | if (DllManipulator.Options.loadingMode == DllLoadingMode.Preload) 140 | { 141 | if (GUILayout.Button(REINITIALIZE_WITH_CHANGES_PRELOADED_GUI_CONTENT)) 142 | t.Reinitialize(); 143 | } 144 | else if(GUILayout.Button(REINITIALIZE_WITH_CHANGES_LAZY_GUI_CONTENT)) 145 | { 146 | t.Reinitialize(); 147 | } 148 | } 149 | 150 | // When enabling enableInEditMode for the first time, allow immediately initializing without waiting for OnEnable 151 | if(DllManipulator.Options == null && t.Options.enableInEditMode && !EditorApplication.isPlaying && 152 | GUILayout.Button(INITIALIZE_ENABLED_EDIT_MODE_GUI_CONTENT)) 153 | { 154 | t.Initialize(); 155 | } 156 | } 157 | 158 | /// 159 | /// Draws GUI related to the current state of the DllManipulator. 160 | /// Buttons to load/unload Dlls as well as details about which Dlls are loaded 161 | /// 162 | /// The OnInspectorGUI target 163 | private void DrawCurrentState(DllManipulatorScript t) 164 | { 165 | if (DllManipulator.Options == null) // Exit if we have not initialized DllManipulator 166 | return; 167 | 168 | var usedDlls = DllManipulator.GetUsedDllsInfos(); 169 | if (usedDlls.Count != 0) 170 | { 171 | if(DllManipulator.Options.loadingMode == DllLoadingMode.Preload && usedDlls.Any(d => !d.isLoaded)) 172 | { 173 | if (EditorApplication.isPaused) 174 | { 175 | if (GUILayout.Button("Load all DLLs & Unpause")) 176 | { 177 | DllManipulator.LoadAll(); 178 | EditorApplication.isPaused = false; 179 | } 180 | } 181 | 182 | if (GUILayout.Button("Load all DLLs")) 183 | DllManipulator.LoadAll(); 184 | } 185 | 186 | if (usedDlls.Any(d => d.isLoaded)) 187 | { 188 | if (EditorApplication.isPlaying && !EditorApplication.isPaused) 189 | { 190 | bool pauseAndUnloadAll; 191 | if(DllManipulator.Options.threadSafe) 192 | pauseAndUnloadAll = GUILayout.Button(UNLOAD_ALL_DLLS_AND_PAUSE_WITH_THREAD_SAFETY_GUI_CONTENT); 193 | else 194 | pauseAndUnloadAll = GUILayout.Button("Unload all DLLs & Pause"); 195 | 196 | if(pauseAndUnloadAll) 197 | { 198 | EditorApplication.isPaused = true; 199 | DllManipulator.UnloadAll(); 200 | } 201 | } 202 | 203 | 204 | bool unloadAll; 205 | if(DllManipulator.Options.threadSafe) 206 | unloadAll = GUILayout.Button(UNLOAD_ALL_DLLS_WITH_THREAD_SAFETY_GUI_CONTENT); 207 | else if (DllManipulator.Options.loadingMode == DllLoadingMode.Preload && (EditorApplication.isPlaying && !EditorApplication.isPaused || DllManipulator.Options.enableInEditMode)) 208 | unloadAll = GUILayout.Button(UNLOAD_ALL_DLLS_IN_PLAY_PRELOADED_GUI_CONTENT); 209 | else 210 | unloadAll = GUILayout.Button("Unload all DLLs"); 211 | 212 | if(unloadAll) 213 | DllManipulator.UnloadAll(); 214 | } 215 | 216 | DrawUsedDlls(usedDlls); 217 | } 218 | else 219 | { 220 | GUILayout.BeginHorizontal(); 221 | GUILayout.FlexibleSpace(); 222 | EditorGUILayout.LabelField("No DLLs to mock"); 223 | GUILayout.FlexibleSpace(); 224 | GUILayout.EndHorizontal(); 225 | } 226 | 227 | if (t.InitializationTime != null) 228 | { 229 | EditorGUILayout.Space(); 230 | EditorGUILayout.Space(); 231 | var time = t.InitializationTime.Value; 232 | EditorGUILayout.LabelField($"Initialized in: {(int)time.TotalSeconds}.{time.Milliseconds.ToString("D3")}s"); 233 | } 234 | } 235 | 236 | private void DrawUsedDlls(IList usedDlls) 237 | { 238 | _showLoadedLibraries = EditorGUILayout.Foldout(_showLoadedLibraries, "Mocked DLLs"); 239 | if (_showLoadedLibraries) 240 | { 241 | var prevIndent = EditorGUI.indentLevel; 242 | EditorGUI.indentLevel += 1; 243 | bool isFirstDll = true; 244 | foreach (var dll in usedDlls) 245 | { 246 | if (!isFirstDll) 247 | EditorGUILayout.Space(); 248 | 249 | var stateAttributes = new List 250 | { 251 | dll.isLoaded ? "LOADED" : "NOT LOADED" 252 | }; 253 | if (dll.loadingError) 254 | stateAttributes.Add("LOAD ERROR"); 255 | if (dll.symbolError) 256 | stateAttributes.Add("SYMBOL ERRROR"); 257 | var state = string.Join(" | ", stateAttributes); 258 | 259 | EditorGUILayout.LabelField($"[{state}] {dll.name}"); 260 | EditorGUILayout.LabelField(dll.path); 261 | isFirstDll = false; 262 | } 263 | EditorGUI.indentLevel = prevIndent; 264 | } 265 | } 266 | 267 | private void DrawOptions(DllManipulatorOptions options) 268 | { 269 | options.onlyInEditor = EditorGUILayout.Toggle(ONLY_IN_EDITOR, options.onlyInEditor); 270 | options.enableInEditMode = EditorGUILayout.Toggle(ENABLE_IN_EDIT_MODE, options.enableInEditMode); 271 | 272 | EditorGUILayout.Separator(); 273 | EditorGUILayout.LabelField("Managed Side", EditorStyles.boldLabel); 274 | 275 | options.mockAllNativeFunctions = EditorGUILayout.Toggle(TARGET_ALL_NATIVE_FUNCTIONS_GUI_CONTENT, options.mockAllNativeFunctions); 276 | 277 | if (EditorGUILayout.Toggle(ONLY_ASSEMBLY_CSHARP_GUI_CONTENT, options.assemblyNames.Count == 0)) 278 | { 279 | options.assemblyNames.Clear(); 280 | } 281 | else 282 | { 283 | var prevIndent1 = EditorGUI.indentLevel; 284 | EditorGUI.indentLevel++; 285 | 286 | if (_possibleTargetAssemblies == null || _lastKnownAssembliesRefreshTime + ASSEMBLIES_REFRESH_INTERVAL < DateTime.Now) 287 | RefreshPossibleTargetAssemblies(); 288 | 289 | if (options.assemblyNames.Count == 0) 290 | options.assemblyNames.AddRange(DllManipulator.DEFAULT_ASSEMBLY_NAMES); 291 | 292 | _showTargetAssemblies = EditorGUILayout.Foldout(_showTargetAssemblies, TARGET_ASSEMBLIES_GUI_CONTENT); 293 | if (_showTargetAssemblies) 294 | { 295 | var prevIndent2 = EditorGUI.indentLevel; 296 | EditorGUI.indentLevel++; 297 | 298 | DrawList(options.assemblyNames, i => 299 | { 300 | var result = EditorGUILayout.TextField(options.assemblyNames[i]); 301 | 302 | // Show a pop up for quickly selecting an assembly 303 | var selectedId = EditorGUILayout.Popup(0, 304 | new[] {"Find"}.Concat(_possibleTargetAssemblies).ToArray(), GUILayout.Width(80)); 305 | 306 | if (selectedId > 0) 307 | result = _possibleTargetAssemblies[selectedId - 1]; 308 | return result; 309 | }, true, () => "", 310 | () => 311 | { 312 | options.assemblyNames = options.assemblyNames 313 | .Concat(_possibleTargetAssemblies).Distinct().ToList(); 314 | }); 315 | 316 | EditorGUI.indentLevel = prevIndent2; 317 | } 318 | 319 | EditorGUI.indentLevel = prevIndent1; 320 | } 321 | 322 | EditorGUILayout.Separator(); 323 | EditorGUILayout.LabelField("Native Side", EditorStyles.boldLabel); 324 | 325 | options.dllPathPattern = EditorGUILayout.TextField(DLL_PATH_PATTERN_GUI_CONTENT, options.dllPathPattern); 326 | 327 | options.loadingMode = (DllLoadingMode)EditorGUILayout.EnumPopup(DLL_LOADING_MODE_GUI_CONTENT, options.loadingMode); 328 | 329 | #if UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX 330 | options.posixDlopenFlags = (PosixDlopenFlags)EditorGUILayout.EnumPopup(POSIX_DLOPEN_FLAGS_GUI_CONTENT, options.posixDlopenFlags); 331 | #endif 332 | 333 | var guiEnabled = GUI.enabled; 334 | if (options.loadingMode != DllLoadingMode.Preload) 335 | { 336 | options.threadSafe = false; 337 | GUI.enabled = false; 338 | } 339 | options.threadSafe = EditorGUILayout.Toggle(THREAD_SAFE_GUI_CONTENT, options.threadSafe); 340 | GUI.enabled = guiEnabled; 341 | 342 | options.enableCrashLogs = EditorGUILayout.Toggle(CRASH_LOGS_GUI_CONTENT, options.enableCrashLogs); 343 | 344 | if (options.enableCrashLogs) 345 | { 346 | var prevIndent = EditorGUI.indentLevel; 347 | EditorGUI.indentLevel += 1; 348 | 349 | options.crashLogsDir = EditorGUILayout.TextField(CRASH_LOGS_DIR_GUI_CONTENT, options.crashLogsDir); 350 | 351 | options.crashLogsStackTrace = EditorGUILayout.Toggle(CRASH_LOGS_STACK_TRACE_GUI_CONTENT, options.crashLogsStackTrace); 352 | 353 | EditorGUI.indentLevel = prevIndent; 354 | } 355 | } 356 | 357 | /// 358 | /// Will search for all managed assemblies and store them in . 359 | /// Excludes assemblies starting with 360 | /// 361 | private void RefreshPossibleTargetAssemblies() 362 | { 363 | var playerCompiledAssemblies = CompilationPipeline.GetAssemblies(AssembliesType.Player) 364 | .Select(a => Path.GetFileNameWithoutExtension(a.outputPath)); 365 | 366 | var editorCompiledAssemblies = CompilationPipeline.GetAssemblies(AssembliesType.Editor) 367 | .Select(a => Path.GetFileNameWithoutExtension(a.outputPath)); 368 | 369 | var assemblyAssets = Resources.FindObjectsOfTypeAll() 370 | .Where(p => !p.isNativePlugin) 371 | .Select(p => Path.GetFileNameWithoutExtension(p.assetPath)); 372 | 373 | _possibleTargetAssemblies = playerCompiledAssemblies 374 | .Concat(editorCompiledAssemblies) 375 | .Concat(assemblyAssets) 376 | .Where(a => !DllManipulator.IGNORED_ASSEMBLY_PREFIXES.Any(a.StartsWith)) 377 | .OrderBy(name => name) 378 | .ToArray(); 379 | _lastKnownAssembliesRefreshTime = DateTime.Now; 380 | } 381 | 382 | private void DrawList(IList elements, Func drawElement, bool canAddNewElement, Func getNewElement, Action addAll) 383 | { 384 | int indexToRemove = -1; 385 | for (int i = 0; i < elements.Count; i++) 386 | { 387 | EditorGUILayout.BeginHorizontal(); 388 | elements[i] = drawElement(i); 389 | if (GUILayout.Button("X", GUILayout.Width(20))) 390 | { 391 | indexToRemove = i; 392 | } 393 | EditorGUILayout.EndHorizontal(); 394 | } 395 | 396 | if (indexToRemove != -1) 397 | elements.RemoveAt(indexToRemove); 398 | 399 | GUILayout.BeginHorizontal(); 400 | GUILayout.Space(EditorGUI.indentLevel * 15); 401 | var prevGuiEnabled = GUI.enabled; 402 | GUI.enabled = prevGuiEnabled && canAddNewElement; 403 | 404 | if (GUILayout.Button("Add", GUILayout.Width(40))) 405 | elements.Add(getNewElement()); 406 | 407 | if (GUILayout.Button("Add All", GUILayout.Width(80))) 408 | addAll(); 409 | 410 | if (GUILayout.Button("Reset", GUILayout.Width(50))) 411 | elements.Clear(); 412 | 413 | GUI.enabled = prevGuiEnabled; 414 | GUILayout.EndHorizontal(); 415 | } 416 | 417 | static string GetFirstAssemblyToList(string[] allAssemblies) 418 | { 419 | return allAssemblies.FirstOrDefault(a => PathUtils.DllPathsEqual(a, typeof(DllManipulator).Assembly.Location)) 420 | ?? allAssemblies.FirstOrDefault(); 421 | } 422 | 423 | #if UNITY_2019_1_OR_NEWER 424 | [Shortcut("Tools/Load All Dlls", KeyCode.D, ShortcutModifiers.Alt | ShortcutModifiers.Shift)] 425 | #else 426 | [MenuItem("Tools/Load All Dlls #&d")] 427 | #endif 428 | public static void LoadAllShortcut() 429 | { 430 | DllManipulator.LoadAll(); 431 | } 432 | 433 | #if UNITY_2019_1_OR_NEWER 434 | [Shortcut("Tools/Unload All Dlls", KeyCode.D, ShortcutModifiers.Alt)] 435 | #else 436 | [MenuItem("Tools/Unload All Dlls &d")] 437 | #endif 438 | public static void UnloadAll() 439 | { 440 | DllManipulator.UnloadAll(); 441 | } 442 | 443 | [NativeDllLoadedTrigger(UseMainThreadQueue = true)] 444 | [NativeDllAfterUnloadTrigger(UseMainThreadQueue = true)] 445 | public static void RepaintAll() 446 | { 447 | RepaintAllEditors?.Invoke(); 448 | } 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /scripts/Editor/DllManipulatorEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ad2b782b2fda4d40a37071c3eaa1d82 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/Editor/DllManipulatorWindowEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using UnityEngine; 3 | using UnityEditor; 4 | 5 | namespace UnityNativeTool.Internal 6 | { 7 | public class DllManipulatorWindowEditor : EditorWindow 8 | { 9 | private static EditorWindow window; 10 | 11 | [MenuItem("Window/Dll manipulator")] 12 | static void Init() 13 | { 14 | window = GetWindow(); 15 | window.Show(); 16 | DllManipulatorEditor.RepaintAllEditors += window.Repaint; 17 | } 18 | 19 | void OnGUI() 20 | { 21 | var dllManipulator = FindObjectOfType(); 22 | if (dllManipulator == null) 23 | dllManipulator = Resources.FindObjectsOfTypeAll() 24 | .FirstOrDefault(d => !EditorUtility.IsPersistent(d) && d.gameObject.scene.IsValid()); 25 | 26 | if (dllManipulator != null) 27 | { 28 | var editor = Editor.CreateEditor(dllManipulator); 29 | editor.OnInspectorGUI(); 30 | } 31 | else 32 | { 33 | EditorGUILayout.Space(); 34 | EditorGUILayout.LabelField($"There is no {nameof(DllManipulatorScript)} script in the scene."); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /scripts/Editor/DllManipulatorWindowEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 01f26ee2eb448d247b2bec6b541e0c06 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/Editor/mcpiroman.UnityNativeTool.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcpiroman.UnityNativeTool.Editor", 3 | "references": [ 4 | "mcpiroman.UnityNativeTool" 5 | ], 6 | "includePlatforms": [ 7 | "Editor" 8 | ], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": false, 11 | "overrideReferences": true, 12 | "precompiledReferences": [], 13 | "autoReferenced": true, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /scripts/Editor/mcpiroman.UnityNativeTool.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95a3621decb49d845a207123b016081e 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /scripts/IlGeneratorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | 3 | namespace UnityNativeTool.Internal 4 | { 5 | internal static class IlGeneratorExtensions 6 | { 7 | public static void EmitFastArgLoad(this ILGenerator il, int argumentIndex) 8 | { 9 | switch (argumentIndex) 10 | { 11 | case 0: 12 | il.Emit(OpCodes.Ldarg_0); 13 | return; 14 | case 1: 15 | il.Emit(OpCodes.Ldarg_1); 16 | return; 17 | case 2: 18 | il.Emit(OpCodes.Ldarg_2); 19 | return; 20 | case 3: 21 | il.Emit(OpCodes.Ldarg_3); 22 | return; 23 | } 24 | 25 | il.Emit(OpCodes.Ldarg_S, argumentIndex); 26 | return; 27 | } 28 | 29 | public static void EmitFastI4Load(this ILGenerator il, int value) 30 | { 31 | switch (value) 32 | { 33 | case -1: 34 | il.Emit(OpCodes.Ldc_I4_M1); 35 | return; 36 | case 0: 37 | il.Emit(OpCodes.Ldc_I4_0); 38 | return; 39 | case 1: 40 | il.Emit(OpCodes.Ldc_I4_1); 41 | return; 42 | case 2: 43 | il.Emit(OpCodes.Ldc_I4_2); 44 | return; 45 | case 3: 46 | il.Emit(OpCodes.Ldc_I4_3); 47 | return; 48 | case 4: 49 | il.Emit(OpCodes.Ldc_I4_4); 50 | return; 51 | case 5: 52 | il.Emit(OpCodes.Ldc_I4_5); 53 | return; 54 | case 6: 55 | il.Emit(OpCodes.Ldc_I4_6); 56 | return; 57 | case 7: 58 | il.Emit(OpCodes.Ldc_I4_7); 59 | return; 60 | case 8: 61 | il.Emit(OpCodes.Ldc_I4_8); 62 | return; 63 | } 64 | 65 | if (value > -129 && value < 128) 66 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); 67 | else 68 | il.Emit(OpCodes.Ldc_I4, value); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /scripts/IlGeneratorExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9808b68bb4537341ae7388fb89a9da9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/LowLevelPluginManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Reflection.Emit; 6 | using System.Runtime.InteropServices; 7 | using System.Runtime.CompilerServices; 8 | using System.Threading; 9 | using System.IO; 10 | using UnityEngine; 11 | 12 | namespace UnityNativeTool.Internal 13 | { 14 | [DisableMocking] 15 | static class LowLevelPluginManager 16 | { 17 | private static bool _triedLoadingStubPlugin = false; 18 | private static IntPtr _unityInterfacePtr = IntPtr.Zero; 19 | 20 | public static void OnDllLoaded(NativeDll dll) 21 | { 22 | if (_triedLoadingStubPlugin && _unityInterfacePtr == IntPtr.Zero) 23 | return; 24 | 25 | var unityPluginLoadFunc = new NativeFunction("UnityPluginLoad", dll) { 26 | delegateType = typeof(UnityPluginLoadDel) 27 | }; 28 | 29 | DllManipulator.LoadTargetFunction(unityPluginLoadFunc, true); 30 | if (unityPluginLoadFunc.@delegate == null) 31 | return; 32 | 33 | if (!_triedLoadingStubPlugin) 34 | { 35 | try 36 | { 37 | _unityInterfacePtr = GetUnityInterfacesPtr(); 38 | if (_unityInterfacePtr == IntPtr.Zero) 39 | throw new Exception($"{nameof(GetUnityInterfacesPtr)} returned null"); 40 | } 41 | catch (DllNotFoundException) 42 | { 43 | Debug.LogWarning("StubLluiPlugin not found. UnityPluginLoad and UnityPluginUnload callbacks won't fire. If need them, please read the README at the github's repo, otherwise you may just comment out this warning."); 44 | } 45 | finally 46 | { 47 | _triedLoadingStubPlugin = true; 48 | } 49 | } 50 | 51 | if (_unityInterfacePtr != IntPtr.Zero) 52 | ((UnityPluginLoadDel)unityPluginLoadFunc.@delegate)(_unityInterfacePtr); 53 | } 54 | 55 | public static void OnBeforeDllUnload(NativeDll dll) 56 | { 57 | if (_unityInterfacePtr == IntPtr.Zero) 58 | return; 59 | 60 | var unityPluginUnloadFunc = new NativeFunction("UnityPluginUnload", dll) { 61 | delegateType = typeof(UnityPluginUnloadDel) 62 | }; 63 | 64 | DllManipulator.LoadTargetFunction(unityPluginUnloadFunc, true); 65 | if (unityPluginUnloadFunc.@delegate != null) 66 | ((UnityPluginUnloadDel)unityPluginUnloadFunc.@delegate)(); 67 | } 68 | 69 | public static void ResetStubPlugin() 70 | { 71 | _triedLoadingStubPlugin = false; 72 | _unityInterfacePtr = IntPtr.Zero; 73 | } 74 | 75 | delegate void UnityPluginLoadDel(IntPtr unityInterfaces); 76 | delegate void UnityPluginUnloadDel(); 77 | 78 | [DllImport("StubLluiPlugin")] 79 | private static extern IntPtr GetUnityInterfacesPtr(); 80 | } 81 | } -------------------------------------------------------------------------------- /scripts/LowLevelPluginManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b74bb8d3dfd914041a96c6064839490a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/MethodInvoker.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Following file is essentially copy of MethodInvoker.cs file from https://github.com/pardeike/Harmony 3 | // 4 | 5 | using System; 6 | using System.Reflection; 7 | using System.Reflection.Emit; 8 | 9 | namespace UnityNativeTool.Internal 10 | { 11 | // Based on https://www.codeproject.com/Articles/14593/A-General-Fast-Method-Invoker 12 | 13 | /// A delegate to invoke a method 14 | /// The instance 15 | /// The method parameters 16 | /// The method result 17 | /// 18 | public delegate object FastInvokeHandler(object target, object[] parameters); 19 | 20 | /// A helper class to invoke method with delegates 21 | public class MethodInvoker 22 | { 23 | /// Creates a fast invocation handler from a method and a module 24 | /// The method to invoke 25 | /// The module context 26 | /// The fast invocation handler 27 | /// 28 | public static FastInvokeHandler GetHandler(DynamicMethod methodInfo, Module module) 29 | { 30 | return Handler(methodInfo, module); 31 | } 32 | 33 | /// Creates a fast invocation handler from a method and a module 34 | /// The method to invoke 35 | /// The fast invocation handler 36 | /// 37 | public static FastInvokeHandler GetHandler(MethodInfo methodInfo) 38 | { 39 | return Handler(methodInfo, methodInfo.DeclaringType.Module); 40 | } 41 | 42 | static FastInvokeHandler Handler(MethodInfo methodInfo, Module module, bool directBoxValueAccess = false) 43 | { 44 | var dynamicMethod = new DynamicMethod("FastInvoke_" + methodInfo.Name + "_" + (directBoxValueAccess ? "direct" : "indirect"), typeof(object), new Type[] { typeof(object), typeof(object[]) }, module, true); 45 | var il = dynamicMethod.GetILGenerator(); 46 | 47 | if (!methodInfo.IsStatic) 48 | { 49 | il.Emit(OpCodes.Ldarg_0); 50 | EmitUnboxIfNeeded(il, methodInfo.DeclaringType); 51 | } 52 | 53 | var generateLocalBoxValuePtr = true; 54 | var ps = methodInfo.GetParameters(); 55 | for (var i = 0; i < ps.Length; i++) 56 | { 57 | var argType = ps[i].ParameterType; 58 | var argIsByRef = argType.IsByRef; 59 | if (argIsByRef) 60 | argType = argType.GetElementType(); 61 | var argIsValueType = argType.IsValueType; 62 | 63 | if (argIsByRef && argIsValueType && !directBoxValueAccess) 64 | { 65 | // used later when storing back the reference to the new box in the array. 66 | il.Emit(OpCodes.Ldarg_1); 67 | il.EmitFastI4Load(i); 68 | } 69 | 70 | il.Emit(OpCodes.Ldarg_1); 71 | il.EmitFastI4Load(i); 72 | 73 | if (argIsByRef && !argIsValueType) 74 | { 75 | il.Emit(OpCodes.Ldelema, typeof(object)); 76 | } 77 | else 78 | { 79 | il.Emit(OpCodes.Ldelem_Ref); 80 | if (argIsValueType) 81 | { 82 | if (!argIsByRef || !directBoxValueAccess) 83 | { 84 | // if !directBoxValueAccess, create a new box if required 85 | il.Emit(OpCodes.Unbox_Any, argType); 86 | if (argIsByRef) 87 | { 88 | // box back 89 | il.Emit(OpCodes.Box, argType); 90 | 91 | // store new box value address to local 0 92 | il.Emit(OpCodes.Dup); 93 | il.Emit(OpCodes.Unbox, argType); 94 | if (generateLocalBoxValuePtr) 95 | { 96 | generateLocalBoxValuePtr = false; 97 | // Yes, you're seeing this right - a local of type void* to store the box value address! 98 | il.DeclareLocal(typeof(void*), true); 99 | } 100 | il.Emit(OpCodes.Stloc_0); 101 | 102 | // arr and index set up already 103 | il.Emit(OpCodes.Stelem_Ref); 104 | 105 | // load address back to stack 106 | il.Emit(OpCodes.Ldloc_0); 107 | } 108 | } 109 | else 110 | { 111 | // if directBoxValueAccess, emit unbox (get value address) 112 | il.Emit(OpCodes.Unbox, argType); 113 | } 114 | } 115 | } 116 | } 117 | 118 | #pragma warning disable XS0001 119 | if (methodInfo.IsStatic) 120 | il.EmitCall(OpCodes.Call, methodInfo, null); 121 | else 122 | il.EmitCall(OpCodes.Callvirt, methodInfo, null); 123 | #pragma warning restore XS0001 124 | 125 | if (methodInfo.ReturnType == typeof(void)) 126 | il.Emit(OpCodes.Ldnull); 127 | else 128 | EmitBoxIfNeeded(il, methodInfo.ReturnType); 129 | 130 | il.Emit(OpCodes.Ret); 131 | 132 | var invoder = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler)); 133 | return invoder; 134 | } 135 | 136 | static void EmitCastToReference(ILGenerator il, Type type) 137 | { 138 | if (type.IsValueType) 139 | il.Emit(OpCodes.Unbox_Any, type); 140 | else 141 | il.Emit(OpCodes.Castclass, type); 142 | } 143 | 144 | static void EmitUnboxIfNeeded(ILGenerator il, Type type) 145 | { 146 | if (type.IsValueType) 147 | il.Emit(OpCodes.Unbox_Any, type); 148 | } 149 | 150 | static void EmitBoxIfNeeded(ILGenerator il, Type type) 151 | { 152 | if (type.IsValueType) 153 | il.Emit(OpCodes.Box, type); 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /scripts/MethodInvoker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cdf6629838e0f6d4ba814d70f1b0f921 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/NativeDll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace UnityNativeTool 5 | { 6 | public class NativeDll 7 | { 8 | public readonly string name; 9 | public string path; 10 | public IntPtr handle = IntPtr.Zero; 11 | public bool loadingError = false; 12 | public bool symbolError = false; 13 | public readonly List functions = new List(); 14 | 15 | public NativeDll(string name, string path) 16 | { 17 | this.name = name; 18 | this.path = path; 19 | } 20 | 21 | public void ResetAsUnloaded() 22 | { 23 | handle = IntPtr.Zero; 24 | loadingError = false; 25 | symbolError = false; 26 | 27 | foreach (var func in functions) 28 | { 29 | func.@delegate = null; 30 | } 31 | } 32 | } 33 | } 34 | 35 | namespace UnityNativeTool.Internal 36 | { 37 | public class NativeDllInfo 38 | { 39 | public string name; 40 | public string path; 41 | public bool isLoaded; 42 | public bool loadingError; 43 | public bool symbolError; 44 | public IList loadedFunctions; 45 | 46 | public NativeDllInfo(string name, string path, bool isLoaded, bool loadingError, bool symbolError, IList loadedFunctions) 47 | { 48 | this.name = name; 49 | this.path = path; 50 | this.isLoaded = isLoaded; 51 | this.loadingError = loadingError; 52 | this.symbolError = symbolError; 53 | this.loadedFunctions = loadedFunctions; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /scripts/NativeDll.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e31ba279e96a28a46a6d97f2a1499f92 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/NativeFunction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityNativeTool 4 | { 5 | public struct NativeFunctionIdentity 6 | { 7 | public string symbol; 8 | public string containingDllName; 9 | 10 | public NativeFunctionIdentity(string symbol, string containingDllName) 11 | { 12 | this.symbol = symbol; 13 | this.containingDllName = containingDllName; 14 | } 15 | 16 | public override bool Equals(object obj) 17 | { 18 | if (obj is NativeFunctionIdentity other) 19 | return symbol == other.symbol && containingDllName == other.containingDllName; 20 | 21 | return false; 22 | } 23 | 24 | public override int GetHashCode() 25 | { 26 | int h1 = symbol.GetHashCode(); 27 | int h2 = containingDllName.GetHashCode(); 28 | uint num = (uint)((h1 << 5) | (int)((uint)h1 >> 27)); 29 | return ((int)num + h1) ^ h2; 30 | } 31 | } 32 | 33 | public class NativeFunction 34 | { 35 | public readonly NativeFunctionIdentity identity; 36 | public NativeDll containingDll; 37 | public Type delegateType = null; 38 | public Delegate @delegate = null; 39 | 40 | public NativeFunction(string symbol, NativeDll containingDll) 41 | { 42 | this.identity = new NativeFunctionIdentity(symbol, containingDll.name); 43 | this.containingDll = containingDll; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /scripts/NativeFunction.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0cd4d21f295d9e84192725524d2c666f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/NativeFunctionSignature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace UnityNativeTool.Internal 8 | { 9 | internal class NativeFunctionSignature 10 | { 11 | public readonly NativeFunctionParameterSignature returnParameter; 12 | public readonly NativeFunctionParameterSignature[] parameters; 13 | public readonly CallingConvention callingConvention; 14 | public readonly bool bestFitMapping; 15 | public readonly CharSet charSet; 16 | public readonly bool setLastError; 17 | public readonly bool throwOnUnmappableChar; 18 | 19 | public NativeFunctionSignature(MethodInfo methodInfo, CallingConvention callingConvention, bool bestFitMapping, CharSet charSet, bool setLastError, bool throwOnUnmappableChar) 20 | { 21 | this.returnParameter = new NativeFunctionParameterSignature(methodInfo.ReturnParameter); 22 | this.parameters = methodInfo.GetParameters().Select(p => new NativeFunctionParameterSignature(p)).ToArray(); 23 | this.callingConvention = callingConvention; 24 | this.bestFitMapping = bestFitMapping; 25 | this.charSet = charSet; 26 | this.setLastError = setLastError; 27 | this.throwOnUnmappableChar = throwOnUnmappableChar; 28 | } 29 | 30 | public override bool Equals(object obj) 31 | { 32 | var other = obj as NativeFunctionSignature; 33 | if (other == null) 34 | return false; 35 | 36 | if(!returnParameter.Equals(other.returnParameter)) 37 | return false; 38 | 39 | if (!parameters.SequenceEqual(other.parameters)) 40 | return false; 41 | 42 | if (callingConvention != other.callingConvention) 43 | return false; 44 | 45 | if (bestFitMapping != other.bestFitMapping) 46 | return false; 47 | 48 | if (charSet != other.charSet) 49 | return false; 50 | 51 | if (setLastError != other.setLastError) 52 | return false; 53 | 54 | if (throwOnUnmappableChar != other.throwOnUnmappableChar) 55 | return false; 56 | 57 | return true; 58 | } 59 | 60 | public override int GetHashCode() 61 | { 62 | var hashCode = 316391695; 63 | hashCode = hashCode * -1521134295 + returnParameter.GetHashCode(); 64 | hashCode = hashCode * -1521134295 + callingConvention.GetHashCode(); 65 | hashCode = hashCode * -1521134295 + bestFitMapping.GetHashCode(); 66 | hashCode = hashCode * -1521134295 + charSet.GetHashCode(); 67 | hashCode = hashCode * -1521134295 + setLastError.GetHashCode(); 68 | hashCode = hashCode * -1521134295 + throwOnUnmappableChar.GetHashCode(); 69 | return hashCode; 70 | } 71 | } 72 | 73 | internal class NativeFunctionParameterSignature 74 | { 75 | public readonly Type type; 76 | public readonly ParameterAttributes parameterAttributes; 77 | public readonly Attribute[] customAttributes; 78 | 79 | public NativeFunctionParameterSignature(ParameterInfo parameterInfo) 80 | { 81 | this.type = parameterInfo.ParameterType; 82 | this.parameterAttributes = parameterInfo.Attributes; 83 | this.customAttributes = parameterInfo.GetCustomAttributes(false).OfType().ToArray(); // Do it this way to bypass Mono bug, see https://github.com/mono/mono/issues/16613 84 | } 85 | 86 | public NativeFunctionParameterSignature(Type type, ParameterAttributes parameterAttributes, Attribute[] customAttributes) 87 | { 88 | this.type = type; 89 | this.parameterAttributes = parameterAttributes; 90 | this.customAttributes = customAttributes; 91 | } 92 | 93 | public override bool Equals(object obj) 94 | { 95 | var other = obj as NativeFunctionParameterSignature; 96 | if(other == null) 97 | return false; 98 | 99 | if (type != other.type) 100 | return false; 101 | 102 | if (parameterAttributes != other.parameterAttributes) 103 | return false; 104 | 105 | if (customAttributes.Except(other.customAttributes).Any()) //Check if arrays have the same elements 106 | return false; 107 | 108 | return true; 109 | } 110 | 111 | public override int GetHashCode() 112 | { 113 | var hashCode = 424392846; 114 | hashCode = hashCode * -1521134295 + type.GetHashCode(); 115 | hashCode = hashCode * -1521134295 + parameterAttributes.GetHashCode(); 116 | return hashCode; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /scripts/NativeFunctionSignature.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9a12f146767c4d44daf22b332d539167 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/PInvokes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace UnityNativeTool.Internal 5 | { 6 | [DisableMocking] 7 | internal static class PInvokes_Windows 8 | { 9 | /// A bit-field of flags for protections 10 | [Flags] 11 | internal enum Protection 12 | { 13 | /// No access 14 | PAGE_NOACCESS = 0x01, 15 | /// Read only 16 | PAGE_READONLY = 0x02, 17 | /// Read write 18 | PAGE_READWRITE = 0x04, 19 | /// Write copy 20 | PAGE_WRITECOPY = 0x08, 21 | /// No access 22 | PAGE_EXECUTE = 0x10, 23 | /// Execute read 24 | PAGE_EXECUTE_READ = 0x20, 25 | /// Execute read write 26 | PAGE_EXECUTE_READWRITE = 0x40, 27 | /// Execute write copy 28 | PAGE_EXECUTE_WRITECOPY = 0x80, 29 | /// guard 30 | PAGE_GUARD = 0x100, 31 | /// No cache 32 | PAGE_NOCACHE = 0x200, 33 | /// Write combine 34 | PAGE_WRITECOMBINE = 0x400 35 | } 36 | 37 | [DllImport("kernel32")] 38 | public static extern IntPtr LoadLibrary(string lpFileName); 39 | 40 | [DllImport("kernel32")] 41 | [return: MarshalAs(UnmanagedType.Bool)] 42 | public static extern bool FreeLibrary(IntPtr hModule); 43 | 44 | [DllImport("kernel32")] 45 | public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); 46 | 47 | [DllImport("kernel32")] 48 | public static extern IntPtr GetCurrentProcess(); 49 | 50 | [DllImport("kernel32")] 51 | internal static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, Protection flNewProtect, out Protection lpflOldProtect); 52 | 53 | [DllImport("kernel32")] 54 | internal static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress, UIntPtr dwSize); 55 | } 56 | 57 | [DisableMocking] 58 | internal static class PInvokes_Linux 59 | { 60 | private const string LIB_DL = "libdl.so"; 61 | private const string LIB_C = "libc.so"; 62 | 63 | public const int _SC_PAGE_SIZE = 30; 64 | 65 | [Flags] 66 | internal enum Prot 67 | { 68 | /// page can be read 69 | PROT_READ = 0x1, 70 | /// page can be written 71 | PROT_WRITE = 0x2, 72 | /// page can be executed 73 | PROT_EXEC = 0x4, 74 | /// page may be used for atomic ops 75 | PROT_SEM = 0x8, 76 | /// page can not be accessed 77 | PROT_NONE = 0x0, 78 | /// extend change to start of growsdown vma 79 | PROT_GROWSDOWN = 0x01000000, 80 | /// extend change to end of growsup vma 81 | PROT_GROWSUP = 0x02000000, 82 | } 83 | 84 | 85 | [DllImport(LIB_DL)] 86 | public static extern IntPtr dlopen(string filename, int flags); 87 | 88 | [DllImport(LIB_DL)] 89 | public static extern IntPtr dlsym(IntPtr handle, string symbol); 90 | 91 | [DllImport(LIB_DL)] 92 | public static extern int dlclose(IntPtr handle); 93 | 94 | [DllImport(LIB_C)] 95 | public static extern int mprotect(IntPtr addr, UIntPtr len, Prot prot); 96 | 97 | [DllImport(LIB_C)] 98 | public static extern IntPtr sysconf(int name); 99 | } 100 | 101 | [DisableMocking] 102 | internal static class PInvokes_Osx 103 | { 104 | [DllImport("libdl.dylib")] 105 | public static extern IntPtr dlopen(string filename, int flags); 106 | 107 | [DllImport("libdl.dylib")] 108 | public static extern IntPtr dlsym(IntPtr handle, string symbol); 109 | 110 | [DllImport("libdl.dylib")] 111 | public static extern int dlclose(IntPtr handle); 112 | } 113 | 114 | public enum PosixDlopenFlags : int 115 | { 116 | Lazy = 0x00001, 117 | Now = 0x00002, 118 | Lazy_Global = 0x00100 | Lazy, 119 | Now_Global = 0x00100 | Now 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /scripts/PInvokes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 49722d5c37d8fb8429ea852f077bf1cb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/PathUtils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace UnityNativeTool.Internal 4 | { 5 | public static class PathUtils 6 | { 7 | /// 8 | /// Compares paths to dll file returned from and 9 | /// 10 | /// Value returned from 11 | /// Value returned from 12 | public static bool DllPathsEqual(string outputPath, string location) 13 | { 14 | return NormallizeUnityAssemblyPath(outputPath) == NormallizeSystemAssemblyPath(location); 15 | } 16 | 17 | /// 18 | /// Normalizes path returned from 19 | /// 20 | public static string NormallizeUnityAssemblyPath(string path) 21 | { 22 | return Path.GetFullPath(path).Replace('\\', '/'); 23 | } 24 | 25 | /// 26 | /// Normalizes path returned from 27 | /// 28 | public static string NormallizeSystemAssemblyPath(string path) 29 | { 30 | return path.Replace('\\', '/'); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/PathUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0f84112e0e879d340a8aa44bcfe5e9bd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /scripts/mcpiroman.UnityNativeTool.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcpiroman.UnityNativeTool", 3 | "references": [], 4 | "includePlatforms": [], 5 | "excludePlatforms": [], 6 | "allowUnsafeCode": true, 7 | "overrideReferences": false, 8 | "precompiledReferences": [], 9 | "autoReferenced": true, 10 | "defineConstraints": [], 11 | "versionDefines": [], 12 | "noEngineReferences": false 13 | } -------------------------------------------------------------------------------- /scripts/mcpiroman.UnityNativeTool.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 727eeb8221224d74aac364bbeda23b38 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /stubLluiPlugin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "IUnityInterface.h" 3 | 4 | static IUnityInterfaces* s_IUnityInterfaces = NULL; 5 | 6 | UNITY_INTERFACE_EXPORT IUnityInterfaces* UNITY_INTERFACE_API GetUnityInterfacesPtr() 7 | { 8 | return s_IUnityInterfaces; 9 | } 10 | 11 | UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) 12 | { 13 | s_IUnityInterfaces = unityInterfaces; 14 | } -------------------------------------------------------------------------------- /stubLluiPlugin.c.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c9016db8f5579a944b899ffb1a7473a9 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 1 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | : Any 16 | second: 17 | enabled: 0 18 | settings: 19 | Exclude Editor: 1 20 | Exclude Linux64: 1 21 | Exclude OSXUniversal: 1 22 | Exclude WebGL: 1 23 | Exclude Win: 1 24 | Exclude Win64: 1 25 | - first: 26 | Any: 27 | second: 28 | enabled: 0 29 | settings: {} 30 | - first: 31 | Editor: Editor 32 | second: 33 | enabled: 0 34 | settings: 35 | DefaultValueInitialized: true 36 | userData: 37 | assetBundleName: 38 | assetBundleVariant: 39 | -------------------------------------------------------------------------------- /tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1e6a333bf28048649b065654eb12ae8d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | --------------------------------------------------------------------------------