| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- using System;
- using System.Reflection;
- using System.Text;
- using Mono.Cecil;
- using Mono.Cecil.Cil;
- using UnityEditor;
- using UnityEditorInternal;
- using System.Linq;
- using System.IO;
- using UnityEngine;
- using System.Collections.Generic;
- using UnityEditor.Compilation;
- using Assembly = System.Reflection.Assembly;
- using FieldAttributes = Mono.Cecil.FieldAttributes;
- using MethodAttributes = Mono.Cecil.MethodAttributes;
- namespace com.bbbirder.injection.editor
- {
- public static class UnityInjectUtils
- {
- const string BACKUP_EXT = ".backup";
- [InitializeOnLoadMethod]
- static void Install()
- {
- CompilationPipeline.compilationStarted += (o) =>
- {
- var settings = GetScriptAssemblySettings();
- if (settings is null)
- {
- Debug.LogError("cannot get BeeScriptCompilationState, Unity Version" + Application.unityVersion);
- return;
- }
- var BuildingForEditor = (bool)settings?.GetMemberValue("BuildingForEditor", true);
- var OutputDirectory = (string)settings?.GetMemberValue("OutputDirectory", true);
- if (!BuildingForEditor)
- {
- runtimeOutputDirectory = OutputDirectory;
- }
- };
- CompilationPipeline.assemblyCompilationFinished += (path, msg) =>
- {
- var fullPath = Path.GetFullPath(path);
- var hasError = msg.Any(m => m.type == CompilerMessageType.Error);
- if (hasError)
- {
- var list = InjectionSettings.instance.compilationErrorAssemblies;
- if (!list.Contains(fullPath)) list.Add(fullPath);
- }
- else
- {
- InjectionSettings.instance.compilationErrorAssemblies.RemoveAll(p => p.Equals(fullPath));
- }
- EditorUtility.SetDirty(InjectionSettings.instance);
- AssetDatabase.SaveAssetIfDirty(InjectionSettings.instance);
- };
- CompilationPipeline.compilationFinished += (o) =>
- {
- var settings = GetScriptAssemblySettings();
- if (settings is null)
- {
- Debug.LogError("cannot get BeeScriptCompilationState, Unity Version" + Application.unityVersion);
- return;
- }
- var BuildingForEditor = (bool)settings.GetMemberValue("BuildingForEditor", true);
- if (!BuildingForEditor && InjectionSettings.instance.ShouldAutoInjectBuild)
- {
- InjectRuntime();
- }
- };
- if (InjectionSettings.instance.ShouldAutoInjectEditor)
- {
- EditorApplication.delayCall += InjectEditorDelayed;
- }
- void InjectEditorDelayed()
- {
- // wait while editor is busy
- var IsEditorBusy = false;
- IsEditorBusy |= EditorApplication.isCompiling;
- IsEditorBusy |= EditorApplication.isUpdating;
- if (IsEditorBusy)
- {
- EditorApplication.delayCall -= InjectEditorDelayed;
- EditorApplication.delayCall += InjectEditorDelayed;
- return;
- }
- var injectionSources = FixHelper.GetAllInjectionSources();
- var outdatedSources = new List<string>();
- // DebugHelper.Log($"injectionSources :\n {string.Join("\n",injectionSources)}");
- InjectionSettings.instance.GetOutdatedSources(injectionSources, outdatedSources);
- if (outdatedSources.Count == 0)
- {
- return;
- }
- // filter out assemblies who has compilation error
- var injectingSources = outdatedSources.Distinct().Where(p =>
- {
- var fullPath = Path.GetFullPath(p);
- var hasError = InjectionSettings.instance.compilationErrorAssemblies
- .Contains(fullPath)
- ;
- if (hasError)
- {
- Debug.LogWarning($"ignore target {p} due to compilation error");
- }
- return !hasError;
- }).ToList();
- var assemblies = GetAssemblies(injectingSources.ToHashSet());
- if (assemblies.Length > 0)
- {
- InjectEditor(assemblies);
- }
- InjectionSettings.instance.SetInjectionSources(injectionSources);
- }
- static Assembly[] GetAssemblies(HashSet<string> assemblyPathes)
- {
- var hashset = assemblyPathes
- .Select(p => p.Replace('\\', '/'))
- .ToHashSet();
- return AppDomain.CurrentDomain.GetAssemblies()
- .Where(a => hashset.Contains(a.GetAssemblyPath()))
- .ToArray();
- }
- }
- public static void InjectEditor(Assembly[] assemblies)
- {
- var allInjections = FixHelper.allInjections;
- var freshInjections = FixHelper.GetAllInjections(assemblies);
- var freshAssemblies = freshInjections
- .Where(inj => inj.InjectedMethod != null)
- .Select(inj => inj.InjectedMethod.DeclaringType.Assembly)
- .Distinct().
- ToHashSet();
- var outcomeInjections = allInjections
- .Where(inj => inj.InjectedMethod != null)
- .Where(inj => freshAssemblies.Contains(inj.InjectedMethod.DeclaringType.Assembly))
- .ToArray();
- DebugHelper.Log($"auto inject {allInjections.Length} injections, {outcomeInjections.Length} to inject");
- bool isWritten = false;
- if (outcomeInjections.Length > 0)
- {
- isWritten = InjectTargetMode(outcomeInjections, true, activeBuildTarget);
- }
- var group = freshInjections
- .Where(inj => inj.DryInjectAssmemble != null)
- .GroupBy(inj => inj.DryInjectAssmemble.Assembly);
- foreach (var g in group)
- {
- isWritten |= VisitAssembly(g.Key.GetAssemblyPath(), g.ToArray(), true, activeBuildTarget);
- }
- if (isWritten)
- {
- DebugHelper.Log($"reload scripts {EditorApplication.isCompiling}");
- #if UNITY_2019_3_OR_NEWER
- EditorUtility.RequestScriptReload();
- #else
- UnityEditorInternal.InternalEditorUtility.RequestScriptReload();
- #endif
- }
- }
- public static void InjectRuntime()
- {
- InjectTargetMode(FixHelper.allInjections, false, activeBuildTarget);
- }
- static bool InjectTargetMode(InjectionInfo[] injections, bool isEditor, BuildTarget buildTarget)
- {
- var group = injections
- .Where(inj => inj.InjectedMethod != null)
- .GroupBy(inj => inj.InjectedMethod.DeclaringType.Assembly);
- var isWritten = false;
- foreach (var g in group)
- {
- var assemblyPath = GetResolvedAssemblyPath(g.Key.GetAssemblyPath(), isEditor, buildTarget);
- isWritten |= VisitAssembly(assemblyPath, g.ToArray(), isEditor, buildTarget);
- }
- return isWritten;
- }
- static string GetResolvedAssemblyPath(string assemblyPath, bool isEditor, BuildTarget buildTarget)
- {
- var fileName = Path.GetFileName(assemblyPath);
- if (!isEditor)
- {
- var preloads = GetPreloadAssemblies(isEditor, buildTarget);
- var matchedAssembly = preloads.FirstOrDefault(path => Path.GetFileName(path) == fileName);
- if (matchedAssembly is not null)
- {
- return matchedAssembly;
- }
- var playerAssemblyPath = Path.Join(runtimeOutputDirectory, fileName);
- if (File.Exists(playerAssemblyPath))
- {
- return playerAssemblyPath;
- }
- }
- return assemblyPath;
- }
- static string GetEditorAssemblyPath(string assemblyPath)
- => assemblyPath;
- static bool VisitAssembly(string assemblyPath, InjectionInfo[] injections, bool isEditor, BuildTarget buildTarget)
- {
- var backPath = assemblyPath + BACKUP_EXT;
- var IsEngineAssembly = Path.GetFullPath(assemblyPath)
- .StartsWith(Path.GetFullPath(EditorApplication.applicationContentsPath));
- // engine dlls should be backed up
- if (IsEngineAssembly && !File.Exists(backPath))
- {
- if (!IsFileAvaliable(assemblyPath))
- {
- LogError($"cannot access file: {assemblyPath}, make sure you has access rights to target");
- return false;
- }
- File.Copy(assemblyPath, backPath, true);
- }
- else
- {
- // File.Copy(backPath, assemblyPath, true);
- }
- // var assemblySearchFolders = GetAssemblySearchFolders(isEditor, buildTarget);
- // var replaceAssemblyPath = miReplace.DeclaringType.Assembly.Location;
- try
- {
- var inputPath = IsEngineAssembly ? backPath : assemblyPath;
- var isWritten = InjectHelper.InjectAssembly(injections, inputPath, assemblyPath, isEditor, buildTarget);
- if (isWritten)
- DebugHelper.Log($"Inject success: {assemblyPath}");
- return isWritten;
- }
- catch (Exception e)
- {
- Debug.LogException(e);
- if (IsEngineAssembly)
- {
- File.Copy(backPath, assemblyPath, true);
- }
- throw e;
- }
- }
- static bool IsFileAvaliable(string path)
- {
- try
- {
- var fs = File.Open(path, FileMode.Open);
- fs?.Close();
- fs?.Dispose();
- }
- catch
- {
- return false;
- }
- return true;
- }
- //TODO : Restore
- //TODO : Partial restore on inject
- // public static void RestoreEditor() {
- // // var backPath = assemblyPath+".backup";
- // // var existsBacked = File.Exists(backPath);
- // RestoreTargetMode(true);
- // RestoreTargetMode(false);
- // void RestoreTargetMode(bool isEditor){
- // var folders = GetAssemblySearchFolders(isEditor,activeBuildTarget);
- // foreach(var folder in folders){
- // var backupFiles = Directory.GetFiles(folder,"*"+BACKUP_EXT);
- // }
- // }
- // }
- internal static string[] GetAssemblySearchFolders(bool isEditor, BuildTarget buildTarget)
- {
- return GetPreloadAssemblies(isEditor, buildTarget)
- .Select(Path.GetDirectoryName)
- .Distinct()
- .ToArray();
- }
- internal static string[] GetPreloadAssemblies(bool isEditor, BuildTarget buildTarget)
- {
- var miGetUnityAssemblies = typeof(InternalEditorUtility).GetMethod("GetUnityAssembliesInternal", bindingFlags);
- var assemblies = (Array)miGetUnityAssemblies.Invoke(null, new object[] { isEditor, buildTarget, });
- var fiPath = miGetUnityAssemblies.ReturnType.GetElementType().GetField("Path", bindingFlags);
- return assemblies.OfType<object>()
- .Select(a => fiPath.GetValue(a))
- .OfType<string>()
- .ToArray();
- }
- static object GetScriptAssemblySettings()
- {
- var t = t_EditorCompilationInterface ??= GetType("UnityEditor.CoreModule", "EditorCompilationInterface");
- var editorCompilation = t.GetProperty("Instance", bindingFlags).GetValue(null);
- if (editorCompilation is null)
- throw new("cannot get editorCompilation,Unity:" + Application.unityVersion);
- var state = editorCompilation.GetMemberValue("activeBeeBuild")
- ?? editorCompilation.GetMemberValue("_currentBeeScriptCompilationState");
- if (state is null)
- throw new("cannot get compile state from editorCompilation,Unity:" + Application.unityVersion);
- return state.GetMemberValue("Settings", true);
- }
- static object GetMemberValue(this object obj, string name, bool IgnoreCase = false)
- {
- var flags = bindingFlags;
- if (IgnoreCase) flags |= BindingFlags.IgnoreCase;
- var memberInfo = obj.GetType().GetMember(name, flags).FirstOrDefault();
- if (memberInfo is null)
- return null;
- // {
- // var fields = obj.GetType().GetFields(flags).Select(f=>"field:"+f.Name);
- // var props = obj.GetType().GetProperties(flags).Select(f=>"prop:"+f.Name);
- // DebugHelper.Log(string.Join("\n",fields)+"\n"+string.Join("\n",props));
- // throw new($"cannot find member {name} in {obj}");
- // }
- if (memberInfo is FieldInfo fi)
- return fi.GetValue(obj);
- if (memberInfo is PropertyInfo pi)
- return pi.GetValue(obj);
- if (memberInfo is MethodInfo mi)
- return mi.Invoke(obj, null);
- return null;
- }
- static Type GetType(string moduleName, string typeName)
- {
- return System.AppDomain.CurrentDomain.GetAssemblies()
- .Where(a => a.FullName.StartsWith(moduleName + ","))
- .SelectMany(a => a.GetTypes())
- .Where(t => t.Name.Equals(typeName))
- .Single();
- }
- static Type t_EditorCompilationInterface;
- static string runtimeOutputDirectory;
- // static BuildTargetGroup activeBuildTargetGroup => EditorUserBuildSettings.selectedBuildTargetGroup;
- static BuildTarget activeBuildTarget => EditorUserBuildSettings.activeBuildTarget;
- static BindingFlags bindingFlags = 0
- | BindingFlags.Static
- | BindingFlags.Instance
- | BindingFlags.Public
- | BindingFlags.NonPublic
- ;
- static void LogError(string message)
- {
- Debug.LogError(message);
- }
- }
- }
|