Skip to content

Commit 1b0bfa8

Browse files
authored
Initial source
1 parent c7aa392 commit 1b0bfa8

8 files changed

Lines changed: 984 additions & 0 deletions

src/DllLoadException.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
4+
namespace DllManipulator
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+
}

src/DllManipulator.cs

Lines changed: 443 additions & 0 deletions
Large diffs are not rendered by default.

src/Editor/DllManipulatorEditor.cs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using UnityEngine;
4+
using UnityEditor;
5+
6+
namespace DllManipulator
7+
{
8+
[CustomEditor(typeof(DllManipulator))]
9+
public class DllManipulatorEditor : Editor
10+
{
11+
private readonly GUIContent DLL_PATH_PATTERN_GUI_CONTENT = new GUIContent("DLL path pattern",
12+
"Available macros:\n\n" +
13+
$"{DllManipulator.DLL_PATH_PATTERN_NAME_MACRO} - name of DLL as specified in [DllImport] attribute.\n\n" +
14+
$"{DllManipulator.DLL_PATH_PATTERN_ASSETS_MACRO} - assets folder of current project.\n\n" +
15+
$"{DllManipulator.DLL_PATH_PATTERN_PROJECT_MACRO} - project folder i.e. one above Assets.");
16+
private readonly GUIContent DLL_LOADING_MODE_GUI_CONTENT = new GUIContent("DLL loading mode",
17+
"Specifies how DLLs and functions will be loaded.\n\n" +
18+
"Lazy - Easiest to use and most flexible way. All DLLs and functions are loaded as they're first called. This allows them to be easily unloaded and loaded within game execution.\n\n" +
19+
"Preloaded - Slight performance benefit over Lazy mode. All DLLs and functions are loaded at startup (OnEnable()). Calls to unloaded DLLs lead to crash, so in mid-execution it's safest to manipulate DLLs if game is paused.");
20+
private readonly GUIContent LINUX_DLOPEN_FLAGS_GUI_CONTENT = new GUIContent("dlopen flags",
21+
$"Flags used in dlopen() P/Invoke on Linux systems. Has minor meaning unless library is large.");
22+
private readonly GUIContent MOCK_ALL_NATIVE_FUNCTIONS_GUI_CONTENT = new GUIContent("Mock all native functions",
23+
$"If true, all native functions in current assembly will be mocked.\n\n" +
24+
$"If false, you have to use [{nameof(MockNativeDeclarationsAttribute)}] or [{nameof(MockNativeDeclarationAttribute)}] in order to select native functions to be mocked.");
25+
private readonly GUIContent MOCK_CALLS_IN_ALL_TYPES_GUI_CONTENT = new GUIContent("Mock native calls in all types",
26+
$"If true, calls of native functions in all methods in current assembly will be mocked. This however can cause significant performance issues at startup in big code base. You may use [{nameof(DisableMockingAttribute)}].\n\n" +
27+
$"If false, you have to use [{nameof(MockNativeCallsAttribute)}] in order to mock native function calls.");
28+
private readonly GUIContent UNLOAD_ALL_DLLS_IN_PLAY_PRELOADED_GUI_CONTENT = new GUIContent("Unload all DLLs [DANGEROUS!]",
29+
"Use only if you are sure no mocked native calls will be made while DLL is unloaded.");
30+
31+
private bool _showLoadedLibraries = true;
32+
33+
public DllManipulatorEditor()
34+
{
35+
EditorApplication.pauseStateChanged += _ => Repaint();
36+
EditorApplication.playModeStateChanged += _ => Repaint();
37+
}
38+
39+
public override void OnInspectorGUI()
40+
{
41+
var t = (DllManipulator)this.target;
42+
43+
t.Options.dllPathPattern = EditorGUILayout.TextField(DLL_PATH_PATTERN_GUI_CONTENT, t.Options.dllPathPattern);
44+
if (EditorApplication.isPlaying)
45+
{
46+
GUI.enabled = false;
47+
}
48+
t.Options.loadingMode = (DllLoadingMode)EditorGUILayout.EnumPopup(DLL_LOADING_MODE_GUI_CONTENT, t.Options.loadingMode);
49+
#if UNITY_STANDALONE_LINUX
50+
t.Options.linuxDlopenFlags = (LinuxDlopenFlags)EditorGUILayout.EnumPopup(LINUX_DLOPEN_FLAGS_GUI_CONTENT, t.Options.linuxDlopenFlags);
51+
#endif
52+
t.Options.mockAllNativeFunctions = EditorGUILayout.Toggle(MOCK_ALL_NATIVE_FUNCTIONS_GUI_CONTENT, t.Options.mockAllNativeFunctions);
53+
t.Options.mockCallsInAllTypes = EditorGUILayout.Toggle(MOCK_CALLS_IN_ALL_TYPES_GUI_CONTENT, t.Options.mockCallsInAllTypes);
54+
GUI.enabled = true;
55+
56+
EditorGUILayout.Space();
57+
58+
var usedDlls = DllManipulator.GetUsedDllsInfos();
59+
if (usedDlls.Count != 0)
60+
{
61+
if(t.Options.loadingMode == DllLoadingMode.Preload && usedDlls.Any(d => !d.isLoaded))
62+
{
63+
if (EditorApplication.isPaused)
64+
{
65+
if (GUILayout.Button("Load all DLLs & Unpause"))
66+
{
67+
DllManipulator.LoadAll();
68+
EditorApplication.isPaused = false;
69+
}
70+
}
71+
72+
if (GUILayout.Button("Load all DLLs"))
73+
{
74+
DllManipulator.LoadAll();
75+
}
76+
}
77+
else if(EditorApplication.isPaused)
78+
{
79+
if (GUILayout.Button("Unpause"))
80+
{
81+
EditorApplication.isPaused = false;
82+
}
83+
}
84+
85+
if (usedDlls.Any(d => d.isLoaded))
86+
{
87+
if (EditorApplication.isPlaying && !EditorApplication.isPaused)
88+
{
89+
if (GUILayout.Button("Unload all DLLs & Pause"))
90+
{
91+
EditorApplication.isPaused = true;
92+
DllManipulator.UnloadAll();
93+
}
94+
}
95+
96+
if(EditorApplication.isPlaying && !EditorApplication.isPaused && t.Options.loadingMode == DllLoadingMode.Preload)
97+
{
98+
if (GUILayout.Button(UNLOAD_ALL_DLLS_IN_PLAY_PRELOADED_GUI_CONTENT))
99+
{
100+
DllManipulator.UnloadAll();
101+
}
102+
}
103+
else
104+
{
105+
if (GUILayout.Button("Unload all DLLs"))
106+
{
107+
DllManipulator.UnloadAll();
108+
}
109+
}
110+
}
111+
112+
_showLoadedLibraries = EditorGUILayout.Foldout(_showLoadedLibraries, "Mocked DLLs");
113+
if (_showLoadedLibraries)
114+
{
115+
var prevIndent = EditorGUI.indentLevel;
116+
EditorGUI.indentLevel += 1;
117+
bool isFirstDll = true;
118+
foreach (var dll in usedDlls)
119+
{
120+
if (!isFirstDll)
121+
{
122+
EditorGUILayout.Space();
123+
}
124+
125+
var stateAttributes = new List<string>
126+
{
127+
dll.isLoaded ? "LOADED" : "NOT LOADED"
128+
};
129+
if (dll.loadingError)
130+
{
131+
stateAttributes.Add("LOAD ERROR");
132+
}
133+
if(dll.symbolError)
134+
{
135+
stateAttributes.Add("SYMBOL ERRROR");
136+
}
137+
var state = string.Join(" | ", stateAttributes);
138+
139+
EditorGUILayout.LabelField($"[{state}] {dll.name}");
140+
EditorGUILayout.LabelField(dll.path);
141+
isFirstDll = false;
142+
}
143+
EditorGUI.indentLevel = prevIndent;
144+
}
145+
}
146+
else if(EditorApplication.isPlaying)
147+
{
148+
GUILayout.BeginHorizontal();
149+
GUILayout.FlexibleSpace();
150+
EditorGUILayout.LabelField("No DLLs to mock");
151+
GUILayout.FlexibleSpace();
152+
GUILayout.EndHorizontal();
153+
}
154+
}
155+
}
156+
}

