Skip to content

Commit 515f730

Browse files
committed
feat: Add Unity Engine Integration with IL2CPP support (#231)
1 parent 67c655b commit 515f730

9 files changed

Lines changed: 455 additions & 0 deletions

File tree

src/BitMono.Host/Extensions/AutofacContainerBuilderExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@ namespace BitMono.Host.Extensions;
55
public static class AutofacContainerBuilderExtensions
66
{
77
private const string ProtectionsFileName = "BitMono.Protections.dll";
8+
private const string UnityFileName = "BitMono.Unity.dll";
89

910
public static ContainerBuilder AddProtections(this ContainerBuilder source, string? file = null)
1011
{
1112
var protectionsFilePath = file ?? Path.Combine(AppContext.BaseDirectory, ProtectionsFileName);
1213
var rawData = File.ReadAllBytes(file ?? protectionsFilePath);
1314
Assembly.Load(rawData);
15+
16+
var unityFilePath = Path.Combine(AppContext.BaseDirectory, UnityFileName);
17+
if (File.Exists(unityFilePath))
18+
{
19+
var unityRawData = File.ReadAllBytes(unityFilePath);
20+
Assembly.Load(unityRawData);
21+
}
1422

1523
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
1624
source.RegisterAssemblyTypes(assemblies)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net9.0;net8.0;net7.0;net6.0;net462;netstandard2.1;netstandard2.0;</TargetFrameworks>
5+
<LangVersion>latest</LangVersion>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<Import Project="$(MSBuildThisFileDirectory)..\..\props\SharedProjectProps.props" />
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\BitMono.API\BitMono.API.csproj" />
13+
<ProjectReference Include="..\BitMono.Core\BitMono.Core.csproj" />
14+
<ProjectReference Include="..\BitMono.Protections\BitMono.Protections.csproj" />
15+
<ProjectReference Include="..\BitMono.Runtime\BitMono.Runtime.csproj" />
16+
<ProjectReference Include="..\BitMono.Utilities\BitMono.Utilities.csproj" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="NullGuard.Fody" Version="3.1.1" PrivateAssets="All" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<Using Include="System" />
25+
<Using Include="System.Collections.Generic" />
26+
<Using Include="System.Linq" />
27+
<Using Include="System.Threading.Tasks" />
28+
</ItemGroup>
29+
30+
</Project>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#if UNITY_EDITOR
2+
using UnityEditor;
3+
using UnityEditor.Build;
4+
using UnityEditor.Build.Reporting;
5+
using UnityEngine;
6+
using System.IO;
7+
using System.Diagnostics;
8+
9+
namespace BitMono.Unity.Editor
10+
{
11+
public class BitMonoBuildProcessor : IPreprocessBuildWithReport
12+
{
13+
public int callbackOrder => 0;
14+
15+
public void OnPreprocessBuild(BuildReport report)
16+
{
17+
if (!EditorPrefs.GetBool("BitMono.AutoObfuscate", false))
18+
return;
19+
20+
UnityEngine.Debug.Log("[BitMono] Starting pre-build obfuscation...");
21+
22+
var outputPath = report.summary.outputPath;
23+
var platform = report.summary.platform;
24+
25+
if (platform == BuildTarget.iOS || platform == BuildTarget.Android)
26+
{
27+
ObfuscateForIL2CPP();
28+
}
29+
else
30+
{
31+
ObfuscateForMono();
32+
}
33+
}
34+
35+
private void ObfuscateForIL2CPP()
36+
{
37+
UnityEngine.Debug.Log("[BitMono] Obfuscating for IL2CPP...");
38+
var assemblies = Directory.GetFiles(Path.Combine(Application.dataPath, "..", "Temp", "StagingArea", "Data", "Managed"), "*.dll");
39+
40+
foreach (var assembly in assemblies)
41+
{
42+
if (assembly.Contains("Assembly-CSharp") || assembly.Contains("Assembly-UnityScript"))
43+
{
44+
RunBitMono(assembly, true);
45+
}
46+
}
47+
}
48+
49+
private void ObfuscateForMono()
50+
{
51+
UnityEngine.Debug.Log("[BitMono] Obfuscating for Mono...");
52+
var assemblyPath = Path.Combine(Application.dataPath, "..", "Library", "ScriptAssemblies", "Assembly-CSharp.dll");
53+
54+
if (File.Exists(assemblyPath))
55+
{
56+
RunBitMono(assemblyPath, false);
57+
}
58+
}
59+
60+
private void RunBitMono(string assemblyPath, bool il2cpp)
61+
{
62+
var arguments = $"--file \"{assemblyPath}\" --unity";
63+
if (il2cpp)
64+
arguments += " --il2cpp";
65+
66+
var process = new Process
67+
{
68+
StartInfo = new ProcessStartInfo
69+
{
70+
FileName = "bitmono",
71+
Arguments = arguments,
72+
UseShellExecute = false,
73+
RedirectStandardOutput = true,
74+
RedirectStandardError = true,
75+
CreateNoWindow = true
76+
}
77+
};
78+
79+
process.Start();
80+
process.WaitForExit();
81+
82+
if (process.ExitCode != 0)
83+
{
84+
var error = process.StandardError.ReadToEnd();
85+
UnityEngine.Debug.LogError($"[BitMono] Obfuscation failed: {error}");
86+
}
87+
else
88+
{
89+
UnityEngine.Debug.Log($"[BitMono] Successfully obfuscated: {Path.GetFileName(assemblyPath)}");
90+
}
91+
}
92+
}
93+
}
94+
#endif
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#if UNITY_EDITOR
2+
using UnityEngine;
3+
using UnityEditor;
4+
using System.Diagnostics;
5+
using System.IO;
6+
7+
namespace BitMono.Unity.Editor
8+
{
9+
public static class BitMonoMenu
10+
{
11+
[MenuItem("Tools/BitMono/Obfuscate Before Build")]
12+
public static void ObfuscateBeforeBuild()
13+
{
14+
var assemblyPath = Path.Combine(Application.dataPath, "..", "Library", "ScriptAssemblies", "Assembly-CSharp.dll");
15+
16+
if (!File.Exists(assemblyPath))
17+
{
18+
EditorUtility.DisplayDialog("BitMono", "Assembly-CSharp.dll not found. Please build your project first.", "OK");
19+
return;
20+
}
21+
22+
RunObfuscation(assemblyPath);
23+
}
24+
25+
[MenuItem("Tools/BitMono/Settings")]
26+
public static void OpenSettings()
27+
{
28+
EditorUtility.DisplayDialog("BitMono Settings", "Configure obfuscation settings in BitMono.json", "OK");
29+
}
30+
31+
private static void RunObfuscation(string assemblyPath)
32+
{
33+
var process = new Process
34+
{
35+
StartInfo = new ProcessStartInfo
36+
{
37+
FileName = "bitmono",
38+
Arguments = $"--file \"{assemblyPath}\" --unity --il2cpp",
39+
UseShellExecute = false,
40+
RedirectStandardOutput = true,
41+
CreateNoWindow = true
42+
}
43+
};
44+
45+
process.Start();
46+
var output = process.StandardOutput.ReadToEnd();
47+
process.WaitForExit();
48+
49+
if (process.ExitCode == 0)
50+
{
51+
EditorUtility.DisplayDialog("BitMono", "Obfuscation completed successfully!", "OK");
52+
}
53+
else
54+
{
55+
EditorUtility.DisplayDialog("BitMono Error", $"Obfuscation failed: {output}", "OK");
56+
}
57+
}
58+
}
59+
}
60+
#endif

src/BitMono.Unity/FodyWeavers.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
2+
<NullGuard />
3+
</Weavers>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "BitMono.Unity.Runtime",
3+
"references": [],
4+
"includePlatforms": [],
5+
"excludePlatforms": [],
6+
"allowUnsafeCode": false,
7+
"overrideReferences": false,
8+
"precompiledReferences": [],
9+
"autoReferenced": true,
10+
"defineConstraints": [],
11+
"versionDefines": [],
12+
"noEngineReferences": false
13+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using AsmResolver.DotNet;
5+
using BitMono.Core.Contexts;
6+
7+
namespace BitMono.Unity;
8+
9+
public class UnityIntegrationModule
10+
{
11+
private readonly ProtectionContext _context;
12+
private readonly UnityMethodDetector _methodDetector;
13+
14+
public UnityIntegrationModule(ProtectionContext context)
15+
{
16+
_context = context ?? throw new ArgumentNullException(nameof(context));
17+
_methodDetector = new UnityMethodDetector();
18+
}
19+
20+
public bool IsUnityAssembly(ModuleDefinition module)
21+
{
22+
var hasUnityEngine = module.AssemblyReferences.Any(a =>
23+
a.Name?.ToString()?.IndexOf("UnityEngine", StringComparison.OrdinalIgnoreCase) >= 0);
24+
25+
var isAssemblyCSharp = module.Assembly?.Name?.ToString()?.IndexOf("Assembly-CSharp", StringComparison.OrdinalIgnoreCase) >= 0;
26+
27+
var hasMonoBehaviour = module.GetAllTypes().Any(t =>
28+
t.BaseType?.FullName?.Contains("UnityEngine.MonoBehaviour") == true);
29+
30+
return hasUnityEngine || isAssemblyCSharp || hasMonoBehaviour;
31+
}
32+
33+
public void PrepareForIL2CPP(ModuleDefinition module)
34+
{
35+
foreach (var type in module.GetAllTypes())
36+
{
37+
if (IsUnityType(type))
38+
{
39+
PreserveUnityMetadata(type);
40+
}
41+
}
42+
}
43+
44+
private bool IsUnityType(TypeDefinition type)
45+
{
46+
var unityBaseTypes = new[]
47+
{
48+
"UnityEngine.MonoBehaviour",
49+
"UnityEngine.ScriptableObject",
50+
"UnityEngine.Component",
51+
"UnityEditor.Editor",
52+
"UnityEditor.EditorWindow"
53+
};
54+
55+
var currentType = type;
56+
while (currentType != null)
57+
{
58+
if (unityBaseTypes.Contains(currentType.BaseType?.FullName))
59+
return true;
60+
61+
currentType = currentType.BaseType?.Resolve();
62+
}
63+
64+
return false;
65+
}
66+
67+
private void PreserveUnityMetadata(TypeDefinition type)
68+
{
69+
type.IsPublic = true;
70+
}
71+
72+
public bool ShouldExcludeFromRenaming(MethodDefinition method)
73+
{
74+
return _methodDetector.IsUnityCallback(method) ||
75+
_methodDetector.IsSerializationCallback(method) ||
76+
_methodDetector.IsCoroutine(method);
77+
}
78+
}
79+
80+
public class UnityMethodDetector
81+
{
82+
private readonly HashSet<string> _unityCallbacks = new()
83+
{
84+
"Awake", "Start", "Update", "FixedUpdate", "LateUpdate",
85+
"OnEnable", "OnDisable", "OnDestroy", "OnApplicationPause",
86+
"OnApplicationFocus", "OnApplicationQuit",
87+
"OnCollisionEnter", "OnCollisionStay", "OnCollisionExit",
88+
"OnCollisionEnter2D", "OnCollisionStay2D", "OnCollisionExit2D",
89+
"OnTriggerEnter", "OnTriggerStay", "OnTriggerExit",
90+
"OnTriggerEnter2D", "OnTriggerStay2D", "OnTriggerExit2D",
91+
"OnBecameVisible", "OnBecameInvisible",
92+
"OnPreCull", "OnPreRender", "OnPostRender",
93+
"OnRenderObject", "OnWillRenderObject",
94+
"OnGUI", "OnMouseDown", "OnMouseUp", "OnMouseOver",
95+
"OnMouseExit", "OnMouseEnter", "OnMouseDrag",
96+
"OnAnimatorMove", "OnAnimatorIK",
97+
"OnStartServer", "OnStopServer", "OnStartClient", "OnStopClient",
98+
"OnServerConnect", "OnServerDisconnect", "OnClientConnect", "OnClientDisconnect"
99+
};
100+
101+
private readonly HashSet<string> _serializationCallbacks = new()
102+
{
103+
"OnBeforeSerialize", "OnAfterDeserialize",
104+
"OnValidate", "Reset"
105+
};
106+
107+
public bool IsUnityCallback(MethodDefinition method)
108+
{
109+
var methodName = method.Name?.ToString();
110+
return !string.IsNullOrEmpty(methodName) && _unityCallbacks.Contains(methodName);
111+
}
112+
113+
public bool IsSerializationCallback(MethodDefinition method)
114+
{
115+
var methodName = method.Name?.ToString();
116+
return !string.IsNullOrEmpty(methodName) && _serializationCallbacks.Contains(methodName);
117+
}
118+
119+
public bool IsCoroutine(MethodDefinition method)
120+
{
121+
return method.Signature?.ReturnType?.FullName?.IndexOf("System.Collections.IEnumerator") >= 0;
122+
}
123+
}

0 commit comments

Comments
 (0)