diff --git a/Assets/00.StaryEvo/Runtime/Tool/EventKit/EventSystem/EnumEventSystem.cs b/Assets/00.StaryEvo/Runtime/Tool/EventKit/EventSystem/EnumEventSystem.cs index 66482f1..77a89a4 100644 --- a/Assets/00.StaryEvo/Runtime/Tool/EventKit/EventSystem/EnumEventSystem.cs +++ b/Assets/00.StaryEvo/Runtime/Tool/EventKit/EventSystem/EnumEventSystem.cs @@ -8,7 +8,7 @@ namespace Stary.Evo { public static readonly EnumEventSystem Global = new EnumEventSystem(); - private readonly Dictionary mEvents = new Dictionary(50); + private readonly Dictionary mEvents = new Dictionary(50); public EnumEventSystem(){} @@ -16,7 +16,7 @@ namespace Stary.Evo public IUnRegister Register(T key, Action onEvent)where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { var easyEvent = e.As(); @@ -31,7 +31,7 @@ namespace Stary.Evo } public IUnRegister Register(T key, Action onEvent)where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { var easyEvent = e.As>(); @@ -46,7 +46,7 @@ namespace Stary.Evo } public IUnRegister Register(T key, Action onEvent)where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { var easyEvent = e.As>(); @@ -61,7 +61,7 @@ namespace Stary.Evo } public IUnRegister Register(T key, Action onEvent) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -77,7 +77,7 @@ namespace Stary.Evo } public IUnRegister Register(T key, Action onEvent) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -93,7 +93,7 @@ namespace Stary.Evo } public void UnRegister(T key, Action onEvent) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -102,7 +102,7 @@ namespace Stary.Evo } public void UnRegister(T key, Action onEvent) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -111,7 +111,7 @@ namespace Stary.Evo } public void UnRegister(T key, Action onEvent) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -120,7 +120,7 @@ namespace Stary.Evo } public void UnRegister(T key, Action onEvent) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -129,7 +129,7 @@ namespace Stary.Evo } public void UnRegister(T key, Action onEvent) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -145,7 +145,7 @@ namespace Stary.Evo } public void Send(T key) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -154,7 +154,7 @@ namespace Stary.Evo } public void Send(T key, T1 t1) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -163,7 +163,7 @@ namespace Stary.Evo } public void Send(T key, T1 t1,T2 t2) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -172,7 +172,7 @@ namespace Stary.Evo } public void Send(T key, T1 t1,T2 t2 ,T3 t3) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { @@ -181,11 +181,11 @@ namespace Stary.Evo } public void Send(T key, params object[] args) where T : IConvertible { - var kv = key.ToInt32(null); + var kv = typeof(T).ToString()+ key.ToInt32(null); if (mEvents.TryGetValue(kv, out var e)) { - e.As>().Trigger(kv,args); + e.As>().Trigger(kv,args); } } diff --git a/Assets/00.StaryEvo/Runtime/Utility/RemoteServices.cs b/Assets/00.StaryEvo/Runtime/Utility/RemoteServices.cs deleted file mode 100644 index 7494e70..0000000 --- a/Assets/00.StaryEvo/Runtime/Utility/RemoteServices.cs +++ /dev/null @@ -1,24 +0,0 @@ -using YooAsset; - -namespace Stary.Evo -{ - public class RemoteServices : IRemoteServices - { - private readonly string _defaultHostServer; - private readonly string _fallbackHostServer; - - public RemoteServices(string defaultHostServer, string fallbackHostServer) - { - _defaultHostServer = defaultHostServer; - _fallbackHostServer = fallbackHostServer; - } - string IRemoteServices.GetRemoteMainURL(string fileName) - { - return $"{_defaultHostServer}/{fileName}"; - } - string IRemoteServices.GetRemoteFallbackURL(string fileName) - { - return $"{_fallbackHostServer}/{fileName}"; - } - } -} \ No newline at end of file diff --git a/Assets/00.StaryEvo/Runtime/Utility/RemoteServices.cs.meta b/Assets/00.StaryEvo/Runtime/Utility/RemoteServices.cs.meta deleted file mode 100644 index c1c1d57..0000000 --- a/Assets/00.StaryEvo/Runtime/Utility/RemoteServices.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 24f14ead8b7e46eaa0eddd6e68db8d56 -timeCreated: 1741248220 \ No newline at end of file diff --git a/Assets/00.StaryEvo/Runtime/Utility/RemoteServicesWithAuth.cs b/Assets/00.StaryEvo/Runtime/Utility/RemoteServicesWithAuth.cs deleted file mode 100644 index 5de6214..0000000 --- a/Assets/00.StaryEvo/Runtime/Utility/RemoteServicesWithAuth.cs +++ /dev/null @@ -1,51 +0,0 @@ -using YooAsset; -using System.Collections; -using Stary.Evo; -using UnityEngine.Networking; - -public class RemoteServicesWithAuth : IRemoteServices -{ - private readonly string _defaultHostServer; - private readonly string _fallbackHostServer; - private string _authToken; - private string _authServiceUrl; - - public string CurrentToken { get; private set; } - - public RemoteServicesWithAuth(string defaultHostServer, - string fallbackHostServer, - string authToken, - string authServiceUrl) - { - _defaultHostServer = defaultHostServer; - _fallbackHostServer = fallbackHostServer; - _authToken = authToken; - _authServiceUrl = authServiceUrl; - RefreshToken(); - } - - private void RefreshToken() - { - // 这里实现具体的token刷新逻辑 - CurrentToken = _authToken; // 简单示例直接使用初始token - // 实际项目应该通过authServiceUrl获取新token - } - - string IRemoteServices.GetRemoteMainURL(string fileName) - { - // 在原始URL后附加鉴权参数 - return $"{_defaultHostServer}/{fileName}?token={CurrentToken}"; - } - - public string GetRemoteFallbackURL(string fileName) - { - return $"{_fallbackHostServer}/{fileName}?token={CurrentToken}"; - } - - // UnityWebRequest CreateRequest(string url) - // { - // var request = base.CreateRequest(url); - // request.SetRequestHeader("Authorization", $"Bearer {CurrentToken}"); - // return request; - // } -} diff --git a/Assets/00.StaryEvo/Runtime/Utility/RemoteServicesWithAuth.cs.meta b/Assets/00.StaryEvo/Runtime/Utility/RemoteServicesWithAuth.cs.meta deleted file mode 100644 index cdc26a3..0000000 --- a/Assets/00.StaryEvo/Runtime/Utility/RemoteServicesWithAuth.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 6dc545bfe9be45789ff05152e3679e20 -timeCreated: 1741319247 \ No newline at end of file diff --git a/Assets/00.StaryEvo/Runtime/Utility/UnityAsyncExtensions.cs b/Assets/00.StaryEvo/Runtime/Utility/UnityAsyncExtensions.cs new file mode 100644 index 0000000..260f773 --- /dev/null +++ b/Assets/00.StaryEvo/Runtime/Utility/UnityAsyncExtensions.cs @@ -0,0 +1,67 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +#if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT) +using UnityEngine.Networking; +#endif + +namespace Stary.Evo +{ + public static partial class UnityAsyncExtensions + { + + +#if !UNITY_2023_1_OR_NEWER + // from Unity2023.1.0a15, AsyncOperationAwaitableExtensions.GetAwaiter is defined in UnityEngine. + public static AsyncOperationAwaiter GetAwaiter(this AsyncOperation asyncOperation) + { + return new AsyncOperationAwaiter(asyncOperation); + } +#endif + + + public struct AsyncOperationAwaiter : ICriticalNotifyCompletion + { + AsyncOperation asyncOperation; + Action continuationAction; + + public AsyncOperationAwaiter(AsyncOperation asyncOperation) + { + this.asyncOperation = asyncOperation; + this.continuationAction = null; + } + + public bool IsCompleted => asyncOperation.isDone; + + public void GetResult() + { + if (continuationAction != null) + { + asyncOperation.completed -= continuationAction; + continuationAction = null; + asyncOperation = null; + } + else + { + asyncOperation = null; + } + } + + public void OnCompleted(Action continuation) + { + UnsafeOnCompleted(continuation); + } + + public void UnsafeOnCompleted(Action continuation) + { + + } + } + + + } +} \ No newline at end of file diff --git a/Assets/00.StaryEvo/Runtime/Utility/UnityAsyncExtensions.cs.meta b/Assets/00.StaryEvo/Runtime/Utility/UnityAsyncExtensions.cs.meta new file mode 100644 index 0000000..3e053f9 --- /dev/null +++ b/Assets/00.StaryEvo/Runtime/Utility/UnityAsyncExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a42a4edb452e470b87e698b7d7b7db4a +timeCreated: 1743403227 \ No newline at end of file diff --git a/Assets/00.StaryEvo/Runtime/Utility/WebRequestSystem.cs b/Assets/00.StaryEvo/Runtime/Utility/WebRequestSystem.cs index f2eddba..bd6bfa7 100644 --- a/Assets/00.StaryEvo/Runtime/Utility/WebRequestSystem.cs +++ b/Assets/00.StaryEvo/Runtime/Utility/WebRequestSystem.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Cysharp.Threading.Tasks; +using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; @@ -8,8 +8,8 @@ namespace Stary.Evo { public interface IWebRequestSystem { - UniTask Post(string url, string postData); - UniTask Get(string url, string token=null); + Task Post(string url, string postData); + Task Get(string url, string token=null); } @@ -21,7 +21,7 @@ namespace Stary.Evo /// 获取Token值的服务URL地址(很重要) /// 传入请求的参数,此处参数为JOSN格式 /// - public async UniTask Post(string url, string postData) + public async Task Post(string url, string postData) { using (UnityWebRequest webRequest = UnityWebRequest.Post(url, postData)) //第二种写法此行注释 { @@ -51,7 +51,7 @@ namespace Stary.Evo /// 请求数据的URL地址 /// token验证的参数,此处为authorization /// - public async UniTask Get(string url, string token=null) + public async Task Get(string url, string token=null) { try { diff --git a/Assets/00.StaryEvo/package.json b/Assets/00.StaryEvo/package.json index 9771a97..5713360 100644 --- a/Assets/00.StaryEvo/package.json +++ b/Assets/00.StaryEvo/package.json @@ -1,6 +1,6 @@ { "name": "com.staryevo.main", - "version": "1.0.0", + "version": "1.0.1", "displayName": "00.StaryEvo", "description": "This is an Framework package", "unity": "2021.3", diff --git a/Assets/00.StaryEvo/~Samples.meta b/Assets/00.StaryEvo/~Samples.meta new file mode 100644 index 0000000..a304446 --- /dev/null +++ b/Assets/00.StaryEvo/~Samples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 50c0b79ec6a68a24cb577c05cefb670d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.0.dll b/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.0.dll new file mode 100644 index 0000000..250cf81 Binary files /dev/null and b/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.0.dll differ diff --git a/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.1.dll b/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.1.dll new file mode 100644 index 0000000..99d1889 Binary files /dev/null and b/Packages/com.code-philosophy.hybridclr/Data~/NetStandard/netstandard2.1.dll differ diff --git a/Packages/com.code-philosophy.hybridclr/Data~/hybridclr_version.json b/Packages/com.code-philosophy.hybridclr/Data~/hybridclr_version.json new file mode 100644 index 0000000..6a1e92e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Data~/hybridclr_version.json @@ -0,0 +1,19 @@ +{ + "versions": [ + { + "unity_version":"2020", + "hybridclr" : { "branch":"v4.0.10"}, + "il2cpp_plus": { "branch":"v2020-4.0.8"} + }, + { + "unity_version":"2021", + "hybridclr" : { "branch":"v4.0.10"}, + "il2cpp_plus": { "branch":"v2021-4.0.8"} + }, + { + "unity_version":"2022", + "hybridclr" : { "branch":"v4.0.10"}, + "il2cpp_plus": { "branch":"v2022-4.0.8"} + } + ] +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor.meta b/Packages/com.code-philosophy.hybridclr/Editor.meta new file mode 100644 index 0000000..003696a --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3fabc41cf17c444995fc01a76c5dbe6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds.meta new file mode 100644 index 0000000..5f5d553 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: daa1e09af240aae4da0741843cb2b3f3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook.meta new file mode 100644 index 0000000..4fab614 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 13fe0cab0b357464d889de45c8d98850 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs new file mode 100644 index 0000000..6ec2fa7 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs @@ -0,0 +1,320 @@ +using DotNetDetour; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System.Linq; + +namespace MonoHook +{ + public unsafe abstract class CodePatcher + { + public bool isValid { get; protected set; } + + protected void* _pTarget, _pReplace, _pProxy; + protected int _jmpCodeSize; + protected byte[] _targetHeaderBackup; + + public CodePatcher(IntPtr target, IntPtr replace, IntPtr proxy, int jmpCodeSize) + { + _pTarget = target.ToPointer(); + _pReplace = replace.ToPointer(); + _pProxy = proxy.ToPointer(); + _jmpCodeSize = jmpCodeSize; + } + + public void ApplyPatch() + { + BackupHeader(); + EnableAddrModifiable(); + PatchTargetMethod(); + PatchProxyMethod(); + FlushICache(); + } + + public void RemovePatch() + { + if (_targetHeaderBackup == null) + return; + + EnableAddrModifiable(); + RestoreHeader(); + FlushICache(); + } + + protected void BackupHeader() + { + if (_targetHeaderBackup != null) + return; + + uint requireSize = LDasm.SizeofMinNumByte(_pTarget, _jmpCodeSize); + _targetHeaderBackup = new byte[requireSize]; + + fixed (void* ptr = _targetHeaderBackup) + HookUtils.MemCpy(ptr, _pTarget, _targetHeaderBackup.Length); + } + + protected void RestoreHeader() + { + if (_targetHeaderBackup == null) + return; + + HookUtils.MemCpy_Jit(_pTarget, _targetHeaderBackup); + } + + protected void PatchTargetMethod() + { + byte[] buff = GenJmpCode(_pTarget, _pReplace); + HookUtils.MemCpy_Jit(_pTarget, buff); + } + protected void PatchProxyMethod() + { + if (_pProxy == null) + return; + + // copy target's code to proxy + HookUtils.MemCpy_Jit(_pProxy, _targetHeaderBackup); + + // jmp to target's new position + long jmpFrom = (long)_pProxy + _targetHeaderBackup.Length; + long jmpTo = (long)_pTarget + _targetHeaderBackup.Length; + + byte[] buff = GenJmpCode((void*)jmpFrom, (void*)jmpTo); + HookUtils.MemCpy_Jit((void*)jmpFrom, buff); + } + + protected void FlushICache() + { + HookUtils.FlushICache(_pTarget, _targetHeaderBackup.Length); + HookUtils.FlushICache(_pProxy, _targetHeaderBackup.Length * 2); + } + protected abstract byte[] GenJmpCode(void* jmpFrom, void* jmpTo); + +#if ENABLE_HOOK_DEBUG + protected string PrintAddrs() + { + if (IntPtr.Size == 4) + return $"target:0x{(uint)_pTarget:x}, replace:0x{(uint)_pReplace:x}, proxy:0x{(uint)_pProxy:x}"; + else + return $"target:0x{(ulong)_pTarget:x}, replace:0x{(ulong)_pReplace:x}, proxy:0x{(ulong)_pProxy:x}"; + } +#endif + + private void EnableAddrModifiable() + { + HookUtils.SetAddrFlagsToRWX(new IntPtr(_pTarget), _targetHeaderBackup.Length); + HookUtils.SetAddrFlagsToRWX(new IntPtr(_pProxy), _targetHeaderBackup.Length + _jmpCodeSize); + } + } + + public unsafe class CodePatcher_x86 : CodePatcher + { + protected static readonly byte[] s_jmpCode = new byte[] // 5 bytes + { + 0xE9, 0x00, 0x00, 0x00, 0x00, // jmp $val ; $val = $dst - $src - 5 + }; + + public CodePatcher_x86(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) { } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + int val = (int)jmpTo - (int)jmpFrom - 5; + + fixed(void * p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr = 0xE9; + int* pOffset = (int*)(ptr + 1); + *pOffset = val; + } + return ret; + } + } + + /// + /// x64下2G 内的跳转 + /// + public unsafe class CodePatcher_x64_near : CodePatcher_x86 // x64_near pathcer code is same to x86 + { + public CodePatcher_x64_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy) { } + } + + /// + /// x64下距离超过2G的跳转 + /// + public unsafe class CodePatcher_x64_far : CodePatcher + { + protected static readonly byte[] s_jmpCode = new byte[] // 12 bytes + { + // 由于 rax 会被函数作为返回值修改,并且不会被做为参数使用,因此修改是安全的 + 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, + 0x50, // push rax + 0xC3 // ret + }; + + //protected static readonly byte[] s_jmpCode2 = new byte[] // 14 bytes + //{ + // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + // 0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF // jmp [rip - 0xe] + //}; + + public CodePatcher_x64_far(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) { } + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + + fixed (void* p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr++ = 0x48; + *ptr++ = 0xB8; + *(long*)ptr = (long)jmpTo; + ptr += 8; + *ptr++ = 0x50; + *ptr++ = 0xC3; + } + return ret; + } + } + + public unsafe class CodePatcher_arm32_near : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 4 bytes + { + 0x00, 0x00, 0x00, 0xEA, // B $val ; $val = (($dst - $src) / 4 - 2) & 0x1FFFFFF + }; + + public CodePatcher_arm32_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) + { + if (Math.Abs((long)target - (long)replace) >= ((1 << 25) - 1)) + throw new ArgumentException("address offset of target and replace must less than ((1 << 25) - 1)"); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"CodePatcher_arm32_near: {PrintAddrs()}"); +#endif + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + int val = ((int)jmpTo - (int)jmpFrom) / 4 - 2; + + fixed (void* p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr++ = (byte)val; + *ptr++ = (byte)(val >> 8); + *ptr++ = (byte)(val >> 16); + *ptr++ = 0xEA; + } + return ret; + } + } + + public unsafe class CodePatcher_arm32_far : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 8 bytes + { + 0x04, 0xF0, 0x1F, 0xE5, // LDR PC, [PC, #-4] + 0x00, 0x00, 0x00, 0x00, // $val + }; + + public CodePatcher_arm32_far(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) + { + if (Math.Abs((long)target - (long)replace) < ((1 << 25) - 1)) + throw new ArgumentException("address offset of target and replace must larger than ((1 << 25) - 1), please use InstructionModifier_arm32_near instead"); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"CodePatcher_arm32_far: {PrintAddrs()}"); +#endif + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + + fixed (void* p = &ret[0]) + { + uint* ptr = (uint*)p; + *ptr++ = 0xE51FF004; + *ptr = (uint)jmpTo; + } + return ret; + } + } + + /// + /// arm64 下 ±128MB 范围内的跳转 + /// + public unsafe class CodePatcher_arm64_near : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 4 bytes + { + /* + * from 0x14 to 0x17 is B opcode + * offset bits is 26 + * https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/B--Branch- + */ + 0x00, 0x00, 0x00, 0x14, // B $val ; $val = (($dst - $src)/4) & 7FFFFFF + }; + + public CodePatcher_arm64_near(IntPtr target, IntPtr replace, IntPtr proxy) : base(target, replace, proxy, s_jmpCode.Length) + { + if (Math.Abs((long)target - (long)replace) >= ((1 << 26) - 1) * 4) + throw new ArgumentException("address offset of target and replace must less than (1 << 26) - 1) * 4"); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"CodePatcher_arm64: {PrintAddrs()}"); +#endif + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + byte[] ret = new byte[s_jmpCode.Length]; + int val = (int)((long)jmpTo - (long)jmpFrom) / 4; + + fixed (void* p = &ret[0]) + { + byte* ptr = (byte*)p; + *ptr++ = (byte)val; + *ptr++ = (byte)(val >> 8); + *ptr++ = (byte)(val >> 16); + + byte last = (byte)(val >> 24); + last &= 0b11; + last |= 0x14; + + *ptr = last; + } + return ret; + } + } + + /// + /// arm64 远距离跳转 + /// + public unsafe class CodePatcher_arm64_far : CodePatcher + { + private static readonly byte[] s_jmpCode = new byte[] // 20 bytes(字节数过多,太危险了,不建议使用) + { + /* + * ADR: https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/ADR--Form-PC-relative-address- + * BR: https://developer.arm.com/documentation/ddi0596/2021-09/Base-Instructions/BR--Branch-to-Register- + */ + 0x6A, 0x00, 0x00, 0x10, // ADR X10, #C + 0x4A, 0x01, 0x40, 0xF9, // LDR X10, [X10,#0] + 0x40, 0x01, 0x1F, 0xD6, // BR X10 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // $dst + }; + + public CodePatcher_arm64_far(IntPtr target, IntPtr replace, IntPtr proxy, int jmpCodeSize) : base(target, replace, proxy, jmpCodeSize) + { + } + + protected override unsafe byte[] GenJmpCode(void* jmpFrom, void* jmpTo) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs.meta new file mode 100644 index 0000000..f745350 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/CodePatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97cc0d26f72fc4148b8370b2252d1585 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs new file mode 100644 index 0000000..34a0599 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs @@ -0,0 +1,74 @@ +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using System.Linq; +using System.IO; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace MonoHook +{ + /// + /// Hook 池,防止重复 Hook + /// + public static class HookPool + { + private static Dictionary _hooks = new Dictionary(); + + public static void AddHook(MethodBase method, MethodHook hook) + { + MethodHook preHook; + if (_hooks.TryGetValue(method, out preHook)) + { + preHook.Uninstall(); + _hooks[method] = hook; + } + else + _hooks.Add(method, hook); + } + + public static MethodHook GetHook(MethodBase method) + { + if (method == null) return null; + + MethodHook hook; + if (_hooks.TryGetValue(method, out hook)) + return hook; + return null; + } + + public static void RemoveHooker(MethodBase method) + { + if (method == null) return; + + _hooks.Remove(method); + } + + public static void UninstallAll() + { + var list = _hooks.Values.ToList(); + foreach (var hook in list) + hook.Uninstall(); + + _hooks.Clear(); + } + + public static void UninstallByTag(string tag) + { + var list = _hooks.Values.ToList(); + foreach (var hook in list) + { + if(hook.tag == tag) + hook.Uninstall(); + } + } + + public static List GetAllHooks() + { + return _hooks.Values.ToList(); + } + } + +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs.meta new file mode 100644 index 0000000..7503859 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b7421e47f0ae1e4ebb72bf18d1d7d48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs new file mode 100644 index 0000000..7acd8c9 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs @@ -0,0 +1,272 @@ +#if !(UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine; + +namespace MonoHook +{ + public static unsafe class HookUtils + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void DelegateFlushICache(void* code, int size); // delegate * unmanaged[Cdecl] native_flush_cache_fun_ptr; // unsupported at C# 8.0 + + static DelegateFlushICache flush_icache; + private static readonly long _Pagesize; + + static HookUtils() + { + PropertyInfo p_SystemPageSize = typeof(Environment).GetProperty("SystemPageSize"); + if (p_SystemPageSize == null) + throw new NotSupportedException("Unsupported runtime"); + _Pagesize = (int)p_SystemPageSize.GetValue(null, new object[0]); + SetupFlushICacheFunc(); + } + + public static void MemCpy(void* pDst, void* pSrc, int len) + { + byte* pDst_ = (byte*)pDst; + byte* pSrc_ = (byte*)pSrc; + + for (int i = 0; i < len; i++) + *pDst_++ = *pSrc_++; + } + + public static void MemCpy_Jit(void* pDst, byte[] src) + { + fixed (void* p = &src[0]) + { + MemCpy(pDst, p, src.Length); + } + } + + /// + /// set flags of address to `read write execute` + /// + public static void SetAddrFlagsToRWX(IntPtr ptr, int size) + { + if (ptr == IntPtr.Zero) + return; + +#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN + uint oldProtect; + bool ret = VirtualProtect(ptr, (uint)size, Protection.PAGE_EXECUTE_READWRITE, out oldProtect); + UnityEngine.Debug.Assert(ret); +#else + SetMemPerms(ptr,(ulong)size,MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC); +#endif + } + + public static void FlushICache(void* code, int size) + { + if (code == null) + return; + + flush_icache?.Invoke(code, size); + +#if ENABLE_HOOK_DEBUG + Debug.Log($"flush icache at 0x{(IntPtr.Size == 4 ? (uint)code : (ulong)code):x}, size:{size}"); +#endif + } + + public static KeyValuePair GetPageAlignedAddr(long code, int size) + { + long pagesize = _Pagesize; + long startPage = (code) & ~(pagesize - 1); + long endPage = (code + size + pagesize - 1) & ~(pagesize - 1); + return new KeyValuePair(startPage, endPage); + } + + + const int PRINT_SPLIT = 4; + const int PRINT_COL_SIZE = PRINT_SPLIT * 4; + public static string HexToString(void* ptr, int size, int offset = 0) + { + Func formatAddr = (IntPtr addr__) => IntPtr.Size == 4 ? $"0x{(uint)addr__:x}" : $"0x{(ulong)addr__:x}"; + + byte* addr = (byte*)ptr; + + StringBuilder sb = new StringBuilder(1024); + sb.AppendLine($"addr:{formatAddr(new IntPtr(addr))}"); + + addr += offset; + size += Math.Abs(offset); + + int count = 0; + while (true) + { + sb.Append($"\r\n{formatAddr(new IntPtr(addr + count))}: "); + for (int i = 1; i < PRINT_COL_SIZE + 1; i++) + { + if (count >= size) + goto END; + + sb.Append($"{*(addr + count):x2}"); + if (i % PRINT_SPLIT == 0) + sb.Append(" "); + + count++; + } + } + END:; + return sb.ToString(); + } + + static void SetupFlushICacheFunc() + { + string processorType = SystemInfo.processorType; + if (processorType.Contains("Intel") || processorType.Contains("AMD")) + return; + + if (IntPtr.Size == 4) + { + // never release, so save GCHandle is unnecessary + s_ptr_flush_icache_arm32 = GCHandle.Alloc(s_flush_icache_arm32, GCHandleType.Pinned).AddrOfPinnedObject().ToPointer(); + SetAddrFlagsToRWX(new IntPtr(s_ptr_flush_icache_arm32), s_flush_icache_arm32.Length); + flush_icache = Marshal.GetDelegateForFunctionPointer(new IntPtr(s_ptr_flush_icache_arm32)); + } + else + { + s_ptr_flush_icache_arm64 = GCHandle.Alloc(s_flush_icache_arm64, GCHandleType.Pinned).AddrOfPinnedObject().ToPointer(); + SetAddrFlagsToRWX(new IntPtr(s_ptr_flush_icache_arm64), s_flush_icache_arm64.Length); + flush_icache = Marshal.GetDelegateForFunctionPointer(new IntPtr(s_ptr_flush_icache_arm64)); + } + +#if ENABLE_HOOK_DEBUG + Debug.Log($"flush_icache delegate is {((flush_icache != null) ? "not " : "")}null"); +#endif + } + + + static void* s_ptr_flush_icache_arm32, s_ptr_flush_icache_arm64; + private static byte[] s_flush_icache_arm32 = new byte[] + { + // void cdecl mono_arch_flush_icache (guint8 *code, gint size) + 0x00, 0x48, 0x2D, 0xE9, // PUSH {R11,LR} + 0x0D, 0xB0, 0xA0, 0xE1, // MOV R11, SP + 0x08, 0xD0, 0x4D, 0xE2, // SUB SP, SP, #8 + 0x04, 0x00, 0x8D, 0xE5, // STR R0, [SP,#8+var_4] + 0x00, 0x10, 0x8D, 0xE5, // STR R1, [SP,#8+var_8] + 0x04, 0x00, 0x9D, 0xE5, // LDR R0, [SP,#8+var_4] + 0x04, 0x10, 0x9D, 0xE5, // LDR R1, [SP,#8+var_4] + 0x00, 0x20, 0x9D, 0xE5, // LDR R2, [SP,#8+var_8] + 0x02, 0x10, 0x81, 0xE0, // ADD R1, R1, R2 + 0x01, 0x00, 0x00, 0xEB, // BL __clear_cache + 0x0B, 0xD0, 0xA0, 0xE1, // MOV SP, R11 + 0x00, 0x88, 0xBD, 0xE8, // POP {R11,PC} + + // __clear_cache ; CODE XREF: j___clear_cache+8↑j + 0x80, 0x00, 0x2D, 0xE9, // PUSH { R7 } + 0x02, 0x70, 0x00, 0xE3, 0x0F, 0x70, 0x40, 0xE3, // MOV R7, #0xF0002 + 0x00, 0x20, 0xA0, 0xE3, // MOV R2, #0 + 0x00, 0x00, 0x00, 0xEF, // SVC 0 + 0x80, 0x00, 0xBD, 0xE8, // POP {R7} + 0x1E, 0xFF, 0x2F, 0xE1, // BX LR + }; + + private static byte[] s_flush_icache_arm64 = new byte[] // X0: code, W1: size + { + // void cdecl mono_arch_flush_icache (guint8 *code, gint size) + 0xFF, 0xC3, 0x00, 0xD1, // SUB SP, SP, #0x30 + 0xE8, 0x03, 0x7E, 0xB2, // MOV X8, #4 + 0xE0, 0x17, 0x00, 0xF9, // STR X0, [SP,#0x30+var_8] + 0xE1, 0x27, 0x00, 0xB9, // STR W1, [SP,#0x30+var_C] + 0xE0, 0x17, 0x40, 0xF9, // LDR X0, [SP,#0x30+var_8] + 0xE9, 0x27, 0x80, 0xB9, // LDRSW X9, [SP,#0x30+var_C] + 0x09, 0x00, 0x09, 0x8B, // ADD X9, X0, X9 + 0xE9, 0x0F, 0x00, 0xF9, // STR X9, [SP,#0x30+var_18] + 0xE8, 0x07, 0x00, 0xF9, // STR X8, [SP,#0x30+var_28] + 0xE8, 0x03, 0x00, 0xF9, // STR X8, [SP,#0x30+var_30] + 0xE8, 0x17, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_8] + 0x08, 0xF5, 0x7E, 0x92, // AND X8, X8, #0xFFFFFFFFFFFFFFFC + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + + // loc_590 ; CODE XREF: mono_arch_flush_icache(uchar*, int)+58↓j + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0xE9, 0x0F, 0x40, 0xF9, // LDR X9, [SP,#0x30+var_18] + 0x1F, 0x01, 0x09, 0xEB, // CMP X8, X9 + 0xE2, 0x00, 0x00, 0x54, // B.CS loc_5B8 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x28, 0x7E, 0x0B, 0xD5, // SYS #3, c7, c14, #1, X8 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x08, 0x11, 0x00, 0x91, // ADD X8, X8, #4 + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + 0xF7, 0xFF, 0xFF, 0x17, // B loc_590 + // ; --------------------------------------------------------------------------- + + // loc_5B8 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+40↑j + 0x9F, 0x3B, 0x03, 0xD5, // DSB ISH + 0xE8, 0x17, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_8] + 0x08, 0xF5, 0x7E, 0x92, // AND X8, X8, #0xFFFFFFFFFFFFFFFC + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + + // loc_5C8 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+90↓j + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0xE9, 0x0F, 0x40, 0xF9, // LDR X9, [SP,#0x30+var_18] + 0x1F, 0x01, 0x09, 0xEB, // CMP X8, X9 + 0xE2, 0x00, 0x00, 0x54, // B.CS loc_5F0 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x28, 0x75, 0x0B, 0xD5, // SYS #3, c7, c5, #1, X8 + 0xE8, 0x0B, 0x40, 0xF9, // LDR X8, [SP,#0x30+var_20] + 0x08, 0x11, 0x00, 0x91, // ADD X8, X8, #4 + 0xE8, 0x0B, 0x00, 0xF9, // STR X8, [SP,#0x30+var_20] + 0xF7, 0xFF, 0xFF, 0x17, // B loc_5C8 + // ; --------------------------------------------------------------------------- + + // loc_5F0 ; CODE XREF: mono_arch_flush_icache(uchar *, int)+78↑j + 0x9F, 0x3B, 0x03, 0xD5, // DSB ISH + 0xDF, 0x3F, 0x03, 0xD5, // ISB + 0xFF, 0xC3, 0x00, 0x91, // ADD SP, SP, #0x30 ; '0' + 0xC0, 0x03, 0x5F, 0xD6, // RET + }; + + +#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN + [Flags] + public enum Protection + { + PAGE_NOACCESS = 0x01, + PAGE_READONLY = 0x02, + PAGE_READWRITE = 0x04, + PAGE_WRITECOPY = 0x08, + PAGE_EXECUTE = 0x10, + PAGE_EXECUTE_READ = 0x20, + PAGE_EXECUTE_READWRITE = 0x40, + PAGE_EXECUTE_WRITECOPY = 0x80, + PAGE_GUARD = 0x100, + PAGE_NOCACHE = 0x200, + PAGE_WRITECOMBINE = 0x400 + } + + [DllImport("kernel32")] + public static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, Protection flNewProtect, out uint lpflOldProtect); + +#else + [Flags] + public enum MmapProts : int { + PROT_READ = 0x1, + PROT_WRITE = 0x2, + PROT_EXEC = 0x4, + PROT_NONE = 0x0, + PROT_GROWSDOWN = 0x01000000, + PROT_GROWSUP = 0x02000000, + } + + [DllImport("libc", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + private static extern int mprotect(IntPtr start, IntPtr len, MmapProts prot); + + public static unsafe void SetMemPerms(IntPtr start, ulong len, MmapProts prot) { + var requiredAddr = GetPageAlignedAddr(start.ToInt64(), (int)len); + long startPage = requiredAddr.Key; + long endPage = requiredAddr.Value; + + if (mprotect((IntPtr) startPage, (IntPtr) (endPage - startPage), prot) != 0) + throw new Exception($"mprotect with prot:{prot} fail!"); + } +#endif + } +} + +#endif \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs.meta new file mode 100644 index 0000000..8ec4cb3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e84139b42a6164e4c93ce4df1be6dcfb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs new file mode 100644 index 0000000..8e4a126 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs @@ -0,0 +1,102 @@ +#if (UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine; + +namespace MonoHook +{ + public static unsafe class HookUtils + { + static bool jit_write_protect_supported; + private static readonly long _Pagesize; + + + static HookUtils() + { + jit_write_protect_supported = pthread_jit_write_protect_supported_np() != 0; + + PropertyInfo p_SystemPageSize = typeof(Environment).GetProperty("SystemPageSize"); + if (p_SystemPageSize == null) + throw new NotSupportedException("Unsupported runtime"); + _Pagesize = (int)p_SystemPageSize.GetValue(null, new object[0]); + } + + public static void MemCpy(void* pDst, void* pSrc, int len) + { + byte* pDst_ = (byte*)pDst; + byte* pSrc_ = (byte*)pSrc; + + for (int i = 0; i < len; i++) + *pDst_++ = *pSrc_++; + } + + public static void MemCpy_Jit(void* pDst, byte[] src) + { + fixed(void * p = &src[0]) + { + memcpy_jit(new IntPtr(pDst), new IntPtr(p), src.Length); + } + } + + /// + /// set flags of address to `read write execute` + /// + public static void SetAddrFlagsToRWX(IntPtr ptr, int size) { } + + public static void FlushICache(void* code, int size) { } + + public static KeyValuePair GetPageAlignedAddr(long code, int size) + { + long pagesize = _Pagesize; + long startPage = (code) & ~(pagesize - 1); + long endPage = (code + size + pagesize - 1) & ~(pagesize - 1); + return new KeyValuePair(startPage, endPage); + } + + + const int PRINT_SPLIT = 4; + const int PRINT_COL_SIZE = PRINT_SPLIT * 4; + public static string HexToString(void* ptr, int size, int offset = 0) + { + Func formatAddr = (IntPtr addr__) => IntPtr.Size == 4 ? $"0x{(uint)addr__:x}" : $"0x{(ulong)addr__:x}"; + + byte* addr = (byte*)ptr; + + StringBuilder sb = new StringBuilder(1024); + sb.AppendLine($"addr:{formatAddr(new IntPtr(addr))}"); + + addr += offset; + size += Math.Abs(offset); + + int count = 0; + while (true) + { + sb.Append($"\r\n{formatAddr(new IntPtr(addr + count))}: "); + for (int i = 1; i < PRINT_COL_SIZE + 1; i++) + { + if (count >= size) + goto END; + + sb.Append($"{*(addr + count):x2}"); + if (i % PRINT_SPLIT == 0) + sb.Append(" "); + + count++; + } + } + END:; + return sb.ToString(); + } + + [DllImport("pthread", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + private static extern int pthread_jit_write_protect_supported_np(); + + [DllImport("libMonoHookUtils_OSX", SetLastError = true, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr memcpy_jit(IntPtr dst, IntPtr src, int len); + } +} + +#endif \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs.meta new file mode 100644 index 0000000..6ecee49 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HookUtils_OSX.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efda6e010e5c6594081c4a62861d469f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks.meta new file mode 100644 index 0000000..8169f8d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d796fc01daee1964586621890988a5ae +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs new file mode 100644 index 0000000..7e3373d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs @@ -0,0 +1,67 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using System.Runtime.CompilerServices; +using MonoHook; +using HybridCLR.Editor.BuildProcessors; +using System.IO; + +namespace HybridCLR.MonoHook +{ +#if UNITY_2021_1_OR_NEWER + [InitializeOnLoad] + public class CopyStrippedAOTAssembliesHook + { + private static MethodHook _hook; + + static CopyStrippedAOTAssembliesHook() + { + if (_hook == null) + { + Type type = typeof(UnityEditor.EditorApplication).Assembly.GetType("UnityEditorInternal.AssemblyStripper"); + MethodInfo miTarget = type.GetMethod("StripAssembliesTo", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + MethodInfo miReplacement = new StripAssembliesDel(OverrideStripAssembliesTo).Method; + MethodInfo miProxy = new StripAssembliesDel(StripAssembliesToProxy).Method; + + _hook = new MethodHook(miTarget, miReplacement, miProxy); + _hook.Install(); + } + } + + private delegate bool StripAssembliesDel(string outputFolder, out string output, out string error, IEnumerable linkXmlFiles, object runInformation); + + private static bool OverrideStripAssembliesTo(string outputFolder, out string output, out string error, IEnumerable linkXmlFiles, object runInformation) + { + bool result = StripAssembliesToProxy(outputFolder, out output, out error, linkXmlFiles, runInformation); + if (!result) + { + return false; + } + UnityEngine.Debug.Log($"== StripAssembliesTo outputDir:{outputFolder}"); + string outputStrippedDir = HybridCLR.Editor.SettingsUtil.GetAssembliesPostIl2CppStripDir(EditorUserBuildSettings.activeBuildTarget); + Directory.CreateDirectory(outputStrippedDir); + foreach (var aotDll in Directory.GetFiles(outputFolder, "*.dll")) + { + string dstFile = $"{outputStrippedDir}/{Path.GetFileName(aotDll)}"; + Debug.Log($"[RunAssemblyStripper] copy aot dll {aotDll} -> {dstFile}"); + File.Copy(aotDll, dstFile, true); + } + return result; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + private static bool StripAssembliesToProxy(string outputFolder, out string output, out string error, IEnumerable linkXmlFiles, object runInformation) + { + Debug.LogError("== StripAssembliesToProxy =="); + output = ""; + error = ""; + return true; + } + } +#endif +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs.meta new file mode 100644 index 0000000..18542c3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/CopyStrippedAOTAssembliesHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf42c4f20b8a1b94baa04a1a5c6b8beb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs new file mode 100644 index 0000000..c3ce1f5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs @@ -0,0 +1,56 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using System.Runtime.CompilerServices; +using MonoHook; +using HybridCLR.Editor.BuildProcessors; +using System.IO; + +namespace HybridCLR.MonoHook +{ +#if UNITY_2022 + [InitializeOnLoad] + public class GetIl2CppFolderHook + { + private static MethodHook _hook; + + static GetIl2CppFolderHook() + { + if (_hook == null) + { + Type type = typeof(UnityEditor.EditorApplication).Assembly.GetType("UnityEditorInternal.IL2CPPUtils"); + MethodInfo miTarget = type.GetMethod("GetIl2CppFolder", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null, + new Type[] { typeof(bool).MakeByRefType() }, null); + + MethodInfo miReplacement = new StripAssembliesDel(OverrideMethod).Method; + MethodInfo miProxy = new StripAssembliesDel(PlaceHolderMethod).Method; + + _hook = new MethodHook(miTarget, miReplacement, miProxy); + _hook.Install(); + } + } + + private delegate string StripAssembliesDel(out bool isDevelopmentLocation); + + private static string OverrideMethod(out bool isDevelopmentLocation) + { + //Debug.Log("[GetIl2CppFolderHook] OverrideMethod"); + string result = PlaceHolderMethod(out isDevelopmentLocation); + isDevelopmentLocation = false; + return result; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + private static string PlaceHolderMethod(out bool isDevelopmentLocation) + { + Debug.LogError("[GetIl2CppFolderHook] PlaceHolderMethod"); + isDevelopmentLocation = false; + return null; + } + } +#endif +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs.meta new file mode 100644 index 0000000..bcde7d8 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/GetIl2CppFolderHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96c2bc28db69e1644892219abef3d4b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs new file mode 100644 index 0000000..169d766 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs @@ -0,0 +1,57 @@ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using System.Runtime.CompilerServices; +using MonoHook; +using HybridCLR.Editor.BuildProcessors; +using System.IO; + +namespace HybridCLR.MonoHook +{ +#if UNITY_2021_1_OR_NEWER && UNITY_WEBGL + [InitializeOnLoad] + public class PatchScriptingAssembliesJsonHook + { + private static MethodHook _hook; + + static PatchScriptingAssembliesJsonHook() + { + if (_hook == null) + { + Type type = typeof(UnityEditor.EditorApplication); + MethodInfo miTarget = type.GetMethod("BuildMainWindowTitle", BindingFlags.Static | BindingFlags.NonPublic); + + MethodInfo miReplacement = new Func(BuildMainWindowTitle).Method; + MethodInfo miProxy = new Func(BuildMainWindowTitleProxy).Method; + + _hook = new MethodHook(miTarget, miReplacement, miProxy); + _hook.Install(); + } + } + + private static string BuildMainWindowTitle() + { + string tempJsonPath = $"{Application.dataPath}/../Library/PlayerDataCache/WebGL/Data/ScriptingAssemblies.json"; + if (File.Exists(tempJsonPath)) + { + var patcher = new PatchScriptingAssemblyList(); + patcher.PathScriptingAssembilesFile(Path.GetDirectoryName(tempJsonPath)); + } + string newTitle = BuildMainWindowTitleProxy(); + return newTitle; + } + + [MethodImpl(MethodImplOptions.NoOptimization)] + private static string BuildMainWindowTitleProxy() + { + // 为满足MonoHook要求的最小代码长度而特地加入的无用填充代码, + UnityEngine.Debug.Log(12345.ToString()); + return string.Empty; + } + } +#endif +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs.meta new file mode 100644 index 0000000..ccf939f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/HybridCLRHooks/PatchScriptingAssembliesJsonHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cc89a9041ab48ac41975fbd1e00b9b98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs new file mode 100644 index 0000000..d87ab91 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs @@ -0,0 +1,903 @@ +using System; +using System.Runtime.InteropServices; + +namespace DotNetDetour +{ + /// + /// 用于计算汇编指令长度,使用的是BlackBone的LDasm.c中的算法,我把他翻译成C#了 + /// + public unsafe class LDasm + { + const int F_INVALID = 0x01; + const int F_PREFIX = 0x02; + const int F_REX = 0x04; + const int F_MODRM = 0x08; + const int F_SIB = 0x10; + const int F_DISP = 0x20; + const int F_IMM = 0x40; + const int F_RELATIVE = 0x80; + + const int OP_NONE = 0x00; + const int OP_INVALID = 0x80; + + const int OP_DATA_I8 = 0x01; + const int OP_DATA_I16 = 0x02; + const int OP_DATA_I16_I32 = 0x04; + const int OP_DATA_I16_I32_I64 = 0x08; + const int OP_EXTENDED = 0x10; + const int OP_RELATIVE = 0x20; + const int OP_MODRM = 0x40; + const int OP_PREFIX = 0x80; + + struct ldasm_data + { + public byte flags; + public byte rex; + public byte modrm; + public byte sib; + public byte opcd_offset; + public byte opcd_size; + public byte disp_offset; + public byte disp_size; + public byte imm_offset; + public byte imm_size; + } + + static byte[] flags_table = + { + /* 00 */ OP_MODRM, + /* 01 */ OP_MODRM, + /* 02 */ OP_MODRM, + /* 03 */ OP_MODRM, + /* 04 */ OP_DATA_I8, + /* 05 */ OP_DATA_I16_I32, + /* 06 */ OP_NONE, + /* 07 */ OP_NONE, + /* 08 */ OP_MODRM, + /* 09 */ OP_MODRM, + /* 0A */ OP_MODRM, + /* 0B */ OP_MODRM, + /* 0C */ OP_DATA_I8, + /* 0D */ OP_DATA_I16_I32, + /* 0E */ OP_NONE, + /* 0F */ OP_NONE, + + /* 10 */ OP_MODRM, + /* 11 */ OP_MODRM, + /* 12 */ OP_MODRM, + /* 13 */ OP_MODRM, + /* 14 */ OP_DATA_I8, + /* 15 */ OP_DATA_I16_I32, + /* 16 */ OP_NONE, + /* 17 */ OP_NONE, + /* 18 */ OP_MODRM, + /* 19 */ OP_MODRM, + /* 1A */ OP_MODRM, + /* 1B */ OP_MODRM, + /* 1C */ OP_DATA_I8, + /* 1D */ OP_DATA_I16_I32, + /* 1E */ OP_NONE, + /* 1F */ OP_NONE, + + /* 20 */ OP_MODRM, + /* 21 */ OP_MODRM, + /* 22 */ OP_MODRM, + /* 23 */ OP_MODRM, + /* 24 */ OP_DATA_I8, + /* 25 */ OP_DATA_I16_I32, + /* 26 */ OP_PREFIX, + /* 27 */ OP_NONE, + /* 28 */ OP_MODRM, + /* 29 */ OP_MODRM, + /* 2A */ OP_MODRM, + /* 2B */ OP_MODRM, + /* 2C */ OP_DATA_I8, + /* 2D */ OP_DATA_I16_I32, + /* 2E */ OP_PREFIX, + /* 2F */ OP_NONE, + + /* 30 */ OP_MODRM, + /* 31 */ OP_MODRM, + /* 32 */ OP_MODRM, + /* 33 */ OP_MODRM, + /* 34 */ OP_DATA_I8, + /* 35 */ OP_DATA_I16_I32, + /* 36 */ OP_PREFIX, + /* 37 */ OP_NONE, + /* 38 */ OP_MODRM, + /* 39 */ OP_MODRM, + /* 3A */ OP_MODRM, + /* 3B */ OP_MODRM, + /* 3C */ OP_DATA_I8, + /* 3D */ OP_DATA_I16_I32, + /* 3E */ OP_PREFIX, + /* 3F */ OP_NONE, + + /* 40 */ OP_NONE, + /* 41 */ OP_NONE, + /* 42 */ OP_NONE, + /* 43 */ OP_NONE, + /* 44 */ OP_NONE, + /* 45 */ OP_NONE, + /* 46 */ OP_NONE, + /* 47 */ OP_NONE, + /* 48 */ OP_NONE, + /* 49 */ OP_NONE, + /* 4A */ OP_NONE, + /* 4B */ OP_NONE, + /* 4C */ OP_NONE, + /* 4D */ OP_NONE, + /* 4E */ OP_NONE, + /* 4F */ OP_NONE, + + /* 50 */ OP_NONE, + /* 51 */ OP_NONE, + /* 52 */ OP_NONE, + /* 53 */ OP_NONE, + /* 54 */ OP_NONE, + /* 55 */ OP_NONE, + /* 56 */ OP_NONE, + /* 57 */ OP_NONE, + /* 58 */ OP_NONE, + /* 59 */ OP_NONE, + /* 5A */ OP_NONE, + /* 5B */ OP_NONE, + /* 5C */ OP_NONE, + /* 5D */ OP_NONE, + /* 5E */ OP_NONE, + /* 5F */ OP_NONE, + /* 60 */ OP_NONE, + + /* 61 */ OP_NONE, + /* 62 */ OP_MODRM, + /* 63 */ OP_MODRM, + /* 64 */ OP_PREFIX, + /* 65 */ OP_PREFIX, + /* 66 */ OP_PREFIX, + /* 67 */ OP_PREFIX, + /* 68 */ OP_DATA_I16_I32, + /* 69 */ OP_MODRM | OP_DATA_I16_I32, + /* 6A */ OP_DATA_I8, + /* 6B */ OP_MODRM | OP_DATA_I8, + /* 6C */ OP_NONE, + /* 6D */ OP_NONE, + /* 6E */ OP_NONE, + /* 6F */ OP_NONE, + + /* 70 */ OP_RELATIVE | OP_DATA_I8, + /* 71 */ OP_RELATIVE | OP_DATA_I8, + /* 72 */ OP_RELATIVE | OP_DATA_I8, + /* 73 */ OP_RELATIVE | OP_DATA_I8, + /* 74 */ OP_RELATIVE | OP_DATA_I8, + /* 75 */ OP_RELATIVE | OP_DATA_I8, + /* 76 */ OP_RELATIVE | OP_DATA_I8, + /* 77 */ OP_RELATIVE | OP_DATA_I8, + /* 78 */ OP_RELATIVE | OP_DATA_I8, + /* 79 */ OP_RELATIVE | OP_DATA_I8, + /* 7A */ OP_RELATIVE | OP_DATA_I8, + /* 7B */ OP_RELATIVE | OP_DATA_I8, + /* 7C */ OP_RELATIVE | OP_DATA_I8, + /* 7D */ OP_RELATIVE | OP_DATA_I8, + /* 7E */ OP_RELATIVE | OP_DATA_I8, + /* 7F */ OP_RELATIVE | OP_DATA_I8, + + /* 80 */ OP_MODRM | OP_DATA_I8, + /* 81 */ OP_MODRM | OP_DATA_I16_I32, + /* 82 */ OP_MODRM | OP_DATA_I8, + /* 83 */ OP_MODRM | OP_DATA_I8, + /* 84 */ OP_MODRM, + /* 85 */ OP_MODRM, + /* 86 */ OP_MODRM, + /* 87 */ OP_MODRM, + /* 88 */ OP_MODRM, + /* 89 */ OP_MODRM, + /* 8A */ OP_MODRM, + /* 8B */ OP_MODRM, + /* 8C */ OP_MODRM, + /* 8D */ OP_MODRM, + /* 8E */ OP_MODRM, + /* 8F */ OP_MODRM, + + /* 90 */ OP_NONE, + /* 91 */ OP_NONE, + /* 92 */ OP_NONE, + /* 93 */ OP_NONE, + /* 94 */ OP_NONE, + /* 95 */ OP_NONE, + /* 96 */ OP_NONE, + /* 97 */ OP_NONE, + /* 98 */ OP_NONE, + /* 99 */ OP_NONE, + /* 9A */ OP_DATA_I16 | OP_DATA_I16_I32, + /* 9B */ OP_NONE, + /* 9C */ OP_NONE, + /* 9D */ OP_NONE, + /* 9E */ OP_NONE, + /* 9F */ OP_NONE, + + /* A0 */ OP_DATA_I8, + /* A1 */ OP_DATA_I16_I32_I64, + /* A2 */ OP_DATA_I8, + /* A3 */ OP_DATA_I16_I32_I64, + /* A4 */ OP_NONE, + /* A5 */ OP_NONE, + /* A6 */ OP_NONE, + /* A7 */ OP_NONE, + /* A8 */ OP_DATA_I8, + /* A9 */ OP_DATA_I16_I32, + /* AA */ OP_NONE, + /* AB */ OP_NONE, + /* AC */ OP_NONE, + /* AD */ OP_NONE, + /* AE */ OP_NONE, + /* AF */ OP_NONE, + + /* B0 */ OP_DATA_I8, + /* B1 */ OP_DATA_I8, + /* B2 */ OP_DATA_I8, + /* B3 */ OP_DATA_I8, + /* B4 */ OP_DATA_I8, + /* B5 */ OP_DATA_I8, + /* B6 */ OP_DATA_I8, + /* B7 */ OP_DATA_I8, + /* B8 */ OP_DATA_I16_I32_I64, + /* B9 */ OP_DATA_I16_I32_I64, + /* BA */ OP_DATA_I16_I32_I64, + /* BB */ OP_DATA_I16_I32_I64, + /* BC */ OP_DATA_I16_I32_I64, + /* BD */ OP_DATA_I16_I32_I64, + /* BE */ OP_DATA_I16_I32_I64, + /* BF */ OP_DATA_I16_I32_I64, + + /* C0 */ OP_MODRM | OP_DATA_I8, + /* C1 */ OP_MODRM | OP_DATA_I8, + /* C2 */ OP_DATA_I16, + /* C3 */ OP_NONE, + /* C4 */ OP_MODRM, + /* C5 */ OP_MODRM, + /* C6 */ OP_MODRM | OP_DATA_I8, + /* C7 */ OP_MODRM | OP_DATA_I16_I32, + /* C8 */ OP_DATA_I8 | OP_DATA_I16, + /* C9 */ OP_NONE, + /* CA */ OP_DATA_I16, + /* CB */ OP_NONE, + /* CC */ OP_NONE, + /* CD */ OP_DATA_I8, + /* CE */ OP_NONE, + /* CF */ OP_NONE, + + /* D0 */ OP_MODRM, + /* D1 */ OP_MODRM, + /* D2 */ OP_MODRM, + /* D3 */ OP_MODRM, + /* D4 */ OP_DATA_I8, + /* D5 */ OP_DATA_I8, + /* D6 */ OP_NONE, + /* D7 */ OP_NONE, + /* D8 */ OP_MODRM, + /* D9 */ OP_MODRM, + /* DA */ OP_MODRM, + /* DB */ OP_MODRM, + /* DC */ OP_MODRM, + /* DD */ OP_MODRM, + /* DE */ OP_MODRM, + /* DF */ OP_MODRM, + + /* E0 */ OP_RELATIVE | OP_DATA_I8, + /* E1 */ OP_RELATIVE | OP_DATA_I8, + /* E2 */ OP_RELATIVE | OP_DATA_I8, + /* E3 */ OP_RELATIVE | OP_DATA_I8, + /* E4 */ OP_DATA_I8, + /* E5 */ OP_DATA_I8, + /* E6 */ OP_DATA_I8, + /* E7 */ OP_DATA_I8, + /* E8 */ OP_RELATIVE | OP_DATA_I16_I32, + /* E9 */ OP_RELATIVE | OP_DATA_I16_I32, + /* EA */ OP_DATA_I16 | OP_DATA_I16_I32, + /* EB */ OP_RELATIVE | OP_DATA_I8, + /* EC */ OP_NONE, + /* ED */ OP_NONE, + /* EE */ OP_NONE, + /* EF */ OP_NONE, + + /* F0 */ OP_PREFIX, + /* F1 */ OP_NONE, + /* F2 */ OP_PREFIX, + /* F3 */ OP_PREFIX, + /* F4 */ OP_NONE, + /* F5 */ OP_NONE, + /* F6 */ OP_MODRM, + /* F7 */ OP_MODRM, + /* F8 */ OP_NONE, + /* F9 */ OP_NONE, + /* FA */ OP_NONE, + /* FB */ OP_NONE, + /* FC */ OP_NONE, + /* FD */ OP_NONE, + /* FE */ OP_MODRM, + /* FF */ OP_MODRM + }; + + static byte[] flags_table_ex = + { + /* 0F00 */ OP_MODRM, + /* 0F01 */ OP_MODRM, + /* 0F02 */ OP_MODRM, + /* 0F03 */ OP_MODRM, + /* 0F04 */ OP_INVALID, + /* 0F05 */ OP_NONE, + /* 0F06 */ OP_NONE, + /* 0F07 */ OP_NONE, + /* 0F08 */ OP_NONE, + /* 0F09 */ OP_NONE, + /* 0F0A */ OP_INVALID, + /* 0F0B */ OP_NONE, + /* 0F0C */ OP_INVALID, + /* 0F0D */ OP_MODRM, + /* 0F0E */ OP_INVALID, + /* 0F0F */ OP_MODRM | OP_DATA_I8, //3Dnow + + /* 0F10 */ OP_MODRM, + /* 0F11 */ OP_MODRM, + /* 0F12 */ OP_MODRM, + /* 0F13 */ OP_MODRM, + /* 0F14 */ OP_MODRM, + /* 0F15 */ OP_MODRM, + /* 0F16 */ OP_MODRM, + /* 0F17 */ OP_MODRM, + /* 0F18 */ OP_MODRM, + /* 0F19 */ OP_INVALID, + /* 0F1A */ OP_INVALID, + /* 0F1B */ OP_INVALID, + /* 0F1C */ OP_INVALID, + /* 0F1D */ OP_INVALID, + /* 0F1E */ OP_INVALID, + /* 0F1F */ OP_NONE, + + /* 0F20 */ OP_MODRM, + /* 0F21 */ OP_MODRM, + /* 0F22 */ OP_MODRM, + /* 0F23 */ OP_MODRM, + /* 0F24 */ OP_MODRM | OP_EXTENDED, //SSE5 + /* 0F25 */ OP_INVALID, + /* 0F26 */ OP_MODRM, + /* 0F27 */ OP_INVALID, + /* 0F28 */ OP_MODRM, + /* 0F29 */ OP_MODRM, + /* 0F2A */ OP_MODRM, + /* 0F2B */ OP_MODRM, + /* 0F2C */ OP_MODRM, + /* 0F2D */ OP_MODRM, + /* 0F2E */ OP_MODRM, + /* 0F2F */ OP_MODRM, + + /* 0F30 */ OP_NONE, + /* 0F31 */ OP_NONE, + /* 0F32 */ OP_NONE, + /* 0F33 */ OP_NONE, + /* 0F34 */ OP_NONE, + /* 0F35 */ OP_NONE, + /* 0F36 */ OP_INVALID, + /* 0F37 */ OP_NONE, + /* 0F38 */ OP_MODRM | OP_EXTENDED, + /* 0F39 */ OP_INVALID, + /* 0F3A */ OP_MODRM | OP_EXTENDED | OP_DATA_I8, + /* 0F3B */ OP_INVALID, + /* 0F3C */ OP_INVALID, + /* 0F3D */ OP_INVALID, + /* 0F3E */ OP_INVALID, + /* 0F3F */ OP_INVALID, + + /* 0F40 */ OP_MODRM, + /* 0F41 */ OP_MODRM, + /* 0F42 */ OP_MODRM, + /* 0F43 */ OP_MODRM, + /* 0F44 */ OP_MODRM, + /* 0F45 */ OP_MODRM, + /* 0F46 */ OP_MODRM, + /* 0F47 */ OP_MODRM, + /* 0F48 */ OP_MODRM, + /* 0F49 */ OP_MODRM, + /* 0F4A */ OP_MODRM, + /* 0F4B */ OP_MODRM, + /* 0F4C */ OP_MODRM, + /* 0F4D */ OP_MODRM, + /* 0F4E */ OP_MODRM, + /* 0F4F */ OP_MODRM, + + /* 0F50 */ OP_MODRM, + /* 0F51 */ OP_MODRM, + /* 0F52 */ OP_MODRM, + /* 0F53 */ OP_MODRM, + /* 0F54 */ OP_MODRM, + /* 0F55 */ OP_MODRM, + /* 0F56 */ OP_MODRM, + /* 0F57 */ OP_MODRM, + /* 0F58 */ OP_MODRM, + /* 0F59 */ OP_MODRM, + /* 0F5A */ OP_MODRM, + /* 0F5B */ OP_MODRM, + /* 0F5C */ OP_MODRM, + /* 0F5D */ OP_MODRM, + /* 0F5E */ OP_MODRM, + /* 0F5F */ OP_MODRM, + + /* 0F60 */ OP_MODRM, + /* 0F61 */ OP_MODRM, + /* 0F62 */ OP_MODRM, + /* 0F63 */ OP_MODRM, + /* 0F64 */ OP_MODRM, + /* 0F65 */ OP_MODRM, + /* 0F66 */ OP_MODRM, + /* 0F67 */ OP_MODRM, + /* 0F68 */ OP_MODRM, + /* 0F69 */ OP_MODRM, + /* 0F6A */ OP_MODRM, + /* 0F6B */ OP_MODRM, + /* 0F6C */ OP_MODRM, + /* 0F6D */ OP_MODRM, + /* 0F6E */ OP_MODRM, + /* 0F6F */ OP_MODRM, + + /* 0F70 */ OP_MODRM | OP_DATA_I8, + /* 0F71 */ OP_MODRM | OP_DATA_I8, + /* 0F72 */ OP_MODRM | OP_DATA_I8, + /* 0F73 */ OP_MODRM | OP_DATA_I8, + /* 0F74 */ OP_MODRM, + /* 0F75 */ OP_MODRM, + /* 0F76 */ OP_MODRM, + /* 0F77 */ OP_NONE, + /* 0F78 */ OP_MODRM, + /* 0F79 */ OP_MODRM, + /* 0F7A */ OP_INVALID, + /* 0F7B */ OP_INVALID, + /* 0F7C */ OP_MODRM, + /* 0F7D */ OP_MODRM, + /* 0F7E */ OP_MODRM, + /* 0F7F */ OP_MODRM, + + /* 0F80 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F81 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F82 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F83 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F84 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F85 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F86 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F87 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F88 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F89 */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8A */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8B */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8C */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8D */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8E */ OP_RELATIVE | OP_DATA_I16_I32, + /* 0F8F */ OP_RELATIVE | OP_DATA_I16_I32, + + /* 0F90 */ OP_MODRM, + /* 0F91 */ OP_MODRM, + /* 0F92 */ OP_MODRM, + /* 0F93 */ OP_MODRM, + /* 0F94 */ OP_MODRM, + /* 0F95 */ OP_MODRM, + /* 0F96 */ OP_MODRM, + /* 0F97 */ OP_MODRM, + /* 0F98 */ OP_MODRM, + /* 0F99 */ OP_MODRM, + /* 0F9A */ OP_MODRM, + /* 0F9B */ OP_MODRM, + /* 0F9C */ OP_MODRM, + /* 0F9D */ OP_MODRM, + /* 0F9E */ OP_MODRM, + /* 0F9F */ OP_MODRM, + + /* 0FA0 */ OP_NONE, + /* 0FA1 */ OP_NONE, + /* 0FA2 */ OP_NONE, + /* 0FA3 */ OP_MODRM, + /* 0FA4 */ OP_MODRM | OP_DATA_I8, + /* 0FA5 */ OP_MODRM, + /* 0FA6 */ OP_INVALID, + /* 0FA7 */ OP_INVALID, + /* 0FA8 */ OP_NONE, + /* 0FA9 */ OP_NONE, + /* 0FAA */ OP_NONE, + /* 0FAB */ OP_MODRM, + /* 0FAC */ OP_MODRM | OP_DATA_I8, + /* 0FAD */ OP_MODRM, + /* 0FAE */ OP_MODRM, + /* 0FAF */ OP_MODRM, + + /* 0FB0 */ OP_MODRM, + /* 0FB1 */ OP_MODRM, + /* 0FB2 */ OP_MODRM, + /* 0FB3 */ OP_MODRM, + /* 0FB4 */ OP_MODRM, + /* 0FB5 */ OP_MODRM, + /* 0FB6 */ OP_MODRM, + /* 0FB7 */ OP_MODRM, + /* 0FB8 */ OP_MODRM, + /* 0FB9 */ OP_MODRM, + /* 0FBA */ OP_MODRM | OP_DATA_I8, + /* 0FBB */ OP_MODRM, + /* 0FBC */ OP_MODRM, + /* 0FBD */ OP_MODRM, + /* 0FBE */ OP_MODRM, + /* 0FBF */ OP_MODRM, + + /* 0FC0 */ OP_MODRM, + /* 0FC1 */ OP_MODRM, + /* 0FC2 */ OP_MODRM | OP_DATA_I8, + /* 0FC3 */ OP_MODRM, + /* 0FC4 */ OP_MODRM | OP_DATA_I8, + /* 0FC5 */ OP_MODRM | OP_DATA_I8, + /* 0FC6 */ OP_MODRM | OP_DATA_I8, + /* 0FC7 */ OP_MODRM, + /* 0FC8 */ OP_NONE, + /* 0FC9 */ OP_NONE, + /* 0FCA */ OP_NONE, + /* 0FCB */ OP_NONE, + /* 0FCC */ OP_NONE, + /* 0FCD */ OP_NONE, + /* 0FCE */ OP_NONE, + /* 0FCF */ OP_NONE, + + /* 0FD0 */ OP_MODRM, + /* 0FD1 */ OP_MODRM, + /* 0FD2 */ OP_MODRM, + /* 0FD3 */ OP_MODRM, + /* 0FD4 */ OP_MODRM, + /* 0FD5 */ OP_MODRM, + /* 0FD6 */ OP_MODRM, + /* 0FD7 */ OP_MODRM, + /* 0FD8 */ OP_MODRM, + /* 0FD9 */ OP_MODRM, + /* 0FDA */ OP_MODRM, + /* 0FDB */ OP_MODRM, + /* 0FDC */ OP_MODRM, + /* 0FDD */ OP_MODRM, + /* 0FDE */ OP_MODRM, + /* 0FDF */ OP_MODRM, + + /* 0FE0 */ OP_MODRM, + /* 0FE1 */ OP_MODRM, + /* 0FE2 */ OP_MODRM, + /* 0FE3 */ OP_MODRM, + /* 0FE4 */ OP_MODRM, + /* 0FE5 */ OP_MODRM, + /* 0FE6 */ OP_MODRM, + /* 0FE7 */ OP_MODRM, + /* 0FE8 */ OP_MODRM, + /* 0FE9 */ OP_MODRM, + /* 0FEA */ OP_MODRM, + /* 0FEB */ OP_MODRM, + /* 0FEC */ OP_MODRM, + /* 0FED */ OP_MODRM, + /* 0FEE */ OP_MODRM, + /* 0FEF */ OP_MODRM, + + /* 0FF0 */ OP_MODRM, + /* 0FF1 */ OP_MODRM, + /* 0FF2 */ OP_MODRM, + /* 0FF3 */ OP_MODRM, + /* 0FF4 */ OP_MODRM, + /* 0FF5 */ OP_MODRM, + /* 0FF6 */ OP_MODRM, + /* 0FF7 */ OP_MODRM, + /* 0FF8 */ OP_MODRM, + /* 0FF9 */ OP_MODRM, + /* 0FFA */ OP_MODRM, + /* 0FFB */ OP_MODRM, + /* 0FFC */ OP_MODRM, + /* 0FFD */ OP_MODRM, + /* 0FFE */ OP_MODRM, + /* 0FFF */ OP_INVALID, + }; + + static byte cflags(byte op) + { + return flags_table[op]; + } + + static byte cflags_ex(byte op) + { + return flags_table_ex[op]; + } + + /// + /// 计算大于等于 size 字节的最少指令的长度 + /// + /// + /// + public static uint SizeofMinNumByte(void* code, int size) + { + if (IsARM()) + return (uint)((size + 3) / 4) * 4; // 此为 jit 模式下的长度,不再支持 thumb + + uint Length; + byte* pOpcode; + uint Result = 0; + ldasm_data data = new ldasm_data(); + bool is64 = IntPtr.Size == 8; + do + { + Length = ldasm(code, data, is64); + + pOpcode = (byte*)code + data.opcd_offset; + Result += Length; + if (Result >= size) + break; + if ((Length == 1) && (*pOpcode == 0xCC)) + break; + + code = (void*)((ulong)code + Length); + + } while (Length>0); + + return Result; + } + + static bool? s_isArm; + public static bool IsARM() + { + if(s_isArm.HasValue) + return s_isArm.Value; + + var arch = RuntimeInformation.ProcessArchitecture; + s_isArm = arch == Architecture.Arm || arch == Architecture.Arm64; + + return s_isArm.Value; + } + + public static bool IsArm32() + { + return IsARM() && IntPtr.Size == 4; + } + + public static bool IsArm64() + { + return IsARM() && IntPtr.Size == 8; + } + + static bool? s_isiOS; + public static bool IsiOS() + { + if(s_isiOS.HasValue) + return s_isiOS.Value; + + s_isiOS = UnityEngine.SystemInfo.operatingSystem.ToLower().Contains("ios"); + return s_isiOS.Value; + } + + static bool? s_isIL2CPP; + public static bool IsIL2CPP() + { + if (s_isIL2CPP.HasValue) + return s_isIL2CPP.Value; + + try + { + byte[] ilBody = typeof(LDasm).GetMethod("IsIL2CPP").GetMethodBody().GetILAsByteArray(); + if (ilBody == null || ilBody.Length == 0) + s_isIL2CPP = true; + else + s_isIL2CPP = false; + } + catch + { + s_isIL2CPP = true; + } + + return s_isIL2CPP.Value; + } + + public static bool IsThumb(IntPtr code) + { + return IsArm32() && ((long)code & 0x1) == 0x1; + } + + /// + /// 计算 thumb 指令长度 + /// + /// + /// + /// + public static uint CalcARMThumbMinLen(void* code, int size) + { + uint len = 0; + + ushort* ins = (ushort*)code; + while (true) + { + if (len >= size) + return len; + + if (((*ins >> 13) & 3) == 3) + { + ins += 2; + len += 4; + } + else + { + ins++; + len += 2; + } + } + } + + static uint ldasm(void* code, ldasm_data ld, bool is64) + { + byte* p = (byte*)code; + byte s, op, f; + byte rexw, pr_66, pr_67; + + s = rexw = pr_66 = pr_67 = 0; + + /* dummy check */ + if ((int)code==0) + return 0; + + /* init output data */ + //memset(ld, 0, sizeof(ldasm_data)); + + /* phase 1: parse prefixies */ + while ((cflags(*p) & OP_PREFIX)!=0) + { + if (*p == 0x66) + pr_66 = 1; + if (*p == 0x67) + pr_67 = 1; + p++; s++; + ld.flags |= F_PREFIX; + if (s == 15) + { + ld.flags |= F_INVALID; + return s; + } + } + + /* parse REX prefix */ + if (is64 && *p >> 4 == 4) + { + ld.rex = *p; + rexw = (byte)((ld.rex >> 3) & 1); + ld.flags |= F_REX; + p++; s++; + } + + /* can be only one REX prefix */ + if (is64 && *p >> 4 == 4) + { + ld.flags |= F_INVALID; + s++; + return s; + } + + /* phase 2: parse opcode */ + ld.opcd_offset = (byte)(p - (byte*)code); + ld.opcd_size = 1; + op = *p++; s++; + + /* is 2 byte opcode? */ + if (op == 0x0F) + { + op = *p++; s++; + ld.opcd_size++; + f = cflags_ex(op); + if ((f & OP_INVALID)!=0) + { + ld.flags |= F_INVALID; + return s; + } + /* for SSE instructions */ + if ((f & OP_EXTENDED)!=0) + { + op = *p++; s++; + ld.opcd_size++; + } + } + else { + f = cflags(op); + /* pr_66 = pr_67 for opcodes A0-A3 */ + if (op >= 0xA0 && op <= 0xA3) + pr_66 = pr_67; + } + + /* phase 3: parse ModR/M, SIB and DISP */ + if ((f & OP_MODRM)!=0) + { + byte mod = (byte)(*p >> 6); + byte ro = (byte)((*p & 0x38) >> 3); + byte rm = (byte)(*p & 7); + + ld.modrm = *p++; s++; + ld.flags |= F_MODRM; + + /* in F6,F7 opcodes immediate data present if R/O == 0 */ + if (op == 0xF6 && (ro == 0 || ro == 1)) + f |= OP_DATA_I8; + if (op == 0xF7 && (ro == 0 || ro == 1)) + f |= OP_DATA_I16_I32_I64; + + /* is SIB byte exist? */ + if (mod != 3 && rm == 4 && !(!is64 && pr_67!=0)) + { + ld.sib = *p++; s++; + ld.flags |= F_SIB; + + /* if base == 5 and mod == 0 */ + if ((ld.sib & 7) == 5 && mod == 0) + { + ld.disp_size = 4; + } + } + + switch (mod) + { + case 0: + if (is64) + { + if (rm == 5) + { + ld.disp_size = 4; + if (is64) + ld.flags |= F_RELATIVE; + } + } + else if (pr_67!=0) + { + if (rm == 6) + ld.disp_size = 2; + } + else { + if (rm == 5) + ld.disp_size = 4; + } + break; + case 1: + ld.disp_size = 1; + break; + case 2: + if (is64) + ld.disp_size = 4; + else if (pr_67!=0) + ld.disp_size = 2; + else + ld.disp_size = 4; + break; + } + + if (ld.disp_size>0) + { + ld.disp_offset = (byte)(p - (byte*)code); + p += ld.disp_size; + s += ld.disp_size; + ld.flags |= F_DISP; + } + } + + /* phase 4: parse immediate data */ + if (rexw!=0 && (f & OP_DATA_I16_I32_I64)!=0) + ld.imm_size = 8; + else if ((f & OP_DATA_I16_I32)!=0 || (f & OP_DATA_I16_I32_I64)!=0) + ld.imm_size = (byte)(4 - (pr_66 << 1)); + + /* if exist, add OP_DATA_I16 and OP_DATA_I8 size */ + ld.imm_size += (byte)(f & 3); + + if ((ld.imm_size)!=0) + { + s += ld.imm_size; + ld.imm_offset = (byte)(p - (byte*)code); + ld.flags |= F_IMM; + if ((f & OP_RELATIVE)!=0) + ld.flags |= F_RELATIVE; + } + + /* instruction is too long */ + if (s > 15) + ld.flags |= F_INVALID; + + return s; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs.meta new file mode 100644 index 0000000..969a5a1 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/LDasm.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c561c9729c367e4fbef63f4ec56f268 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs new file mode 100644 index 0000000..2c7c862 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs @@ -0,0 +1,381 @@ +/* + Desc: 一个可以运行时 Hook Mono 方法的工具,让你可以无需修改 UnityEditor.dll 等文件就可以重写其函数功能 + Author: Misaka Mikoto + Github: https://github.com/Misaka-Mikoto-Tech/MonoHook + */ + +using DotNetDetour; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using Unity.Collections.LowLevel.Unsafe; +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; +using System.Runtime.CompilerServices; + + +/* +>>>>>>> 原始 UnityEditor.LogEntries.Clear 一型(.net 4.x) +0000000000403A00 < | 55 | push rbp | +0000000000403A01 | 48 8B EC | mov rbp,rsp | +0000000000403A04 | 48 81 EC 80 00 00 00 | sub rsp,80 | +0000000000403A0B | 48 89 65 B0 | mov qword ptr ss:[rbp-50],rsp | +0000000000403A0F | 48 89 6D A8 | mov qword ptr ss:[rbp-58],rbp | +0000000000403A13 | 48 89 5D C8 | mov qword ptr ss:[rbp-38],rbx | << +0000000000403A17 | 48 89 75 D0 | mov qword ptr ss:[rbp-30],rsi | +0000000000403A1B | 48 89 7D D8 | mov qword ptr ss:[rbp-28],rdi | +0000000000403A1F | 4C 89 65 E0 | mov qword ptr ss:[rbp-20],r12 | +0000000000403A23 | 4C 89 6D E8 | mov qword ptr ss:[rbp-18],r13 | +0000000000403A27 | 4C 89 75 F0 | mov qword ptr ss:[rbp-10],r14 | +0000000000403A2B | 4C 89 7D F8 | mov qword ptr ss:[rbp-8],r15 | +0000000000403A2F | 49 BB 00 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D00 | +0000000000403A39 | 4C 89 5D B8 | mov qword ptr ss:[rbp-48],r11 | +0000000000403A3D | 49 BB 08 2D 1E 1A FE 7F 00 00 | mov r11,7FFE1A1E2D08 | + + +>>>>>>> 二型(.net 2.x) +0000000000403E8F | 55 | push rbp | +0000000000403E90 | 48 8B EC | mov rbp,rsp | +0000000000403E93 | 48 83 EC 70 | sub rsp,70 | +0000000000403E97 | 48 89 65 C8 | mov qword ptr ss:[rbp-38],rsp | +0000000000403E9B | 48 89 5D B8 | mov qword ptr ss:[rbp-48],rbx | +0000000000403E9F | 48 89 6D C0 | mov qword ptr ss:[rbp-40],rbp | <<(16) +0000000000403EA3 | 48 89 75 F8 | mov qword ptr ss:[rbp-8],rsi | +0000000000403EA7 | 48 89 7D F0 | mov qword ptr ss:[rbp-10],rdi | +0000000000403EAB | 4C 89 65 D0 | mov qword ptr ss:[rbp-30],r12 | +0000000000403EAF | 4C 89 6D D8 | mov qword ptr ss:[rbp-28],r13 | +0000000000403EB3 | 4C 89 75 E0 | mov qword ptr ss:[rbp-20],r14 | +0000000000403EB7 | 4C 89 7D E8 | mov qword ptr ss:[rbp-18],r15 | +0000000000403EBB | 48 83 EC 20 | sub rsp,20 | +0000000000403EBF | 49 BB 18 3F 15 13 FE 7F 00 00 | mov r11,7FFE13153F18 | +0000000000403EC9 | 41 FF D3 | call r11 | +0000000000403ECC | 48 83 C4 20 | add rsp,20 | + +>>>>>>>>> arm64 +il2cpp:00000000003DE714 F5 0F 1D F8 STR X21, [SP,#-0x10+var_20]! | << absolute safe +il2cpp:00000000003DE718 F4 4F 01 A9 STP X20, X19, [SP,#0x20+var_10] | << may be safe +il2cpp:00000000003DE71C FD 7B 02 A9 STP X29, X30, [SP,#0x20+var_s0] | +il2cpp:00000000003DE720 FD 83 00 91 ADD X29, SP, #0x20 | +il2cpp:00000000003DE724 B5 30 00 B0 ADRP X21, #_ZZ62GameObject_SetActive_mCF1EEF2A314F3AE | << dangerous: relative instruction, can not be overwritten +il2cpp:00000000003DE728 A2 56 47 F9 LDR method, [X21,#_ZZ62GameObject_SetActive_mCF] ; | +il2cpp:00000000003DE72C F3 03 01 2A MOV W19, W1 | + */ + +namespace MonoHook +{ + /// + /// Hook 类,用来 Hook 某个 C# 方法 + /// + public unsafe class MethodHook + { + public string tag; + public bool isHooked { get; private set; } + public bool isPlayModeHook { get; private set; } + + public MethodBase targetMethod { get; private set; } // 需要被hook的目标方法 + public MethodBase replacementMethod { get; private set; } // 被hook后的替代方法 + public MethodBase proxyMethod { get; private set; } // 目标方法的代理方法(可以通过此方法调用被hook后的原方法) + + private IntPtr _targetPtr; // 目标方法被 jit 后的地址指针 + private IntPtr _replacementPtr; + private IntPtr _proxyPtr; + + private CodePatcher _codePatcher; + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + /// + /// call `MethodInfo.MethodHandle.GetFunctionPointer()` + /// will visit static class `UnityEditor.IMGUI.Controls.TreeViewGUI.Styles` and invoke its static constructor, + /// and init static filed `foldout`, but `GUISKin.current` is null now, + /// so we should wait until `GUISKin.current` has a valid value + /// + private static FieldInfo s_fi_GUISkin_current; +#endif + + static MethodHook() + { +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + s_fi_GUISkin_current = typeof(GUISkin).GetField("current", BindingFlags.Static | BindingFlags.NonPublic); +#endif + } + + /// + /// 创建一个 Hook + /// + /// 需要替换的目标方法 + /// 准备好的替换方法 + /// 如果还需要调用原始目标方法,可以通过此参数的方法调用,如果不需要可以填 null + public MethodHook(MethodBase targetMethod, MethodBase replacementMethod, MethodBase proxyMethod, string data = "") + { + this.targetMethod = targetMethod; + this.replacementMethod = replacementMethod; + this.proxyMethod = proxyMethod; + this.tag = data; + + CheckMethod(); + } + + public void Install() + { + if (LDasm.IsiOS()) // iOS 不支持修改 code 所在区域 page + return; + + if (isHooked) + return; + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + if (s_fi_GUISkin_current.GetValue(null) != null) + DoInstall(); + else + EditorApplication.update += OnEditorUpdate; +#else + DoInstall(); +#endif + isPlayModeHook = Application.isPlaying; + } + + public void Uninstall() + { + if (!isHooked) + return; + + _codePatcher.RemovePatch(); + + isHooked = false; + HookPool.RemoveHooker(targetMethod); + } + + #region private + private void DoInstall() + { + if (targetMethod == null || replacementMethod == null) + throw new Exception("none of methods targetMethod or replacementMethod can be null"); + + HookPool.AddHook(targetMethod, this); + + if (_codePatcher == null) + { + if (GetFunctionAddr()) + { +#if ENABLE_HOOK_DEBUG + UnityEngine.Debug.Log($"Original [{targetMethod.DeclaringType.Name}.{targetMethod.Name}]: {HookUtils.HexToString(_targetPtr.ToPointer(), 64, -16)}"); + UnityEngine.Debug.Log($"Original [{replacementMethod.DeclaringType.Name}.{replacementMethod.Name}]: {HookUtils.HexToString(_replacementPtr.ToPointer(), 64, -16)}"); + if(proxyMethod != null) + UnityEngine.Debug.Log($"Original [{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}]: {HookUtils.HexToString(_proxyPtr.ToPointer(), 64, -16)}"); +#endif + + CreateCodePatcher(); + _codePatcher.ApplyPatch(); + +#if ENABLE_HOOK_DEBUG + UnityEngine.Debug.Log($"New [{targetMethod.DeclaringType.Name}.{targetMethod.Name}]: {HookUtils.HexToString(_targetPtr.ToPointer(), 64, -16)}"); + UnityEngine.Debug.Log($"New [{replacementMethod.DeclaringType.Name}.{replacementMethod.Name}]: {HookUtils.HexToString(_replacementPtr.ToPointer(), 64, -16)}"); + if(proxyMethod != null) + UnityEngine.Debug.Log($"New [{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}]: {HookUtils.HexToString(_proxyPtr.ToPointer(), 64, -16)}"); +#endif + } + } + + isHooked = true; + } + + private void CheckMethod() + { + if (targetMethod == null || replacementMethod == null) + throw new Exception("MethodHook:targetMethod and replacementMethod and proxyMethod can not be null"); + + string methodName = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}"; + if (targetMethod.IsAbstract) + throw new Exception($"WRANING: you can not hook abstract method [{methodName}]"); + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + int minMethodBodySize = 10; + + { + if ((targetMethod.MethodImplementationFlags & MethodImplAttributes.InternalCall) != MethodImplAttributes.InternalCall) + { + int codeSize = targetMethod.GetMethodBody().GetILAsByteArray().Length; // GetMethodBody can not call on il2cpp + if (codeSize < minMethodBodySize) + UnityEngine.Debug.LogWarning($"WRANING: you can not hook method [{methodName}], cause its method body is too short({codeSize}), will random crash on IL2CPP release mode"); + } + } + + if(proxyMethod != null) + { + methodName = $"{proxyMethod.DeclaringType.Name}.{proxyMethod.Name}"; + int codeSize = proxyMethod.GetMethodBody().GetILAsByteArray().Length; + if (codeSize < minMethodBodySize) + UnityEngine.Debug.LogWarning($"WRANING: size of method body[{methodName}] is too short({codeSize}), will random crash on IL2CPP release mode, please fill some dummy code inside"); + + if ((proxyMethod.MethodImplementationFlags & MethodImplAttributes.NoOptimization) != MethodImplAttributes.NoOptimization) + throw new Exception($"WRANING: method [{methodName}] must has a Attribute `MethodImpl(MethodImplOptions.NoOptimization)` to prevent code call to this optimized by compiler(pass args by shared stack)"); + } +#endif + } + + private void CreateCodePatcher() + { + long addrOffset = Math.Abs(_targetPtr.ToInt64() - _proxyPtr.ToInt64()); + + if(_proxyPtr != IntPtr.Zero) + addrOffset = Math.Max(addrOffset, Math.Abs(_targetPtr.ToInt64() - _proxyPtr.ToInt64())); + + if (LDasm.IsARM()) + { + if (IntPtr.Size == 8) + _codePatcher = new CodePatcher_arm64_near(_targetPtr, _replacementPtr, _proxyPtr); + else if (addrOffset < ((1 << 25) - 1)) + _codePatcher = new CodePatcher_arm32_near(_targetPtr, _replacementPtr, _proxyPtr); + else if (addrOffset < ((1 << 27) - 1)) + _codePatcher = new CodePatcher_arm32_far(_targetPtr, _replacementPtr, _proxyPtr); + else + throw new Exception("address of target method and replacement method are too far, can not hook"); + } + else + { + if (IntPtr.Size == 8) + { + if(addrOffset < 0x7fffffff) // 2G + _codePatcher = new CodePatcher_x64_near(_targetPtr, _replacementPtr, _proxyPtr); + else + _codePatcher = new CodePatcher_x64_far(_targetPtr, _replacementPtr, _proxyPtr); + } + else + _codePatcher = new CodePatcher_x86(_targetPtr, _replacementPtr, _proxyPtr); + } + } + + /// + /// 获取对应函数jit后的native code的地址 + /// + private bool GetFunctionAddr() + { + _targetPtr = GetFunctionAddr(targetMethod); + _replacementPtr = GetFunctionAddr(replacementMethod); + _proxyPtr = GetFunctionAddr(proxyMethod); + + if (_targetPtr == IntPtr.Zero || _replacementPtr == IntPtr.Zero) + return false; + + if (proxyMethod != null && _proxyPtr == null) + return false; + + if(_replacementPtr == _targetPtr) + { + throw new Exception($"the addresses of target method {targetMethod.Name} and replacement method {replacementMethod.Name} can not be same"); + } + + if (LDasm.IsThumb(_targetPtr) || LDasm.IsThumb(_replacementPtr)) + { + throw new Exception("does not support thumb arch"); + } + + return true; + } + + + [StructLayout(LayoutKind.Sequential, Pack = 1)] // 好像在 IL2CPP 里无效 + private struct __ForCopy + { + public long __dummy; + public MethodBase method; + } + /// + /// 获取方法指令地址 + /// + /// + /// + private IntPtr GetFunctionAddr(MethodBase method) + { + if (method == null) + return IntPtr.Zero; + + if (!LDasm.IsIL2CPP()) + return method.MethodHandle.GetFunctionPointer(); + else + { + /* + // System.Reflection.MonoMethod + typedef struct Il2CppReflectionMethod + { + Il2CppObject object; + const MethodInfo *method; + Il2CppString *name; + Il2CppReflectionType *reftype; + } Il2CppReflectionMethod; + + typedef Il2CppClass Il2CppVTable; + typedef struct Il2CppObject + { + union + { + Il2CppClass *klass; + Il2CppVTable *vtable; + }; + MonitorData *monitor; + } Il2CppObject; + + typedef struct MethodInfo + { + Il2CppMethodPointer methodPointer; // this is the pointer to native code of method + InvokerMethod invoker_method; + const char* name; + Il2CppClass *klass; + const Il2CppType *return_type; + const ParameterInfo* parameters; + // ... + } + */ + + __ForCopy __forCopy = new __ForCopy() { method = method }; + + long* ptr = &__forCopy.__dummy; + ptr++; // addr of _forCopy.method + + IntPtr methodAddr = IntPtr.Zero; + if (sizeof(IntPtr) == 8) + { + long methodDataAddr = *(long*)ptr; + byte* ptrData = (byte*)methodDataAddr + sizeof(IntPtr) * 2; // offset of Il2CppReflectionMethod::const MethodInfo *method; + + long methodPtr = 0; + methodPtr = *(long*)ptrData; + methodAddr = new IntPtr(*(long*)methodPtr); // MethodInfo::Il2CppMethodPointer methodPointer; + } + else + { + int methodDataAddr = *(int*)ptr; + byte* ptrData = (byte*)methodDataAddr + sizeof(IntPtr) * 2; // offset of Il2CppReflectionMethod::const MethodInfo *method; + + int methodPtr = 0; + methodPtr = *(int*)ptrData; + methodAddr = new IntPtr(*(int*)methodPtr); + } + return methodAddr; + } + } + +#if UNITY_EDITOR && !UNITY_2020_3_OR_NEWER + private void OnEditorUpdate() + { + if (s_fi_GUISkin_current.GetValue(null) != null) + { + try + { + DoInstall(); + } + finally + { + EditorApplication.update -= OnEditorUpdate; + } + } + } +#endif + + #endregion + } + +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs.meta new file mode 100644 index 0000000..007e62c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/MethodHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd0b8071cf434d6498160259e3829980 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins.meta new file mode 100644 index 0000000..1f7f284 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16b9dc031f67b4fe5ad79c230f75768c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp new file mode 100644 index 0000000..106d990 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp @@ -0,0 +1,23 @@ +// +// Utils.cpp +// MonoHookUtils_OSX +// +// Created by Misaka-Mikoto on 2022/8/31. +// +#include +#include +#include +#include +#include + +extern "C"{ + +void* memcpy_jit(void* dst, void* src, int32_t size) +{ + pthread_jit_write_protect_np(0); + void* ret = memcpy(dst, src, size); + pthread_jit_write_protect_np(1); + sys_icache_invalidate (dst, size); + return ret; +} +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp.meta new file mode 100644 index 0000000..447c3db --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/Utils.cpp.meta @@ -0,0 +1,81 @@ +fileFormatVersion: 2 +guid: 56b28b5583a184c669dcb968d175544c +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: ARM64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh new file mode 100644 index 0000000..8de985c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +clang -shared -undefined dynamic_lookup -o libMonoHookUtils_OSX.dylib Utils.cpp + diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh.meta new file mode 100644 index 0000000..ed9f26d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/build_libMonoHookUtils_OSX.dylib.sh.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 69eeb734e262a0a4fbe0887249198f73 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon.meta new file mode 100644 index 0000000..2e09562 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7adba4475cf0bdc4fa7995c0d748f480 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib new file mode 100644 index 0000000..edabc8a Binary files /dev/null and b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib differ diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib.meta new file mode 100644 index 0000000..3045ab9 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/silicon/libMonoHookUtils_OSX.dylib.meta @@ -0,0 +1,81 @@ +fileFormatVersion: 2 +guid: e092a73910a69894daea44290d7292f6 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: ARM64 + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64.meta new file mode 100644 index 0000000..d4267c3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 31f6a810e38e66f4c832b135770a04bb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib new file mode 100644 index 0000000..07f9063 Binary files /dev/null and b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib differ diff --git a/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib.meta b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib.meta new file mode 100644 index 0000000..f301feb --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/3rds/UnityHook/Plugins/x86_64/libMonoHookUtils_OSX.dylib.meta @@ -0,0 +1,81 @@ +fileFormatVersion: 2 +guid: 4adb23596911347faa69537b900c9f5e +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI.meta new file mode 100644 index 0000000..0557322 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d26c8b77c84f09442a05f2c67e5e09b8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs new file mode 100644 index 0000000..43846b0 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.ABI +{ + public static class ABIUtil + { + public static string GetHybridCLRPlatformMacro(PlatformABI abi) + { + switch(abi) + { + case PlatformABI.Arm64: return "HYBRIDCLR_ABI_ARM_64"; + case PlatformABI.Universal64: return "HYBRIDCLR_ABI_UNIVERSAL_64"; + case PlatformABI.Universal32: return "HYBRIDCLR_ABI_UNIVERSAL_32"; + case PlatformABI.WebGL32: return "HYBRIDCLR_ABI_WEBGL32"; + default: throw new NotSupportedException(); + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs.meta new file mode 100644 index 0000000..79a2ec0 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ABIUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: edeb86f4b5b13ca4cb0fe9d87ce509bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs new file mode 100644 index 0000000..6675f34 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs @@ -0,0 +1,77 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.ABI +{ + public class MethodDesc : IEquatable + { + public string Sig { get; private set; } + + public MethodDef MethodDef { get; set; } + + public ReturnInfo ReturnInfo { get; set; } + + public List ParamInfos { get; set; } + + public void Init() + { + for(int i = 0; i < ParamInfos.Count; i++) + { + ParamInfos[i].Index = i; + } + Sig = CreateCallSigName(); + } + + public void TransfromSigTypes(Func transformer) + { + ReturnInfo.Type = transformer(ReturnInfo.Type, true); + foreach(var paramType in ParamInfos) + { + paramType.Type = transformer(paramType.Type, false); + } + } + + public string CreateCallSigName() + { + var n = new StringBuilder(); + n.Append(ReturnInfo.Type.CreateSigName()); + foreach(var param in ParamInfos) + { + n.Append(param.Type.CreateSigName()); + } + return n.ToString(); + } + + public string CreateInvokeSigName() + { + var n = new StringBuilder(); + n.Append(ReturnInfo.Type.CreateSigName()); + foreach (var param in ParamInfos) + { + n.Append(param.Type.CreateSigName()); + } + return n.ToString(); + } + + public override bool Equals(object obj) + { + return Equals((MethodDesc)obj); + } + + public bool Equals(MethodDesc other) + { + return Sig == other.Sig; + } + + public override int GetHashCode() + { + return Sig.GetHashCode(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs.meta new file mode 100644 index 0000000..0a0a97a --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/MethodDesc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28e06667d06f37b4990b16f54f903a35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs new file mode 100644 index 0000000..fc2cc7f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.ABI +{ + + public class ParamInfo + { + public TypeInfo Type { get; set; } + + public int Index { get; set; } + + } + + public class ReturnInfo + { + public TypeInfo Type { get; set; } + + public bool IsVoid => Type.PorType == ParamOrReturnType.VOID; + + public override string ToString() + { + return Type.GetTypeName(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs.meta new file mode 100644 index 0000000..6b4174e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2ba16cf4bf82374c814789b6ced3abd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs new file mode 100644 index 0000000..f8a478c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.ABI +{ + public enum ParamOrReturnType + { + VOID, + I1, + U1, + I2, + U2, + I4, + U4, + I8, + U8, + R4, + R8, + I, + U, + TYPEDBYREF, + STRUCT, + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs.meta new file mode 100644 index 0000000..12ffd4b --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ParamOrReturnType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80682e47c38a2f04f8af94d356688cf0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs new file mode 100644 index 0000000..62fdb16 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs @@ -0,0 +1,10 @@ +namespace HybridCLR.Editor.ABI +{ + public enum PlatformABI + { + Universal32, + Universal64, + Arm64, + WebGL32, + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs.meta new file mode 100644 index 0000000..bea367c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/PlatformABI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9f06ff0612105b4ea20e0309e759e24 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs new file mode 100644 index 0000000..12af7ae --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs @@ -0,0 +1,102 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.ABI +{ + public class TypeCreator + { + private readonly Dictionary _typeInfoCache = new Dictionary(TypeEqualityComparer.Instance); + + private int _nextStructId = 0; + + public TypeInfo CreateTypeInfo(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + if (!_typeInfoCache.TryGetValue(type, out var typeInfo)) + { + typeInfo = CreateTypeInfo0(type); + _typeInfoCache.Add(type, typeInfo); + } + return typeInfo; + } + + TypeInfo CreateTypeInfo0(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + if (type.IsByRef) + { + return TypeInfo.s_u; + } + switch (type.ElementType) + { + case ElementType.Void: return TypeInfo.s_void; + case ElementType.Boolean: return TypeInfo.s_u1; + case ElementType.I1: return TypeInfo.s_i1; + case ElementType.U1: return TypeInfo.s_u1; + case ElementType.I2: return TypeInfo.s_i2; + case ElementType.Char: + case ElementType.U2: return TypeInfo.s_u2; + case ElementType.I4: return TypeInfo.s_i4; + case ElementType.U4: return TypeInfo.s_u4; + case ElementType.I8: return TypeInfo.s_i8; + case ElementType.U8: return TypeInfo.s_u8; + case ElementType.R4: return TypeInfo.s_r4; + case ElementType.R8: return TypeInfo.s_r8; + case ElementType.I: return TypeInfo.s_i; + case ElementType.U: + case ElementType.String: + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.FnPtr: + case ElementType.Object: + case ElementType.Module: + case ElementType.Var: + case ElementType.MVar: + return TypeInfo.s_u; + case ElementType.TypedByRef: return TypeInfo.s_typedByRef; + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{type} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function"); + } + if (typeDef.IsEnum) + { + return CreateTypeInfo(typeDef.GetEnumUnderlyingType()); + } + return CreateValueType(type); + } + case ElementType.GenericInst: + { + GenericInstSig gis = (GenericInstSig)type; + if (!gis.GenericType.IsValueType) + { + return TypeInfo.s_u; + } + TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return CreateTypeInfo(typeDef.GetEnumUnderlyingType()); + } + return CreateValueType(type); + } + default: throw new NotSupportedException($"{type.ElementType}"); + } + } + + protected TypeInfo CreateValueType(TypeSig type) + { + return new TypeInfo(ParamOrReturnType.STRUCT, type, _nextStructId++); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs.meta new file mode 100644 index 0000000..7452840 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b1df5760b488fa43a68843c46fda63a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs new file mode 100644 index 0000000..6d0b404 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs @@ -0,0 +1,116 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace HybridCLR.Editor.ABI +{ + public class TypeInfo : IEquatable + { + + public static readonly TypeInfo s_void = new TypeInfo(ParamOrReturnType.VOID); + public static readonly TypeInfo s_i1 = new TypeInfo(ParamOrReturnType.I1); + public static readonly TypeInfo s_u1 = new TypeInfo(ParamOrReturnType.U1); + public static readonly TypeInfo s_i2 = new TypeInfo(ParamOrReturnType.I2); + public static readonly TypeInfo s_u2 = new TypeInfo(ParamOrReturnType.U2); + public static readonly TypeInfo s_i4 = new TypeInfo(ParamOrReturnType.I4); + public static readonly TypeInfo s_u4 = new TypeInfo(ParamOrReturnType.U4); + public static readonly TypeInfo s_i8 = new TypeInfo(ParamOrReturnType.I8); + public static readonly TypeInfo s_u8 = new TypeInfo(ParamOrReturnType.U8); + public static readonly TypeInfo s_r4 = new TypeInfo(ParamOrReturnType.R4); + public static readonly TypeInfo s_r8 = new TypeInfo(ParamOrReturnType.R8); + public static readonly TypeInfo s_i = new TypeInfo(ParamOrReturnType.I); + public static readonly TypeInfo s_u = new TypeInfo(ParamOrReturnType.U); + public static readonly TypeInfo s_typedByRef = new TypeInfo(ParamOrReturnType.TYPEDBYREF); + + public const string strTypedByRef = "typedbyref"; + + public TypeInfo(ParamOrReturnType portype, TypeSig klass = null, int typeId = 0) + { + PorType = portype; + Klass = klass; + _typeId = typeId; + } + + public ParamOrReturnType PorType { get; } + + public TypeSig Klass { get; } + + public bool IsStruct => PorType == ParamOrReturnType.STRUCT; + + public bool IsPrimitiveType => PorType <= ParamOrReturnType.U; + + private readonly int _typeId; + + public int TypeId => _typeId; + + public bool Equals(TypeInfo other) + { + return PorType == other.PorType && TypeEqualityComparer.Instance.Equals(Klass, other.Klass); + } + + public override bool Equals(object obj) + { + return Equals((TypeInfo)obj); + } + + public override int GetHashCode() + { + return (int)PorType * 23 + (Klass != null ? TypeEqualityComparer.Instance.GetHashCode(Klass) : 0); + } + + public bool NeedExpandValue() + { + return PorType >= ParamOrReturnType.I1 && PorType <= ParamOrReturnType.U2; + } + + public string CreateSigName() + { + switch (PorType) + { + case ParamOrReturnType.VOID: return "v"; + case ParamOrReturnType.I1: return "i1"; + case ParamOrReturnType.U1: return "u1"; + case ParamOrReturnType.I2: return "i2"; + case ParamOrReturnType.U2: return "u2"; + case ParamOrReturnType.I4: return "i4"; + case ParamOrReturnType.U4: return "u4"; + case ParamOrReturnType.I8: return "i8"; + case ParamOrReturnType.U8: return "u8"; + case ParamOrReturnType.R4: return "r4"; + case ParamOrReturnType.R8: return "r8"; + case ParamOrReturnType.I: return "i"; + case ParamOrReturnType.U: return "u"; + case ParamOrReturnType.TYPEDBYREF: return strTypedByRef; + case ParamOrReturnType.STRUCT: return $"s{_typeId}"; + default: throw new NotSupportedException(PorType.ToString()); + }; + } + + public string GetTypeName() + { + switch (PorType) + { + case ParamOrReturnType.VOID: return "void"; + case ParamOrReturnType.I1: return "int8_t"; + case ParamOrReturnType.U1: return "uint8_t"; + case ParamOrReturnType.I2: return "int16_t"; + case ParamOrReturnType.U2: return "uint16_t"; + case ParamOrReturnType.I4: return "int32_t"; + case ParamOrReturnType.U4: return "uint32_t"; + case ParamOrReturnType.I8: return "int64_t"; + case ParamOrReturnType.U8: return "uint64_t"; + case ParamOrReturnType.R4: return "float"; + case ParamOrReturnType.R8: return "double"; + case ParamOrReturnType.I: return "intptr_t"; + case ParamOrReturnType.U: return "uintptr_t"; + case ParamOrReturnType.TYPEDBYREF: return "Il2CppTypedRef"; + case ParamOrReturnType.STRUCT: return $"__struct_{_typeId}__"; + default: throw new NotImplementedException(PorType.ToString()); + }; + } + + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs.meta new file mode 100644 index 0000000..5df8577 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/TypeInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ffafce7f1f0bf614d95b48ca39385377 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs new file mode 100644 index 0000000..a97dd02 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs @@ -0,0 +1,181 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.ABI +{ + public class ValueTypeSizeAligmentCalculator + { + public static ValueTypeSizeAligmentCalculator Caculator64 { get; } = new ValueTypeSizeAligmentCalculator(false); + + public static ValueTypeSizeAligmentCalculator Caculator32 { get; } = new ValueTypeSizeAligmentCalculator(true); + + public ValueTypeSizeAligmentCalculator(bool arch32) + { + _referenceSize = arch32 ? 4 : 8; + } + + private readonly int _referenceSize; + + private static bool IsIgnoreField(FieldDef field) + { + var ignoreAttr = field.CustomAttributes.Where(a => a.AttributeType.FullName == "UnityEngine.Bindings.IgnoreAttribute").FirstOrDefault(); + if (ignoreAttr == null) + { + return false; + } + CANamedArgument arg = ignoreAttr.GetProperty("DoesNotContributeToSize"); + if(arg != null && (bool)arg.Value) + { + //Debug.Log($"IgnoreField.DoesNotContributeToSize = true:{field}"); + return true; + } + return false; + } + + private (int Size, int Aligment) SizeAndAligmentOfStruct(TypeSig type) + { + int totalSize = 0; + int packAligment = 8; + int maxAligment = 1; + + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow(); + + List klassInst = type.ToGenericInstSig()?.GenericArguments?.ToList(); + GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null; + + ClassLayout sa = typeDef.ClassLayout; + if (sa != null && sa.PackingSize > 0) + { + packAligment = sa.PackingSize; + } + bool useSLSize = true; + foreach (FieldDef field in typeDef.Fields) + { + if (field.IsStatic) + { + continue; + } + TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType; + var (fs, fa) = SizeAndAligmentOf(fieldType); + fa = Math.Min(fa, packAligment); + if (fa > maxAligment) + { + maxAligment = fa; + } + if (IsIgnoreField(field)) + { + continue; + } + if (typeDef.Layout.HasFlag(dnlib.DotNet.TypeAttributes.ExplicitLayout)) + { + int offset = (int)field.FieldOffset.Value; + totalSize = Math.Max(totalSize, offset + fs); + if (sa != null && offset > sa.ClassSize) + { + useSLSize = false; + } + } + else + { + if (totalSize % fa != 0) + { + totalSize = (totalSize + fa - 1) / fa * fa; + } + totalSize += fs; + if (sa != null && totalSize > sa.ClassSize) + { + useSLSize = false; + } + } + } + if (totalSize == 0) + { + totalSize = maxAligment; + } + if (totalSize % maxAligment != 0) + { + totalSize = (totalSize + maxAligment - 1) / maxAligment * maxAligment; + } + if (sa != null && sa.ClassSize > 0) + { + if (/*sa.Value == LayoutKind.Explicit &&*/ useSLSize) + { + totalSize = (int)sa.ClassSize; + while(totalSize % maxAligment != 0) + { + maxAligment /= 2; + } + } + } + return (totalSize, maxAligment); + } + + public (int Size, int Aligment) SizeAndAligmentOf(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + if (type.IsByRef || !type.IsValueType || type.IsArray) + return (_referenceSize, _referenceSize); + + switch (type.ElementType) + { + case ElementType.Void: throw new NotSupportedException(type.ToString()); + case ElementType.Boolean: + case ElementType.I1: + case ElementType.U1: return (1, 1); + case ElementType.Char: + case ElementType.I2: + case ElementType.U2: return (2, 2); + case ElementType.I4: + case ElementType.U4: return (4, 4); + case ElementType.I8: + case ElementType.U8: return (8, 8); + case ElementType.R4: return (4, 4); + case ElementType.R8: return (8, 8); + case ElementType.I: + case ElementType.U: + case ElementType.String: + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.FnPtr: + case ElementType.Object: + case ElementType.Module: return (_referenceSize, _referenceSize); + case ElementType.TypedByRef: return SizeAndAligmentOfStruct(type); + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return SizeAndAligmentOf(typeDef.GetEnumUnderlyingType()); + } + return SizeAndAligmentOfStruct(type); + } + case ElementType.GenericInst: + { + GenericInstSig gis = (GenericInstSig)type; + if (!gis.GenericType.IsValueType) + { + return (_referenceSize, _referenceSize); + } + TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return SizeAndAligmentOf(typeDef.GetEnumUnderlyingType()); + } + return SizeAndAligmentOfStruct(type); + } + default: throw new NotSupportedException(type.ToString()); + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs.meta new file mode 100644 index 0000000..4b83d63 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ABI/ValueTypeSizeAligmentCalculator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7af32bdf1cf55c42bfc449820d401cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/AOT.meta b/Packages/com.code-philosophy.hybridclr/Editor/AOT.meta new file mode 100644 index 0000000..9b9539f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/AOT.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b4071bf66ac9c544487ae88b5ee9b20a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs b/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs new file mode 100644 index 0000000..5d9c475 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs @@ -0,0 +1,208 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.AOT +{ + + public class Analyzer + { + public class Options + { + public AssemblyReferenceDeepCollector Collector { get; set; } + + public int MaxIterationCount { get; set; } + } + + private readonly int _maxInterationCount; + + private readonly AssemblyReferenceDeepCollector _assemblyCollector; + + private readonly HashSet _genericTypes = new HashSet(); + private readonly HashSet _genericMethods = new HashSet(); + + private List _processingMethods = new List(); + private List _newMethods = new List(); + + public IReadOnlyCollection GenericTypes => _genericTypes; + + public IReadOnlyCollection GenericMethods => _genericMethods; + + private readonly MethodReferenceAnalyzer _methodReferenceAnalyzer; + + private readonly HashSet _hotUpdateAssemblyFiles; + + public ConstraintContext ConstraintContext { get; } = new ConstraintContext(); + + public List AotGenericTypes { get; } = new List(); + + public List AotGenericMethods { get; } = new List(); + + public Analyzer(Options options) + { + _assemblyCollector = options.Collector; + _maxInterationCount = options.MaxIterationCount; + _methodReferenceAnalyzer = new MethodReferenceAnalyzer(this.OnNewMethod); + _hotUpdateAssemblyFiles = new HashSet(options.Collector.GetRootAssemblyNames().Select(assName => assName + ".dll")); + } + + private void TryAddAndWalkGenericType(GenericClass gc) + { + if (gc == null) + { + return; + } + gc = gc.ToGenericShare(); + if (_genericTypes.Add(gc) && NeedWalk(null, gc.Type)) + { + WalkType(gc); + } + } + + private bool NeedWalk(MethodDef callFrom, TypeDef type) + { + return _hotUpdateAssemblyFiles.Contains(type.Module.Name) || callFrom == null || callFrom.HasGenericParameters; + } + + private bool IsAotType(TypeDef type) + { + return !_hotUpdateAssemblyFiles.Contains(type.Module.Name); + } + + private bool IsAotGenericMethod(MethodDef method) + { + return IsAotType(method.DeclaringType) && method.HasGenericParameters; + } + + private void OnNewMethod(MethodDef methodDef, List klassGenericInst, List methodGenericInst, GenericMethod method) + { + if(method == null) + { + return; + } + if (NeedWalk(methodDef, method.Method.DeclaringType) && _genericMethods.Add(method)) + { + _newMethods.Add(method); + } + if (method.KlassInst != null) + { + TryAddAndWalkGenericType(new GenericClass(method.Method.DeclaringType, method.KlassInst)); + } + } + + private void TryAddMethodNotWalkType(GenericMethod method) + { + if (method == null) + { + return; + } + if (NeedWalk(null, method.Method.DeclaringType) && _genericMethods.Add(method)) + { + _newMethods.Add(method); + } + } + + private void WalkType(GenericClass gc) + { + //Debug.Log($"typespec:{sig} {sig.GenericType} {sig.GenericType.TypeDefOrRef.ResolveTypeDef()}"); + //Debug.Log($"== walk generic type:{new GenericInstSig(gc.Type.ToTypeSig().ToClassOrValueTypeSig(), gc.KlassInst)}"); + ITypeDefOrRef baseType = gc.Type.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass parentType = GenericClass.ResolveClass((TypeSpec)baseType, new GenericArgumentContext(gc.KlassInst, null)); + TryAddAndWalkGenericType(parentType); + } + foreach (var method in gc.Type.Methods) + { + if (method.HasGenericParameters || !method.HasBody || method.Body.Instructions == null) + { + continue; + } + var gm = new GenericMethod(method, gc.KlassInst, null).ToGenericShare(); + //Debug.Log($"add method:{gm.Method} {gm.KlassInst}"); + TryAddMethodNotWalkType(gm); + } + } + + private void WalkType(TypeDef typeDef) + { + if (typeDef.HasGenericParameters) + { + return; + } + ITypeDefOrRef baseType = typeDef.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass gc = GenericClass.ResolveClass((TypeSpec)baseType, null); + TryAddAndWalkGenericType(gc); + } + } + + private void Prepare() + { + // 将所有非泛型函数全部加入函数列表,同时立马walk这些method。 + // 后续迭代中将只遍历MethodSpec + foreach (var ass in _assemblyCollector.GetLoadedModulesOfRootAssemblies()) + { + foreach (TypeDef typeDef in ass.GetTypes()) + { + WalkType(typeDef); + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.TypeSpecTable.Rows; rid <= n; rid++) + { + var ts = ass.ResolveTypeSpec(rid); + var cs = GenericClass.ResolveClass(ts, null)?.ToGenericShare(); + if (cs != null) + { + TryAddAndWalkGenericType(cs); + } + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.MethodSpecTable.Rows; rid <= n; rid++) + { + var ms = ass.ResolveMethodSpec(rid); + var gm = GenericMethod.ResolveMethod(ms, null)?.ToGenericShare(); + TryAddMethodNotWalkType(gm); + } + } + Debug.Log($"PostPrepare genericTypes:{_genericTypes.Count} genericMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + + private void RecursiveCollect() + { + for (int i = 0; i < _maxInterationCount && _newMethods.Count > 0; i++) + { + var temp = _processingMethods; + _processingMethods = _newMethods; + _newMethods = temp; + _newMethods.Clear(); + + foreach (var method in _processingMethods) + { + _methodReferenceAnalyzer.WalkMethod(method.Method, method.KlassInst, method.MethodInst); + } + Debug.Log($"iteration:[{i}] genericClass:{_genericTypes.Count} genericMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + } + + private void FilterAOTGenericTypeAndMethods() + { + ConstraintContext cc = this.ConstraintContext; + AotGenericTypes.AddRange(_genericTypes.Where(type => IsAotType(type.Type)).Select(gc => cc.ApplyConstraints(gc))); + AotGenericMethods.AddRange(_genericMethods.Where(method => IsAotGenericMethod(method.Method)).Select(gm => cc.ApplyConstraints(gm))); + } + + public void Run() + { + Prepare(); + RecursiveCollect(); + FilterAOTGenericTypeAndMethods(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs.meta new file mode 100644 index 0000000..2e573fc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/AOT/Analyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30bbf4a80a6cf3a43b3f489747d9dd6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs b/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs new file mode 100644 index 0000000..4a9cd3c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs @@ -0,0 +1,73 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.AOT +{ + + public class ConstraintContext + { + public class ImplType + { + public TypeSig BaseType { get; } + + public List Interfaces { get; } + + public bool ValueType { get; } + + private readonly int _hash; + + public ImplType(TypeSig baseType, List interfaces, bool valueType) + { + BaseType = baseType; + Interfaces = interfaces; + ValueType = valueType; + _hash = ComputHash(); + } + + public override bool Equals(object obj) + { + ImplType o = (ImplType)obj; + return MetaUtil.EqualsTypeSig(this.BaseType, o.BaseType) + && MetaUtil.EqualsTypeSigArray(this.Interfaces, o.Interfaces) + && this.ValueType == o.ValueType; + } + + public override int GetHashCode() + { + return _hash; + } + + private int ComputHash() + { + int hash = 0; + if (BaseType != null) + { + hash = HashUtil.CombineHash(hash, TypeEqualityComparer.Instance.GetHashCode(BaseType)); + } + if (Interfaces.Count > 0) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(Interfaces)); + } + + return hash; + } + } + + public HashSet ImplTypes { get; } = new HashSet(); + + public GenericClass ApplyConstraints(GenericClass gc) + { + return gc; + } + + public GenericMethod ApplyConstraints(GenericMethod gm) + { + return gm; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs.meta new file mode 100644 index 0000000..3e99cab --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/AOT/ConstraintContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 812d81a75b690394bbe16ef5f0bcbc46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs b/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs new file mode 100644 index 0000000..20612c4 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs @@ -0,0 +1,139 @@ +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.AOT +{ + public class GenericReferenceWriter + { + private static readonly Dictionary _typeNameMapping = new Dictionary + { + {typeof(bool), "bool" }, + {typeof(byte), "byte" }, + {typeof(sbyte), "sbyte" }, + {typeof(short), "short" }, + {typeof(ushort), "ushort" }, + {typeof(int), "int" }, + {typeof(uint), "uint" }, + {typeof(long), "long" }, + {typeof(ulong), "ulong" }, + {typeof(float), "float" }, + {typeof(double), "double" }, + {typeof(object), "object" }, + {typeof(string), "string" }, + }; + + private readonly Dictionary _typeSimpleNameMapping = new Dictionary(); + private readonly Regex _systemTypePattern; + private readonly Regex _genericPattern = new Regex(@"`\d+"); + + public GenericReferenceWriter() + { + foreach (var e in _typeNameMapping) + { + _typeSimpleNameMapping.Add(e.Key.FullName, e.Value); + } + _systemTypePattern = new Regex(string.Join("|", _typeSimpleNameMapping.Keys.Select (k => $@"\b{k}\b"))); + } + + public string PrettifyTypeSig(string typeSig) + { + string s = _genericPattern.Replace(typeSig, "").Replace('/', '.'); + return _systemTypePattern.Replace(s, m => _typeSimpleNameMapping[m.Groups[0].Value]); + } + + public string PrettifyMethodSig(string methodSig) + { + string s = PrettifyTypeSig(methodSig).Replace("::", "."); + if (s.Contains(".ctor(")) + { + s = "new " + s.Replace(".ctor(", "("); + } + return s; + } + + public void Write(List types, List methods, string outputFile) + { + string parentDir = Directory.GetParent(outputFile).FullName; + Directory.CreateDirectory(parentDir); + + List codes = new List(); + codes.Add("using System.Collections.Generic;"); + codes.Add("public class AOTGenericReferences : UnityEngine.MonoBehaviour"); + codes.Add("{"); + + codes.Add(""); + codes.Add("\t// {{ AOT assemblies"); + codes.Add("\tpublic static readonly IReadOnlyList PatchedAOTAssemblyList = new List"); + codes.Add("\t{"); + List modules = new HashSet( + types.Select(t => t.Type.Module).Concat(methods.Select(m => m.Method.Module))).ToList(); + modules.Sort((a, b) => a.Name.CompareTo(b.Name)); + foreach (dnlib.DotNet.ModuleDef module in modules) + { + codes.Add($"\t\t\"{module.Name}\","); + } + codes.Add("\t};"); + codes.Add("\t// }}"); + + + codes.Add(""); + codes.Add("\t// {{ constraint implement type"); + + codes.Add("\t// }} "); + + codes.Add(""); + codes.Add("\t// {{ AOT generic types"); + + List typeNames = types.Select(t => PrettifyTypeSig(t.ToTypeSig().ToString())).ToList(); + typeNames.Sort(string.CompareOrdinal); + foreach(var typeName in typeNames) + { + codes.Add($"\t// {typeName}"); + } + + codes.Add("\t// }}"); + + codes.Add(""); + codes.Add("\tpublic void RefMethods()"); + codes.Add("\t{"); + + List<(string, string, string)> methodTypeAndNames = methods.Select(m => + (PrettifyTypeSig(m.Method.DeclaringType.ToString()), PrettifyMethodSig(m.Method.Name), PrettifyMethodSig(m.ToMethodSpec().ToString()))) + .ToList(); + methodTypeAndNames.Sort((a, b) => + { + int c = String.Compare(a.Item1, b.Item1, StringComparison.Ordinal); + if (c != 0) + { + return c; + } + + c = String.Compare(a.Item2, b.Item2, StringComparison.Ordinal); + if (c != 0) + { + return c; + } + return String.Compare(a.Item3, b.Item3, StringComparison.Ordinal); + }); + foreach(var method in methodTypeAndNames) + { + codes.Add($"\t\t// {PrettifyMethodSig(method.Item3)}"); + } + codes.Add("\t}"); + + codes.Add("}"); + + + var utf8WithoutBom = new System.Text.UTF8Encoding(false); + File.WriteAllText(outputFile, string.Join("\n", codes), utf8WithoutBom); + Debug.Log($"[GenericReferenceWriter] write {outputFile}"); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs.meta new file mode 100644 index 0000000..9fc1497 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/AOT/GenericReferenceWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1243cf04685361478972f93b5ca868a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors.meta new file mode 100644 index 0000000..428a0a1 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f80d2287f01c89642a74b0a60f7a3305 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2021OrOlder.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2021OrOlder.cs new file mode 100644 index 0000000..b0788aa --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2021OrOlder.cs @@ -0,0 +1,254 @@ +using System; +using HybridCLR.Editor.Installer; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEditor; +using System.Reflection; +using HybridCLR.Editor.Settings; +#if (UNITY_2020 || UNITY_2021) && UNITY_IOS +using UnityEditor.Build; +using UnityEditor.Callbacks; +using UnityEditor.iOS.Xcode; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + public static class AddLil2cppSourceCodeToXcodeproj2021OrOlder + { + //[MenuItem("Test/GenProj")] + //public static void Modify() + //{ + // OnPostProcessBuild(BuildTarget.iOS, $"{SettingsUtil.ProjectDir}/Build-iOS"); + //} + + //[MenuItem("Test/CreateLumps")] + //public static void CreateLumpsCmd() + //{ + // CreateLumps($"{SettingsUtil.LocalIl2CppDir}/libil2cpp", $"{SettingsUtil.HybridCLRDataDir}/lumps"); + //} + + [PostProcessBuild] + public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) + { + if (target != BuildTarget.iOS || !HybridCLRSettings.Instance.enable) + return; + /* + * 1. 生成lump,并且添加到工程 + 3. 将libil2cpp目录复制到 Library/. 删除旧的. search paths里修改 libil2cpp/include为libil2cpp + 3. Libraries/bdwgc/include -> Libraries/external/bdwgc/include + 4. 将external目录复制到 Library/external。删除旧目录 + 5. 将Library/external/baselib/Platforms/OSX改名为 IOS 全大写 + 6. 将 external/zlib下c 文件添加到工程 + 7. 移除libil2cpp.a + 8. Include path add libil2cpp/os/ClassLibraryPAL/brotli/include + 9. add external/xxHash + */ + + string pbxprojFile = $"{pathToBuiltProject}/Unity-iPhone.xcodeproj/project.pbxproj"; + string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + string dstLibil2cppDir = $"{pathToBuiltProject}/Libraries/libil2cpp"; + string lumpDir = $"{pathToBuiltProject}/Libraries/lumps"; + string srcExternalDir = $"{SettingsUtil.LocalIl2CppDir}/external"; + string dstExternalDir = $"{pathToBuiltProject}/Libraries/external"; + //RemoveExternalLibil2cppOption(srcExternalDir, dstExternalDir); + CopyLibil2cppToXcodeProj(srcLibil2cppDir, dstLibil2cppDir); + CopyExternalToXcodeProj(srcExternalDir, dstExternalDir); + var lumpFiles = CreateLumps(dstLibil2cppDir, lumpDir); + var extraSources = GetExtraSourceFiles(dstExternalDir, dstLibil2cppDir); + var cflags = new List() + { + "-DIL2CPP_MONO_DEBUGGER_DISABLED", + }; + ModifyPBXProject(pathToBuiltProject, pbxprojFile, lumpFiles, extraSources, cflags); + } + + private static string GetRelativePathFromProj(string path) + { + return path.Substring(path.IndexOf("Libraries", StringComparison.Ordinal)).Replace('\\', '/'); + } + + private static void ModifyPBXProject(string pathToBuiltProject, string pbxprojFile, List lumpFiles, List extraFiles, List cflags) + { + var proj = new PBXProject(); + proj.ReadFromFile(pbxprojFile); + string targetGUID = proj.GetUnityFrameworkTargetGuid(); + // 移除旧的libil2cpp.a + var libil2cppGUID = proj.FindFileGuidByProjectPath("Libraries/libil2cpp.a"); + if (!string.IsNullOrEmpty(libil2cppGUID)) + { + proj.RemoveFileFromBuild(targetGUID, libil2cppGUID); + proj.RemoveFile(libil2cppGUID); + File.Delete(Path.Combine(pathToBuiltProject, "Libraries", "libil2cpp.a")); + } + + //var lumpGroupGuid = proj.AddFile("Lumps", $"Classes/Lumps", PBXSourceTree.Group); + + foreach (var lumpFile in lumpFiles) + { + string lumpFileName = Path.GetFileName(lumpFile.lumpFile); + string projPathOfFile = $"Classes/Lumps/{lumpFileName}"; + string relativePathOfFile = GetRelativePathFromProj(lumpFile.lumpFile); + string lumpGuid = proj.FindFileGuidByProjectPath(projPathOfFile); + if (!string.IsNullOrEmpty(lumpGuid)) + { + proj.RemoveFileFromBuild(targetGUID, lumpGuid); + proj.RemoveFile(lumpGuid); + } + lumpGuid = proj.AddFile(relativePathOfFile, projPathOfFile, PBXSourceTree.Source); + proj.AddFileToBuild(targetGUID, lumpGuid); + } + + foreach (var extraFile in extraFiles) + { + string projPathOfFile = $"Classes/Extrals/{Path.GetFileName(extraFile)}"; + string extraFileGuid = proj.FindFileGuidByProjectPath(projPathOfFile); + if (!string.IsNullOrEmpty(extraFileGuid)) + { + proj.RemoveFileFromBuild(targetGUID, extraFileGuid); + proj.RemoveFile(extraFileGuid); + //Debug.LogWarning($"remove exist extra file:{projPathOfFile} guid:{extraFileGuid}"); + } + var lumpGuid = proj.AddFile(GetRelativePathFromProj(extraFile), projPathOfFile, PBXSourceTree.Source); + proj.AddFileToBuild(targetGUID, lumpGuid); + } + + foreach(var configName in proj.BuildConfigNames()) + { + //Debug.Log($"build config:{bcn}"); + string configGuid = proj.BuildConfigByName(targetGUID, configName); + string headerSearchPaths = "HEADER_SEARCH_PATHS"; + string hspProp = proj.GetBuildPropertyForConfig(configGuid, headerSearchPaths); + //Debug.Log($"config guid:{configGuid} prop:{hspProp}"); + string newPro = hspProp.Replace("libil2cpp/include", "libil2cpp") + .Replace("Libraries/bdwgc", "Libraries/external/bdwgc"); + + if (!newPro.Contains("Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include")) + { + newPro += " $(SRCROOT)/Libraries/libil2cpp/os/ClassLibraryPAL/brotli/include"; + } + if (!newPro.Contains("Libraries/external/xxHash")) + { + newPro += " $(SRCROOT)/Libraries/external/xxHash"; + } + //Debug.Log($"config:{bcn} new prop:{newPro}"); + proj.SetBuildPropertyForConfig(configGuid, headerSearchPaths, newPro); + + string cflagKey = "OTHER_CFLAGS"; + string cfProp = proj.GetBuildPropertyForConfig(configGuid, cflagKey); + foreach (var flag in cflags) + { + if (!cfProp.Contains(flag)) + { + cfProp += " " + flag; + } + } + if (configName.Contains("Debug") && !cfProp.Contains("-DIL2CPP_DEBUG=")) + { + cfProp += " -DIL2CPP_DEBUG=1 -DDEBUG=1"; + } + proj.SetBuildPropertyForConfig(configGuid, cflagKey, cfProp); + + } + proj.WriteToFile(pbxprojFile); + } + + private static void CopyLibil2cppToXcodeProj(string srcLibil2cppDir, string dstLibil2cppDir) + { + BashUtil.RemoveDir(dstLibil2cppDir); + BashUtil.CopyDir(srcLibil2cppDir, dstLibil2cppDir, true); + } + + + private static void CopyExternalToXcodeProj(string srcExternalDir, string dstExternalDir) + { + BashUtil.RemoveDir(dstExternalDir); + BashUtil.CopyDir(srcExternalDir, dstExternalDir, true); + + string baselibPlatfromsDir = $"{dstExternalDir}/baselib/Platforms"; + BashUtil.RemoveDir($"{baselibPlatfromsDir}/IOS"); + BashUtil.CopyDir($"{baselibPlatfromsDir}/OSX", $"{baselibPlatfromsDir}/IOS", true); + } + + class LumpFile + { + public List cppFiles = new List(); + + public readonly string lumpFile; + + public readonly string il2cppConfigFile; + + public LumpFile(string lumpFile, string il2cppConfigFile) + { + this.lumpFile = lumpFile; + this.il2cppConfigFile = il2cppConfigFile; + this.cppFiles.Add(il2cppConfigFile); + } + + public void SaveFile() + { + var lumpFileContent = new List(); + foreach (var file in cppFiles) + { + lumpFileContent.Add($"#include \"{GetRelativePathFromProj(file)}\""); + } + File.WriteAllLines(lumpFile, lumpFileContent, Encoding.UTF8); + Debug.Log($"create lump file:{lumpFile}"); + } + } + + private static List CreateLumps(string libil2cppDir, string outputDir) + { + BashUtil.RecreateDir(outputDir); + + string il2cppConfigFile = $"{libil2cppDir}/il2cpp-config.h"; + var lumpFiles = new List(); + int lumpFileIndex = 0; + foreach (var cppDir in Directory.GetDirectories(libil2cppDir, "*", SearchOption.AllDirectories).Concat(new string[] {libil2cppDir})) + { + var lumpFile = new LumpFile($"{outputDir}/lump_{Path.GetFileName(cppDir)}_{lumpFileIndex}.cpp", il2cppConfigFile); + foreach (var file in Directory.GetFiles(cppDir, "*.cpp", SearchOption.TopDirectoryOnly)) + { + lumpFile.cppFiles.Add(file); + } + lumpFile.SaveFile(); + lumpFiles.Add(lumpFile); + ++lumpFileIndex; + } + + var mmFiles = Directory.GetFiles(libil2cppDir, "*.mm", SearchOption.AllDirectories); + if (mmFiles.Length > 0) + { + var lumpFile = new LumpFile($"{outputDir}/lump_mm.mm", il2cppConfigFile); + foreach (var file in mmFiles) + { + lumpFile.cppFiles.Add(file); + } + lumpFile.SaveFile(); + lumpFiles.Add(lumpFile); + } + return lumpFiles; + } + + private static List GetExtraSourceFiles(string externalDir, string libil2cppDir) + { + var files = new List(); + foreach (string extraDir in new string[] + { + $"{externalDir}/zlib", + $"{externalDir}/xxHash", + $"{libil2cppDir}/os/ClassLibraryPAL/brotli", + }) + { + if (!Directory.Exists(extraDir)) + { + continue; + } + files.AddRange(Directory.GetFiles(extraDir, "*.c", SearchOption.AllDirectories)); + } + return files; + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2021OrOlder.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2021OrOlder.cs.meta new file mode 100644 index 0000000..7eba538 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2021OrOlder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61948fcb1bc40ba47b8c10b0ae801ebb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs new file mode 100644 index 0000000..e1cdd9e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs @@ -0,0 +1,57 @@ +using HybridCLR.Editor.Installer; +using HybridCLR.Editor.Settings; +using System.IO; +using System.Text; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Callbacks; +using UnityEngine; + +#if UNITY_2022_2_OR_NEWER && UNITY_IOS + +namespace HybridCLR.Editor.BuildProcessors +{ + public static class AddLil2cppSourceCodeToXcodeproj2022OrNewer + { + //[MenuItem("HybridCLR/Modfiyxcode")] + //public static void Modify() + //{ + // OnPostProcessBuild(BuildTarget.iOS, $"{SettingsUtil.ProjectDir}/Build-iOS"); + //} + + [PostProcessBuild] + public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject) + { + if (target != BuildTarget.iOS || !HybridCLRSettings.Instance.enable) + return; + + string pbxprojFile = $"{pathToBuiltProject}/Unity-iPhone.xcodeproj/project.pbxproj"; + RemoveExternalLibil2cppOption(pbxprojFile); + CopyLibil2cppToXcodeProj(pathToBuiltProject); + } + + private static void RemoveExternalLibil2cppOption(string pbxprojFile) + { + string pbxprojContent = File.ReadAllText(pbxprojFile, Encoding.UTF8); + string removeBuildOption = @"--external-lib-il2-cpp=\""$PROJECT_DIR/Libraries/libil2cpp.a\"""; + if (!pbxprojContent.Contains(removeBuildOption)) + { + //throw new BuildFailedException("modified project.pbxproj fail"); + Debug.LogError("[AddLil2cppSourceCodeToXcodeproj] modified project.pbxproj fail"); + return; + } + pbxprojContent = pbxprojContent.Replace(removeBuildOption, ""); + File.WriteAllText(pbxprojFile, pbxprojContent, Encoding.UTF8); + Debug.Log($"[AddLil2cppSourceCodeToXcodeproj] remove il2cpp build option '{removeBuildOption}' from file '{pbxprojFile}'"); + } + + private static void CopyLibil2cppToXcodeProj(string pathToBuiltProject) + { + string srcLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + string destLibil2cppDir = $"{pathToBuiltProject}/Il2CppOutputProject/IL2CPP/libil2cpp"; + BashUtil.RemoveDir(destLibil2cppDir); + BashUtil.CopyDir(srcLibil2cppDir, destLibil2cppDir, true); + } + } +} +#endif \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs.meta new file mode 100644 index 0000000..e71a765 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/AddLil2cppSourceCodeToXcodeproj2022OrNewer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4ce072f7e4a17248a3d9ebfd011356b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs new file mode 100644 index 0000000..a5840bc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs @@ -0,0 +1,72 @@ +using HybridCLR.Editor.Settings; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using static UnityEngine.GraphicsBuffer; + +namespace HybridCLR.Editor.BuildProcessors +{ + internal class CheckSettings : IPreprocessBuildWithReport + { + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { + HybridCLRSettings globalSettings = SettingsUtil.HybridCLRSettings; + if (!globalSettings.enable || globalSettings.useGlobalIl2cpp) + { + string oldIl2cppPath = Environment.GetEnvironmentVariable("UNITY_IL2CPP_PATH"); + if (!string.IsNullOrEmpty(oldIl2cppPath)) + { + Environment.SetEnvironmentVariable("UNITY_IL2CPP_PATH", ""); + Debug.Log($"[CheckSettings] clean process environment variable: UNITY_IL2CPP_PATH, old vlaue:'{oldIl2cppPath}'"); + } + } + else + { + string curIl2cppPath = Environment.GetEnvironmentVariable("UNITY_IL2CPP_PATH"); + if (curIl2cppPath != SettingsUtil.LocalIl2CppDir) + { + Environment.SetEnvironmentVariable("UNITY_IL2CPP_PATH", SettingsUtil.LocalIl2CppDir); + Debug.Log($"[CheckSettings] UNITY_IL2CPP_PATH old value:'{curIl2cppPath}', new value:'{SettingsUtil.LocalIl2CppDir}'"); + } + } + if (!globalSettings.enable) + { + return; + } + BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); + ScriptingImplementation curScriptingImplementation = PlayerSettings.GetScriptingBackend(buildTargetGroup); + ScriptingImplementation targetScriptingImplementation = ScriptingImplementation.IL2CPP; + if (curScriptingImplementation != targetScriptingImplementation) + { + Debug.LogError($"[CheckSettings] current ScriptingBackend:{curScriptingImplementation},have been switched to:{targetScriptingImplementation} automatically"); + PlayerSettings.SetScriptingBackend(buildTargetGroup, targetScriptingImplementation); + } + + var installer = new Installer.InstallerController(); + if (!installer.HasInstalledHybridCLR()) + { + throw new BuildFailedException($"You have not initialized HybridCLR, please install it via menu 'HybridCLR/Installer'"); + } + + if (installer.PackageVersion != installer.InstalledLibil2cppVersion) + { + throw new BuildFailedException($"You must run `HybridCLR/Installer` after upgrading package"); + } + + HybridCLRSettings gs = SettingsUtil.HybridCLRSettings; + if (((gs.hotUpdateAssemblies?.Length + gs.hotUpdateAssemblyDefinitions?.Length) ?? 0) == 0) + { + Debug.LogWarning("[CheckSettings] No hot update modules configured in HybridCLRSettings"); + } + + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs.meta new file mode 100644 index 0000000..0cc3643 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CheckSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb4ba063068b17247b2d0233420aa5f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs new file mode 100644 index 0000000..1d3259e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs @@ -0,0 +1,107 @@ +using HybridCLR.Editor.Installer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.Il2Cpp; +using UnityEditor.UnityLinker; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + internal class CopyStrippedAOTAssemblies : IPostprocessBuildWithReport, IPreprocessBuildWithReport +#if !UNITY_2021_1_OR_NEWER + , IIl2CppProcessor +#endif + { + + public int callbackOrder => 0; + +#if UNITY_2021_1_OR_NEWER + public static string GetStripAssembliesDir2021(BuildTarget target) + { + string projectDir = SettingsUtil.ProjectDir; + switch (target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: + return $"{projectDir}/Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped"; + case BuildTarget.StandaloneLinux64: + return $"{projectDir}/Library/Bee/artifacts/LinuxPlayerBuildProgram/ManagedStripped"; + case BuildTarget.Android: + return $"{projectDir}/Library/Bee/artifacts/Android/ManagedStripped"; + case BuildTarget.iOS: + return $"{projectDir}/Library/Bee/artifacts/iOS/ManagedStripped"; + case BuildTarget.WebGL: + return $"{projectDir}/Library/Bee/artifacts/WebGL/ManagedStripped"; + case BuildTarget.StandaloneOSX: + return $"{projectDir}/Library/Bee/artifacts/MacStandalonePlayerBuildProgram/ManagedStripped"; + case BuildTarget.PS4: + return $"{projectDir}/Library/Bee/artifacts/PS4PlayerBuildProgram/ManagedStripped"; + case BuildTarget.PS5: + return $"{projectDir}/Library/Bee/artifacts/PS5PlayerBuildProgram/ManagedStripped"; + default: return ""; + } + } +#else + private string GetStripAssembliesDir2020(BuildTarget target) + { + string subPath = target == BuildTarget.Android ? + "assets/bin/Data/Managed" : + "Data/Managed/"; + return $"{SettingsUtil.ProjectDir}/Temp/StagingArea/{subPath}"; + } + + public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data) + { + // 此回调只在 2020中调用 + CopyStripDlls(GetStripAssembliesDir2020(data.target), data.target); + } +#endif + + public static void CopyStripDlls(string srcStripDllPath, BuildTarget target) + { + if (!SettingsUtil.Enable) + { + Debug.Log($"[CopyStrippedAOTAssemblies] disabled"); + return; + } + Debug.Log($"[CopyStrippedAOTAssemblies] CopyScripDlls. src:{srcStripDllPath} target:{target}"); + + var dstPath = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + + Directory.CreateDirectory(dstPath); + + foreach (var fileFullPath in Directory.GetFiles(srcStripDllPath, "*.dll")) + { + var file = Path.GetFileName(fileFullPath); + Debug.Log($"[CopyStrippedAOTAssemblies] copy strip dll {fileFullPath} ==> {dstPath}/{file}"); + File.Copy($"{fileFullPath}", $"{dstPath}/{file}", true); + } + } + + public void OnPostprocessBuild(BuildReport report) + { +#if UNITY_2021_1_OR_NEWER + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + string srcStripDllPath = GetStripAssembliesDir2021(target); + if (!string.IsNullOrEmpty(srcStripDllPath) && Directory.Exists(srcStripDllPath)) + { + CopyStripDlls(srcStripDllPath, target); + } +#endif + } + + public void OnPreprocessBuild(BuildReport report) + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + var dstPath = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + BashUtil.RecreateDir(dstPath); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs.meta new file mode 100644 index 0000000..3ebc0e1 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/CopyStrippedAOTAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7884710ec2f8e545b3fe9aa05def5a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs new file mode 100644 index 0000000..1cb56b6 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs @@ -0,0 +1,68 @@ +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + /// + /// 将热更新dll从Build过程中过滤,防止打包到主工程中 + /// + internal class FilterHotFixAssemblies : IFilterBuildAssemblies + { + public int callbackOrder => 0; + + public string[] OnFilterAssemblies(BuildOptions buildOptions, string[] assemblies) + { + if (!SettingsUtil.Enable) + { + Debug.Log($"[FilterHotFixAssemblies] disabled"); + return assemblies; + } + List allHotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + // 检查是否重复填写 + var hotUpdateDllSet = new HashSet(); + foreach(var hotUpdateDll in allHotUpdateDllNames) + { + if (string.IsNullOrWhiteSpace(hotUpdateDll)) + { + throw new BuildFailedException($"hot update assembly name cann't be empty"); + } + if (!hotUpdateDllSet.Add(hotUpdateDll)) + { + throw new BuildFailedException($"hot update assembly:{hotUpdateDll} is duplicated"); + } + } + + var assResolver = MetaUtil.CreateHotUpdateAssemblyResolver(EditorUserBuildSettings.activeBuildTarget, allHotUpdateDllNames); + // 检查是否填写了正确的dll名称 + foreach (var hotUpdateDllName in allHotUpdateDllNames) + { + if (assemblies.Select(Path.GetFileNameWithoutExtension).All(ass => ass != hotUpdateDllName) + && string.IsNullOrEmpty(assResolver.ResolveAssembly(hotUpdateDllName, false))) + { + throw new BuildFailedException($"hot update assembly:{hotUpdateDllName} doesn't exist"); + } + } + + // 将热更dll从打包列表中移除 + return assemblies.Where(ass => + { + string assName = Path.GetFileNameWithoutExtension(ass); + bool reserved = allHotUpdateDllNames.All(dll => !assName.Equals(dll, StringComparison.Ordinal)); + if (!reserved) + { + Debug.Log($"[FilterHotFixAssemblies] filter assembly:{assName}"); + } + return reserved; + }).ToArray(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs.meta new file mode 100644 index 0000000..2ab4ba5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/FilterHotFixAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9dec2922e3df5464aa047b636eb19e0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs new file mode 100644 index 0000000..f01a4dc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs @@ -0,0 +1,32 @@ +#if UNITY_EDITOR +using System; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; + +namespace HybridCLR.Editor.BuildProcessors +{ + + public class MsvcStdextWorkaround : IPreprocessBuildWithReport + { + const string kWorkaroundFlag = "/D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS"; + + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { + var clEnv = Environment.GetEnvironmentVariable("_CL_"); + + if (string.IsNullOrEmpty(clEnv)) + { + Environment.SetEnvironmentVariable("_CL_", kWorkaroundFlag); + } + else if (!clEnv.Contains(kWorkaroundFlag)) + { + clEnv += " " + kWorkaroundFlag; + Environment.SetEnvironmentVariable("_CL_", clEnv); + } + } + } +} + +#endif // UNITY_EDITOR diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs.meta new file mode 100644 index 0000000..ef28c51 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/MsvcStdextWorkaround.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8bff6cadf0b8db54b87ba51b24d080f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs new file mode 100644 index 0000000..ee21b9b --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Android; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.Il2Cpp; +using UnityEditor.UnityLinker; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + public class PatchScriptingAssemblyList : +#if UNITY_ANDROID + IPostGenerateGradleAndroidProject, +#endif + IPostprocessBuildWithReport +#if !UNITY_2021_1_OR_NEWER && UNITY_WEBGL + , IIl2CppProcessor +#endif + +#if UNITY_PS5 + , IUnityLinkerProcessor +#endif + + { + public int callbackOrder => 0; + + public void OnPostGenerateGradleAndroidProject(string path) + { + // 如果直接打包apk,没有机会在PostprocessBuild中修改ScriptingAssemblies.json。 + // 因此需要在这个时机处理 + // Unity有bug,偶然情况下会传入apk的路径,导致替换失败 + if (Directory.Exists(path)) + { + PathScriptingAssembilesFile(path); + } + else + { + PathScriptingAssembilesFile($"{SettingsUtil.ProjectDir}/Library"); + } + } + + public void OnPostprocessBuild(BuildReport report) + { + // 如果target为Android,由于已经在OnPostGenerateGradelAndroidProject中处理过, + // 这里不再重复处理 +#if !UNITY_ANDROID && !UNITY_WEBGL + PathScriptingAssembilesFile(report.summary.outputPath); +#endif + } + +#if UNITY_PS5 + /// + /// 打包模式如果是 Package 需要在这个阶段提前处理 .json , PC Hosted 和 GP5 模式不受影响 + /// + + public string GenerateAdditionalLinkXmlFile(UnityEditor.Build.Reporting.BuildReport report, UnityEditor.UnityLinker.UnityLinkerBuildPipelineData data) + { + string path = $"{SettingsUtil.ProjectDir}/Library/PlayerDataCache/PS5/Data"; + PathScriptingAssembilesFile(path); + return null; + } +#endif + public void PathScriptingAssembilesFile(string path) + { + if (!SettingsUtil.Enable) + { + Debug.Log($"[PatchScriptingAssemblyList] disabled"); + return; + } + Debug.Log($"[PatchScriptingAssemblyList]. path:{path}"); + if (!Directory.Exists(path)) + { + path = Path.GetDirectoryName(path); + Debug.Log($"[PatchScriptingAssemblyList] get path parent:{path}"); + } + AddHotFixAssembliesToScriptingAssembliesJson(path); + } + + private void AddHotFixAssembliesToScriptingAssembliesJson(string path) + { + Debug.Log($"[PatchScriptingAssemblyList]. path:{path}"); + /* + * ScriptingAssemblies.json 文件中记录了所有的dll名称,此列表在游戏启动时自动加载, + * 不在此列表中的dll在资源反序列化时无法被找到其类型 + * 因此 OnFilterAssemblies 中移除的条目需要再加回来 + */ + string[] jsonFiles = Directory.GetFiles(path, SettingsUtil.ScriptingAssembliesJsonFile, SearchOption.AllDirectories); + + if (jsonFiles.Length == 0) + { + Debug.LogWarning($"can not find file {SettingsUtil.ScriptingAssembliesJsonFile}"); + return; + } + + foreach (string file in jsonFiles) + { + var patcher = new ScriptingAssembliesJsonPatcher(); + patcher.Load(file); + patcher.AddScriptingAssemblies(SettingsUtil.HotUpdateAssemblyFilesIncludePreserved); + patcher.Save(file); + } + } + +#if UNITY_WEBGL + public void OnBeforeConvertRun(BuildReport report, Il2CppBuildPipelineData data) + { + PathScriptingAssembilesFile($"{SettingsUtil.ProjectDir}/Temp/StagingArea/Data"); + } +#endif + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs.meta new file mode 100644 index 0000000..0affdc7 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/PatchScriptingAssemblyList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9bb6e2908d8948648979c9ff6bb7937d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs new file mode 100644 index 0000000..a124e34 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.BuildProcessors +{ + public class ScriptingAssembliesJsonPatcher + { + [Serializable] + private class ScriptingAssemblies + { + public List names; + public List types; + } + + private string _file; + ScriptingAssemblies _scriptingAssemblies; + + public void Load(string file) + { + _file = file; + string content = File.ReadAllText(file); + _scriptingAssemblies = JsonUtility.FromJson(content); + } + + public void AddScriptingAssemblies(List assemblies) + { + foreach (string name in assemblies) + { + if (!_scriptingAssemblies.names.Contains(name)) + { + _scriptingAssemblies.names.Add(name); + _scriptingAssemblies.types.Add(16); // user dll type + Debug.Log($"[PatchScriptAssembliesJson] add hotfix assembly:{name} to {_file}"); + } + } + } + + public void Save(string jsonFile) + { + string content = JsonUtility.ToJson(_scriptingAssemblies); + + File.WriteAllText(jsonFile, content); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs.meta new file mode 100644 index 0000000..c4c07e0 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/BuildProcessors/ScriptingAssembliesJsonPatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4455f7304f8678f408dd6cf21734f55e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands.meta new file mode 100644 index 0000000..c61c778 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 92f51c069d2607447ae2f61de80540fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs new file mode 100644 index 0000000..c426f1d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs @@ -0,0 +1,46 @@ +using HybridCLR.Editor.AOT; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + using Analyzer = HybridCLR.Editor.AOT.Analyzer; + public static class AOTReferenceGeneratorCommand + { + + [MenuItem("HybridCLR/Generate/AOTGenericReference", priority = 102)] + public static void CompileAndGenerateAOTGenericReference() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + GenerateAOTGenericReference(target); + } + + public static void GenerateAOTGenericReference(BuildTarget target) + { + var gs = SettingsUtil.HybridCLRSettings; + List hotUpdateDllNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + using (AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDllNames), hotUpdateDllNames)) + { + var analyzer = new Analyzer(new Analyzer.Options + { + MaxIterationCount = Math.Min(20, gs.maxGenericReferenceIteration), + Collector = collector, + }); + + analyzer.Run(); + + var writer = new GenericReferenceWriter(); + writer.Write(analyzer.AotGenericTypes.ToList(), analyzer.AotGenericMethods.ToList(), $"{Application.dataPath}/{gs.outputAOTGenericReferenceFile}"); + AssetDatabase.Refresh(); + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs.meta new file mode 100644 index 0000000..39760e8 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/AOTReferenceGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b464872c07f6ba4f9a4e4a02ca9a28c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs new file mode 100644 index 0000000..a26b97d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build.Player; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + public class CompileDllCommand + { + public static void CompileDll(string buildDir, BuildTarget target, bool developmentBuild) + { + var group = BuildPipeline.GetBuildTargetGroup(target); + + ScriptCompilationSettings scriptCompilationSettings = new ScriptCompilationSettings(); + scriptCompilationSettings.group = group; + scriptCompilationSettings.target = target; + scriptCompilationSettings.options = developmentBuild ? ScriptCompilationOptions.DevelopmentBuild : ScriptCompilationOptions.None; + Directory.CreateDirectory(buildDir); + ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, buildDir); +#if UNITY_2022 + UnityEditor.EditorUtility.ClearProgressBar(); +#endif + Debug.Log("compile finish!!!"); + } + + public static void CompileDll(BuildTarget target, bool developmentBuild = false) + { + CompileDll(SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target), target, developmentBuild); + } + + [MenuItem("HybridCLR/CompileDll/ActiveBuildTarget", priority = 100)] + public static void CompileDllActiveBuildTarget() + { + CompileDll(EditorUserBuildSettings.activeBuildTarget); + } + + [MenuItem("HybridCLR/CompileDll/ActiveBuildTarget_Development", priority = 101)] + public static void CompileDllActiveBuildTargetDevelopment() + { + CompileDll(EditorUserBuildSettings.activeBuildTarget, true); + } + + [MenuItem("HybridCLR/CompileDll/Win32", priority = 200)] + public static void CompileDllWin32() + { + CompileDll(BuildTarget.StandaloneWindows); + } + + [MenuItem("HybridCLR/CompileDll/Win64", priority = 201)] + public static void CompileDllWin64() + { + CompileDll(BuildTarget.StandaloneWindows64); + } + + [MenuItem("HybridCLR/CompileDll/MacOS", priority = 202)] + public static void CompileDllMacOS() + { + CompileDll(BuildTarget.StandaloneOSX); + } + + [MenuItem("HybridCLR/CompileDll/Linux", priority = 203)] + public static void CompileDllLinux() + { + CompileDll(BuildTarget.StandaloneLinux64); + } + + [MenuItem("HybridCLR/CompileDll/Android", priority = 210)] + public static void CompileDllAndroid() + { + CompileDll(BuildTarget.Android); + } + + [MenuItem("HybridCLR/CompileDll/IOS", priority = 220)] + public static void CompileDllIOS() + { + CompileDll(BuildTarget.iOS); + } + + [MenuItem("HybridCLR/CompileDll/WebGL", priority = 230)] + public static void CompileDllWebGL() + { + CompileDll(BuildTarget.WebGL); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs.meta new file mode 100644 index 0000000..88b2e30 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/CompileDllCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf11b6c8bbc5afd4cb4a11921e5bd81e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs new file mode 100644 index 0000000..a56c2b0 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs @@ -0,0 +1,30 @@ +using HybridCLR.Editor.Link; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + + public static class Il2CppDefGeneratorCommand + { + + [MenuItem("HybridCLR/Generate/Il2CppDef", priority = 104)] + public static void GenerateIl2CppDef() + { + var options = new Il2CppDef.Il2CppDefGenerator.Options() + { + UnityVersion = Application.unityVersion, + HotUpdateAssemblies = SettingsUtil.HotUpdateAssemblyNamesIncludePreserved, + OutputFile = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/UnityVersion.h", + OutputFile2 = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/AssemblyManifest.cpp", + }; + + var g = new Il2CppDef.Il2CppDefGenerator(options); + g.Generate(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs.meta new file mode 100644 index 0000000..21f9427 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/Il2CppDefGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5165a065d05497c43a2fff885f31ed07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs new file mode 100644 index 0000000..c545ff5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs @@ -0,0 +1,39 @@ +using HybridCLR.Editor.Link; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + using Analyzer = HybridCLR.Editor.Link.Analyzer; + + public static class LinkGeneratorCommand + { + + [MenuItem("HybridCLR/Generate/LinkXml", priority = 100)] + public static void GenerateLinkXml() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + GenerateLinkXml(target); + } + + public static void GenerateLinkXml(BuildTarget target) + { + var ls = SettingsUtil.HybridCLRSettings; + + List hotfixAssemblies = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + + var analyzer = new Analyzer(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotfixAssemblies)); + var refTypes = analyzer.CollectRefs(hotfixAssemblies); + + Debug.Log($"[LinkGeneratorCommand] hotfix assembly count:{hotfixAssemblies.Count}, ref type count:{refTypes.Count} output:{Application.dataPath}/{ls.outputLinkFile}"); + var linkXmlWriter = new LinkXmlWriter(); + linkXmlWriter.Write($"{Application.dataPath}/{ls.outputLinkFile}", refTypes); + AssetDatabase.Refresh(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs.meta new file mode 100644 index 0000000..0eeba1f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/LinkGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f5b96abdbc4c424eb1bc3bc34b3a1a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs new file mode 100644 index 0000000..fde1084 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs @@ -0,0 +1,81 @@ +using HybridCLR.Editor; +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Meta; +using HybridCLR.Editor.MethodBridge; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + using Analyzer = HybridCLR.Editor.MethodBridge.Analyzer; + public class MethodBridgeGeneratorCommand + { + + public static void CleanIl2CppBuildCache() + { + string il2cppBuildCachePath = SettingsUtil.Il2CppBuildCacheDir; + if (!Directory.Exists(il2cppBuildCachePath)) + { + return; + } + Debug.Log($"clean il2cpp build cache:{il2cppBuildCachePath}"); + Directory.Delete(il2cppBuildCachePath, true); + } + + private static void GenerateMethodBridgeCppFile(Analyzer analyzer, string outputFile) + { + string templateCode = File.ReadAllText(outputFile, Encoding.UTF8); + var g = new Generator(new Generator.Options() + { + TemplateCode = templateCode, + OutputFile = outputFile, + GenericMethods = analyzer.GenericMethods, + }); + + g.PrepareMethods(); + g.Generate(); + Debug.LogFormat("[MethodBridgeGeneratorCommand] output:{0}", outputFile); + } + + [MenuItem("HybridCLR/Generate/MethodBridge", priority = 101)] + public static void CompileAndGenerateMethodBridge() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + GenerateMethodBridge(target); + } + + public static void GenerateMethodBridge(BuildTarget target) + { + string aotDllDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(target); + List aotAssemblyNames = Directory.Exists(aotDllDir) ? + Directory.GetFiles(aotDllDir, "*.dll", SearchOption.TopDirectoryOnly).Select(Path.GetFileNameWithoutExtension).ToList() + : new List(); + if (aotAssemblyNames.Count == 0) + { + throw new Exception($"no aot assembly found. please run `HybridCLR/Generate/All` or `HybridCLR/Generate/AotDlls` to generate aot dlls before runing `HybridCLR/Generate/MethodBridge`"); + } + using (AssemblyReferenceDeepCollector collector = new AssemblyReferenceDeepCollector(MetaUtil.CreateAOTAssemblyResolver(target), aotAssemblyNames)) + { + var analyzer = new Analyzer(new Analyzer.Options + { + MaxIterationCount = Math.Min(20, SettingsUtil.HybridCLRSettings.maxMethodBridgeGenericIteration), + Collector = collector, + }); + + analyzer.Run(); + string outputFile = $"{SettingsUtil.GeneratedCppDir}/MethodBridge.cpp"; + GenerateMethodBridgeCppFile(analyzer, outputFile); + } + + CleanIl2CppBuildCache(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs.meta new file mode 100644 index 0000000..9827997 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/MethodBridgeGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 46bc62d5236f5e941850776c435a9560 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs new file mode 100644 index 0000000..8fe2a46 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; + +namespace HybridCLR.Editor.Commands +{ + public static class PrebuildCommand + { + /// + /// 按照必要的顺序,执行所有生成操作,适合打包前操作 + /// + [MenuItem("HybridCLR/Generate/All", priority = 200)] + public static void GenerateAll() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + Il2CppDefGeneratorCommand.GenerateIl2CppDef(); + + // 这几个生成依赖HotUpdateDlls + LinkGeneratorCommand.GenerateLinkXml(target); + + // 生成裁剪后的aot dll + StripAOTDllCommand.GenerateStripedAOTDlls(target); + + // 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll + MethodBridgeGeneratorCommand.GenerateMethodBridge(target); + ReversePInvokeWrapperGeneratorCommand.GenerateReversePInvokeWrapper(target); + AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs.meta new file mode 100644 index 0000000..a27aea2 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/PrebuildCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c20f09bfbe3f32143aae872d3813d9e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/ReversePInvokeWrapperGeneratorCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/ReversePInvokeWrapperGeneratorCommand.cs new file mode 100644 index 0000000..706e748 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/ReversePInvokeWrapperGeneratorCommand.cs @@ -0,0 +1,49 @@ +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Link; +using HybridCLR.Editor.Meta; +using HybridCLR.Editor.ReversePInvokeWrap; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Commands +{ + + public static class ReversePInvokeWrapperGeneratorCommand + { + + [MenuItem("HybridCLR/Generate/ReversePInvokeWrapper", priority = 103)] + + public static void CompileAndGenerateReversePInvokeWrapper() + { + BuildTarget target = EditorUserBuildSettings.activeBuildTarget; + CompileDllCommand.CompileDll(target); + GenerateReversePInvokeWrapper(target); + } + + public static void GenerateReversePInvokeWrapper(BuildTarget target) + { + List hotUpdateDlls = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved; + using (var cache = new AssemblyCache(MetaUtil.CreateHotUpdateAndAOTAssemblyResolver(target, hotUpdateDlls))) + { + var analyzer = new ReversePInvokeWrap.Analyzer(cache, hotUpdateDlls); + analyzer.Run(); + + string outputFile = $"{SettingsUtil.GeneratedCppDir}/ReversePInvokeMethodStub.cpp"; + + List methods = analyzer.BuildABIMethods(); + Debug.Log($"GenerateReversePInvokeWrapper. wraperCount:{methods.Sum(m => m.Count)} output:{outputFile}"); + var generator = new Generator(); + generator.Generate(methods, outputFile); + Debug.LogFormat("[ReversePInvokeWrapperGeneratorCommand] output:{0}", outputFile); + } + MethodBridgeGeneratorCommand.CleanIl2CppBuildCache(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/ReversePInvokeWrapperGeneratorCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/ReversePInvokeWrapperGeneratorCommand.cs.meta new file mode 100644 index 0000000..79fb3d8 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/ReversePInvokeWrapperGeneratorCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7db18e1736f593c4089c85d764cf8620 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs b/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs new file mode 100644 index 0000000..f340c1e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs @@ -0,0 +1,162 @@ +using HybridCLR.Editor.Installer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; +using static UnityEngine.Networking.UnityWebRequest; + +namespace HybridCLR.Editor.Commands +{ + public static class StripAOTDllCommand + { + [MenuItem("HybridCLR/Generate/AOTDlls", priority = 105)] + public static void GenerateStripedAOTDlls() + { + GenerateStripedAOTDlls(EditorUserBuildSettings.activeBuildTarget); + } + + static BuildOptions GetBuildPlayerOptions(BuildTarget buildTarget) + { + BuildOptions options = BuildOptions.None; + bool development = EditorUserBuildSettings.development; + if (development) + { + options |= BuildOptions.Development; + } + + if (EditorUserBuildSettings.allowDebugging && development) + { + options |= BuildOptions.AllowDebugging; + } + + if (EditorUserBuildSettings.connectProfiler && (development || buildTarget == BuildTarget.WSAPlayer)) + { + options |= BuildOptions.ConnectWithProfiler; + } + + if (EditorUserBuildSettings.buildWithDeepProfilingSupport && development) + { + options |= BuildOptions.EnableDeepProfilingSupport; + } + +#if UNITY_2021_2_OR_NEWER + options |= BuildOptions.CleanBuildCache; +#endif + + return options; + } + + private static string GetLocationPathName(string buildDir, BuildTarget target) + { + switch(target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: return $"{buildDir}/{target}"; + case BuildTarget.StandaloneOSX: return buildDir; + case BuildTarget.iOS: return buildDir; + case BuildTarget.Android: return buildDir; + case BuildTarget.StandaloneLinux64: return buildDir; + default: return buildDir; + } + } + + public static void GenerateStripedAOTDlls(BuildTarget target) + { + string outputPath = $"{SettingsUtil.HybridCLRDataDir}/StrippedAOTDllsTempProj/{target}"; + BashUtil.RemoveDir(outputPath); + + var buildOptions = GetBuildPlayerOptions(target); +#if UNITY_2021_2_OR_NEWER + buildOptions |= BuildOptions.CleanBuildCache; +#endif + + bool oldExportAndroidProj = EditorUserBuildSettings.exportAsGoogleAndroidProject; +#if UNITY_EDITOR_OSX + bool oldCreateSolution = UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject; +#elif UNITY_EDITOR_WIN + bool oldCreateSolution = UnityEditor.WindowsStandalone.UserBuildSettings.createSolution; +#endif + bool oldBuildScriptsOnly = EditorUserBuildSettings.buildScriptsOnly; + EditorUserBuildSettings.buildScriptsOnly = true; + + string location = GetLocationPathName(outputPath, target); + string oldBuildLocation = EditorUserBuildSettings.GetBuildLocation(target); + EditorUserBuildSettings.SetBuildLocation(target, location); + + switch (target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: + { +#if UNITY_EDITOR_WIN + UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = true; +#endif + break; + } + case BuildTarget.StandaloneOSX: + { +#if UNITY_EDITOR_OSX + UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = true; +#endif + break; + } + case BuildTarget.Android: + { + EditorUserBuildSettings.exportAsGoogleAndroidProject = true; + break; + } + } + + Debug.Log($"GenerateStripedAOTDlls build option:{buildOptions}"); + + BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions() + { + scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray(), + locationPathName = location, + options = buildOptions, + target = target, + targetGroup = BuildPipeline.GetBuildTargetGroup(target), + }; + + var report = BuildPipeline.BuildPlayer(buildPlayerOptions); + + EditorUserBuildSettings.buildScriptsOnly = oldBuildScriptsOnly; + EditorUserBuildSettings.SetBuildLocation(target, oldBuildLocation); + + switch (target) + { + case BuildTarget.StandaloneWindows: + case BuildTarget.StandaloneWindows64: + { +#if UNITY_EDITOR_WIN + UnityEditor.WindowsStandalone.UserBuildSettings.createSolution = oldCreateSolution; +#endif + break; + } + case BuildTarget.StandaloneOSX: + { +#if UNITY_EDITOR_OSX + UnityEditor.OSXStandalone.UserBuildSettings.createXcodeProject = oldCreateSolution; +#endif + break; + } + case BuildTarget.Android: + { + EditorUserBuildSettings.exportAsGoogleAndroidProject = oldExportAndroidProj; + break; + } + } + + if (report.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded) + { + throw new Exception("GenerateStripedAOTDlls failed"); + } + Debug.Log($"GenerateStripedAOTDlls target:{target} path:{outputPath}"); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs.meta new file mode 100644 index 0000000..3979b80 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Commands/StripAOTDllCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21fb0a02f23185141a4a3df67fe61789 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs b/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs new file mode 100644 index 0000000..38e83e3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs @@ -0,0 +1,28 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor +{ + public static class HashUtil + { + public static int CombineHash(int hash1, int hash2) + { + return hash1 * 1566083941 + hash2; + } + + public static int ComputHash(List sigs) + { + int hash = 135781321; + TypeEqualityComparer tc = TypeEqualityComparer.Instance; + foreach (var sig in sigs) + { + hash = hash * 1566083941 + tc.GetHashCode(sig); + } + return hash; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs.meta new file mode 100644 index 0000000..9cdce22 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/HashUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d4ae4a5c0bba49469c525887d812717 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef b/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef new file mode 100644 index 0000000..4612a6d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "HybridCLR.Editor", + "rootNamespace": "", + "references": [ + "HybridCLR.Runtime" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef.meta b/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef.meta new file mode 100644 index 0000000..be1417b --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/HybridCLR.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2373f786d14518f44b0f475db77ba4de +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef.meta b/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef.meta new file mode 100644 index 0000000..dc951ca --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: da46bc9f1a4dece41a5c193166be9a30 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs b/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs new file mode 100644 index 0000000..8728c65 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs @@ -0,0 +1,86 @@ +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Template; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Il2CppDef +{ + public class Il2CppDefGenerator + { + public class Options + { + public List HotUpdateAssemblies { get; set; } + + public string OutputFile { get; set; } + + public string OutputFile2 { get; set; } + + public string UnityVersion { get; set; } + } + + private readonly Options _options; + public Il2CppDefGenerator(Options options) + { + _options = options; + } + + + private static readonly Regex s_unityVersionPat = new Regex(@"(\d+)\.(\d+)\.(\d+)"); + + public void Generate() + { + GenerateIl2CppConfig(); + GeneratePlaceHolderAssemblies(); + } + + private void GenerateIl2CppConfig() + { + var frr = new FileRegionReplace(File.ReadAllText(_options.OutputFile)); + + List lines = new List(); + + var match = s_unityVersionPat.Matches(_options.UnityVersion)[0]; + int majorVer = int.Parse(match.Groups[1].Value); + int minorVer1 = int.Parse(match.Groups[2].Value); + int minorVer2 = int.Parse(match.Groups[3].Value); + + lines.Add($"#define HYBRIDCLR_UNITY_VERSION {majorVer}{minorVer1.ToString("D2")}{minorVer2.ToString("D2")}"); + lines.Add($"#define HYBRIDCLR_UNITY_{majorVer} 1"); + for (int ver = 2019; ver <= 2024; ver++) + { + if (majorVer >= ver) + { + lines.Add($"#define HYBRIDCLR_UNITY_{ver}_OR_NEW 1"); + } + } + + frr.Replace("UNITY_VERSION", string.Join("\n", lines)); + + frr.Commit(_options.OutputFile); + Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.OutputFile}"); + } + + private void GeneratePlaceHolderAssemblies() + { + var frr = new FileRegionReplace(File.ReadAllText(_options.OutputFile2)); + + List lines = new List(); + + foreach (var ass in _options.HotUpdateAssemblies) + { + lines.Add($"\t\t\"{ass}\","); + } + + frr.Replace("PLACE_HOLDER", string.Join("\n", lines)); + + frr.Commit(_options.OutputFile2); + Debug.Log($"[HybridCLR.Editor.Il2CppDef.Generator] output:{_options.OutputFile2}"); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs.meta new file mode 100644 index 0000000..b7fe9d7 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Il2CppDef/Il2CppDefGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 590419ee7e82ac24cbac9b8a48891fe0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Installer.meta b/Packages/com.code-philosophy.hybridclr/Editor/Installer.meta new file mode 100644 index 0000000..b27774d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Installer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a2c8f84b297371d4cbcd5ca655bf360d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs b/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs new file mode 100644 index 0000000..555f3ca --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Installer +{ + public static class BashUtil + { + public static int RunCommand(string workingDir, string program, string[] args, bool log = true) + { + using (Process p = new Process()) + { + p.StartInfo.WorkingDirectory = workingDir; + p.StartInfo.FileName = program; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + string argsStr = string.Join(" ", args.Select(arg => "\"" + arg + "\"")); + p.StartInfo.Arguments = argsStr; + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] run => {program} {argsStr}"); + } + p.Start(); + p.WaitForExit(); + return p.ExitCode; + } + } + + + public static (int ExitCode, string StdOut, string StdErr) RunCommand2(string workingDir, string program, string[] args, bool log = true) + { + using (Process p = new Process()) + { + p.StartInfo.WorkingDirectory = workingDir; + p.StartInfo.FileName = program; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + string argsStr = string.Join(" ", args); + p.StartInfo.Arguments = argsStr; + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] run => {program} {argsStr}"); + } + p.Start(); + p.WaitForExit(); + + string stdOut = p.StandardOutput.ReadToEnd(); + string stdErr = p.StandardError.ReadToEnd(); + return (p.ExitCode, stdOut, stdErr); + } + } + + + public static void RemoveDir(string dir, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] RemoveDir dir:{dir}"); + } + + int maxTryCount = 5; + for (int i = 0; i < maxTryCount; ++i) + { + try + { + if (!Directory.Exists(dir)) + { + return; + } + foreach (var file in Directory.GetFiles(dir)) + { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + } + foreach (var subDir in Directory.GetDirectories(dir)) + { + RemoveDir(subDir); + } + Directory.Delete(dir); + break; + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"[BashUtil] RemoveDir:{dir} with exception:{e}. try count:{i}"); + Thread.Sleep(100); + } + } + } + + public static void RecreateDir(string dir) + { + if(Directory.Exists(dir)) + { + RemoveDir(dir, true); + } + Directory.CreateDirectory(dir); + } + + private static void CopyWithCheckLongFile(string srcFile, string dstFile) + { + var maxPathLength = 255; +#if UNITY_EDITOR_OSX + maxPathLength = 1024; +#endif + if (srcFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"srcFile:{srcFile} path is too long. copy ignore!"); + return; + } + if (dstFile.Length > maxPathLength) + { + UnityEngine.Debug.LogError($"dstFile:{dstFile} path is too long. copy ignore!"); + return; + } + File.Copy(srcFile, dstFile); + } + + public static void CopyDir(string src, string dst, bool log = false) + { + if (log) + { + UnityEngine.Debug.Log($"[BashUtil] CopyDir {src} => {dst}"); + } + RemoveDir(dst); + Directory.CreateDirectory(dst); + foreach(var file in Directory.GetFiles(src)) + { + CopyWithCheckLongFile(file, $"{dst}/{Path.GetFileName(file)}"); + } + foreach(var subDir in Directory.GetDirectories(src)) + { + CopyDir(subDir, $"{dst}/{Path.GetFileName(subDir)}"); + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs.meta new file mode 100644 index 0000000..ef7e11d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Installer/BashUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 960a0257c3a17f64b810193308ce1558 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs new file mode 100644 index 0000000..1118994 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEditor; +using UnityEngine; +using Debug = UnityEngine.Debug; +using System.Text.RegularExpressions; +using System.Linq; +using HybridCLR.Editor.Settings; + +namespace HybridCLR.Editor.Installer +{ + + public class InstallerController + { + private const string hybridclr_repo_path = "hybridclr_repo"; + + private const string il2cpp_plus_repo_path = "il2cpp_plus_repo"; + + public int MajorVersion => _curVersion.major; + + private readonly UnityVersion _curVersion; + + private readonly HybridclrVersionManifest _versionManifest; + private readonly HybridclrVersionInfo _curDefaultVersion; + + public string PackageVersion { get; private set; } + + public string InstalledLibil2cppVersion { get; private set; } + + public InstallerController() + { + _curVersion = ParseUnityVersion(Application.unityVersion); + _versionManifest = GetHybridCLRVersionManifest(); + _curDefaultVersion = _versionManifest.versions.FirstOrDefault(v => v.unity_version == _curVersion.major.ToString()); + PackageVersion = LoadPackageInfo().version; + InstalledLibil2cppVersion = ReadLocalVersion(); + } + + private HybridclrVersionManifest GetHybridCLRVersionManifest() + { + string versionFile = $"{SettingsUtil.ProjectDir}/{SettingsUtil.HybridCLRDataPathInPackage}/hybridclr_version.json"; + return JsonUtility.FromJson(File.ReadAllText(versionFile, Encoding.UTF8)); + } + + private PackageInfo LoadPackageInfo() + { + string packageJson = $"{SettingsUtil.ProjectDir}/Packages/{SettingsUtil.PackageName}/package.json"; + return JsonUtility.FromJson(File.ReadAllText(packageJson, Encoding.UTF8)); + } + + + [Serializable] + class PackageInfo + { + public string name; + + public string version; + } + + [Serializable] + class VersionDesc + { + public string branch; + + //public string hash; + } + + [Serializable] + class HybridclrVersionInfo + { + public string unity_version; + + public VersionDesc hybridclr; + + public VersionDesc il2cpp_plus; + } + + [Serializable] + class HybridclrVersionManifest + { + public List versions; + } + + private class UnityVersion + { + public int major; + public int minor1; + public int minor2; + + public override string ToString() + { + return $"{major}.{minor1}.{minor2}"; + } + } + + private static readonly Regex s_unityVersionPat = new Regex(@"(\d+)\.(\d+)\.(\d+)"); + + private UnityVersion ParseUnityVersion(string versionStr) + { + var matches = s_unityVersionPat.Matches(versionStr); + if (matches.Count == 0) + { + return null; + } + Match match = matches[matches.Count - 1]; + int major = int.Parse(match.Groups[1].Value); + int minor1 = int.Parse(match.Groups[2].Value); + int minor2 = int.Parse(match.Groups[3].Value); + return new UnityVersion { major = major, minor1 = minor1, minor2 = minor2 }; + } + + public string GetCurrentUnityVersionMinCompatibleVersionStr() + { + return GetMinCompatibleVersion(MajorVersion); + } + + public string GetMinCompatibleVersion(int majorVersion) + { + switch(majorVersion) + { + case 2020: return $"2020.3.0"; + case 2021: return $"2021.3.0"; + case 2022: return $"2022.3.0"; + default: return $"2020.3.0"; + } + } + + public enum CompatibleType + { + Compatible, + MaybeIncompatible, + Incompatible, + } + + public CompatibleType GetCompatibleType() + { + UnityVersion version = _curVersion; + if (version == null) + { + return CompatibleType.Incompatible; + } + if (version.minor1 != 3) + { + return CompatibleType.MaybeIncompatible; + } + return CompatibleType.Compatible; + } + + public string HybridclrLocalVersion => _curDefaultVersion?.hybridclr?.branch; + + public string Il2cppPlusLocalVersion => _curDefaultVersion?.il2cpp_plus?.branch; + + + private string GetIl2CppPathByContentPath(string contentPath) + { + return $"{contentPath}/il2cpp"; + } + + public string ApplicationIl2cppPath => GetIl2CppPathByContentPath(EditorApplication.applicationContentsPath); + + public string LocalVersionFile => $"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr/generated/libil2cpp-version.txt"; + + private string ReadLocalVersion() + { + if (!File.Exists(LocalVersionFile)) + { + return null; + } + return File.ReadAllText(LocalVersionFile, Encoding.UTF8); + } + + public void WriteLocalVersion() + { + InstalledLibil2cppVersion = PackageVersion; + File.WriteAllText(LocalVersionFile, PackageVersion, Encoding.UTF8); + Debug.Log($"Write installed version:'{PackageVersion}' to {LocalVersionFile}"); + } + + public void InstallDefaultHybridCLR() + { + InstallFromLocal(PrepareLibil2cppWithHybridclrFromGitRepo()); + } + + public bool HasInstalledHybridCLR() + { + return Directory.Exists($"{SettingsUtil.LocalIl2CppDir}/libil2cpp/hybridclr"); + } + + void CloneBranch(string workDir, string repoUrl, string branch, string repoDir) + { + BashUtil.RemoveDir(repoDir); + BashUtil.RunCommand(workDir, "git", new string[] {"clone", "-b", branch, "--depth", "1", repoUrl, repoDir}); + } + + private string PrepareLibil2cppWithHybridclrFromGitRepo() + { + string workDir = SettingsUtil.HybridCLRDataDir; + Directory.CreateDirectory(workDir); + //BashUtil.RecreateDir(workDir); + + // clone hybridclr + string hybridclrRepoURL = HybridCLRSettings.Instance.hybridclrRepoURL; + string hybridclrRepoDir = $"{workDir}/{hybridclr_repo_path}"; + CloneBranch(workDir, hybridclrRepoURL, _curDefaultVersion.hybridclr.branch, hybridclrRepoDir); + + if (!Directory.Exists(hybridclrRepoDir)) + { + throw new Exception($"clone hybridclr fail. url: {hybridclrRepoURL}"); + } + + // clone il2cpp_plus + string il2cppPlusRepoURL = HybridCLRSettings.Instance.il2cppPlusRepoURL; + string il2cppPlusRepoDir = $"{workDir}/{il2cpp_plus_repo_path}"; + CloneBranch(workDir, il2cppPlusRepoURL, _curDefaultVersion.il2cpp_plus.branch, il2cppPlusRepoDir); + + if (!Directory.Exists(il2cppPlusRepoDir)) + { + throw new Exception($"clone il2cpp_plus fail. url: {il2cppPlusRepoDir}"); + } + + Directory.Move($"{hybridclrRepoDir}/hybridclr", $"{il2cppPlusRepoDir}/libil2cpp/hybridclr"); + return $"{il2cppPlusRepoDir}/libil2cpp"; + } + + public void InstallFromLocal(string libil2cppWithHybridclrSourceDir) + { + RunInitLocalIl2CppData(ApplicationIl2cppPath, libil2cppWithHybridclrSourceDir, _curVersion); + } + + private void RunInitLocalIl2CppData(string editorIl2cppPath, string libil2cppWithHybridclrSourceDir, UnityVersion version) + { + if (GetCompatibleType() == CompatibleType.Incompatible) + { + Debug.LogError($"Incompatible with current version, minimum compatible version: {GetCurrentUnityVersionMinCompatibleVersionStr()}"); + return; + } + string workDir = SettingsUtil.HybridCLRDataDir; + Directory.CreateDirectory(workDir); + + // create LocalIl2Cpp + string localUnityDataDir = SettingsUtil.LocalUnityDataDir; + BashUtil.RecreateDir(localUnityDataDir); + + // copy MonoBleedingEdge + BashUtil.CopyDir($"{Directory.GetParent(editorIl2cppPath)}/MonoBleedingEdge", $"{localUnityDataDir}/MonoBleedingEdge", true); + + // copy il2cpp + BashUtil.CopyDir(editorIl2cppPath, SettingsUtil.LocalIl2CppDir, true); + + // replace libil2cpp + string dstLibil2cppDir = $"{SettingsUtil.LocalIl2CppDir}/libil2cpp"; + BashUtil.CopyDir($"{libil2cppWithHybridclrSourceDir}", dstLibil2cppDir, true); + + // clean Il2cppBuildCache + BashUtil.RemoveDir($"{SettingsUtil.ProjectDir}/Library/Il2cppBuildCache", true); + + if (HasInstalledHybridCLR()) + { + WriteLocalVersion(); + Debug.Log("Install Sucessfully"); + } + else + { + Debug.LogError("Installation failed!"); + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs.meta new file mode 100644 index 0000000..6b30944 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44c8627d126b30d4e9560b1f738264ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs new file mode 100644 index 0000000..676fe6e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using System.Reflection; +using UnityEditor; +using UnityEngine; + + +namespace HybridCLR.Editor.Installer +{ + public class InstallerWindow : EditorWindow + { + private InstallerController _controller; + + private bool _installFromDir; + + private string _installLibil2cppWithHybridclrSourceDir; + + private void OnEnable() + { + _controller = new InstallerController(); + } + + private void OnGUI() + { + var rect = new Rect + { + x = EditorGUIUtility.currentViewWidth - 24, + y = 5, + width = 24, + height = 24 + }; + var content = EditorGUIUtility.IconContent("Settings"); + content.tooltip = "HybridCLR Settings"; + if (GUI.Button(rect, content, GUI.skin.GetStyle("IconButton"))) + { + SettingsService.OpenProjectSettings("Project/HybridCLR Settings"); + } + + bool hasInstall = _controller.HasInstalledHybridCLR(); + + GUILayout.Space(10f); + + EditorGUILayout.BeginVertical("box"); + EditorGUILayout.LabelField($"Installed: {hasInstall}", EditorStyles.boldLabel); + GUILayout.Space(10f); + + EditorGUILayout.LabelField($"Package Version: v{_controller.PackageVersion}"); + GUILayout.Space(5f); + EditorGUILayout.LabelField($"Installed Version: {_controller.InstalledLibil2cppVersion ?? "Unknown"}"); + GUILayout.Space(5f); + + GUILayout.Space(10f); + + InstallerController.CompatibleType compatibleType = _controller.GetCompatibleType(); + if (compatibleType != InstallerController.CompatibleType.Incompatible) + { + if (compatibleType == InstallerController.CompatibleType.MaybeIncompatible) + { + EditorGUILayout.HelpBox($"Maybe incompatible with current version, recommend minimum compatible version:{_controller.GetCurrentUnityVersionMinCompatibleVersionStr()}", MessageType.Warning); + } + + EditorGUILayout.BeginHorizontal(); + _installFromDir = EditorGUILayout.Toggle("Copy libil2cpp from local", _installFromDir, GUILayout.MinWidth(100)); + EditorGUI.BeginDisabledGroup(!_installFromDir); + EditorGUILayout.TextField(_installLibil2cppWithHybridclrSourceDir, GUILayout.Width(400)); + if (GUILayout.Button("Choose", GUILayout.Width(100))) + { + _installLibil2cppWithHybridclrSourceDir = EditorUtility.OpenFolderPanel("Select libil2cpp", Application.dataPath, "libil2cpp"); + } + EditorGUI.EndDisabledGroup(); + EditorGUILayout.EndHorizontal(); + + GUILayout.Space(20f); + + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Install", GUILayout.Width(100))) + { + InstallLocalHybridCLR(); + GUIUtility.ExitGUI(); + } + EditorGUILayout.EndHorizontal(); + } + else + { + EditorGUILayout.HelpBox($"Incompatible with current version, minimum compatible version:{_controller.GetCurrentUnityVersionMinCompatibleVersionStr()}", MessageType.Error); + } + + EditorGUILayout.EndVertical(); + } + + private void InstallLocalHybridCLR() + { + if (_installFromDir) + { + if (!Directory.Exists(_installLibil2cppWithHybridclrSourceDir)) + { + Debug.LogError($"Source libil2cpp:'{_installLibil2cppWithHybridclrSourceDir}' doesn't exist."); + return; + } + if (!File.Exists($"{_installLibil2cppWithHybridclrSourceDir}/il2cpp-config.h") || !File.Exists($"{_installLibil2cppWithHybridclrSourceDir}/hybridclr/RuntimeApi.cpp")) + { + Debug.LogError($"Source libil2cpp:' {_installLibil2cppWithHybridclrSourceDir} ' is invalid"); + return; + } + _controller.InstallFromLocal(_installLibil2cppWithHybridclrSourceDir); + } + else + { + _controller.InstallDefaultHybridCLR(); + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs.meta new file mode 100644 index 0000000..f404c28 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Installer/InstallerWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 959fbf0bb06629542969354505189240 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Link.meta b/Packages/com.code-philosophy.hybridclr/Editor/Link.meta new file mode 100644 index 0000000..1cad1b3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Link.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5186a137e0258034cb3832bdf6b16a70 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs b/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs new file mode 100644 index 0000000..b54d5f3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using UnityEditor; +using IAssemblyResolver = HybridCLR.Editor.Meta.IAssemblyResolver; + +namespace HybridCLR.Editor.Link +{ + public class Analyzer + { + private readonly IAssemblyResolver _resolver; + + public Analyzer(IAssemblyResolver resolver) + { + _resolver = resolver; + } + + public HashSet CollectRefs(List rootAssemblies) + { + using (var assCollector = new AssemblyCache(_resolver)) + { + var rootAssemblyNames = new HashSet(rootAssemblies); + + var typeRefs = new HashSet(TypeEqualityComparer.Instance); + foreach (var rootAss in rootAssemblies) + { + var dnAss = assCollector.LoadModule(rootAss, false); + foreach (var type in dnAss.GetTypeRefs()) + { + if (!rootAssemblyNames.Contains(type.DefinitionAssembly.Name.ToString())) + { + typeRefs.Add(type); + } + } + } + + assCollector.Dispose(); + return typeRefs; + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs.meta new file mode 100644 index 0000000..0c6fa90 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Link/Analyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd3dd4871efd10e46947cb61c13797fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs b/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs new file mode 100644 index 0000000..0ede314 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs @@ -0,0 +1,47 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Link +{ + internal class LinkXmlWriter + { + public void Write(string outputLinkXmlFile, HashSet refTypes) + { + string parentDir = Directory.GetParent(outputLinkXmlFile).FullName; + Directory.CreateDirectory(parentDir); + var writer = System.Xml.XmlWriter.Create(outputLinkXmlFile, + new System.Xml.XmlWriterSettings { Encoding = Encoding.UTF8, Indent = true}); + + writer.WriteStartDocument(); + writer.WriteStartElement("linker"); + + var typesByAssembly = refTypes.GroupBy(t => t.DefinitionAssembly.Name.String).ToList(); + typesByAssembly.Sort((a, b) => String.Compare(a.Key, b.Key, StringComparison.Ordinal)); + + foreach(var assembly in typesByAssembly) + { + writer.WriteStartElement("assembly"); + writer.WriteAttributeString("fullname", assembly.Key); + List assTypeNames = assembly.Select(t => t.FullName).ToList(); + assTypeNames.Sort(string.CompareOrdinal); + foreach(var typeName in assTypeNames) + { + writer.WriteStartElement("type"); + writer.WriteAttributeString("fullname", typeName); + writer.WriteAttributeString("preserve", "all"); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.WriteEndDocument(); + writer.Close(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs.meta new file mode 100644 index 0000000..4c6c61c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Link/LinkXmlWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5cc4ae4adc319b4bb1e115567d7613e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta.meta new file mode 100644 index 0000000..3291c30 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3787c7d8b775c754aa4ae06bf78e96ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs new file mode 100644 index 0000000..7fed930 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs @@ -0,0 +1,20 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class AssemblyCache : AssemblyCacheBase + { + + public AssemblyCache(IAssemblyResolver assemblyResolver) : base(assemblyResolver) + { + + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs.meta new file mode 100644 index 0000000..a99c3b2 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa4650e79a52228488aa85e0690ca52c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs new file mode 100644 index 0000000..82fb007 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs @@ -0,0 +1,107 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public abstract class AssemblyCacheBase : IDisposable + { + private readonly IAssemblyResolver _assemblyPathResolver; + private readonly ModuleContext _modCtx; + private readonly AssemblyResolver _asmResolver; + private bool disposedValue; + private bool _loadedNetstandard; + + public Dictionary LoadedModules { get; } = new Dictionary(); + + private readonly List _loadedModulesIncludeNetstandard = new List(); + + protected AssemblyCacheBase(IAssemblyResolver assemblyResolver) + { + _assemblyPathResolver = assemblyResolver; + _modCtx = ModuleDef.CreateModuleContext(); + _asmResolver = (AssemblyResolver)_modCtx.AssemblyResolver; + _asmResolver.EnableTypeDefCache = true; + _asmResolver.UseGAC = false; + } + + public ModuleDefMD LoadModule(string moduleName, bool loadReferenceAssemblies = true) + { + // Debug.Log($"load module:{moduleName}"); + if (LoadedModules.TryGetValue(moduleName, out var mod)) + { + return mod; + } + if (moduleName == "netstandard") + { + if (!_loadedNetstandard) + { + LoadNetStandard(); + } + return null; + } + mod = DoLoadModule(_assemblyPathResolver.ResolveAssembly(moduleName, true)); + LoadedModules.Add(moduleName, mod); + + if (loadReferenceAssemblies) + { + foreach (var refAsm in mod.GetAssemblyRefs()) + { + LoadModule(refAsm.Name); + } + } + + return mod; + } + + private void LoadNetStandard() + { + string netstandardDllPath = _assemblyPathResolver.ResolveAssembly("netstandard", false); + if (!string.IsNullOrEmpty(netstandardDllPath)) + { + DoLoadModule(netstandardDllPath); + } + else + { + DoLoadModule(MetaUtil.ResolveNetStandardAssemblyPath("netstandard2.0")); + DoLoadModule(MetaUtil.ResolveNetStandardAssemblyPath("netstandard2.1")); + } + _loadedNetstandard = true; + } + + private ModuleDefMD DoLoadModule(string dllPath) + { + //Debug.Log($"do load module:{dllPath}"); + ModuleDefMD mod = ModuleDefMD.Load(dllPath, _modCtx); + _asmResolver.AddToCache(mod); + _loadedModulesIncludeNetstandard.Add(mod); + return mod; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + foreach (var mod in _loadedModulesIncludeNetstandard) + { + mod.Dispose(); + } + _loadedModulesIncludeNetstandard.Clear(); + LoadedModules.Clear(); + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs.meta new file mode 100644 index 0000000..5c02171 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyCacheBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b01fa99119e72141bfee5628c0ffce1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs new file mode 100644 index 0000000..44c1c71 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs @@ -0,0 +1,50 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class AssemblyReferenceDeepCollector : AssemblyCacheBase + { + private readonly List _rootAssemblies; + + public IReadOnlyList GetRootAssemblyNames() + { + return _rootAssemblies; + } + + public List GetLoadedModulesExcludeRootAssemblies() + { + return LoadedModules.Where(e => !_rootAssemblies.Contains(e.Key)).Select(e => e.Value).ToList(); + } + + public List GetLoadedModules() + { + return LoadedModules.Select(e => e.Value).ToList(); + } + + public List GetLoadedModulesOfRootAssemblies() + { + return _rootAssemblies.Select(ass => LoadedModules[ass]).ToList(); + } + + public AssemblyReferenceDeepCollector(IAssemblyResolver assemblyResolver, List rootAssemblies) : base(assemblyResolver) + { + _rootAssemblies = rootAssemblies; + LoadAllAssembiles(); + } + + private void LoadAllAssembiles() + { + foreach (var asm in _rootAssemblies) + { + LoadModule(asm); + } + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs.meta new file mode 100644 index 0000000..e93354f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyReferenceDeepCollector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0342c7d8575fdea49896260c77285286 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs new file mode 100644 index 0000000..973619a --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public abstract class AssemblyResolverBase : IAssemblyResolver + { + public string ResolveAssembly(string assemblyName, bool throwExIfNotFind) + { + if (TryResolveAssembly(assemblyName, out string assemblyPath)) + { + return assemblyPath; + } + if (throwExIfNotFind) + { + if (SettingsUtil.HotUpdateAssemblyNamesIncludePreserved.Contains(assemblyName)) + { + throw new Exception($"resolve Hot update dll:{assemblyName} failed! Please make sure that this hot update dll exists or the search path is configured in the external hot update path."); + } + else + { + throw new Exception($"resolve AOT dll:{assemblyName} failed! Please make sure that the AOT project has referenced the dll and generated the trimmed AOT dll correctly."); + } + } + return null; + } + + protected abstract bool TryResolveAssembly(string assemblyName, out string assemblyPath); + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs.meta new file mode 100644 index 0000000..e153d4e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/AssemblyResolverBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f8d48774b790364cbd36f1f68fd6614 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs new file mode 100644 index 0000000..31a0c51 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public class CombinedAssemblyResolver : AssemblyResolverBase + { + private readonly IAssemblyResolver[] _resolvers; + + public CombinedAssemblyResolver(params IAssemblyResolver[] resolvers) + { + _resolvers = resolvers; + } + + protected override bool TryResolveAssembly(string assemblyName, out string assemblyPath) + { + foreach(var resolver in _resolvers) + { + var assembly = resolver.ResolveAssembly(assemblyName, false); + if (assembly != null) + { + assemblyPath = assembly; + return true; + } + } + assemblyPath = null; + return false; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs.meta new file mode 100644 index 0000000..81b443c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/CombinedAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89b83906438c52d4b9af4aaef055f177 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs new file mode 100644 index 0000000..5bee4e4 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class FixedSetAssemblyResolver : AssemblyResolverBase + { + private readonly string _rootDir; + private readonly HashSet _fileNames; + + public FixedSetAssemblyResolver(string rootDir, IEnumerable fileNameNotExts) + { + _rootDir = rootDir; + _fileNames = new HashSet(fileNameNotExts); + } + + protected override bool TryResolveAssembly(string assemblyName, out string assemblyPath) + { + if (_fileNames.Contains(assemblyName)) + { + assemblyPath = $"{_rootDir}/{assemblyName}.dll"; + if (File.Exists(assemblyPath)) + { + Debug.Log($"[FixedSetAssemblyResolver] resolve:{assemblyName} path:{assemblyPath}"); + return true; + } + } + assemblyPath = null; + return false; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs.meta new file mode 100644 index 0000000..cc4fb61 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/FixedSetAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f135accd10f42c64b9735c3aa8cb1e77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs new file mode 100644 index 0000000..47883f5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs @@ -0,0 +1,104 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + /// + /// Replaces generic type/method var with its generic argument + /// + public sealed class GenericArgumentContext + { + List typeArgsStack = new List(); + List methodArgsStack = new List(); + + public GenericArgumentContext(List typeArgsStack, List methodArgsStack) + { + this.typeArgsStack = typeArgsStack; + this.methodArgsStack = methodArgsStack; + } + + + + /// + /// Replaces a generic type/method var with its generic argument (if any). If + /// isn't a generic type/method var or if it can't + /// be resolved, it itself is returned. Else the resolved type is returned. + /// + /// Type signature + /// New which is never null unless + /// is null + public TypeSig Resolve(TypeSig typeSig) + { + if (!typeSig.ContainsGenericParameter) + { + return typeSig; + } + typeSig = typeSig.RemovePinnedAndModifiers(); + switch (typeSig.ElementType) + { + case ElementType.Ptr: return new PtrSig(Resolve(typeSig.Next)); + case ElementType.ByRef: return new PtrSig(Resolve(typeSig.Next)); + + case ElementType.SZArray: return new PtrSig(Resolve(typeSig.Next)); + case ElementType.Array: + { + var ara = (ArraySig)typeSig; + return new ArraySig(Resolve(typeSig.Next), ara.Rank, ara.Sizes, ara.LowerBounds); + } + + case ElementType.Var: + { + GenericVar genericVar = (GenericVar)typeSig; + var newSig = Resolve(typeArgsStack, genericVar.Number, true); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + + case ElementType.MVar: + { + GenericMVar genericVar = (GenericMVar)typeSig; + var newSig = Resolve(methodArgsStack, genericVar.Number, true); + if (newSig == null) + { + throw new Exception(); + } + return newSig; + } + case ElementType.GenericInst: + { + var gia = (GenericInstSig)typeSig; + return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => Resolve(ga)).ToList()); + } + + case ElementType.FnPtr: + { + throw new NotSupportedException(typeSig.ToString()); + } + + case ElementType.ValueArray: + { + var vas = (ValueArraySig)typeSig; + return new ValueArraySig(Resolve(vas.Next), vas.Size); + } + default: return typeSig; + } + } + + private TypeSig Resolve(List args, uint number, bool isTypeVar) + { + var typeSig = args[(int)number]; + var gvar = typeSig as GenericSig; + if (gvar is null || gvar.IsTypeVar != isTypeVar) + return typeSig; + return gvar; + } + } + +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs.meta new file mode 100644 index 0000000..acb9355 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericArgumentContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07595a9b5b2f54c44a67022ae3e077d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs new file mode 100644 index 0000000..0479f6f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs @@ -0,0 +1,74 @@ +using dnlib.DotNet; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class GenericClass + { + public TypeDef Type { get; } + + public List KlassInst { get; } + + private readonly int _hashCode; + + public GenericClass(TypeDef type, List classInst) + { + Type = type; + KlassInst = classInst; + _hashCode = ComputHashCode(); + } + + public GenericClass ToGenericShare() + { + return new GenericClass(Type, MetaUtil.ToShareTypeSigs(Type.Module.CorLibTypes, KlassInst)); + } + + public override bool Equals(object obj) + { + if (obj is GenericClass gc) + { + return Type == gc.Type && MetaUtil.EqualsTypeSigArray(KlassInst, gc.KlassInst); + } + return false; + } + + public override int GetHashCode() + { + return _hashCode; + } + + private int ComputHashCode() + { + int hash = TypeEqualityComparer.Instance.GetHashCode(Type); + if (KlassInst != null) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(KlassInst)); + } + return hash; + } + + public TypeSig ToTypeSig() + { + return new GenericInstSig(this.Type.ToTypeSig().ToClassOrValueTypeSig(), this.KlassInst); + } + + public static GenericClass ResolveClass(TypeSpec type, GenericArgumentContext ctx) + { + var sig = type.TypeSig.ToGenericInstSig(); + if (sig == null) + { + return null; + } + TypeDef def = type.ResolveTypeDef(); + if (def == null) + { + Debug.LogWarning($"type:{type} ResolveTypeDef() == null"); + return null; + } + var klassInst = ctx != null ? sig.GenericArguments.Select(ga => MetaUtil.Inflate(ga, ctx)).ToList() : sig.GenericArguments.ToList(); + return new GenericClass(def, klassInst); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs.meta new file mode 100644 index 0000000..438a1a3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c95ff173013909548bd9e2008812f9ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs new file mode 100644 index 0000000..96252fc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs @@ -0,0 +1,109 @@ +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class GenericMethod + { + public MethodDef Method { get; } + + public List KlassInst { get; } + + public List MethodInst { get; } + + private readonly int _hashCode; + + public GenericMethod(MethodDef method, List classInst, List methodInst) + { + Method = method; + KlassInst = classInst; + MethodInst = methodInst; + _hashCode = ComputHashCode(); + } + + public GenericMethod ToGenericShare() + { + ICorLibTypes corLibTypes = Method.Module.CorLibTypes; + return new GenericMethod(Method, MetaUtil.ToShareTypeSigs(corLibTypes, KlassInst), MetaUtil.ToShareTypeSigs(corLibTypes, MethodInst)); + } + + public override bool Equals(object obj) + { + GenericMethod o = (GenericMethod)obj; + return Method == o.Method + && MetaUtil.EqualsTypeSigArray(KlassInst, o.KlassInst) + && MetaUtil.EqualsTypeSigArray(MethodInst, o.MethodInst); + } + + public override int GetHashCode() + { + return _hashCode; + } + + public override string ToString() + { + return $"{Method}|{string.Join(",", (IEnumerable)KlassInst ?? Array.Empty())}|{string.Join(",", (IEnumerable)MethodInst ?? Array.Empty())}"; + } + + private int ComputHashCode() + { + int hash = MethodEqualityComparer.CompareDeclaringTypes.GetHashCode(Method); + if (KlassInst != null) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(KlassInst)); + } + if (MethodInst != null) + { + hash = HashUtil.CombineHash(hash, HashUtil.ComputHash(MethodInst)); + } + return hash; + } + + public MethodSpec ToMethodSpec() + { + IMethodDefOrRef mt = KlassInst != null ? + (IMethodDefOrRef)new MemberRefUser(this.Method.Module, Method.Name, Method.MethodSig, new TypeSpecUser(new GenericInstSig(this.Method.DeclaringType.ToTypeSig().ToClassOrValueTypeSig(), this.KlassInst))) + : this.Method; + return new MethodSpecUser(mt, new GenericInstMethodSig(MethodInst)); + } + + public static GenericMethod ResolveMethod(IMethod method, GenericArgumentContext ctx) + { + //Debug.Log($"== resolve method:{method}"); + TypeDef typeDef = null; + List klassInst = null; + List methodInst = null; + + MethodDef methodDef = null; + + + var decalringType = method.DeclaringType; + typeDef = decalringType.ResolveTypeDef(); + if (typeDef == null) + { + return null; + } + GenericInstSig gis = decalringType.TryGetGenericInstSig(); + if (gis != null) + { + klassInst = ctx != null ? gis.GenericArguments.Select(ga => MetaUtil.Inflate(ga, ctx)).ToList() : gis.GenericArguments.ToList(); + } + methodDef = method.ResolveMethodDef(); + if (methodDef == null) + { + Debug.LogWarning($"method:{method} ResolveMethodDef() == null"); + return null; + } + if (method is MethodSpec methodSpec) + { + methodInst = ctx != null ? methodSpec.GenericInstMethodSig.GenericArguments.Select(ga => MetaUtil.Inflate(ga, ctx)).ToList() + : methodSpec.GenericInstMethodSig.GenericArguments.ToList(); + } + return new GenericMethod(methodDef, klassInst, methodInst); + } + + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs.meta new file mode 100644 index 0000000..ef15a5c --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/GenericMethod.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88ecf3d52ec393b4cac142518944e487 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs new file mode 100644 index 0000000..25a6d42 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public interface IAssemblyResolver + { + string ResolveAssembly(string assemblyName, bool throwExIfNotFind); + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs.meta new file mode 100644 index 0000000..d56059d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/IAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f962a018018dbb945a19f82d2e098686 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs new file mode 100644 index 0000000..eb95a7a --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs @@ -0,0 +1,193 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using HybridCLR.Editor.Settings; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; + +namespace HybridCLR.Editor.Meta +{ + public static class MetaUtil + { + + public static bool EqualsTypeSig(TypeSig a, TypeSig b) + { + if (a == b) + { + return true; + } + if (a != null && b != null) + { + return TypeEqualityComparer.Instance.Equals(a, b); + } + return false; + } + + public static bool EqualsTypeSigArray(List a, List b) + { + if (a == b) + { + return true; + } + if (a != null && b != null) + { + if (a.Count != b.Count) + { + return false; + } + for (int i = 0; i < a.Count; i++) + { + if (!TypeEqualityComparer.Instance.Equals(a[i], b[i])) + { + return false; + } + } + return true; + } + return false; + } + + public static TypeSig Inflate(TypeSig sig, GenericArgumentContext ctx) + { + if (!sig.ContainsGenericParameter) + { + return sig; + } + return ctx.Resolve(sig); + } + + public static TypeSig ToShareTypeSig(ICorLibTypes corTypes, TypeSig typeSig) + { + var a = typeSig.RemovePinnedAndModifiers(); + switch (a.ElementType) + { + case ElementType.Void: return corTypes.Void; + case ElementType.Boolean: return corTypes.Byte; + case ElementType.Char: return corTypes.UInt16; + case ElementType.I1: return corTypes.SByte; + case ElementType.U1:return corTypes.Byte; + case ElementType.I2: return corTypes.Int16; + case ElementType.U2: return corTypes.UInt16; + case ElementType.I4: return corTypes.Int32; + case ElementType.U4: return corTypes.UInt32; + case ElementType.I8: return corTypes.Int64; + case ElementType.U8: return corTypes.UInt64; + case ElementType.R4: return corTypes.Single; + case ElementType.R8: return corTypes.Double; + case ElementType.String: return corTypes.Object; + case ElementType.TypedByRef: return corTypes.TypedReference; + case ElementType.I: return corTypes.IntPtr; + case ElementType.U: return corTypes.UIntPtr; + case ElementType.Object: return corTypes.Object; + case ElementType.Sentinel: return typeSig; + case ElementType.Ptr: return corTypes.UIntPtr; + case ElementType.ByRef: return corTypes.UIntPtr; + case ElementType.SZArray: return corTypes.Object; + case ElementType.Array: return corTypes.Object; + case ElementType.ValueType: + { + TypeDef typeDef = a.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + return typeSig; + } + case ElementType.Var: + case ElementType.MVar: + case ElementType.Class: return corTypes.Object; + case ElementType.GenericInst: + { + var gia = (GenericInstSig)a; + TypeDef typeDef = gia.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{a} definition could not be found"); + } + if (typeDef.IsEnum) + { + return ToShareTypeSig(corTypes, typeDef.GetEnumUnderlyingType()); + } + if (!typeDef.IsValueType) + { + return corTypes.Object; + } + return new GenericInstSig(gia.GenericType, gia.GenericArguments.Select(ga => ToShareTypeSig(corTypes, ga)).ToList()); + } + case ElementType.FnPtr: return corTypes.IntPtr; + case ElementType.ValueArray: return typeSig; + case ElementType.Module: return typeSig; + default: + throw new NotSupportedException(typeSig.ToString()); + } + } + + public static List ToShareTypeSigs(ICorLibTypes corTypes, IList typeSigs) + { + if (typeSigs == null) + { + return null; + } + return typeSigs.Select(s => ToShareTypeSig(corTypes, s)).ToList(); + } + + public static IAssemblyResolver CreateHotUpdateAssemblyResolver(BuildTarget target, List hotUpdateDlls) + { + var externalDirs = HybridCLRSettings.Instance.externalHotUpdateAssembliyDirs; + var defaultHotUpdateOutputDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target); + IAssemblyResolver defaultHotUpdateResolver = new FixedSetAssemblyResolver(defaultHotUpdateOutputDir, hotUpdateDlls); + if (externalDirs == null || externalDirs.Length == 0) + { + return defaultHotUpdateResolver; + } + else + { + var resolvers = new List(); + foreach (var dir in externalDirs) + { + resolvers.Add(new FixedSetAssemblyResolver($"{dir}/{target}", hotUpdateDlls)); + resolvers.Add(new FixedSetAssemblyResolver(dir, hotUpdateDlls)); + } + resolvers.Add(defaultHotUpdateResolver); + return new CombinedAssemblyResolver(resolvers.ToArray()); + } + } + + public static IAssemblyResolver CreateAOTAssemblyResolver(BuildTarget target) + { + return new PathAssemblyResolver(SettingsUtil.GetAssembliesPostIl2CppStripDir(target)); + } + + public static IAssemblyResolver CreateHotUpdateAndAOTAssemblyResolver(BuildTarget target, List hotUpdateDlls) + { + return new CombinedAssemblyResolver( + CreateHotUpdateAssemblyResolver(target, hotUpdateDlls), + CreateAOTAssemblyResolver(target) + ); + } + + public static string ResolveNetStandardAssemblyPath(string assemblyName) + { + return $"{SettingsUtil.HybridCLRDataPathInPackage}/NetStandard/{assemblyName}.dll"; + } + + + public static List CreateDefaultGenericParams(ModuleDef module, int genericParamCount) + { + var methodGenericParams = new List(); + for (int i = 0; i < genericParamCount; i++) + { + methodGenericParams.Add(module.CorLibTypes.Object); + } + return methodGenericParams; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs.meta new file mode 100644 index 0000000..d9342a4 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MetaUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3dbfe2e8b6a92742b18e287c5d281dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs new file mode 100644 index 0000000..bb36692 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs @@ -0,0 +1,79 @@ +using dnlib.DotNet; +using HybridCLR.Editor.ABI; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Meta +{ + public class MethodReferenceAnalyzer + { + private readonly Action, List, GenericMethod> _onNewMethod; + + private readonly ConcurrentDictionary> _methodEffectInsts = new ConcurrentDictionary>(); + + public MethodReferenceAnalyzer(Action, List, GenericMethod> onNewMethod) + { + _onNewMethod = onNewMethod; + } + + public void WalkMethod(MethodDef method, List klassGenericInst, List methodGenericInst) + { + var ctx = new GenericArgumentContext(klassGenericInst, methodGenericInst); + + if (_methodEffectInsts.TryGetValue(method, out var effectInsts)) + { + foreach (var met in effectInsts) + { + var resolveMet = GenericMethod.ResolveMethod(met, ctx)?.ToGenericShare(); + _onNewMethod(method, klassGenericInst, methodGenericInst, resolveMet); + } + return; + } + + var body = method.Body; + if (body == null || !body.HasInstructions) + { + return; + } + + effectInsts = new List(); + foreach (var inst in body.Instructions) + { + if (inst.Operand == null) + { + continue; + } + switch (inst.Operand) + { + case IMethod met: + { + if (!met.IsMethod) + { + continue; + } + var resolveMet = GenericMethod.ResolveMethod(met, ctx)?.ToGenericShare(); + if (resolveMet == null) + { + continue; + } + effectInsts.Add(met); + _onNewMethod(method, klassGenericInst, methodGenericInst, resolveMet); + break; + } + case ITokenOperand token: + { + //GenericParamContext paramContext = method.HasGenericParameters || method.DeclaringType.HasGenericParameters ? + // new GenericParamContext(method.DeclaringType, method) : default; + //method.Module.ResolveToken(token.MDToken, paramContext); + break; + } + } + } + _methodEffectInsts.TryAdd(method, effectInsts); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs.meta new file mode 100644 index 0000000..7554a58 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/MethodReferenceAnalyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c644b0c018fb87498d69c3202439d21 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs b/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs new file mode 100644 index 0000000..a356117 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.Meta +{ + public class PathAssemblyResolver : AssemblyResolverBase + { + private readonly string[] _searchPaths; + public PathAssemblyResolver(params string[] searchPaths) + { + _searchPaths = searchPaths; + } + + protected override bool TryResolveAssembly(string assemblyName, out string assemblyPath) + { + foreach(var path in _searchPaths) + { + string assPath = Path.Combine(path, assemblyName + ".dll"); + if (File.Exists(assPath)) + { + Debug.Log($"resolve {assemblyName} at {assPath}"); + assemblyPath = assPath; + return true; + } + } + assemblyPath = null; + return false; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs.meta new file mode 100644 index 0000000..af02cb8 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Meta/PathAssemblyResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 121d574bf01969444aa6619a8f6dbb4c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge.meta b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge.meta new file mode 100644 index 0000000..5ff8e80 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c2444f09010bce41a52d951b7100c49 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs new file mode 100644 index 0000000..59e2dd7 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs @@ -0,0 +1,204 @@ +using dnlib.DotNet; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.MethodBridge +{ + + public class Analyzer + { + public class Options + { + public AssemblyReferenceDeepCollector Collector { get; set; } + + public int MaxIterationCount { get; set; } + } + + private readonly int _maxInterationCount; + private readonly AssemblyReferenceDeepCollector _assemblyCollector; + + private readonly object _lock = new object(); + + private readonly List _typeDefs = new List(); + + private readonly HashSet _genericTypes = new HashSet(); + private readonly HashSet _genericMethods = new HashSet(); + + private List _processingMethods = new List(); + private List _newMethods = new List(); + + public IReadOnlyList TypeDefs => _typeDefs; + + public IReadOnlyCollection GenericTypes => _genericTypes; + + public IReadOnlyCollection GenericMethods => _genericMethods; + + + private readonly MethodReferenceAnalyzer _methodReferenceAnalyzer; + + public Analyzer(Options options) + { + _maxInterationCount = options.MaxIterationCount; + _assemblyCollector = options.Collector; + _methodReferenceAnalyzer = new MethodReferenceAnalyzer(this.OnNewMethod); + } + + private void TryAddAndWalkGenericType(GenericClass gc) + { + if (gc == null) + { + return; + } + lock(_lock) + { + gc = StandardizeClass(gc); + if (_genericTypes.Add(gc)) + { + WalkType(gc); + } + } + } + + private GenericClass StandardizeClass(GenericClass gc) + { + TypeDef typeDef = gc.Type; + ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes; + List klassGenericParams = gc.KlassInst != null ? MetaUtil.ToShareTypeSigs(corLibTypes, gc.KlassInst) : (typeDef.GenericParameters.Count > 0 ? MetaUtil.CreateDefaultGenericParams(typeDef.Module, typeDef.GenericParameters.Count) : null); + return new GenericClass(typeDef, klassGenericParams); + } + + private GenericMethod StandardizeMethod(GenericMethod gm) + { + TypeDef typeDef = gm.Method.DeclaringType; + ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes; + List klassGenericParams = gm.KlassInst != null ? MetaUtil.ToShareTypeSigs(corLibTypes, gm.KlassInst) : (typeDef.GenericParameters.Count > 0 ? MetaUtil.CreateDefaultGenericParams(typeDef.Module, typeDef.GenericParameters.Count) : null); + List methodGenericParams = gm.MethodInst != null ? MetaUtil.ToShareTypeSigs(corLibTypes, gm.MethodInst) : (gm.Method.GenericParameters.Count > 0 ? MetaUtil.CreateDefaultGenericParams(typeDef.Module, gm.Method.GenericParameters.Count) : null); + return new GenericMethod(gm.Method, klassGenericParams, methodGenericParams); + } + + private void OnNewMethod(MethodDef methodDef, List klassGenericInst, List methodGenericInst, GenericMethod method) + { + lock(_lock) + { + method = StandardizeMethod(method); + if (_genericMethods.Add(method)) + { + _newMethods.Add(method); + } + if (method.KlassInst != null) + { + TryAddAndWalkGenericType(new GenericClass(method.Method.DeclaringType, method.KlassInst)); + } + } + } + + private void WalkType(GenericClass gc) + { + //Debug.Log($"typespec:{sig} {sig.GenericType} {sig.GenericType.TypeDefOrRef.ResolveTypeDef()}"); + //Debug.Log($"== walk generic type:{new GenericInstSig(gc.Type.ToTypeSig().ToClassOrValueTypeSig(), gc.KlassInst)}"); + ITypeDefOrRef baseType = gc.Type.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass parentType = GenericClass.ResolveClass((TypeSpec)baseType, new GenericArgumentContext(gc.KlassInst, null)); + TryAddAndWalkGenericType(parentType); + } + foreach (var method in gc.Type.Methods) + { + var gm = StandardizeMethod(new GenericMethod(method, gc.KlassInst, null)); + //Debug.Log($"add method:{gm.Method} {gm.KlassInst}"); + + if (_genericMethods.Add(gm)) + { + if (method.HasBody && method.Body.Instructions != null) + { + _newMethods.Add(gm); + } + } + } + } + + private void WalkType(TypeDef typeDef) + { + _typeDefs.Add(typeDef); + ITypeDefOrRef baseType = typeDef.BaseType; + if (baseType != null && baseType.TryGetGenericInstSig() != null) + { + GenericClass gc = GenericClass.ResolveClass((TypeSpec)baseType, null); + TryAddAndWalkGenericType(gc); + } + foreach (var method in typeDef.Methods) + { + // 对于带泛型的参数,统一泛型共享为object + var gm = StandardizeMethod(new GenericMethod(method, null, null)); + _genericMethods.Add(gm); + } + } + + private void Prepare() + { + // 将所有非泛型函数全部加入函数列表,同时立马walk这些method。 + // 后续迭代中将只遍历MethodSpec + foreach (var ass in _assemblyCollector.GetLoadedModules()) + { + foreach (TypeDef typeDef in ass.GetTypes()) + { + WalkType(typeDef); + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.TypeSpecTable.Rows; rid <= n; rid++) + { + var ts = ass.ResolveTypeSpec(rid); + var cs = GenericClass.ResolveClass(ts, null); + if (cs != null) + { + TryAddAndWalkGenericType(cs); + } + } + + for (uint rid = 1, n = ass.Metadata.TablesStream.MethodSpecTable.Rows; rid <= n; rid++) + { + var ms = ass.ResolveMethodSpec(rid); + var gm = GenericMethod.ResolveMethod(ms, null)?.ToGenericShare(); + if (gm == null) + { + continue; + } + gm = StandardizeMethod(gm); + if (_genericMethods.Add(gm)) + { + _newMethods.Add(gm); + } + } + } + Debug.Log($"PostPrepare allMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + + private void RecursiveCollect() + { + for (int i = 0; i < _maxInterationCount && _newMethods.Count > 0; i++) + { + var temp = _processingMethods; + _processingMethods = _newMethods; + _newMethods = temp; + _newMethods.Clear(); + + Task.WaitAll(_processingMethods.Select(method => Task.Run(() => + { + _methodReferenceAnalyzer.WalkMethod(method.Method, method.KlassInst, method.MethodInst); + })).ToArray()); + Debug.Log($"iteration:[{i}] genericClass:{_genericTypes.Count} genericMethods:{_genericMethods.Count} newMethods:{_newMethods.Count}"); + } + } + + public void Run() + { + Prepare(); + RecursiveCollect(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs.meta new file mode 100644 index 0000000..4fd796f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Analyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee1ec106190e514489c7ba32bc7bc2e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs new file mode 100644 index 0000000..bb9f56e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs @@ -0,0 +1,828 @@ +using dnlib.DotNet; +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Meta; +using HybridCLR.Editor.Template; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; +using TypeInfo = HybridCLR.Editor.ABI.TypeInfo; + +namespace HybridCLR.Editor.MethodBridge +{ + public class Generator + { + public class Options + { + public string TemplateCode { get; set; } + + public string OutputFile { get; set; } + + public IReadOnlyCollection GenericMethods { get; set; } + } + + private readonly List _genericMethods; + + private readonly string _templateCode; + + private readonly string _outputFile; + + private readonly TypeCreator _typeCreator; + + private readonly HashSet _managed2nativeMethodSet = new HashSet(); + + private readonly HashSet _native2managedMethodSet = new HashSet(); + + private readonly HashSet _adjustThunkMethodSet = new HashSet(); + + public Generator(Options options) + { + List<(GenericMethod, string)> genericMethodInfo = options.GenericMethods.Select(m => (m, m.ToString())).ToList(); + genericMethodInfo.Sort((a, b) => string.CompareOrdinal(a.Item2, b.Item2)); + _genericMethods = genericMethodInfo.Select(m => m.Item1).ToList(); + + _templateCode = options.TemplateCode; + _outputFile = options.OutputFile; + _typeCreator = new TypeCreator(); + } + + private readonly Dictionary _sig2Types = new Dictionary(); + + private TypeInfo GetSharedTypeInfo(TypeSig type) + { + var typeInfo = _typeCreator.CreateTypeInfo(type); + if (!typeInfo.IsStruct) + { + return typeInfo; + } + string sigName = ToFullName(typeInfo.Klass); + if (!_sig2Types.TryGetValue(sigName, out var sharedTypeInfo)) + { + sharedTypeInfo = typeInfo; + _sig2Types.Add(sigName, sharedTypeInfo); + } + return sharedTypeInfo; + } + + private MethodDesc CreateMethodDesc(MethodDef methodDef, bool forceRemoveThis, TypeSig returnType, List parameters) + { + var paramInfos = new List(); + if (forceRemoveThis && !methodDef.IsStatic) + { + parameters.RemoveAt(0); + } + if (returnType.ContainsGenericParameter) + { + throw new Exception($"[PreservedMethod] method:{methodDef} has generic parameters"); + } + foreach (var paramInfo in parameters) + { + if (paramInfo.ContainsGenericParameter) + { + throw new Exception($"[PreservedMethod] method:{methodDef} has generic parameters"); + } + paramInfos.Add(new ParamInfo() { Type = GetSharedTypeInfo(paramInfo) }); + } + var mbs = new MethodDesc() + { + MethodDef = methodDef, + ReturnInfo = new ReturnInfo() { Type = returnType != null ? GetSharedTypeInfo(returnType) : TypeInfo.s_void }, + ParamInfos = paramInfos, + }; + return mbs; + } + + private void AddManaged2NativeMethod(MethodDesc method) + { + method.Init(); + _managed2nativeMethodSet.Add(method); + } + + private void AddNative2ManagedMethod(MethodDesc method) + { + method.Init(); + _native2managedMethodSet.Add(method); + } + + private void AddAdjustThunkMethod(MethodDesc method) + { + method.Init(); + _adjustThunkMethodSet.Add(method); + } + + private void ProcessMethod(MethodDef method, List klassInst, List methodInst) + { + if (method.IsPrivate || (method.IsAssembly && !method.IsPublic && !method.IsFamily)) + { + if (klassInst == null && methodInst == null) + { + return; + } + else + { + //Debug.Log($"[PreservedMethod] method:{method}"); + } + } + ICorLibTypes corLibTypes = method.Module.CorLibTypes; + TypeSig returnType; + List parameters; + if (klassInst == null && methodInst == null) + { + if (method.HasGenericParameters) + { + throw new Exception($"[PreservedMethod] method:{method} has generic parameters"); + } + returnType = MetaUtil.ToShareTypeSig(corLibTypes, method.ReturnType); + parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, p.Type)).ToList(); + } + else + { + var gc = new GenericArgumentContext(klassInst, methodInst); + returnType = MetaUtil.ToShareTypeSig(corLibTypes, MetaUtil.Inflate(method.ReturnType, gc)); + parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, MetaUtil.Inflate(p.Type, gc))).ToList(); + } + + var m2nMethod = CreateMethodDesc(method, false, returnType, parameters); + AddManaged2NativeMethod(m2nMethod); + + if (method.IsVirtual) + { + if (method.DeclaringType.IsInterface) + { + AddAdjustThunkMethod(m2nMethod); + } + //var adjustThunkMethod = CreateMethodDesc(method, true, returnType, parameters); + AddNative2ManagedMethod(m2nMethod); + } + if (method.Name == "Invoke" && method.DeclaringType.IsDelegate) + { + var openMethod = CreateMethodDesc(method, true, returnType, parameters); + AddNative2ManagedMethod(openMethod); + } + } + + public void PrepareMethods() + { + foreach(var method in _genericMethods) + { + ProcessMethod(method.Method, method.KlassInst, method.MethodInst); + } + } + + static void CheckUnique(IEnumerable names) + { + var set = new HashSet(); + foreach (var name in names) + { + if (!set.Add(name)) + { + throw new Exception($"[CheckUnique] duplicate name:{name}"); + } + } + } + + + private List _managed2NativeMethodList0; + private List _native2ManagedMethodList0; + private List _adjustThunkMethodList0; + + private List _structTypes0; + + private void CollectTypesAndMethods() + { + _managed2NativeMethodList0 = _managed2nativeMethodSet.ToList(); + _managed2NativeMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); + + _native2ManagedMethodList0 = _native2managedMethodSet.ToList(); + _native2ManagedMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); + + _adjustThunkMethodList0 = _adjustThunkMethodSet.ToList(); + _adjustThunkMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig)); + + + var structTypeSet = new HashSet(); + CollectStructDefs(_managed2NativeMethodList0, structTypeSet); + CollectStructDefs(_native2ManagedMethodList0, structTypeSet); + CollectStructDefs(_adjustThunkMethodList0, structTypeSet); + _structTypes0 = structTypeSet.ToList(); + _structTypes0.Sort((a, b) => a.TypeId - b.TypeId); + + CheckUnique(_structTypes0.Select(t => ToFullName(t.Klass))); + CheckUnique(_structTypes0.Select(t => t.CreateSigName())); + + Debug.LogFormat("== before optimization struct:{3} managed2native:{0} native2managed:{1} adjustThunk:{2}", + _managed2NativeMethodList0.Count, _native2ManagedMethodList0.Count, _adjustThunkMethodList0.Count, _structTypes0.Count); + } + + private class AnalyzeTypeInfo + { + public TypeInfo toSharedType; + public List fields; + public string signature; + } + + private readonly Dictionary _analyzeTypeInfos = new Dictionary(); + + private readonly Dictionary _signature2Type = new Dictionary(); + + private AnalyzeTypeInfo CalculateAnalyzeTypeInfoBasic(TypeInfo typeInfo) + { + TypeSig type = typeInfo.Klass; + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow(); + + List klassInst = type.ToGenericInstSig()?.GenericArguments?.ToList(); + GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null; + + ClassLayout sa = typeDef.ClassLayout; + var analyzeTypeInfo = new AnalyzeTypeInfo(); + + // don't share type with explicit layout + if (sa != null) + { + analyzeTypeInfo.toSharedType = typeInfo; + analyzeTypeInfo.signature = typeInfo.CreateSigName(); + _signature2Type.Add(analyzeTypeInfo.signature, typeInfo); + return analyzeTypeInfo; + } + + var fields = analyzeTypeInfo.fields = new List(); + + foreach (FieldDef field in typeDef.Fields) + { + if (field.IsStatic) + { + continue; + } + TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType; + fields.Add(GetSharedTypeInfo(fieldType)); + } + return analyzeTypeInfo; + } + + private string GetOrCalculateTypeInfoSignature(TypeInfo typeInfo) + { + if (!typeInfo.IsStruct) + { + return typeInfo.CreateSigName(); + } + + var ati = _analyzeTypeInfos[typeInfo]; + + //if (_analyzeTypeInfos.TryGetValue(typeInfo, out var ati)) + //{ + // return ati.signature; + //} + //ati = CalculateAnalyzeTypeInfoBasic(typeInfo); + //_analyzeTypeInfos.Add(typeInfo, ati); + if (ati.signature != null) + { + return ati.signature; + } + + var sigBuf = new StringBuilder(); + foreach (var field in ati.fields) + { + sigBuf.Append(GetOrCalculateTypeInfoSignature(ToIsomorphicType(field))); + } + return ati.signature = sigBuf.ToString(); + } + + private TypeInfo ToIsomorphicType(TypeInfo type) + { + if (!type.IsStruct) + { + return type; + } + if (!_analyzeTypeInfos.TryGetValue(type, out var ati)) + { + ati = CalculateAnalyzeTypeInfoBasic(type); + _analyzeTypeInfos.Add(type, ati); + } + if (ati.toSharedType == null) + { + string signature = GetOrCalculateTypeInfoSignature(type); + Debug.Assert(signature == ati.signature); + if (_signature2Type.TryGetValue(signature, out var sharedType)) + { + // Debug.Log($"[ToIsomorphicType] type:{type.Klass} ==> sharedType:{sharedType.Klass} signature:{signature} "); + ati.toSharedType = sharedType; + } + else + { + ati.toSharedType = type; + _signature2Type.Add(signature, type); + } + } + return ati.toSharedType; + } + + private MethodDesc ToIsomorphicMethod(MethodDesc method) + { + var paramInfos = new List(); + foreach (var paramInfo in method.ParamInfos) + { + paramInfos.Add(new ParamInfo() { Type = ToIsomorphicType(paramInfo.Type) }); + } + var mbs = new MethodDesc() + { + MethodDef = method.MethodDef, + ReturnInfo = new ReturnInfo() { Type = ToIsomorphicType(method.ReturnInfo.Type) }, + ParamInfos = paramInfos, + }; + mbs.Init(); + return mbs; + } + + private List _managed2NativeMethodList; + private List _native2ManagedMethodList; + private List _adjustThunkMethodList; + + private List structTypes; + + private void BuildAnalyzeTypeInfos() + { + foreach (var type in _structTypes0) + { + ToIsomorphicType(type); + } + structTypes = _signature2Type.Values.ToList(); + structTypes.Sort((a, b) => a.TypeId - b.TypeId); + } + + private List ToUniqueOrderedList(List methods) + { + var methodMap = new SortedDictionary(); + foreach (var method in methods) + { + var sharedMethod = ToIsomorphicMethod(method); + var sig = sharedMethod.Sig; + if (!methodMap.TryGetValue(sig, out var _)) + { + methodMap.Add(sig, sharedMethod); + } + } + return methodMap.Values.ToList(); + } + + private void BuildOptimizedMethods() + { + _managed2NativeMethodList = ToUniqueOrderedList(_managed2NativeMethodList0); + _native2ManagedMethodList = ToUniqueOrderedList(_native2ManagedMethodList0); + _adjustThunkMethodList = ToUniqueOrderedList(_adjustThunkMethodList0); + } + + private void OptimizationTypesAndMethods() + { + BuildAnalyzeTypeInfos(); + BuildOptimizedMethods(); + Debug.LogFormat("== after optimization struct:{3} managed2native:{0} native2managed:{1} adjustThunk:{2}", + _managed2NativeMethodList.Count, _native2ManagedMethodList.Count, _adjustThunkMethodList.Count, structTypes.Count); + } + + private void GenerateCode() + { + var frr = new FileRegionReplace(_templateCode); + + List lines = new List(20_0000); + + var classInfos = new List(); + var classTypeSet = new HashSet(); + foreach (var type in structTypes) + { + GenerateClassInfo(type, classTypeSet, classInfos); + } + + GenerateStructDefines(classInfos, lines); + + // use structTypes0 to generate signature + GenerateStructureSignatureStub(_structTypes0, lines); + + foreach (var method in _managed2NativeMethodList) + { + GenerateManaged2NativeMethod(method, lines); + } + + GenerateManaged2NativeStub(_managed2NativeMethodList, lines); + + foreach (var method in _native2ManagedMethodList) + { + GenerateNative2ManagedMethod(method, lines); + } + + GenerateNative2ManagedStub(_native2ManagedMethodList, lines); + + foreach (var method in _adjustThunkMethodList) + { + GenerateAdjustThunkMethod(method, lines); + } + + GenerateAdjustThunkStub(_adjustThunkMethodList, lines); + + frr.Replace("CODE", string.Join("\n", lines)); + + Directory.CreateDirectory(Path.GetDirectoryName(_outputFile)); + + frr.Commit(_outputFile); + } + + public void Generate() + { + CollectTypesAndMethods(); + OptimizationTypesAndMethods(); + GenerateCode(); + } + + private void CollectStructDefs(List methods, HashSet structTypes) + { + foreach (var method in methods) + { + foreach(var paramInfo in method.ParamInfos) + { + if (paramInfo.Type.IsStruct) + { + structTypes.Add(paramInfo.Type); + if (paramInfo.Type.Klass.ContainsGenericParameter) + { + throw new Exception($"[CollectStructDefs] method:{method.MethodDef} type:{paramInfo.Type.Klass} contains generic parameter"); + } + } + + } + if (method.ReturnInfo.Type.IsStruct) + { + structTypes.Add(method.ReturnInfo.Type); + if (method.ReturnInfo.Type.Klass.ContainsGenericParameter) + { + throw new Exception($"[CollectStructDefs] method:{method.MethodDef} type:{method.ReturnInfo.Type.Klass} contains generic parameter"); + } + } + } + + } + + class FieldInfo + { + public FieldDef field; + public TypeInfo type; + } + + class ClassInfo + { + public TypeInfo type; + + public TypeDef typeDef; + + public List fields = new List(); + + public ClassLayout layout; + } + + private void GenerateClassInfo(TypeInfo type, HashSet typeSet, List classInfos) + { + if (!typeSet.Add(type)) + { + return; + } + TypeSig typeSig = type.Klass; + var fields = new List(); + + TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow(); + + List klassInst = typeSig.ToGenericInstSig()?.GenericArguments?.ToList(); + GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null; + + ClassLayout sa = typeDef.ClassLayout; + + ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes; + foreach (FieldDef field in typeDef.Fields) + { + if (field.IsStatic) + { + continue; + } + TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType; + fieldType = MetaUtil.ToShareTypeSig(corLibTypes, fieldType); + var fieldTypeInfo = ToIsomorphicType(_typeCreator.CreateTypeInfo(fieldType)); + if (fieldTypeInfo.IsStruct) + { + GenerateClassInfo(fieldTypeInfo, typeSet, classInfos); + } + fields.Add(new FieldInfo { field = field, type = fieldTypeInfo }); + } + classInfos.Add(new ClassInfo() { type = type, typeDef = typeDef, fields = fields, layout = sa }); + } + + private void GenerateStructDefines(List classInfos, List lines) + { + foreach (var ci in classInfos) + { + lines.Add($"// {ci.type.Klass}"); + uint packingSize = ci.layout?.PackingSize ?? 0; + if (packingSize != 0) + { + lines.Add($"#pragma pack(push, {packingSize})"); + } + uint classSize = ci.layout?.ClassSize ?? 0; + + if (ci.typeDef.IsExplicitLayout) + { + lines.Add($"union {ci.type.GetTypeName()} {{"); + if (classSize > 0) + { + lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};"); + } + int index = 0; + foreach (var field in ci.fields) + { + uint offset = field.field.FieldOffset.Value; + string fieldName = $"__{index}"; + string commentFieldName = $"{field.field.Name}"; + lines.Add("\t#pragma pack(push, 1)"); + lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding[{offset}];" : "")} {field.type.GetTypeName()} {fieldName};}}; // {commentFieldName}"); + lines.Add($"\t#pragma pack(pop)"); + lines.Add($"\tstruct {{ {field.type.GetTypeName()} {fieldName}_forAlignmentOnly;}}; // {commentFieldName}"); + ++index; + } + } + else + { + lines.Add($"{(classSize > 0 ? "union" : "struct")} {ci.type.GetTypeName()} {{"); + if (classSize > 0) + { + lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};"); + lines.Add("\tstruct {"); + } + int index = 0; + foreach (var field in ci.fields) + { + string fieldName = $"__{index}"; + string commentFieldName = $"{field.field.Name}"; + lines.Add($"\t{field.type.GetTypeName()} {fieldName}; // {commentFieldName}"); + ++index; + } + if (classSize > 0) + { + lines.Add("\t};"); + } + } + lines.Add("};"); + if (packingSize != 0) + { + lines.Add($"#pragma pack(pop)"); + } + } + } + + public const string SigOfObj = "u"; + + public static string ToFullName(TypeSig type) + { + type = type.RemovePinnedAndModifiers(); + switch (type.ElementType) + { + case ElementType.Void: return "v"; + case ElementType.Boolean: return "u1"; + case ElementType.I1: return "i1"; + case ElementType.U1: return "u1"; + case ElementType.I2: return "i2"; + case ElementType.Char: + case ElementType.U2: return "u2"; + case ElementType.I4: return "i4"; + case ElementType.U4: return "u4"; + case ElementType.I8: return "i8"; + case ElementType.U8: return "u8"; + case ElementType.R4: return "r4"; + case ElementType.R8: return "r8"; + case ElementType.I: return "i"; + case ElementType.U: + case ElementType.String: + case ElementType.Ptr: + case ElementType.ByRef: + case ElementType.Class: + case ElementType.Array: + case ElementType.SZArray: + case ElementType.FnPtr: + case ElementType.Object: + return SigOfObj; + case ElementType.Module: + case ElementType.Var: + case ElementType.MVar: + throw new NotSupportedException($"ToFullName type:{type}"); + case ElementType.TypedByRef: return TypeInfo.strTypedByRef; + case ElementType.ValueType: + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{type} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function"); + } + if (typeDef.IsEnum) + { + return ToFullName(typeDef.GetEnumUnderlyingType()); + } + return ToValueTypeFullName((ClassOrValueTypeSig)type); + } + case ElementType.GenericInst: + { + GenericInstSig gis = (GenericInstSig)type; + if (!gis.GenericType.IsValueType) + { + return SigOfObj; + } + TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef.IsEnum) + { + return ToFullName(typeDef.GetEnumUnderlyingType()); + } + return $"{ToValueTypeFullName(gis.GenericType)}<{string.Join(",", gis.GenericArguments.Select(a => ToFullName(a)))}>"; + } + default: throw new NotSupportedException($"{type.ElementType}"); + } + } + + private static bool IsSystemOrUnityAssembly(ModuleDef module) + { + if (module.IsCoreLibraryModule == true) + { + return true; + } + string assName = module.Assembly.Name.String; + return assName.StartsWith("System.") || assName.StartsWith("UnityEngine."); + } + + private static string ToValueTypeFullName(ClassOrValueTypeSig type) + { + TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef(); + if (typeDef == null) + { + throw new Exception($"type:{type} resolve fail"); + } + + if (typeDef.DeclaringType != null) + { + return $"{ToValueTypeFullName((ClassOrValueTypeSig)typeDef.DeclaringType.ToTypeSig())}/{typeDef.Name}"; + } + + if (IsSystemOrUnityAssembly(typeDef.Module)) + { + return type.FullName; + } + return $"{Path.GetFileNameWithoutExtension(typeDef.Module.Name)}:{typeDef.FullName}"; + } + + public void GenerateStructureSignatureStub(List types, List lines) + { + lines.Add("FullName2Signature hybridclr::interpreter::g_fullName2SignatureStub[] = {"); + foreach (var type in types) + { + TypeInfo isoType = ToIsomorphicType(type); + lines.Add($"\t{{\"{ToFullName(type.Klass)}\", \"{isoType.CreateSigName()}\"}},"); + } + lines.Add("\t{ nullptr, nullptr},"); + lines.Add("};"); + } + + public void GenerateManaged2NativeStub(List methods, List lines) + { + lines.Add($@" +Managed2NativeMethodInfo hybridclr::interpreter::g_managed2nativeStub[] = +{{ +"); + + foreach (var method in methods) + { + lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", __M2N_{method.CreateInvokeSigName()}}},"); + } + + lines.Add($"\t{{nullptr, nullptr}},"); + lines.Add("};"); + } + + public void GenerateNative2ManagedStub(List methods, List lines) + { + lines.Add($@" +Native2ManagedMethodInfo hybridclr::interpreter::g_native2managedStub[] = +{{ +"); + + foreach (var method in methods) + { + lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", (Il2CppMethodPointer)__N2M_{method.CreateInvokeSigName()}}},"); + } + + lines.Add($"\t{{nullptr, nullptr}},"); + lines.Add("};"); + } + + public void GenerateAdjustThunkStub(List methods, List lines) + { + lines.Add($@" +NativeAdjustThunkMethodInfo hybridclr::interpreter::g_adjustThunkStub[] = +{{ +"); + + foreach (var method in methods) + { + lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", (Il2CppMethodPointer)__N2M_AdjustorThunk_{method.CreateCallSigName()}}},"); + } + + lines.Add($"\t{{nullptr, nullptr}},"); + lines.Add("};"); + } + + private string GetManaged2NativePassParam(TypeInfo type, string varName) + { + return $"M2NFromValueOrAddress<{type.GetTypeName()}>({varName})"; + } + + private string GetNative2ManagedPassParam(TypeInfo type, string varName) + { + return type.NeedExpandValue() ? $"(uint64_t)({varName})" : $"N2MAsUint64ValueOrAddress<{type.GetTypeName()}>({varName})"; + } + + public void GenerateManaged2NativeMethod(MethodDesc method, List lines) + { + string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" })); + string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]")).Concat(new string[] { "method" })); + + lines.Add($@" +static void __M2N_{method.CreateCallSigName()}(const MethodInfo* method, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret) +{{ + typedef {method.ReturnInfo.Type.GetTypeName()} (*NativeMethod)({paramListStr}); + {(!method.ReturnInfo.IsVoid ? $"*({method.ReturnInfo.Type.GetTypeName()}*)ret = " : "")}((NativeMethod)(method->methodPointerCallByInterp))({paramNameListStr}); +}} +"); + } + + public string GenerateArgumentSizeAndOffset(List paramInfos) + { + StringBuilder s = new StringBuilder(); + int index = 0; + foreach (var param in paramInfos) + { + s.AppendLine($"\tconstexpr int __ARG_OFFSET_{index}__ = {(index > 0 ? $"__ARG_OFFSET_{index - 1}__ + __ARG_SIZE_{index-1}__" : "0")};"); + s.AppendLine($"\tconstexpr int __ARG_SIZE_{index}__ = (sizeof(__arg{index}) + 7)/8;"); + index++; + } + s.AppendLine($"\tconstexpr int __TOTAL_ARG_SIZE__ = {(paramInfos.Count > 0 ? $"__ARG_OFFSET_{index-1}__ + __ARG_SIZE_{index-1}__" : "1")};"); + return s.ToString(); + } + + public string GenerateCopyArgumentToInterpreterStack(List paramInfos) + { + StringBuilder s = new StringBuilder(); + int index = 0; + foreach (var param in paramInfos) + { + if (param.Type.IsPrimitiveType) + { + if (param.Type.NeedExpandValue()) + { + s.AppendLine($"\targs[__ARG_OFFSET_{index}__].u64 = __arg{index};"); + } + else + { + s.AppendLine($"\t*({param.Type.GetTypeName()}*)(args + __ARG_OFFSET_{index}__) = __arg{index};"); + } + } + else + { + s.AppendLine($"\t*({param.Type.GetTypeName()}*)(args + __ARG_OFFSET_{index}__) = __arg{index};"); + } + index++; + } + return s.ToString(); + } + + private void GenerateNative2ManagedMethod0(MethodDesc method, bool adjustorThunk, List lines) + { + string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" })); + lines.Add($@" +static {method.ReturnInfo.Type.GetTypeName()} __N2M_{(adjustorThunk ? "AdjustorThunk_" : "")}{method.CreateCallSigName()}({paramListStr}) +{{ + {(adjustorThunk ? "__arg0 += sizeof(Il2CppObject);" : "")} +{GenerateArgumentSizeAndOffset(method.ParamInfos)} + StackObject args[__TOTAL_ARG_SIZE__]; +{GenerateCopyArgumentToInterpreterStack(method.ParamInfos)} + {(method.ReturnInfo.IsVoid ? "Interpreter::Execute(method, args, nullptr);" : $"{method.ReturnInfo.Type.GetTypeName()} ret; Interpreter::Execute(method, args, &ret); return ret;")} +}} +"); + } + + public void GenerateNative2ManagedMethod(MethodDesc method, List lines) + { + GenerateNative2ManagedMethod0(method, false, lines); + } + + public void GenerateAdjustThunkMethod(MethodDesc method, List lines) + { + GenerateNative2ManagedMethod0(method, true, lines); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs.meta new file mode 100644 index 0000000..914508e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/MethodBridge/Generator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e42a0f3bcbc5ddf438a85ae16c1b3116 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap.meta b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap.meta new file mode 100644 index 0000000..234a1bc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 259c1cb7fe681f74eb435ab8f268890d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Analyzer.cs b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Analyzer.cs new file mode 100644 index 0000000..e089c5b --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Analyzer.cs @@ -0,0 +1,108 @@ +using dnlib.DotNet; +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Meta; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace HybridCLR.Editor.ReversePInvokeWrap +{ + public class RawReversePInvokeMethodInfo + { + public MethodDef Method { get; set; } + + public CustomAttribute GenerationAttribute { get; set; } + } + + public class ABIReversePInvokeMethodInfo + { + public MethodDesc Method { get; set; } + + public int Count { get; set; } + } + + public class Analyzer + { + + private readonly List _rootModules = new List(); + + private readonly List _reversePInvokeMethods = new List(); + + public Analyzer(AssemblyCache cache, List assemblyNames) + { + foreach (var assemblyName in assemblyNames) + { + _rootModules.Add(cache.LoadModule(assemblyName)); + } + } + + private void CollectReversePInvokeMethods() + { + foreach (var mod in _rootModules) + { + Debug.Log($"ass:{mod.FullName} methodcount:{mod.Metadata.TablesStream.MethodTable.Rows}"); + for (uint rid = 1, n = mod.Metadata.TablesStream.MethodTable.Rows; rid <= n; rid++) + { + var method = mod.ResolveMethod(rid); + //Debug.Log($"method:{method}"); + if (!method.IsStatic || !method.HasCustomAttributes) + { + continue; + } + CustomAttribute wa = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Name == "MonoPInvokeCallbackAttribute"); + if (wa == null) + { + continue; + } + //foreach (var ca in method.CustomAttributes) + //{ + // Debug.Log($"{ca.AttributeType.FullName} {ca.TypeFullName}"); + //} + _reversePInvokeMethods.Add(new RawReversePInvokeMethodInfo() + { + Method = method, + GenerationAttribute = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "HybridCLR.ReversePInvokeWrapperGenerationAttribute"), + }); + } + } + } + + public List BuildABIMethods() + { + var methodsBySig = new Dictionary(); + var typeCreator = new TypeCreator(); + foreach(var method in _reversePInvokeMethods) + { + MethodDesc desc = new MethodDesc + { + MethodDef = method.Method, + ReturnInfo = new ReturnInfo { Type = typeCreator.CreateTypeInfo(method.Method.ReturnType)}, + ParamInfos = method.Method.Parameters.Select(p => new ParamInfo { Type = typeCreator.CreateTypeInfo(p.Type)}).ToList(), + }; + desc.Init(); + if (!methodsBySig.TryGetValue(desc.Sig, out var arm)) + { + arm = new ABIReversePInvokeMethodInfo() + { + Method = desc, + Count = 0, + }; + methodsBySig.Add(desc.Sig, arm); + } + int preserveCount = method.GenerationAttribute != null ? (int)method.GenerationAttribute.ConstructorArguments[0].Value : 1; + arm.Count += preserveCount; + } + var methods = methodsBySig.Values.ToList(); + methods.Sort((a, b) => String.CompareOrdinal(a.Method.Sig, b.Method.Sig)); + return methods; + } + + public void Run() + { + CollectReversePInvokeMethods(); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Analyzer.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Analyzer.cs.meta new file mode 100644 index 0000000..792b5b5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Analyzer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c172068b408c0e349b2ceee4c4635085 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Generator.cs b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Generator.cs new file mode 100644 index 0000000..fdf6df5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Generator.cs @@ -0,0 +1,59 @@ +using HybridCLR.Editor.ABI; +using HybridCLR.Editor.Template; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace HybridCLR.Editor.ReversePInvokeWrap +{ + public class Generator + { + public void Generate(List methods, string outputFile) + { + string template = File.ReadAllText(outputFile, Encoding.UTF8); + var frr = new FileRegionReplace(template); + var codes = new List(); + + int methodIndex = 0; + var stubCodes = new List(); + foreach(var methodInfo in methods) + { + MethodDesc method = methodInfo.Method; + string paramDeclaringListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}")); + string paramNameListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"__arg{p.Index}").Concat(new string[] { "method" })); + string paramTypeListWithMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()}").Concat(new string[] { "const MethodInfo*" })); + string methodTypeDef = $"typedef {method.ReturnInfo.Type.GetTypeName()} (*Callback)({paramTypeListWithMethodInfoStr})"; + for (int i = 0; i < methodInfo.Count; i++, methodIndex++) + { + codes.Add($@" + {method.ReturnInfo.Type.GetTypeName()} __ReversePInvokeMethod_{methodIndex}({paramDeclaringListWithoutMethodInfoStr}) + {{ + const MethodInfo* method = MetadataModule::GetMethodInfoByReversePInvokeWrapperIndex({methodIndex}); + {methodTypeDef}; + {(method.ReturnInfo.IsVoid ? "" : "return ")}((Callback)(method->methodPointerCallByInterp))({paramNameListWithoutMethodInfoStr}); + }} +"); + stubCodes.Add($"\t\t{{\"{method.Sig}\", (Il2CppMethodPointer)__ReversePInvokeMethod_{methodIndex}}},\n"); + } + Debug.Log($"[ReversePInvokeWrap.Generator] method:{method.MethodDef} wrapperCount:{methodInfo.Count}"); + } + + codes.Add(@" + ReversePInvokeMethodData g_reversePInvokeMethodStub[] + { +"); + codes.AddRange(stubCodes); + + codes.Add(@" + {nullptr, nullptr}, + }; +"); + + frr.Replace("CODE", string.Join("", codes)); + frr.Commit(outputFile); + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Generator.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Generator.cs.meta new file mode 100644 index 0000000..36a7c55 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/ReversePInvokeWrap/Generator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7d883f182f206fa4db31f4085ce0ecdc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings.meta b/Packages/com.code-philosophy.hybridclr/Editor/Settings.meta new file mode 100644 index 0000000..f73ef75 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3708ee1d4035cb14abaa4d64a8ec8148 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/EditorStatusWatcher.cs b/Packages/com.code-philosophy.hybridclr/Editor/Settings/EditorStatusWatcher.cs new file mode 100644 index 0000000..0c3865e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/EditorStatusWatcher.cs @@ -0,0 +1,32 @@ +using HybridCLR.Editor; +using System; +using UnityEditor; +using UnityEditorInternal; + +namespace HybridCLR.Editor.Settings +{ + + /// + /// 监听编辑器状态,当编辑器重新 focus 时,重新加载实例,避免某些情景下 svn 、git 等外部修改了数据却无法同步的异常。 + /// + [InitializeOnLoad] + public static class EditorStatusWatcher + { + public static Action OnEditorFocused; + static bool isFocused; + static EditorStatusWatcher() => EditorApplication.update += Update; + static void Update() + { + if (isFocused != InternalEditorUtility.isApplicationActive) + { + isFocused = InternalEditorUtility.isApplicationActive; + if (isFocused) + { + HybridCLRSettings.LoadOrCreate(); + OnEditorFocused?.Invoke(); + } + } + } + } + +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/EditorStatusWatcher.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Settings/EditorStatusWatcher.cs.meta new file mode 100644 index 0000000..317bfd7 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/EditorStatusWatcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 093a9e6c1e7399244bbcd8983fdbfdee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs new file mode 100644 index 0000000..5ada220 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs @@ -0,0 +1,178 @@ +using System; +using System.Reflection; +using UnityEditor; +using UnityEditor.Presets; +using UnityEngine; +using UnityEngine.UIElements; + +namespace HybridCLR.Editor.Settings +{ + public class HybridCLRSettingsProvider : SettingsProvider + { + private SerializedObject _serializedObject; + private SerializedProperty _enable; + private SerializedProperty _useGlobalIl2cpp; + private SerializedProperty _hybridclrRepoURL; + private SerializedProperty _il2cppPlusRepoURL; + private SerializedProperty _hotUpdateAssemblyDefinitions; + private SerializedProperty _hotUpdateAssemblies; + private SerializedProperty _preserveHotUpdateAssemblies; + private SerializedProperty _hotUpdateDllCompileOutputRootDir; + private SerializedProperty _externalHotUpdateAssembliyDirs; + private SerializedProperty _strippedAOTDllOutputRootDir; + private SerializedProperty _patchAOTAssemblies; + private SerializedProperty _outputLinkFile; + private SerializedProperty _outputAOTGenericReferenceFile; + private SerializedProperty _maxGenericReferenceIteration; + private SerializedProperty _maxMethodBridgeGenericIteration; + private GUIStyle buttonStyle; + public HybridCLRSettingsProvider() : base("Project/HybridCLR Settings", SettingsScope.Project) { } + public override void OnActivate(string searchContext, VisualElement rootElement) + { + EditorStatusWatcher.OnEditorFocused += OnEditorFocused; + InitGUI(); + } + private void InitGUI() + { + var setting = HybridCLRSettings.LoadOrCreate(); + _serializedObject?.Dispose(); + _serializedObject = new SerializedObject(setting); + _enable = _serializedObject.FindProperty("enable"); + _useGlobalIl2cpp = _serializedObject.FindProperty("useGlobalIl2cpp"); + _hybridclrRepoURL = _serializedObject.FindProperty("hybridclrRepoURL"); + _il2cppPlusRepoURL = _serializedObject.FindProperty("il2cppPlusRepoURL"); + _hotUpdateAssemblyDefinitions = _serializedObject.FindProperty("hotUpdateAssemblyDefinitions"); + _hotUpdateAssemblies = _serializedObject.FindProperty("hotUpdateAssemblies"); + _preserveHotUpdateAssemblies = _serializedObject.FindProperty("preserveHotUpdateAssemblies"); + _hotUpdateDllCompileOutputRootDir = _serializedObject.FindProperty("hotUpdateDllCompileOutputRootDir"); + _externalHotUpdateAssembliyDirs = _serializedObject.FindProperty("externalHotUpdateAssembliyDirs"); + _strippedAOTDllOutputRootDir = _serializedObject.FindProperty("strippedAOTDllOutputRootDir"); + _patchAOTAssemblies = _serializedObject.FindProperty("patchAOTAssemblies"); + _outputLinkFile = _serializedObject.FindProperty("outputLinkFile"); + _outputAOTGenericReferenceFile = _serializedObject.FindProperty("outputAOTGenericReferenceFile"); + _maxGenericReferenceIteration = _serializedObject.FindProperty("maxGenericReferenceIteration"); + _maxMethodBridgeGenericIteration = _serializedObject.FindProperty("maxMethodBridgeGenericIteration"); + } + private void OnEditorFocused() + { + InitGUI(); + Repaint(); + } + public override void OnTitleBarGUI() + { + base.OnTitleBarGUI(); + var rect = GUILayoutUtility.GetLastRect(); + buttonStyle = buttonStyle ?? GUI.skin.GetStyle("IconButton"); + + #region 绘制官方网站跳转按钮 + var w = rect.x + rect.width; + rect.x = w - 57; + rect.y += 6; + rect.width = rect.height = 18; + var content = EditorGUIUtility.IconContent("_Help"); + content.tooltip = "点击访问 HybridCLR 官方文档"; + if (GUI.Button(rect, content, buttonStyle)) + { + Application.OpenURL("https://focus-creative-games.github.io/hybridclr/"); + } + #endregion + #region 绘制 Preset + rect.x += 19; + content = EditorGUIUtility.IconContent("Preset.Context"); + content.tooltip = "点击存储或加载 Preset ."; + if (GUI.Button(rect, content, buttonStyle)) + { + var target = HybridCLRSettings.Instance; + var receiver = ScriptableObject.CreateInstance(); + receiver.Init(target, this); + PresetSelector.ShowSelector(target, null, true, receiver); + } + #endregion + #region 绘制 Reset + rect.x += 19; + content = EditorGUIUtility.IconContent( +#if UNITY_2021_3_OR_NEWER + "pane options" +#else + "_Popup" +#endif + ); + content.tooltip = "Reset"; + if (GUI.Button(rect, content, buttonStyle)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Reset"), false, () => + { + Undo.RecordObject(HybridCLRSettings.Instance, "Capture Value for Reset"); + var dv = ScriptableObject.CreateInstance(); + var json = EditorJsonUtility.ToJson(dv); + UnityEngine.Object.DestroyImmediate(dv); + EditorJsonUtility.FromJsonOverwrite(json, HybridCLRSettings.Instance); + HybridCLRSettings.Save(); + }); + menu.ShowAsContext(); + } + #endregion + } + public override void OnGUI(string searchContext) + { + using (CreateSettingsWindowGUIScope()) + { + //解决编辑器打包时出现的 _serializedObject.targetObject 意外销毁的情况 + if (_serializedObject == null||!_serializedObject.targetObject) + { + InitGUI(); + } + _serializedObject.Update(); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(_enable); + EditorGUILayout.PropertyField(_hybridclrRepoURL); + EditorGUILayout.PropertyField(_il2cppPlusRepoURL); + EditorGUILayout.PropertyField(_useGlobalIl2cpp); + EditorGUILayout.PropertyField(_hotUpdateAssemblyDefinitions); + EditorGUILayout.PropertyField(_hotUpdateAssemblies); + EditorGUILayout.PropertyField(_preserveHotUpdateAssemblies); + EditorGUILayout.PropertyField(_hotUpdateDllCompileOutputRootDir); + EditorGUILayout.PropertyField(_externalHotUpdateAssembliyDirs); + EditorGUILayout.PropertyField(_strippedAOTDllOutputRootDir); + EditorGUILayout.PropertyField(_patchAOTAssemblies); + EditorGUILayout.PropertyField(_outputLinkFile); + EditorGUILayout.PropertyField(_outputAOTGenericReferenceFile); + EditorGUILayout.PropertyField(_maxGenericReferenceIteration); + EditorGUILayout.PropertyField(_maxMethodBridgeGenericIteration); + if (EditorGUI.EndChangeCheck()) + { + _serializedObject.ApplyModifiedProperties(); + HybridCLRSettings.Save(); + } + } + } + private IDisposable CreateSettingsWindowGUIScope() + { + var unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow)); + var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope"); + return Activator.CreateInstance(type) as IDisposable; + } + public override void OnDeactivate() + { + base.OnDeactivate(); + EditorStatusWatcher.OnEditorFocused -= OnEditorFocused; + HybridCLRSettings.Save(); + } + + static HybridCLRSettingsProvider provider; + [SettingsProvider] + public static SettingsProvider CreateMyCustomSettingsProvider() + { + if (HybridCLRSettings.Instance && provider == null) + { + provider = new HybridCLRSettingsProvider(); + using (var so = new SerializedObject(HybridCLRSettings.Instance)) + { + provider.keywords = GetSearchKeywordsFromSerializedObject(so); + } + } + return provider; + } + } +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs.meta new file mode 100644 index 0000000..0a21f37 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettingProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2bd1694fedc8b54c88bb9f6c67907d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs new file mode 100644 index 0000000..92247bf --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs @@ -0,0 +1,54 @@ +using UnityEditorInternal; +using UnityEngine; + +namespace HybridCLR.Editor.Settings +{ + [FilePath("ProjectSettings/HybridCLRSettings.asset")] + public class HybridCLRSettings : ScriptableSingleton + { + [Header("开启HybridCLR插件")] + public bool enable = true; + + [Header("使用全局安装的il2cpp")] + public bool useGlobalIl2cpp; + + [Header("hybridclr 仓库 URL")] + public string hybridclrRepoURL = "https://gitee.com/focus-creative-games/hybridclr"; + + [Header("il2cpp_plus 仓库 URL")] + public string il2cppPlusRepoURL = "https://gitee.com/focus-creative-games/il2cpp_plus"; + + [Header("热更新Assembly Definitions")] + public AssemblyDefinitionAsset[] hotUpdateAssemblyDefinitions; + + [Header("热更新dlls")] + public string[] hotUpdateAssemblies; + + [Header("预留的热更新dlls")] + public string[] preserveHotUpdateAssemblies; + + [Header("热更新dll编译输出根目录")] + public string hotUpdateDllCompileOutputRootDir = "HybridCLRData/HotUpdateDlls"; + + [Header("外部热更新dll搜索路径")] + public string[] externalHotUpdateAssembliyDirs; + + [Header("裁减后AOT dll输出根目录")] + public string strippedAOTDllOutputRootDir = "HybridCLRData/AssembliesPostIl2CppStrip"; + + [Header("补充元数据AOT dlls")] + public string[] patchAOTAssemblies; + + [Header("生成的link.xml路径")] + public string outputLinkFile = "HybridCLRGenerate/link.xml"; + + [Header("自动扫描生成的AOTGenericReferences.cs路径")] + public string outputAOTGenericReferenceFile = "HybridCLRGenerate/AOTGenericReferences.cs"; + + [Header("AOT泛型实例化搜索迭代次数")] + public int maxGenericReferenceIteration = 10; + + [Header("MethodBridge泛型搜索迭代次数")] + public int maxMethodBridgeGenericIteration = 10; + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs.meta new file mode 100644 index 0000000..570bc8f --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/HybridCLRSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e189374413a3f00468e49d51d8b27a09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs b/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs new file mode 100644 index 0000000..c21c46d --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs @@ -0,0 +1,39 @@ +using HybridCLR.Editor.Installer; +using UnityEditor; +using UnityEngine; + +namespace HybridCLR.Editor.Settings +{ + public static class MenuProvider + { + + [MenuItem("HybridCLR/About", priority = 0)] + public static void OpenAbout() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/intro"); + + [MenuItem("HybridCLR/Installer...", priority = 60)] + private static void Open() + { + InstallerWindow window = EditorWindow.GetWindow("HybridCLR Installer", true); + window.minSize = new Vector2(800f, 500f); + } + + [MenuItem("HybridCLR/Settings...", priority = 61)] + public static void OpenSettings() => SettingsService.OpenProjectSettings("Project/HybridCLR Settings"); + + [MenuItem("HybridCLR/Documents/Quick Start")] + public static void OpenQuickStart() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/beginner/quickstart"); + + [MenuItem("HybridCLR/Documents/Performance")] + public static void OpenPerformance() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/basic/performance"); + + [MenuItem("HybridCLR/Documents/FAQ")] + public static void OpenFAQ() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/faq"); + + [MenuItem("HybridCLR/Documents/Common Errors")] + public static void OpenCommonErrors() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/commonerrors"); + + [MenuItem("HybridCLR/Documents/Bug Report")] + public static void OpenBugReport() => Application.OpenURL("https://hybridclr.doc.code-philosophy.com/docs/help/issue"); + } + +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs.meta new file mode 100644 index 0000000..a4474b2 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/MenuProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a475a5f281298b84da32373694704c68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/ScriptableSignleton.cs b/Packages/com.code-philosophy.hybridclr/Editor/Settings/ScriptableSignleton.cs new file mode 100644 index 0000000..903f802 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/ScriptableSignleton.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace HybridCLR.Editor.Settings +{ + public class ScriptableSingleton : ScriptableObject where T : ScriptableObject + { + private static T s_Instance; + public static T Instance + { + get + { + if (!s_Instance) + { + LoadOrCreate(); + } + return s_Instance; + } + } + public static T LoadOrCreate() + { + string filePath = GetFilePath(); + if (!string.IsNullOrEmpty(filePath)) + { + var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath); + s_Instance = arr.Length > 0 ? arr[0] as T : s_Instance??CreateInstance(); + } + else + { + Debug.LogError($"save location of {nameof(ScriptableSingleton)} is invalid"); + } + return s_Instance; + } + + public static void Save(bool saveAsText = true) + { + if (!s_Instance) + { + Debug.LogError("Cannot save ScriptableSingleton: no instance!"); + return; + } + + string filePath = GetFilePath(); + if (!string.IsNullOrEmpty(filePath)) + { + string directoryName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + UnityEngine.Object[] obj = new T[1] { s_Instance }; + InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, saveAsText); + } + } + protected static string GetFilePath() + { + return typeof(T).GetCustomAttributes(inherit: true) + .Where(v => v is FilePathAttribute) + .Cast() + .FirstOrDefault() + ?.filepath; + } + } + [AttributeUsage(AttributeTargets.Class)] + public class FilePathAttribute : Attribute + { + internal string filepath; + /// + /// 单例存放路径 + /// + /// 相对 Project 路径 + public FilePathAttribute(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("Invalid relative path (it is empty)"); + } + if (path[0] == '/') + { + path = path.Substring(1); + } + filepath = path; + } + } +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/ScriptableSignleton.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Settings/ScriptableSignleton.cs.meta new file mode 100644 index 0000000..fa1923b --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/ScriptableSignleton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 851c44a8da67a9742a7ea68815383f27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/SettingsPresetReceiver.cs b/Packages/com.code-philosophy.hybridclr/Editor/Settings/SettingsPresetReceiver.cs new file mode 100644 index 0000000..e60f3aa --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/SettingsPresetReceiver.cs @@ -0,0 +1,39 @@ +using UnityEditor; +using UnityEditor.Presets; +using UnityEngine; + +namespace HybridCLR.Editor.Settings +{ + public class SettingsPresetReceiver : PresetSelectorReceiver + { + private Object m_Target; + private Preset m_InitialValue; + private SettingsProvider m_Provider; + + internal void Init(Object target, SettingsProvider provider) + { + m_Target = target; + m_InitialValue = new Preset(target); + m_Provider = provider; + } + public override void OnSelectionChanged(Preset selection) + { + if (selection != null) + { + Undo.RecordObject(m_Target, "Apply Preset " + selection.name); + selection.ApplyTo(m_Target); + } + else + { + Undo.RecordObject(m_Target, "Cancel Preset"); + m_InitialValue.ApplyTo(m_Target); + } + m_Provider.Repaint(); + } + public override void OnSelectionClosed(Preset selection) + { + OnSelectionChanged(selection); + Object.DestroyImmediate(this); + } + } +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Settings/SettingsPresetReceiver.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Settings/SettingsPresetReceiver.cs.meta new file mode 100644 index 0000000..5047283 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Settings/SettingsPresetReceiver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8373e0cb30c4894db7cd4d0b77a7a48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs b/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs new file mode 100644 index 0000000..9a074ea --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using HybridCLR.Editor.Settings; + + +namespace HybridCLR.Editor +{ + public static class SettingsUtil + { + public static bool Enable + { + get => HybridCLRSettings.Instance.enable; + set + { + HybridCLRSettings.Instance.enable = value; + HybridCLRSettings.Save(); + } + } + + public static string PackageName { get; } = "com.code-philosophy.hybridclr"; + + public static string HybridCLRDataPathInPackage => $"Packages/{PackageName}/Data~"; + + public static string TemplatePathInPackage => $"{HybridCLRDataPathInPackage}/Templates"; + + public static string ProjectDir { get; } = Directory.GetParent(Application.dataPath).ToString(); + + public static string ScriptingAssembliesJsonFile { get; } = "ScriptingAssemblies.json"; + + public static string HotUpdateDllsRootOutputDir => HybridCLRSettings.Instance.hotUpdateDllCompileOutputRootDir; + + public static string AssembliesPostIl2CppStripDir => HybridCLRSettings.Instance.strippedAOTDllOutputRootDir; + + public static string HybridCLRDataDir => $"{ProjectDir}/HybridCLRData"; + + public static string LocalUnityDataDir => $"{HybridCLRDataDir}/LocalIl2CppData-{Application.platform}"; + + public static string LocalIl2CppDir => $"{LocalUnityDataDir}/il2cpp"; + + public static string GeneratedCppDir => $"{LocalIl2CppDir}/libil2cpp/hybridclr/generated"; + + public static string Il2CppBuildCacheDir { get; } = $"{ProjectDir}/Library/Il2cppBuildCache"; + + public static string GetHotUpdateDllsOutputDirByTarget(BuildTarget target) + { + return $"{HotUpdateDllsRootOutputDir}/{target}"; + } + + public static string GetAssembliesPostIl2CppStripDir(BuildTarget target) + { + return $"{AssembliesPostIl2CppStripDir}/{target}"; + } + + class AssemblyDefinitionData + { + public string name; + } + + /// + /// 热更新dll列表。不包含 preserveHotUpdateAssemblies。 + /// + public static List HotUpdateAssemblyNamesExcludePreserved + { + get + { + var gs = HybridCLRSettings.Instance; + var hotfixAssNames = (gs.hotUpdateAssemblyDefinitions ?? Array.Empty()).Select(ad => JsonUtility.FromJson(ad.text)); + + var hotfixAssembles = new List(); + foreach (var assName in hotfixAssNames) + { + hotfixAssembles.Add(assName.name); + } + hotfixAssembles.AddRange(gs.hotUpdateAssemblies ?? Array.Empty()); + return hotfixAssembles.ToList(); + } + } + + public static List HotUpdateAssemblyFilesExcludePreserved => HotUpdateAssemblyNamesExcludePreserved.Select(dll => dll + ".dll").ToList(); + + + public static List HotUpdateAssemblyNamesIncludePreserved + { + get + { + List allAsses = HotUpdateAssemblyNamesExcludePreserved; + string[] preserveAssemblyNames = HybridCLRSettings.Instance.preserveHotUpdateAssemblies; + if (preserveAssemblyNames != null && preserveAssemblyNames.Length > 0) + { + foreach (var assemblyName in preserveAssemblyNames) + { + if (allAsses.Contains(assemblyName)) + { + throw new Exception($"[HotUpdateAssemblyNamesIncludePreserved] assembly:'{assemblyName}' is duplicated"); + } + allAsses.Add(assemblyName); + } + } + + return allAsses; + } + } + + public static List HotUpdateAssemblyFilesIncludePreserved => HotUpdateAssemblyNamesIncludePreserved.Select(ass => ass + ".dll").ToList(); + + public static List AOTAssemblyNames => HybridCLRSettings.Instance.patchAOTAssemblies.ToList(); + + public static HybridCLRSettings HybridCLRSettings => HybridCLRSettings.Instance; + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs.meta new file mode 100644 index 0000000..323dcf6 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/SettingsUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 381c08faeafbc004f97504eeba87380d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Template.meta b/Packages/com.code-philosophy.hybridclr/Editor/Template.meta new file mode 100644 index 0000000..f2430d5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Template.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9af6345cc5ab1ae4a81262ab4b537911 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs b/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs new file mode 100644 index 0000000..797477b --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR.Editor.Template +{ + public class FileRegionReplace + { + private readonly string _tplCode; + + private readonly Dictionary _regionReplaceContents = new Dictionary(); + + public FileRegionReplace(string tplCode) + { + _tplCode = tplCode; + } + + public void Replace(string regionName, string regionContent) + { + _regionReplaceContents.Add(regionName, regionContent); + } + + public string GenFinalString() + { + string originContent = _tplCode; + + string resultContent = originContent; + + foreach (var c in _regionReplaceContents) + { + resultContent = ReplaceRegion(resultContent, c.Key, c.Value); + } + return resultContent; + } + + public void Commit(string outputFile) + { + string dir = Path.GetDirectoryName(outputFile); + Directory.CreateDirectory(dir); + string resultContent = GenFinalString(); + var utf8WithoutBOM = new System.Text.UTF8Encoding(false); + File.WriteAllText(outputFile, resultContent, utf8WithoutBOM); + } + + public static string ReplaceRegion(string resultText, string region, string replaceContent) + { + int startIndex = resultText.IndexOf("//!!!{{" + region); + if (startIndex == -1) + { + throw new Exception($"region:{region} start not find"); + } + int endIndex = resultText.IndexOf("//!!!}}" + region); + if (endIndex == -1) + { + throw new Exception($"region:{region} end not find"); + } + int replaceStart = resultText.IndexOf('\n', startIndex); + int replaceEnd = resultText.LastIndexOf('\n', endIndex); + if (replaceStart == -1 || replaceEnd == -1) + { + throw new Exception($"region:{region} not find"); + } + if (resultText.Substring(replaceStart, replaceEnd - replaceStart) == replaceContent) + { + return resultText; + } + resultText = resultText.Substring(0, replaceStart) + "\n" + replaceContent + "\n" + resultText.Substring(replaceEnd); + return resultText; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs.meta b/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs.meta new file mode 100644 index 0000000..14a642a --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Editor/Template/FileRegionReplace.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15d4563ad83546c42bc65c99be9bd54a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/LICENSE b/Packages/com.code-philosophy.hybridclr/LICENSE new file mode 100644 index 0000000..dcad5cf --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Code Philosophy Technology Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Packages/com.code-philosophy.hybridclr/LICENSE.meta b/Packages/com.code-philosophy.hybridclr/LICENSE.meta new file mode 100644 index 0000000..d5478d5 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f09e582706a5776448316f6c584e63a6 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Plugins.meta b/Packages/com.code-philosophy.hybridclr/Plugins.meta new file mode 100644 index 0000000..ccdd127 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a26873724919287449e2c9eec68ef1da +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Plugins/dnlib.dll b/Packages/com.code-philosophy.hybridclr/Plugins/dnlib.dll new file mode 100644 index 0000000..170ffde Binary files /dev/null and b/Packages/com.code-philosophy.hybridclr/Plugins/dnlib.dll differ diff --git a/Packages/com.code-philosophy.hybridclr/Plugins/dnlib.dll.meta b/Packages/com.code-philosophy.hybridclr/Plugins/dnlib.dll.meta new file mode 100644 index 0000000..bdb7a88 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Plugins/dnlib.dll.meta @@ -0,0 +1,87 @@ +fileFormatVersion: 2 +guid: b93c6604eb031674b80de14cd4458dc0 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/README.md b/Packages/com.code-philosophy.hybridclr/README.md new file mode 100644 index 0000000..9b30ebb --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/README.md @@ -0,0 +1,91 @@ + +- [README 中文](./README_zh.md) +- [README English](./README.md) + +# HybridCLR + +[![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) + +![logo](https://github.com/focus-creative-games/hybridclr/raw/main/docs/images/logo.jpg) + +
+
+ +HybridCLR is a **almost perfect** full-platform native c# hot update solution for Unity with complete features, zero cost, high performance, and low memory**. + +HybridCLR expands the ability of il2cpp, making it change from pure [AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime to 'AOT+Interpreter' hybrid runtime, and then natively supports dynamic loading of assembly , so that the games packaged based on il2cpp backend can be executed not only on the Android platform, but also on IOS, Consoles and other platforms that limit JIT efficiently in **AOT+interpreter** hybrid mode, completely supporting hot updates from the bottom layer. + +HybridCLR not only supports the traditional fully interpreted execution mode, but also pioneered [Differential Hybrid Execution](https://hybridclr.doc.code-philosophy.com/en/docs/business/differentialhybridexecution) technique. That is, you can add, delete, or modify the AOT dll at will, and intelligently make the changed or newly added classes and functions run in interpreter mode, but the unchanged classes and functions run in AOT mode, so that the running performance of the hot-updated game logic basically reaches the original AOT level. + +Welcome to embrace modern native C# hot update technology! ! ! + +## Documentation + +- [Official Documentation](https://hybridclr.doc.code-philosophy.com/en/docs/intro) +- [Quickstart](https://hybridclr.doc.code-philosophy.com/en/docs/beginner/quickstart) +- [Release Log](./RELEASELOG.md) + +## Features + +- Features complete. A nearly complete implementation of the [ECMA-335 specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/), with only a very small number of [unsupported features](https://hybridclr.doc.code-philosophy.com/en/docs/basic/notsupportedfeatures). +- Zero learning and use costs. HybridCLR enhances the pure AOT runtime into a complete runtime, making hot update code work seamlessly with AOT code. The script class and the AOT class are in the same runtime, and you can freely write codes such as inheritance, reflection, and multi-threading (volatile, ThreadStatic, Task, async). No need to write any special code, no code generation, almost unlimited. +- Efficient execution. Implemented an extremely efficient register interpreter, all indicators are significantly better than other hot update schemes. [Performance Test Report](https://hybridclr.doc.code-philosophy.com/en/docs/basic/performance) +- Memory efficient. The classes defined in the hot update script occupy the same memory space as ordinary c# classes, which is far superior to other hot update solutions. [Memory and GC](https://hybridclr.doc.code-philosophy.com/en/docs/basic/memory) +- Due to the perfect support for generics, libraries that are not compatible with il2cpp due to AOT generics issues can now run perfectly under il2cpp +- Support some features not supported by il2cpp, such as __makeref, __reftype, __refvalue directives +- [Differential Hybrid Execution](https://hybridclr.doc.code-philosophy.com/en/docs/business/differentialhybridexecution) + +## Working Principle + +HybridCLR is inspired by mono's [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) technology, and provides additional AOT runtimes such as unity's il2cpp The interpreter module is provided to transform them from pure AOT runtime to "AOT + Interpreter" hybrid operation mode. + +![icon](https://github.com/focus-creative-games/hybridclr/raw/main/docs/images/architecture.png) + +More specifically, HybridCLR does the following: + +- Implemented an efficient metadata (dll) parsing library +- Modified the metadata management module to realize the dynamic registration of metadata +- Implemented a compiler from IL instruction set to custom register instruction set +- Implemented an efficient register interpreter +- Provide a large number of instinct functions to improve the performance of the interpreter + +## The Difference From Other C# Hot-Update Solution + +HybridCLR is a native c# hot update solution. In layman's terms, il2cpp is equivalent to the aot module of mono, and HybridCLR is equivalent to the interpreter module of mono, and the combination of the two becomes a complete mono. HybridCLR makes il2cpp a full-featured runtime, natively (that is, through System.Reflection.Assembly.Load) supports dynamic loading of dlls, thereby supporting hot updates of the ios platform. + +Just because HybridCLR is implemented at the native runtime level, the types of the hot update part and the AOT part of the main project are completely equivalent and seamlessly unified. You can call, inherit, reflect, and multi-thread at will, without generating code or writing adapters. + +Other hot update solutions are independent vm, and the relationship with il2cpp is essentially equivalent to the relationship of embedding lua in mono. Therefore, the type system is not uniform. In order to allow the hot update type to inherit some AOT types, an adapter needs to be written, and the type in the interpreter cannot be recognized by the type system of the main project. Incomplete features, troublesome development, and low operating efficiency. + +## Supported Versions And Platforms + +- Support 2019.4.x, 2020.3.x, 2021.3.x, 2022.3.x full series of LTS versions. The `2023.2.0ax` version is also supported, but not released to the public. +- Supports all il2cpp supported platforms + +## Stability Status + +HybridCLR has been widely verified as a very efficient and stable solution for hot update of Unity. + +The official versions of **extremely stable** 1.x, 2.x, and 3.x are currently released, which are sufficient to meet the stability requirements of large and medium-sized commercial projects. +At present, at least thousands of commercial game projects have been integrated, and hundreds of them have been launched on both ends. The online projects include MMORPG, heavy card, heavy tower defense and other games. **Most top game companies** (such as Tencent and NetEase) are already using HybridCLR. + +You can read the [game projects in top game companies](https://hybridclr.doc.code-philosophy.com/en/docs/other/businesscase) those are using HybridCLR and has been launched. + +## Support And Contact + +- Official 1 group: 651188171 (full) +- Novice QQ group 1: 428404198 (full) +- Novice QQ group 2: **680274677 (recommended)** +- discord channel [https://discord.gg/BATfNfJnm2](https://discord.gg/BATfNfJnm2) +- Business cooperation email: business#code-philosophy.com +- [Commercial Support](https://hybridclr.doc.code-philosophy.com/en/docs/business/intro) + +## About The Author + +**walon** : Founder of **Code Philosophy (code philosophy)** + +Graduated from the Department of Physics of Tsinghua University, won the CMO gold medal in 2006, a member of the National Mathematical Olympiad Training Team, and was recommended to Tsinghua University for basic courses. Focus on game technology, good at developing architecture and basic technical facilities. + +## License + +HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license diff --git a/Packages/com.code-philosophy.hybridclr/README.md.meta b/Packages/com.code-philosophy.hybridclr/README.md.meta new file mode 100644 index 0000000..b674416 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b4b6051e2483d664facc72a5102dcffc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/README_zh.md b/Packages/com.code-philosophy.hybridclr/README_zh.md new file mode 100644 index 0000000..d5bb6b7 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/README_zh.md @@ -0,0 +1,91 @@ +- [README 中文](./README_zh.md) +- [README English](./README.md) + +# HybridCLR + +[![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) + +![logo](https://github.com/focus-creative-games/hybridclr/raw/main/docs/images/logo.jpg) + +
+
+ +HybridCLR是一个**特性完整、零成本、高性能、低内存**的**近乎完美**的Unity全平台原生c#热更方案。 + +HybridCLR扩充了il2cpp的代码,使它由纯[AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation) runtime变成‘AOT+Interpreter’ 混合runtime,进而原生支持动态加载assembly,使得基于il2cpp backend打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以**AOT+interpreter**混合模式执行,从底层彻底支持了热更新。 + +HybridCLR不仅支持传统的全解释执行模式,还开创性地实现了 [Differential Hybrid Execution(差分混合执行技术)](https://hybridclr.doc.code-philosophy.com/docs/business/differentialhybridexecution) 差分混合执行技术。即可以对AOT dll任意增删改,会智能地让变化或者新增的类和函数以interpreter模式运行,但未改动的类和函数以AOT方式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平。 + +欢迎拥抱现代原生C#热更新技术 !!! + +## 文档 + +- [官方文档](https://hybridclr.doc.code-philosophy.com/docs/intro) +- [快速上手](https://hybridclr.doc.code-philosophy.com/docs/beginner/quickstart) +- [发布日志](./RELEASELOG.md) + +## 特性 + +- 特性完整。 近乎完整实现了[ECMA-335规范](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/),只有极少量的[不支持的特性](https://hybridclr.doc.code-philosophy.com/docs/basic/notsupportedfeatures)。 +- 零学习和使用成本。 HybridCLR将纯AOT runtime增强为完整的runtime,使得热更新代码与AOT代码无缝工作。脚本类与AOT类在同一个运行时内,可以随意写继承、反射、多线程(volatile、ThreadStatic、Task、async)之类的代码。不需要额外写任何特殊代码、没有代码生成,几乎没有限制。 +- 执行高效。实现了一个极其高效的寄存器解释器,所有指标都大幅优于其他热更新方案。[性能测试报告](https://hybridclr.doc.code-philosophy.com/docs/basic/performance) +- 内存高效。 热更新脚本中定义的类跟普通c#类占用一样的内存空间,远优于其他热更新方案。[内存与GC](https://hybridclr.doc.code-philosophy.com/docs/basic/memory) +- 由于对泛型的完美支持,使得因为AOT泛型问题跟il2cpp不兼容的库现在能够完美地在il2cpp下运行 +- 支持一些il2cpp不支持的特性,如__makeref、 __reftype、__refvalue指令 +- [Differential Hybrid Execution(差分混合执行技术)](https://hybridclr.doc.code-philosophy.com/docs/business/differentialhybridexecution) + +## 工作原理 + +HybridCLR从mono的 [mixed mode execution](https://www.mono-project.com/news/2017/11/13/mono-interpreter/) 技术中得到启发,为unity的il2cpp之类的AOT runtime额外提供了interpreter模块,将它们由纯AOT运行时改造为"AOT + Interpreter"混合运行方式。 + +![icon](https://github.com/focus-creative-games/hybridclr/raw/main/docs/images/architecture.png) + +更具体地说,HybridCLR做了以下几点工作: + +- 实现了一个高效的元数据(dll)解析库 +- 改造了元数据管理模块,实现了元数据的动态注册 +- 实现了一个IL指令集到自定义的寄存器指令集的compiler +- 实现了一个高效的寄存器解释器 +- 额外提供大量的instinct函数,提升解释器性能 + +## 与其他流行的c#热更新方案的区别 + +HybridCLR是原生的c#热更新方案。通俗地说,il2cpp相当于mono的aot模块,HybridCLR相当于mono的interpreter模块,两者合一成为完整mono。HybridCLR使得il2cpp变成一个全功能的runtime,原生(即通过System.Reflection.Assembly.Load)支持动态加载dll,从而支持ios平台的热更新。 + +正因为HybridCLR是原生runtime级别实现,热更新部分的类型与主工程AOT部分类型是完全等价并且无缝统一的。可以随意调用、继承、反射、多线程,不需要生成代码或者写适配器。 + +其他热更新方案则是独立vm,与il2cpp的关系本质上相当于mono中嵌入lua的关系。因此类型系统不统一,为了让热更新类型能够继承AOT部分类型,需要写适配器,并且解释器中的类型不能为主工程的类型系统所识别。特性不完整、开发麻烦、运行效率低下。 + +## 支持的版本与平台 + +- 支持2019.4.x、2020.3.x、2021.3.x、2022.3.x全系列LTS版本。`2023.2.0ax`版本也已支持,但未对外发布。 +- 支持所有il2cpp支持的平台 + +## 稳定性状况 + +HybridCLR已经被广泛验证是非常高效、稳定的Unity热更新解决方案。 + +当前发布了**极其稳定**的1.x、2.x、3.x正式版本,足以满足大中型商业项目的稳定性要求。 +目前至少有上千个商业游戏项目完成接入,其中有几百款已经双端上线,上线的项目中包括MMORPG、重度卡牌、重度塔防之类的游戏。**绝大多数头部游戏公司**(如腾讯、网易)都已经在使用HybridCLR。 + +可查看我们已知的头部公司中使用HybridCLR并且已经上线的[项目列表](https://hybridclr.doc.code-philosophy.com/docs/other/businesscase)。 + + +## 支持与联系 + +- 官方1群:651188171(满) +- 新手1群:428404198(满) +- 新手2群:**680274677(推荐)** +- discord频道 https://discord.gg/BATfNfJnm2 +- 商业合作邮箱: business#code-philosophy.com +- [商业化支持](https://hybridclr.doc.code-philosophy.com/docs/business/intro) + +## 关于作者 + +**walon** :**Code Philosophy(代码哲学)** 创始人 + +毕业于清华大学物理系,2006年CMO金牌,奥数国家集训队成员,保送清华基科班。专注于游戏技术,擅长开发架构和基础技术设施。 + +## license + +HybridCLR is licensed under the [MIT](https://github.com/focus-creative-games/hybridclr/blob/main/LICENSE) license diff --git a/Packages/com.code-philosophy.hybridclr/README_zh.md.meta b/Packages/com.code-philosophy.hybridclr/README_zh.md.meta new file mode 100644 index 0000000..9da1eac --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/README_zh.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 52688f4469ff9454e8c3d8aaf045d9f4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/RELEASELOG.md b/Packages/com.code-philosophy.hybridclr/RELEASELOG.md new file mode 100644 index 0000000..61ffc5e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/RELEASELOG.md @@ -0,0 +1,436 @@ +# 发布日志 + +## 4.0.15 + +发布日期 2024年1月2日. + +### Runtime + +- [fix] 修复计算未完全实例化的泛型类时将VAR和MVAR类型参数大小计算成sizeof(void*),导致计算出无效且过大的instance,在执行LayoutFieldsLocked过程中调用UpdateInstanceSizeForGenericClass错误地使用泛型基类instance覆盖设置了实例类型的instance值的严重bug +- [change] 支持打印热更新栈,虽然顺序不太正确 +- [change] 使用HYBRIDCLR_MALLOC之类分配函数替换IL2CPP_MALLOC +- [refactor] 重构Config接口,统一通过GetRuntimeOption和SetRuntimeOption获取和设置选项 +- [opt] 删除NewValueTypeVar和NewValueTypeInterpVar指令不必要的对结构memset操作 + +### Editor + +- [fix] 修复Additional Compiler Arguments中输入 -nullable:enable 之后,Editor抛出InvalidCastException的bug。来自报告 https://github.com/focus-creative-games/hybridclr/issues/116 +- [fix] 修复某些情况下报错:BuildFailedException: Build path contains a project previously built without the "Create Visual Studio Solution" +- [opt] 优化桥接函数生成,将同构的struct映射到同一个结构,减少了30-35%的桥接函数数量 +- [change] StripAOTDllCommand导出时不再设置BuildScriptsOnly选项 +- [change] 调整Installer窗口的显示内容 +- [refactor] RuntimeApi中设置hybridclr参数的功能统一通过GetRuntimeOption和SetRuntimeOption函数 + +## 4.0.14 + +发布日期 2023.12.11. + +### Runtime + +- [fix] 修复优化 box; brtrue|brfalse序列时,当类型为class或nullable类型时,无条件转换为无条件branch语句的bug +- [fix] 修复 ClassFieldLayoutCalculator未释放 _classMap的每个key-value对中value对象,造成内存泄露的bug +- [fix] 修复计算 ExplicitLayout的struct的native_size的bug +- [fix] 修复当出现签名完全相同的虚函数与虚泛型函数时,计算override未考虑泛型签名,错误地返回了不匹配的函数,导致虚表错误的bug +- [fix][2021] 修复开启faster(smaller) build选项后某些情况下完全泛型共享AOT函数未使用补充元数据来设置函数指针,导致调用时出错的bug + +## 4.0.13 + +发布日期 2023.11.27. + +### Runtime + +- [fix] 修复ConvertInvokeArgs有可能传递了非对齐args,导致CopyStackObject在armv7这种要求内存对齐的平台发生崩溃的bug +- [fix] 修复通过StructLayout指定size时,计算ClassFieldLayout的严重bug +- [fix] 修复bgt之类指令未取双重取反进行判断,导致当浮点数与Nan比较时由于不满足对称性执行了错误的分支的bug +- [fix] 修复Class::FromGenericParameter错误地设置了thread_static_fields_size=-1,导致为其分配ThreadStatic内存的严重bug +- [opt] Il2CppGenericInst分配统一使用MetadataCache::GetGenericInst分配唯一池对象,优化内存分配 +- [opt] 由于Interpreter部分Il2CppGenericInst统一使用MetadataCache::GetGenericInst,比较 Il2CppGenericContext时直接比较 class_inst和method_inst指针 + +### Editor + +- [fix] 修复裁剪aot dll中出现netstandard时,生成桥接函数异常的bug +- [fix] 修复当出现非常规字段名时生成的桥接函数代码文件有编译错误的bug +- [change] 删除不必要的Datas~/Templates目录,直接以原始文件为模板 +- [refactor] 重构 AssemblyCache和 AssemblyReferenceDeepCollector,消除冗余代码 + +## 4.0.12 + +发布日期 2023.11.02. + +### Editor + +- [fix] 修复BashUtil.RemoveDir的bug导致Installer安装失败的bug + +## 4.0.11 + +发布日期 2023.11.02. + +### Runtime + +- [fix] 修复开启完全泛型共享后, 对于某些MethodInfo,由于methodPointer与virtualMethodPointer使用补充元数据后的解释器函数,而invoker_method仍然为支持完全泛型共享的调用形式,导致invoker_method与methodPointer及virtualMethodPointer不匹配的bug +- [fix] 修复Il2CppGenericContextCompare比较时仅仅对比inst指针的bug,造成热更新模块大量泛型函数重复 +- [fix] 修复完全泛型共享时未正确设置MethodInfo的bug + +### Editor + +- [new] 检查当前安装的libil2cpp版本是否与package版本匹配,避免升级package后未重新install的问题 +- [new] Generate支持 netstandard +- [fix] 修复 ReversePInvokeWrap生成不必要地解析referenced dll,导致如果有aot dll引用了netstandard会出现解析错误的bug +- [fix] 修复BashUtil.RemoveDir在偶然情况下出现删除目录失败的问题。新增多次重试 +- [fix] 修复桥接函数计算时未归结函数参数类型,导致出现多个同名签名的bug + + +## 4.0.10 + +发布日期 2023.10.12. + +### Runtime + +- [merge][il2cpp] 合并2022.3.10-2022.3.11f1的il2cpp改动,修复2022.3.11版本不兼容的问题 + +## 4.0.9 + +发布日期 2023.10.11. + +### Runtime + +- [merge][il2cpp][fix] 合并2021.3.29-2021.3.31f1的il2cpp改动,修复在2021.3.31版本的不兼容问题 +- [merge][il2cpp] 合并2022.3.7-2022.3.10f1的il2cpp改动 + +### Editor + +- [fix] 修复2022版本iOS平台AddLil2cppSourceCodeToXcodeproj2022OrNewer的编译错误 + +## 4.0.8 + +发布日期 2023.10.10. + +### Runtime + +- [fix] 修复计算值类型泛型桥接函数签名时,错误地将值类型泛型参数类型也换成签名,导致与Editor计算的签名不一致的bug +- [fix][refactor] RuntimeApi相关函数由PInvoke改为InternalCall,解决Android平台调用RuntimeApi时触发重新加载libil2cpp.a的问题 + +### Editor + +- [refactor] RuntimeApi相关函数由PInvoke改为InternalCall +- [refactor] 调整HybridCLR.Editor模块一些不规范的命名空间 + +## 4.0.7 + +发布日期 2023.10.09. + +### Runtime + +- [fix] 修复initobj调用了CopyN,但CopyN未考虑对象的内存对齐的情况,在32位这种的平台可能发生未对齐访问异常的bug +- [fix] 修复计算未完全实例化的泛型函数的桥接函数签名时崩溃的bug +- [fix] 修复Il2cpp代码生成选项为faster(smaller)时,2021和2022版本GenericMethod::CreateMethodLocked的bug +- [remove] 移除所有array相关指令中index为int64_t的指令,简化代码 +- [remove] 移除ldfld_xxx_ref系列指令 + +### Editor + +- [fix] 修复生成桥接函数时,如果热更新程序集未包含任何代码直接引用了某个aot程序集,则没有为该aot程序集生成桥接函数,导致出现NotSupportNative2Managed异常的bug +- [fix] 修复mac下面路径过长导致拷贝文件失败的bug +- [fix] 修复发布PS5目标时未处理ScriptingAssemblies.json的bug +- [change] 打包时清空裁减aot dll目录 + +## 4.0.6 + +发布日期 2023.09.26. + +### Runtime + +- [fix] 修复2021和2022版本开启完全泛型共享后的bug +- [fix] 修复加载PlaceHolder Assembly后未增加assemblyVersion导致Assembly::GetAssemblies()错误地获得了旧程序集列表的bug + +## 4.0.5 + +发布日期 2023.09.25. + +### Runtime + +- [fix] 修复Transform中未析构pendingFlows造成内存泄露的bug +- [fix] 修复多维数组SetMdArrElement未区分带ref与不带ref结构的bug +- [fix] 修复CpobjVarVAr_WriteBarrier_n_4未设置size的bug +- [fix] 修复计算interface成员函数slot时未考虑到static之类函数的bug +- [fix] 修复2022版本ExplicitLayout未设置layout.alignment,导致计算出size==0的bug +- [fix] 修复InterpreterInvoke在完全泛型共享时,class类型的methodPointer与virtualMethodPointer有可能不一致,导致失误对this指针+1的bug +- [fix] ldobj当T为byte之类size<4的类型时,未将数据展开为int的bug +- [fix] 修复CopySize未考虑到内存对齐的问题 +- [opt] 优化stelem当元素为size较大的struct时统一当作含ref结构的问题 +- [opt] TemporaryMemoryArena默认内存块大小由1M调整8K +- [opt] 将Image::Image中Assembly::GetAllAssemblies()换成Assembly::GetAllAssemblies(AssemblyVector&),避免创建assembly快照而造成不必要的内存泄露 + +### Editor + +- [fix] 修复StandaloneLinux平台DllImport的dllName和裁剪dll路径的错误 +- [change] 对于小版本不兼容的Unity版本,不再禁止安装,而是提示警告 +- [fix] 修复桥接函数计算中MetaUtil.ToShareTypeSig将Ptr和ByRef计算成IntPtr的bug,正确应该是UIntPtr + +## 4.0.4 + +发布日期 2023.09.11。 + +### Runtime + +- [new][platform] 彻底支持所有平台,包括UWP和PS5 +- [fix][严重] 修复计算interpreter部分enum类型的桥接函数签名的bug +- [fix] 修复在某些平台下有编译错误的问题 +- [fix] 修复转换STOBJ指令未正确处理增量式GC的bug +- [fix] [fix] 修复 StindVarVar_ref指令未正确设置WriteBarrier的bug +- [fix] 修复2020 GenericMethod::CreateMethodLocked调用vm::MetadataAllocGenericMethod()未持有s_GenericMethodMutex锁的线程安全问题 + +### Editor + +- [fix] 修复AddLil2cppSourceCodeToXcodeproj2021OrOlder在Unity 2020下偶然同时包含了不同目录的两个ThreadPool.cpp文件导致出现编译错误的问题 +- [fix] 修复不正确地从EditorUserBuildSettings.selectedBuildTargetGroup获得BuildGroupTarget的bug +- [fix] StripAOTDllCommand生成AOT dll时的BuildOption采用当前Player的设置,避免当打包开启development时,StripAOTDllCommand生成Release aot dll,而打包生成debug aot dll,产生补充元数据及桥接函数生成不匹配的严重错误 +- [change] 为了更好地支持全平台,调整了RuntimeApi.cs中dllName的实现,默认取 __Internal +- [change] 为了更好地支持全平台,自2021起裁剪AOT dll全都通过MonoHook复制 + +## 4.0.3 + +发布日期 2023.08.31。 + +### Editor + +- [fix] 修复桥接函数计算的bug + +## 4.0.2 + +发布日期 2023.08.29。 + +### Runtime + +- [fix][严重] 修复LdobjVarVar_ref指令的bug。此bug由增量式GC代码引入 +- [fix] 修复未处理ResolveField获得的Field为nullptr时情形导致崩溃的bug +- [fix] 修复未正确处理AOT及interpreter interface中显式实现父接口函数的bug + +## 4.0.1 + +发布日期 2023.08.28。 + +### Runtime + +- [fix] 修复2020版本开启增量式GC后出现编译错误的问题 + +## 4.0.0 + +发布日期 2023.08.28。 + +### Runtime + +- [new] 支持增量式GC +- [refactor] 重构桥接函数,彻底支持所有il2cpp支持的平台 +- [opt] 大幅优化Native2Managed方向的传参 + +### Editor + +- [change] 删除增量式GC选项检查 +- [refactor] 重构桥接函数生成 + +## 3.4.2 + +发布日期 2023.08.14。 + +### Runtime + +- [fix] 修复RawImage::LoadTables读取_4byteGUIDIndex的bug +- [version] 支持2022.3.7版本 +- [version] 支持2021.3.29版本 + +### Editor + +- [fix] 修复计算AOTGenericReference未考虑到泛型调用泛型的情况,导致少计算了泛型及补充元数据 + +## 3.4.1 + +发布日期 2023.07.31。 + +### Runtime + +- [fix] 修复 InitializeRuntimeMetadata的内存可见性问题 +- [fix] 修复CustomAttribute未正确处理父类NamedArg导致崩溃的bug +- [opt] 优化Transfrom Instinct指令的代码,从HashMap中快速查找而不是挨个匹配 + +### Editor + +- [fix] 修复FilterHotFixAssemblies只对比程序集名尾部,导致有AOT的尾部与某个热更新程序集匹配时意外被过滤的bug +- [change] 检查Settings中热更新程序集列表配置中程序集名不能为空 + +## 3.4.0 + +发布日期 2023.07.17。 + +### Runtime + +- [version] 支持2021.3.28和2022.3.4版本 +- [opt] 删除MachineState::InitEvalStack分配_StackBase后不必要的memset +- [fix] 修复Exception机制的bug +- [fix] 修复CustomAttribute不支持Type[]类型参数的bug +- [fix] 修复不支持new string(xxx)用法的问题 +- [refactor] 重构VTableSetup实现 +- [fix] 修复未计算子interface中显式实现父interface的函数的bug +- [opt] Lazy初始化CustomAttributeData,而不是加载时全部初始化,明显减少Assembly.Load时间 +- [fix] 修复2022 当new byte\[]{a,b,c...}方式初始化较长的byte[]数据时,返回错误数据的bug + +### Editor + +- [fix] 修复计算桥接函数未考虑到泛型类的成员函数中可能包含的Native2Managed调用 +- [change] link.xml及AOTGenericReferences.cs默认输出路径改为HybridCLRGenerate,避免与顶层HybridCLRData混淆 +- [fix] 修复Win下生成的Lump文件中include路径以\为目录分隔符导致同步到Mac后找不到路径的bug +- [refactor] 重构Installer + + +## 3.3.0 + +发布日期 2023.07.03。 + +### Runtime + +- [fix] 修复localloc分配的内存未释放的bug +- [change] MachineState改用RegisterRoot的方式注册执行栈,避免GC时扫描整个堆栈 +- [opt] 优化Managed2NativeCallByReflectionInvoke性能,提前计算好传参方式 +- [refactor] 重构ConvertInvokeArgs + +### Editor + +- [fix] 修复2020-2021编译libil2cpp.a未包含brotli相关代码文件导致出现编译错误的bug +- [fix] 修复从导出xcode项目包含绝对路径导致传送到其他机器上编译时找不到路径的bug +- [fix] 解决Generate LinkXml、 MethodBridge、AOTGenericReference、ReversePInvokeWrap 生成不稳定的问题 +- [fix] 修复使用不兼容版本打开Installer时出现异常的bug +- [change] 禁用hybridclr后打包ios时不再修改导出的xcode工程 + +## 3.2.1 + +### Runtime + +- [fix] 修复il2cpp TypeNameParser未将类型名中转义字符'\'去掉,导致找不到嵌套子类型的bug + +### Editor + +- [new] Installer界面新增显示package版本 +- [new] CompileDll新增MacOS、Linux、WebGL目标 +- [fix] 修复重构文档站后的帮助文档的链接错误 +- [change] 为Anaylizer加上using 限定,解决某些情况下与项目的类型同名而产生编译冲突的问题 + +## 3.2.0 + +### Runtime + +- [fix] 修复未在PlaceHolder中的Assembly加载时,如果由于不在Assembly列表,也没有任何解释器栈,导致Class::resolve_parse_info_internal查找不到类型的bug +- [fix] 修复读取CustomAttribute System.Type类型数据崩溃的bug + +### Editor + +- [new] 支持直接从源码打包iOS,不再需要单独编译libil2cpp.a +- [opt] 优化版本不兼容时错误提示,不再抛出异常,而是显示"与当前版本不兼容" + + +## 3.1.1 + +### Runtime + +- [fix] 修复2021及更高版本,InterpreterModule::Managed2NativeCallByReflectionInvoke调用值类型成员函数时,对this指针多余this=this-1操作。 +- [fix] 修复解析CustomAttribute中Enum[]类型字段的bug +- [fix] 修复2021及更高版本反射调用值类型 close Delegate的Invoke函数时未修复target指针的bug +- [new] 新增对增量式GC宏的检查,避免build.gradle中意外开启增量式GC引发的极其隐蔽的问题 + +### Editor + +- [fix] 修复 Win32、Android32、WebGL平台的编译错误 +- [fix] 修复计算桥接函数时未考虑到补充元数据泛型实例化会导致访问到一些非公开的函数的情况,导致少生成一些必要的桥接函数 +- [opt] 生成AOTGenericReferences时,补充元数据assembly列表由注释改成List列表,方便在代码中直接使用。 +- [change] CheckSettings中不再自动设置Api Compatible Level + +## 3.1.0 + +### Runtime + +- [rollback] 还原对Unity 2020.3.x支持 +- [fix] 修复 WebGL平台ABI的bug + +### Editor + +- [rollback] 还原对Unity 2020.3.x支持 + +## 3.0.3 + +### Runtime + +- [fix] 修复Enum::GetValues返回值不正确的bug + +## 3.0.2 + +### Runtime + +- [fix] 修复Memory Profiler中创建内存快照时崩溃的bug + +### Editor + +- [remove] 移除 `HybridCLR/CreateAOTDllSnapshot`菜单 + + +## 3.0.1 + +### Runtime + +- [new] 支持2022.3.0 + +## 3.0.0 + +### Runtime + +- [fix] 修复不支持访问CustomData字段及值的bug +- [remove] 移除对2019及2020版本支持 + +### Editor + +- 包名更改为com.code-philosophy.hybridclr +- 移除UnityFS插件 +- 移除Zip插件 +- HybridCLR菜单位置调整 + +## 2.4.2 + +### Runtime + +- [version] 支持 2020.3.48,最后一个2020LTS版本 +- [version] 支持 2021.3.25 + +## 2.4.1 + +### Runtime + +### Editor + +- [fix] 修复遗漏 RELEASELOG.md.meta 文件的问题 + +## 2.4.0 + +### Runtime + +### Editor + +- [new] CheckSettings中检查ScriptingBackend及ApiCompatibleLevel,切换为正确的值 +- [new] 新增 MsvcStdextWorkaround.cs 解决2020 vs下stdext编译错误的问题 +- [fix] 修复当struct只包含一个float或double字段时,在arm64上计算桥接函数签名错误的bug + +## 2.3.1 + +### Runtime + +### Editor + +- [fix] 修复本地复制libil2cpp却仍然从仓库下载安装的bug + +## 2.3.0 + +### Runtime + +### Editor + +- [new] Installer支持从本地目录复制改造后的libil2cpp +- [fix] 修复2019版本MonoBleedingEdge的子目录中包含了过长路径的文件导致Installer复制文件出错的问题 + + diff --git a/Packages/com.code-philosophy.hybridclr/RELEASELOG.md.meta b/Packages/com.code-philosophy.hybridclr/RELEASELOG.md.meta new file mode 100644 index 0000000..47abd21 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/RELEASELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8e53ce54bd8e88c4785c625555308dba +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Runtime.meta b/Packages/com.code-philosophy.hybridclr/Runtime.meta new file mode 100644 index 0000000..d5e99d3 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6d5e365e1b7d9742bee023ea54b31f2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs b/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs new file mode 100644 index 0000000..dd839ca --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs @@ -0,0 +1,10 @@ + +namespace HybridCLR +{ + public enum HomologousImageMode + { + Consistent, // AOT dll需要跟主工程精确一致,即为裁剪后的AO dll + SuperSet, // AOT dll不需要跟主工程精确一致,但必须包含裁剪后的AOT dll的所有元数据,即为裁剪后dll的超集。推荐使用原始aot dll + } +} + diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs.meta b/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs.meta new file mode 100644 index 0000000..dde78bf --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/HomologousImageMode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f0351553ad90e74aa586746b5965ded +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef b/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef new file mode 100644 index 0000000..98f5d3e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef @@ -0,0 +1,14 @@ +{ + "name": "HybridCLR.Runtime", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef.meta b/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef.meta new file mode 100644 index 0000000..c73a6d4 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/HybridCLR.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 13ba8ce62aa80c74598530029cb2d649 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs b/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs new file mode 100644 index 0000000..195122a --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs @@ -0,0 +1,15 @@ + +namespace HybridCLR +{ + public enum LoadImageErrorCode + { + OK = 0, + BAD_IMAGE, // dll 不合法 + NOT_IMPLEMENT, // 不支持的元数据特性 + AOT_ASSEMBLY_NOT_FIND, // 对应的AOT assembly未找到 + HOMOLOGOUS_ONLY_SUPPORT_AOT_ASSEMBLY, // 不能给解释器assembly补充元数据 + HOMOLOGOUS_ASSEMBLY_HAS_LOADED, // 已经补充过了,不能再次补充 + INVALID_HOMOLOGOUS_MODE, // 非法HomologousImageMode + }; +} + diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs.meta b/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs.meta new file mode 100644 index 0000000..4f77029 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/LoadImageErrorCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c7d5b71981fba643b4c21ed01bcb675 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs b/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs new file mode 100644 index 0000000..3308313 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class ReversePInvokeWrapperGenerationAttribute : Attribute + { + public int ReserveWrapperCount { get; } + + public ReversePInvokeWrapperGenerationAttribute(int reserveWrapperCount) + { + ReserveWrapperCount = reserveWrapperCount; + } + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs.meta b/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs.meta new file mode 100644 index 0000000..a46bd5a --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/ReversePInvokeWrapperGenerationAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f99dd22d9d81b2540b4663b3bcdf0a79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs new file mode 100644 index 0000000..8169063 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace HybridCLR +{ + public static class RuntimeApi + { + /// + /// 加载补充元数据assembly + /// + /// + /// + /// +#if UNITY_EDITOR + public static unsafe LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode) + { + return LoadImageErrorCode.OK; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern LoadImageErrorCode LoadMetadataForAOTAssembly(byte[] dllBytes, HomologousImageMode mode); +#endif + + /// + /// 获取解释器线程栈的最大StackObject个数(size*8 为最终占用的内存大小) + /// + /// + public static int GetInterpreterThreadObjectStackSize() + { + return GetRuntimeOption(RuntimeOptionId.InterpreterThreadObjectStackSize); + } + + /// + /// 设置解释器线程栈的最大StackObject个数(size*8 为最终占用的内存大小) + /// + /// + public static void SetInterpreterThreadObjectStackSize(int size) + { + SetRuntimeOption(RuntimeOptionId.InterpreterThreadObjectStackSize, size); + } + + + /// + /// 获取解释器线程函数帧数量(sizeof(InterpreterFrame)*size 为最终占用的内存大小) + /// + /// + public static int GetInterpreterThreadFrameStackSize() + { + return GetRuntimeOption(RuntimeOptionId.InterpreterThreadFrameStackSize); + } + + /// + /// 设置解释器线程函数帧数量(sizeof(InterpreterFrame)*size 为最终占用的内存大小) + /// + /// + public static void SetInterpreterThreadFrameStackSize(int size) + { + SetRuntimeOption(RuntimeOptionId.InterpreterThreadFrameStackSize, size); + } + + +#if UNITY_EDITOR + + private static readonly Dictionary s_runtimeOptions = new Dictionary(); + + public static void SetRuntimeOption(RuntimeOptionId optionId, int value) + { + s_runtimeOptions[optionId] = value; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void SetRuntimeOption(RuntimeOptionId optionId, int value); +#endif + +#if UNITY_EDITOR + public static int GetRuntimeOption(RuntimeOptionId optionId) + { + if (s_runtimeOptions.TryGetValue(optionId, out var value)) + { + return value; + } + return 0; + } +#else + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern int GetRuntimeOption(RuntimeOptionId optionId); +#endif + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs.meta b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs.meta new file mode 100644 index 0000000..6ed5cdc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeApi.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d58bdc22b6d6b54ab6791baf16a0a3d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs new file mode 100644 index 0000000..3dc24dc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs @@ -0,0 +1,9 @@ +namespace HybridCLR +{ + public enum RuntimeOptionId + { + InterpreterThreadObjectStackSize = 1, + InterpreterThreadFrameStackSize = 2, + ThreadExceptionFlowSize = 3, + } +} diff --git a/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs.meta b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs.meta new file mode 100644 index 0000000..9c7181e --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/Runtime/RuntimeOptionId.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 64be598b47302644a96013c74d945653 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.code-philosophy.hybridclr/package.json b/Packages/com.code-philosophy.hybridclr/package.json new file mode 100644 index 0000000..c484abc --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/package.json @@ -0,0 +1,22 @@ +{ + "name": "com.code-philosophy.hybridclr", + "version": "4.0.15", + "displayName": "HybridCLR", + "description": "HybridCLR is a fully featured, zero-cost, high-performance, low-memory solution for Unity's all-platform native c# hotupdate.", + "category": "Runtime", + "documentationUrl": "https://hybridclr.doc.code-philosophy.com/#/", + "changelogUrl": "https://hybridclr.doc.code-philosophy.com/#/other/changelog", + "licensesUrl": "https://github.com/focus-creative-games/hybridclr_unity/blob/main/LICENSE", + "keywords": [ + "HybridCLR", + "hotupdate", + "hotfix", + "focus-creative-games", + "code-philosophy" + ], + "author": { + "name": "Code Philosophy", + "email": "hybridclr@code-philosophy.com", + "url": "https://code-philosophy.com" + } +} \ No newline at end of file diff --git a/Packages/com.code-philosophy.hybridclr/package.json.meta b/Packages/com.code-philosophy.hybridclr/package.json.meta new file mode 100644 index 0000000..65ee986 --- /dev/null +++ b/Packages/com.code-philosophy.hybridclr/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8ea8eca3a387d9d4988a2fca1036f2e7 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json index d5c98c7..d1db711 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,5 +1,6 @@ { "dependencies": { + "com.tuyoogame.yooasset": "2.3.6", "com.unity.collab-proxy": "2.5.1", "com.unity.feature.development": "1.0.1", "com.unity.ide.rider": "3.0.31", @@ -42,5 +43,14 @@ "com.unity.modules.vr": "1.0.0", "com.unity.modules.wind": "1.0.0", "com.unity.modules.xr": "1.0.0" - } + }, + "scopedRegistries": [ + { + "name": "package.openupm.com", + "url": "https://package.openupm.com", + "scopes": [ + "com.tuyoogame.yooasset" + ] + } + ] } diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 9aab4c1..209fa47 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,5 +1,23 @@ { "dependencies": { + "com.code-philosophy.hybridclr": { + "version": "file:com.code-philosophy.hybridclr", + "depth": 0, + "source": "embedded", + "dependencies": {} + }, + "com.tuyoogame.yooasset": { + "version": "2.3.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.scriptablebuildpipeline": "1.21.25", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0" + }, + "url": "https://package.openupm.com" + }, "com.unity.collab-proxy": { "version": "2.5.1", "depth": 0, @@ -74,6 +92,13 @@ "dependencies": {}, "url": "https://packages.unity.cn" }, + "com.unity.scriptablebuildpipeline": { + "version": "1.21.25", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.cn" + }, "com.unity.settings-manager": { "version": "1.0.3", "depth": 2, diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index c62d4e4..3fd2e4c 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -26,11 +26,19 @@ MonoBehaviour: m_IsDefault: 1 m_Capabilities: 7 m_ConfigSource: 0 - m_UserSelectedRegistryName: + - m_Id: scoped:project:package.openupm.com + m_Name: package.openupm.com + m_Url: https://package.openupm.com + m_Scopes: + - com.tuyoogame.yooasset + m_IsDefault: 0 + m_Capabilities: 0 + m_ConfigSource: 4 + m_UserSelectedRegistryName: package.openupm.com m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: m_Modified: 0 m_ErrorMessage: - m_UserModificationsInstanceId: -830 - m_OriginalInstanceId: -832 + m_UserModificationsInstanceId: -826 + m_OriginalInstanceId: -828 m_LoadAssets: 0