// /*=============================================================================== // Copyright (C) 2020 PhantomsXR Ltd. All Rights Reserved. // // This file is part of the AR-MOD SDK. // // The AR-MOD SDK cannot be copied, distributed, or made available to // third-parties for commercial purposes without written permission of PhantomsXR Ltd. // // Contact info@phantomsxr.com for licensing requests. // ===============================================================================*/ using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Phantom.XRMOD.XRMODPackageTools.Runtime; using Phantom.XRMOD.UnityFusion.Runtime; using UnityFusion.CLR.Method; using UnityFusion.CLR.TypeSystem; using UnityFusion.Runtime.Intepreter; using UnityEngine; using AppDomain = UnityFusion.Runtime.Enviorment.AppDomain; using Phantom.XRMOD.ActionNotification.Runtime; using Phantom.XRMOD.UnityFusion.Runtime.CodeHook; using UnityFusion.CLRBinding.Adapter; using UnityEngine.Assertions; using Phantom.XRMOD.Core.Runtime; using Object = UnityEngine.Object; namespace Phantom.XRMOD.UnityFusion.Runtime { // ReSharper disable once InconsistentNaming public class CodesHook { public bool debug { get; set; } private static bool _INITIALIZED; private static AppDomain _APPDOMAIN; private MemoryStream dllStream; private MemoryStream symbolStream; // For multi-XR Experiences private List updateMethods; private IMethod onReleaseMethod; private IMethod onEventMethod; // Parameters private readonly object[] onEventData = new object[1]; // private IMethod onMultiplayerMethod; private ILTypeInstance ilTypeInstance; private string projectName; public static AppDomain GetAppDomain => _APPDOMAIN; // ??= new AppDomain(); public static Dictionary AssetReferences { get; private set; } = new(); public void InitializeHook(string _projectName, string _domainName, string _mainEntryPoint, int _jitFlag) { _INITIALIZED = _APPDOMAIN != null; _APPDOMAIN ??= new AppDomain(_jitFlag); projectName = _projectName; LoadExpandDll(_projectName, _domainName, _mainEntryPoint, _jitFlag); IocContainer.GetIoc.Register(new UserInstantiatedObjects()); } private async void LoadExpandDll(string _projectName, string _domainName, string _mainEntryPoint, int _jitFlag) { try { if (!Application.isEditor) { if (debug) { var tmp_DllBytes = await BasePackageLoaderUtility.LoadAssetFromPackage(_projectName, $"{_projectName.ToLower()}.runtime.dll"); var tmp_PdbBytes = await BasePackageLoaderUtility.LoadAssetFromPackage(_projectName, $"{_projectName.ToLower()}.runtime.pdb"); dllStream = new MemoryStream(tmp_DllBytes.bytes); symbolStream = new MemoryStream(tmp_PdbBytes.bytes); _APPDOMAIN.LoadAssembly(dllStream, symbolStream, new global::UnityFusion.Mono.Cecil.Pdb.PdbReaderProvider()); _APPDOMAIN.DebugService.StartDebugService(56000); } else { var tmp_DllBytes = await BasePackageLoaderUtility.LoadAssetFromPackage(_projectName, $"{_projectName.ToLower()}.runtime.dll"); dllStream = new MemoryStream(tmp_DllBytes.bytes); _APPDOMAIN.LoadAssembly(dllStream, null, new global::UnityFusion.Mono.Cecil.Pdb.PdbReaderProvider()); } } else { var tmp_DllBytes = await BasePackageLoaderUtility.LoadAssetFromPackage(_projectName, $"{_projectName.ToLower()}.runtime.dll"); var tmp_PdbBytes = await BasePackageLoaderUtility.LoadAssetFromPackage(_projectName, $"{_projectName.ToLower()}.runtime.pdb"); dllStream = new MemoryStream(tmp_DllBytes.bytes); symbolStream = new MemoryStream(tmp_PdbBytes.bytes); _APPDOMAIN.LoadAssembly(dllStream, symbolStream, new global::UnityFusion.Mono.Cecil.Pdb.PdbReaderProvider()); _APPDOMAIN.DebugService.StartDebugService(56000); } if (!_INITIALIZED) { InitializeUnityFusion(); // Send register clr command to every modules(Extra CLR). ActionNotificationCenter.DefaultCenter.PostNotification( nameof(ActionParameterDataType.RegisterExtraCLR), new UnityFusionArgs { AppDomain = GetAppDomain }); _INITIALIZED = true; } // Fix null exception after second load. // Since the release of AR Experience, the notification list will be cleaned up. // So we need to warm it. ActionNotificationCenter.DefaultCenter.AddObserver(InstantiatingNotificationAction, nameof(ActionParameterDataType.Instantiate)); try { var tmp_ProjectName = _domainName.Split(".")[0]; var tmp_Db = await LoadRuntimeReferenceDatabase(tmp_ProjectName); if (tmp_Db) AssetReferences.TryAdd(tmp_ProjectName, tmp_Db); } catch (Exception tmp_Exception) { // ignored } StartHookingCode($"{_domainName}.{_mainEntryPoint}"); } catch (Exception tmp_Exception) { Debug.LogError($"{tmp_Exception.Message}\n{tmp_Exception.StackTrace}"); } } private void InitializeUnityFusion() { #if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE) _APPDOMAIN.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId; #endif Singleton.GetInstance.Register(_APPDOMAIN); Singleton.GetInstance.Register(_APPDOMAIN); Singleton.GetInstance.Register(_APPDOMAIN); Singleton.GetInstance.Register(_APPDOMAIN); Singleton.GetInstance.Register(_APPDOMAIN); GetAppDomain.InitializeBindings(); ActionNotificationCenter.DefaultCenter.PostNotification(nameof(ActionParameterDataType.RegisterBuiltInCLR), new UnityFusionArgs() { AppDomain = _APPDOMAIN }); } private void StartHookingCode(string _runtimeEntryPoint) { var tmp_MethodDirectory = _APPDOMAIN.LoadedTypes[_runtimeEntryPoint]; ilTypeInstance = ((ILType) tmp_MethodDirectory).Instantiate(); var tmp_Constructor = ilTypeInstance.Type.GetConstructor(new List()); if (tmp_Constructor != null) { _APPDOMAIN.Invoke(tmp_Constructor, ilTypeInstance, null); } try { _APPDOMAIN.Invoke(_runtimeEntryPoint, MethodKey.ONLOAD, ilTypeInstance, null); if (tmp_MethodDirectory == null) return; onEventMethod = tmp_MethodDirectory.GetMethod(MethodKey.ONEVENT, 1); onReleaseMethod = tmp_MethodDirectory.GetMethod(MethodKey.RELEASEMEMORY, 1); updateMethods ??= new List(); updateMethods.Add(tmp_MethodDirectory.GetMethod(MethodKey.ONUPDATE, 0)); } catch (Exception tmp_Exception) { throw tmp_Exception; } } #region Unity Life cycle methods //////////////////////// //Call back function//// //////////////////////// public void OnUpdate() { if (!_INITIALIZED || updateMethods == null || dllStream == null) return; foreach (var tmp_UpdateMethod in updateMethods) { _APPDOMAIN.Invoke(tmp_UpdateMethod, ilTypeInstance); } } /// /// Only work XR-MOD not work in XR-Widgets /// /// public void OnEvent(BaseNotificationData _data) { if (!_INITIALIZED || onEventMethod == null || dllStream == null) return; onEventData[0] = _data; _APPDOMAIN.Invoke(onEventMethod, ilTypeInstance, onEventData); } /// /// Only work XR-MOD not work in XR-Widgets /// /// public void ReleaseMemory(string _projectName) { if (!_INITIALIZED || dllStream == null || onReleaseMethod == null) return; _APPDOMAIN.Invoke(onReleaseMethod, ilTypeInstance, _projectName ?? projectName); } public async void Dispose(string _projectName = null) { try { ReleaseMemory(_projectName); //Release MonoBehaviour and XRMODBehaviour List tmp_AllMonoAdaptors = new List(); tmp_AllMonoAdaptors.AddRange(Object.FindObjectsOfType()); foreach (var tmp_MonoBehaviour in tmp_AllMonoAdaptors) { if (tmp_MonoBehaviour == null) continue; tmp_MonoBehaviour.enabled = false; Object.DestroyImmediate(tmp_MonoBehaviour, true); } IocContainer.GetIoc.UnRegister(); ActionNotificationCenter.DefaultCenter.RemoveObserver(nameof(ActionParameterDataType.OnEvent)); updateMethods = null; onEventMethod = null; ilTypeInstance = null; GetAppDomain.Dispose(); await Task.Delay(500); dllStream?.Dispose(); symbolStream?.Dispose(); dllStream = symbolStream = null; AssetReferences.Clear(); #if UNITY_EDITOR _APPDOMAIN = null; #endif } catch (Exception tmp_Exception) { throw new Exception($"CodesHook dispose error:{tmp_Exception.Message}"); } } #endregion private object InstantiatingNotificationAction(BaseNotificationData _arg) { if (_arg is not InstantiateArgs tmp_Data) return null; if (GetAppDomain == null) { Assert.IsNotNull(tmp_Data.Prefab); return tmp_Data.Parent ? Object.Instantiate(tmp_Data.Prefab, tmp_Data.Parent) : Object.Instantiate(tmp_Data.Prefab); } Utility.CheckoutIlTypeInstance(tmp_Data.Prefab, out var tmp_Go, out var tmp_Type); var tmp_ResultOfThisMethod = tmp_Data.Parent ? Object.Instantiate(tmp_Data.Prefab, tmp_Data.Parent) : Object.Instantiate(tmp_Data.Prefab); var tmp_Res = Utility.DoBinding(tmp_Go, tmp_ResultOfThisMethod, GetAppDomain, tmp_Type); if (tmp_Type == null && tmp_Res is GameObject tmp_GameObject && tmp_GameObject.GetType() != tmp_Data.Prefab.GetType()) { return tmp_GameObject.GetComponent(tmp_Data.Prefab.GetType()); } return tmp_Res; } private async Task LoadRuntimeReferenceDatabase(string _projectName) { var tmp_RuntimeAssetDb = await BasePackageLoaderUtility.LoadAssetFromPackage(_projectName, nameof(RuntimeAssetReferenceDatabase)); await tmp_RuntimeAssetDb.Initialize(_projectName); return tmp_RuntimeAssetDb; } } }