src/IlGeneratorExtensions.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Reflection.Emit;
2+
3+
namespace DllManipulator.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+
{
67+
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
68+
}
69+
else
70+
{
71+
il.Emit(OpCodes.Ldc_I4, value);
72+
}
73+
}
74+
}
75+
}

src/MockAttributes.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
3+
namespace DllManipulator
4+
{
5+
/// <summary>
6+
/// Calls of all native functions declared inside types with this attributes may be mocked. This attribute is redundant if <see cref="DllManipulatorOptions.mockAllNativeFunctions"/> is true.
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
9+
public class MockNativeDeclarationsAttribute : Attribute
10+
{
11+
12+
}
13+
14+
/// <summary>
15+
/// Calls of native functions with this attribute may be mocked. This attribute is redundant if <see cref="DllManipulatorOptions.mockAllNativeFunctions"/> is true.
16+
/// </summary>
17+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
18+
public class MockNativeDeclarationAttribute : Attribute
19+
{
20+
21+
}
22+
23+
/// <summary>
24+
/// Calls of suitably tagged native functions in all methods declared inside types with this attribute will be mocked. This attribute is redundant if <see cref="DllManipulatorOptions.mockCallsInAllTypes"/> is true.
25+
/// </summary>
26+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
27+
public class MockNativeCallsAttribute : Attribute
28+
{
29+
30+
}
31+
32+
/// <summary>
33+
/// Calls of native functions with this attribute won't be mocked.
34+
/// </summary>
35+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
36+
public class DisableMockingAttribute : Attribute
37+
{
38+
39+
}
40+
}

src/NativeDll.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace DllManipulator.Internal
5+
{
6+
internal 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<NativeFunction> functions = new List<NativeFunction>();
14+
15+
public NativeDll(string name, string path)
16+
{
17+
this.name = name;
18+
this.path = path;
19+
}
20+
}
21+
}
22+
23+
namespace DllManipulator
24+
{
25+
public class NativeDllInfo
26+
{
27+
public string name;
28+
public string path;
29+
public bool isLoaded;
30+
public bool loadingError;
31+
public bool symbolError;
32+
public IList<string> loadedFunctions;
33+
34+
public NativeDllInfo(string name, string path, bool isLoaded, bool loadingError, bool symbolError, IList<string> loadedFunctions)
35+
{
36+
this.name = name;
37+
this.path = path;
38+
this.isLoaded = isLoaded;
39+
this.loadingError = loadingError;
40+
this.symbolError = symbolError;
41+
this.loadedFunctions = loadedFunctions;
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)