Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

针对Unity2022.3版本Editor下可以注入,真机注入失败(assembly may be not injected yet)的解决方法 #417

Open
Dante0927 opened this issue Mar 18, 2024 · 7 comments

Comments

@Dante0927
Copy link

  1. 把IFixEditor类里的AutoInjectAssemblys删掉不用;
  2. 创建一个新类用于Build Player的时候回调,继承IPostBuildPlayerScriptDLLs,实现OnPostBuildPlayerScriptDLLs方法;
  3. 将Inject的目录定位到:ProjectFolder/Temp/StagingArea/Data/Managed,对该目录下的程序集进行Inject;
  4. 通过上述步骤成功解决打包真机注入失败的问题;
    具体代码如下:
/*
 * Tencent is pleased to support the open source community by making InjectFix available.
 * Copyright (C) 2019 THL A29 Limited, a Tencent company.  All rights reserved.
 * InjectFix is licensed under the MIT License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms. 
 * This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package.
 */

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System;
using System.Linq;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Text;
using System.Reflection;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using Debug = UnityEngine.Debug;
#if UNITY_2018_3_OR_NEWER
using UnityEditor.Build.Player;
#endif

namespace IFix.Editor
{
    /// <summary>
    /// 打包时,脚本编译后的回调,
    /// 该回调在DLL编译之后,IL2CPP之前,
    /// 并且实现该接口之后,IL2CPP会将该目录下的C#转义为CPP,而非Library/Bee/PlayerScriptAssemblies
    /// </summary>
    public class AutoInject_OnBuild : IPostBuildPlayerScriptDLLs
    {
        public int callbackOrder { get { return 0; } }
        
        public void OnPostBuildPlayerScriptDLLs(BuildReport report)
        {
            //脚本临时存放目录,可以对该目录下的文件进行操作
            var path = Directory.GetCurrentDirectory() + "/Temp/StagingArea/Data/Managed";

            IFixEditor.InjectAssemblysOnBuild(path);

            Debug.Log("恭喜,打包自动Inject成功!!");
        }
    }


    //版本选择窗口
    public class VersionSelector : EditorWindow
    {
        public string buttonText = "Patch";
        public string[] options = new string[] { };
        public int index = 0;
        public Action<int> callback = null;

        public static void Show(string[] options, Action<int> callback, string buttonText = "Patch")
        {
            VersionSelector window = GetWindow<VersionSelector>();
            window.options = options;
            window.callback = callback;
            window.buttonText = buttonText;
            window.Show();
        }

        void OnGUI()
        {
            index = EditorGUILayout.Popup("Please select a version: ", index, options);
            if (GUILayout.Button(buttonText))
                doPatch();
        }

        void doPatch()
        {
            if (callback != null)
            {
                callback(index);
            }
            Close();
        }
    }

    public class IFixEditor
    {
        //备份目录
        const string BACKUP_PATH = "./IFixDllBackup";
        //备份文件的时间戳生成格式
        const string TIMESTAMP_FORMAT = "yyyyMMddHHmmss";

        //注入的目标文件夹
        private static string targetAssembliesFolder = "";

        //system("mono ifix.exe [args]")
        public static void CallIFix(List<string> args)
        {
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
            var mono_path = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName),
                "../MonoBleedingEdge/bin/mono");
            if(!File.Exists(mono_path))
            {
                mono_path = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName),
                    "../../MonoBleedingEdge/bin/mono");
            }
#elif UNITY_EDITOR_WIN
            var mono_path = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName),
                "Data/MonoBleedingEdge/bin/mono.exe");
#endif
            if (!File.Exists(mono_path))
            {
                UnityEngine.Debug.LogError("can not find mono!");
            }
            var inject_tool_path = "./IFixToolKit/IFix.exe";
            //"--runtime = v4.0.30319"
            if (!File.Exists(inject_tool_path))
            {
                UnityEngine.Debug.LogError("please install the ToolKit");
                return;
            }

            Process hotfix_injection = new Process();
            hotfix_injection.StartInfo.FileName = mono_path;
#if UNITY_5_6_OR_NEWER
            hotfix_injection.StartInfo.Arguments = "--debug --runtime=v4.0.30319 \"" + inject_tool_path + "\" \""
#else
            hotfix_injection.StartInfo.Arguments = "--debug \"" + inject_tool_path + "\" \""
#endif
                + string.Join("\" \"", args.ToArray()) + "\"";
            hotfix_injection.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            hotfix_injection.StartInfo.RedirectStandardOutput = true;
            hotfix_injection.StartInfo.UseShellExecute = false;
            hotfix_injection.StartInfo.CreateNoWindow = true;
            hotfix_injection.Start();

            //UnityEngine.Debug.Log(hotfix_injection.StartInfo.FileName);
            //UnityEngine.Debug.Log(hotfix_injection.StartInfo.Arguments);

            StringBuilder exceptionInfo = null;
            while (!hotfix_injection.StandardOutput.EndOfStream)
            {
                string line = hotfix_injection.StandardOutput.ReadLine();
                if (exceptionInfo != null)
                {
                    exceptionInfo.AppendLine(line);
                }
                else
                {
                    if (line.StartsWith("Warning:"))
                    {
                        UnityEngine.Debug.LogWarning(line);
                    }
                    else if (line.StartsWith("Error:"))
                    {
                        UnityEngine.Debug.LogError(line);
                    }
                    else if (line.StartsWith("Unhandled Exception:"))
                    {
                        exceptionInfo = new StringBuilder(line);
                    }
                    else
                    {
                        UnityEngine.Debug.Log(line);
                    }
                }
            }
            hotfix_injection.WaitForExit();
            if (exceptionInfo != null)
            {
                UnityEngine.Debug.LogError(exceptionInfo);
            }
        }

        [MenuItem("InjectFix/Inject", false, 1)]
        public static void InjectAssemblys()
        {
            if (EditorApplication.isCompiling || Application.isPlaying)
            {
                UnityEngine.Debug.LogError("compiling or playing");
                return;
            }

            if (!EditorUtility.DisplayDialog("注意", "是否确定要进行代码注入?", "确定", "取消"))
            {
                return;
            }


            EditorUtility.DisplayProgressBar("Inject", "injecting...", 0);
            try
            {
                InjectAllAssemblys();
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError(e);
            }
            EditorUtility.ClearProgressBar();
#if UNITY_2019_3_OR_NEWER
            EditorUtility.RequestScriptReload();
#endif
        }

        public static bool AutoInject = true; //可以在外部禁用掉自动注入

        public static bool InjectOnce = false; //AutoInjectAssemblys只调用一次,可以防止自动化打包时,很多场景导致AutoInjectAssemblys被多次调用

        static bool injected = false;

        //[UnityEditor.Callbacks.PostProcessScene]
        //public static void AutoInjectAssemblys()
        //{
        //    if (AutoInject && !injected)
        //    {
        //        InjectAllAssemblys();
        //        if (InjectOnce)
        //        {
        //            injected = true;
        //        }
        //    }
        //}

        public static void InjectAssemblysOnBuild(string dir)
        {
            if (AutoInject && !injected)
            {
                InjectAllAssemblys(dir);
                if (InjectOnce)
                {
                    injected = true;
                }
            }
        }



        //获取备份文件信息
        public static void GetBackupInfo(out string[] backups, out string[] timestamps)
        {
            string pattern = @"Assembly-CSharp-(\d{14})\.dll$";
            Regex r = new Regex(pattern);

            var allBackup = Directory.GetFiles(BACKUP_PATH).Where(path => r.Match(path).Success)
                .Select(path => path.Replace('\\', '/')).ToList();
            allBackup.Sort();

            backups = allBackup.Select(path => r.Match(path).Groups[1].Captures[0].Value).ToArray();
            timestamps = allBackup.Select(path => DateTime.ParseExact(r.Match(path).Groups[1].Captures[0].Value,
                TIMESTAMP_FORMAT, System.Globalization.CultureInfo.InvariantCulture)
                .ToString("yyyy-MM-dd hh:mm:ss tt")).ToArray();
        }

        //选择备份
        public static void SelectBackup(string buttonText, Action<string> cb)
        {
            string[] backups;
            string[] timestamps;
            GetBackupInfo(out backups, out timestamps);

            VersionSelector.Show(timestamps.ToArray(), index =>
            {
                cb(backups[index]);
            }, buttonText);
        }

        /// <summary>
        /// 对指定的程序集注入
        /// </summary>
        /// <param name="assembly">程序集路径</param>
        public static void InjectAssembly(string assembly)
        {
            var configure = Configure.GetConfigureByTags(new List<string>() {
                "IFix.IFixAttribute",
                "IFix.InterpretAttribute",
                "IFix.ReverseWrapperAttribute",
            });

            var filters = Configure.GetFilters();

            var processCfgPath = "./process_cfg";

            //该程序集是否有配置了些类,如果没有就跳过注入操作
            bool hasSomethingToDo = false;

            var blackList = new List<MethodInfo>();

            using (BinaryWriter writer = new BinaryWriter(new FileStream(processCfgPath, FileMode.Create,
                FileAccess.Write)))
            {
                writer.Write(configure.Count);

                foreach (var kv in configure)
                {
                    writer.Write(kv.Key);

                    var typeList = kv.Value.Where(item => item.Key is Type)
                        .Select(item => new KeyValuePair<Type, int>(item.Key as Type, item.Value))
                        .Where(item => item.Key.Assembly.GetName().Name == assembly)
                        .ToList();
                    writer.Write(typeList.Count);

                    if (typeList.Count > 0)
                    {
                        hasSomethingToDo = true;
                    }

                    foreach (var cfgItem in typeList)
                    {
                        writer.Write(GetCecilTypeName(cfgItem.Key));
                        writer.Write(cfgItem.Value);
                        if (filters.Count > 0 && kv.Key == "IFix.IFixAttribute")
                        {
                            foreach (var method in cfgItem.Key.GetMethods(BindingFlags.Instance
                                | BindingFlags.Static | BindingFlags.Public
                                | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
                            {
                                foreach (var filter in filters)
                                {
                                    if ((bool)filter.Invoke(null, new object[]
                                    {
                                        method
                                    }))
                                    {
                                        blackList.Add(method);
                                    }
                                }
                            }
                        }
                    }
                }

                writeMethods(writer, blackList);
            }

            if (hasSomethingToDo)
            {

                var core_path = "./Assets/Plugins/IFix.Core.dll";

                var assembly_path = string.Format("{0}/{1}.dll", targetAssembliesFolder, assembly);

                var patch_path = string.Format("./{0}.ill.bytes", assembly);
                List<string> args = new List<string>() { "-inject", core_path, assembly_path,
                    processCfgPath, patch_path, assembly_path };

                foreach (var path in
                    (from asm in AppDomain.CurrentDomain.GetAssemblies()
                     select Path.GetDirectoryName(asm.ManifestModule.FullyQualifiedName)).Distinct())
                {
                    try
                    {
                        //UnityEngine.Debug.Log("searchPath:" + path);
                        args.Add(path);
                    }
                    catch { }
                }

                CallIFix(args);
            }

            File.Delete(processCfgPath);
        }

        /// <summary>
        /// 对injectAssemblys里的程序集进行注入,然后备份
        /// </summary>
        public static void InjectAllAssemblys()
        {
            if (EditorApplication.isCompiling || Application.isPlaying)
            {
                return;
            }

            targetAssembliesFolder = GetScriptAssembliesFolder();

            foreach (var assembly in injectAssemblys)
            {
                InjectAssembly(assembly);
            }

            //doBackup(DateTime.Now.ToString(TIMESTAMP_FORMAT));

            AssetDatabase.Refresh();
        }

        public static void InjectAllAssemblys(string dir)
        {
            if (EditorApplication.isCompiling || Application.isPlaying)
            {
                return;
            }

            targetAssembliesFolder = GetScriptAssembliesFolder(dir);

            foreach (var assembly in injectAssemblys)
            {
                InjectAssembly(assembly);
            }

            //doBackup(DateTime.Now.ToString(TIMESTAMP_FORMAT));

            AssetDatabase.Refresh();
        }

        /// <summary>
        /// 针对2022.3版本的程序集目录进行修改
        /// 保障Editor下测试补丁可用
        /// </summary>
        /// <param name="assembliesFolder"></param>
        /// <returns></returns>
        private static string GetScriptAssembliesFolder(string assembliesFolder = "")
        {
            if (string.IsNullOrEmpty(assembliesFolder))
            {
                Debug.Log("Not in build Process, Inject to Editor Assemblies");

                assembliesFolder = "./Library/ScriptAssemblies";
            }

            return assembliesFolder;
        }



        //默认的注入及备份程序集
        //另外可以直接调用InjectAssembly对其它程序集进行注入。
        static string[] injectAssemblys = new string[]
        {
            "Assembly-CSharp",
            "Assembly-CSharp-firstpass"
        };

        /// <summary>
        /// 把注入后的程序集备份
        /// </summary>
        /// <param name="ts">时间戳</param>
        static void doBackup(string ts)
        {
            if (!Directory.Exists(BACKUP_PATH))
            {
                Directory.CreateDirectory(BACKUP_PATH);
            }

            var scriptAssembliesDir = string.Format("{0}/", targetAssembliesFolder);

            foreach (var assembly in injectAssemblys)
            {
                var dllFile = string.Format("{0}{1}.dll", scriptAssembliesDir, assembly);
                if (!File.Exists(dllFile))
                {
                    continue;
                }
                File.Copy(dllFile, string.Format("{0}/{1}-{2}.dll", BACKUP_PATH, assembly, ts), true);

                var mdbFile = string.Format("{0}{1}.dll.mdb", scriptAssembliesDir, assembly);
                if (File.Exists(mdbFile))
                {
                    File.Copy(mdbFile, string.Format("{0}/{1}-{2}.dll.mdb", BACKUP_PATH, assembly, ts), true);
                }

                var pdbFile = string.Format("{0}{1}.dll.pdb", scriptAssembliesDir, assembly);
                if (File.Exists(pdbFile))
                {
                    File.Copy(pdbFile, string.Format("{0}/{1}-{2}.dll.pdb", BACKUP_PATH, assembly, ts), true);
                }
            }
        }

        /// <summary>
        /// 恢复某个选定的备份
        /// </summary>
        /// <param name="ts">时间戳</param>
        static void doRestore(string ts)
        {
            var scriptAssembliesDir = string.Format("{0}/", targetAssembliesFolder);

            foreach (var assembly in injectAssemblys)
            {
                var dllFile = string.Format("{0}/{1}-{2}.dll", BACKUP_PATH, assembly, ts);
                if (!File.Exists(dllFile))
                {
                    continue;
                }
                File.Copy(dllFile, string.Format("{0}{1}.dll", scriptAssembliesDir, assembly), true);
                UnityEngine.Debug.Log("Revert to: " + dllFile);

                var mdbFile = string.Format("{0}/{1}-{2}.dll.mdb", BACKUP_PATH, assembly, ts);
                if (File.Exists(mdbFile))
                {
                    File.Copy(mdbFile, string.Format("{0}{1}.dll.mdb", scriptAssembliesDir, assembly), true);
                    UnityEngine.Debug.Log("Revert to: " + mdbFile);
                }

                var pdbFile = string.Format("{0}/{1}-{2}.dll.pdb", BACKUP_PATH, assembly, ts);
                if (File.Exists(pdbFile))
                {
                    File.Copy(pdbFile, string.Format("{0}{1}.dll.pdb", scriptAssembliesDir, assembly), true);
                    UnityEngine.Debug.Log("Revert to: " + pdbFile);
                }
            }
        }

        //cecil里的类名表示和.net标准并不一样,这里做些转换
        static string GetCecilTypeName(Type type)
        {
            if (type.IsByRef && type.GetElementType().IsGenericType)
            {
                return GetCecilTypeName(type.GetElementType()) + "&";
            }
            else if (type.IsGenericType)
            {
                if (type.IsGenericTypeDefinition)
                {
                    return type.ToString().Replace('+', '/').Replace('[', '<').Replace(']', '>');
                }
                else
                {
                    return Regex.Replace(type.ToString().Replace('+', '/'), @"(`\d).+", "$1")
                        + "<" + string.Join(",", type.GetGenericArguments().Select(t => GetCecilTypeName(t))
                        .ToArray()) + ">";
                }
            }
            else
            {
                return type.FullName.Replace('+', '/');
            }
        }

        //目前支持的平台编译
        public enum Platform
        {
            android,
            ios,
            standalone
        }

        //缓存:解析好的编译参数
        private static Dictionary<Platform, string> compileTemplates = new Dictionary<Platform, string>();

        //解析unity的编译参数
        private static string parseCompileTemplate(string path)
        {
            return string.Join("\n", File.ReadAllLines(path).Where(line => !line.StartsWith("Assets/")
                && !line.StartsWith("\"Assets/")
                && !line.StartsWith("'Assets/")
                && !line.StartsWith("-r:Assets/")
                && !line.StartsWith("-r:\"Assets/")
                && !line.StartsWith("-r:'Assets/")
                && !line.StartsWith("-out")
                ).ToArray());
        }

        //对路径预处理,然后添加到StringBuilder
        //规则:如果路径含空格,则加上双引号
        static void appendFile(StringBuilder sb, string path)
        {
            if (path.IndexOf(' ') > 0)
            {
                sb.Append('"');
                sb.Append(path);
                sb.Append('"');
                sb.AppendLine();
            }
            else
            {
                sb.AppendLine(path);
            }
        }

        //自动加入源码
        private static void appendDirectory(StringBuilder src, string dir)
        {
            foreach (var file in Directory.GetFiles(dir))
            {
                //排除调Editor下的东西
                if (file.IndexOf(Path.DirectorySeparatorChar + "Editor" + Path.DirectorySeparatorChar) > 0)
                {
                    continue;
                }
                //排除Assembly-CSharp-firstpass
                if (file.Substring(file.Length - 3).ToLower() == ".cs")
                {
                    if (file.StartsWith("Assets" + Path.DirectorySeparatorChar + "Plugins") ||
                        file.StartsWith("Assets" + Path.DirectorySeparatorChar + "Standard Assets") ||
                        file.StartsWith("Assets" + Path.DirectorySeparatorChar + "Pro Standard Assets"))
                    {
                        continue;
                    }
                    appendFile(src, file);
                }
            }

            foreach (var subDir in Directory.GetDirectories(dir))
            {
                appendDirectory(src, subDir);
            }
        }

        //通过模板文件,获取编译参数
        private static string getCompileArguments(Platform platform, string output)
        {
            string compileTemplate;
            if (!compileTemplates.TryGetValue(platform, out compileTemplate))
            {
#if UNITY_EDITOR_WIN
                var hostPlatform = "win";
#elif UNITY_EDITOR_OSX
                var hostPlatform = "osx";
#else
                var hostPlatform = "linux";
#endif
                var path = "IFixToolKit/" + platform + "." + hostPlatform + ".tpl";
                if (!File.Exists(path))
                {
                    path = "IFixToolKit/" + platform + ".tpl";
                    if (!File.Exists(path))
                    {
                        throw new InvalidOperationException("please put template file for " + platform
                            + " in IFixToolKit directory!");
                    }
                }
                compileTemplate = parseCompileTemplate(path);
                compileTemplates.Add(platform, compileTemplate);
            }
            StringBuilder cmd = new StringBuilder();
            StringBuilder src = new StringBuilder();
            StringBuilder dll = new StringBuilder();

            appendDirectory(src, "Assets");
            var projectDir = Application.dataPath.Replace(Path.DirectorySeparatorChar, '/');
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0
                    if (!(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder))
                    {
#endif
                        var assemblyPath = assembly.ManifestModule.FullyQualifiedName
                            .Replace(Path.DirectorySeparatorChar, '/');
                        if (assemblyPath.StartsWith(projectDir))
                        {
                            dll.Append("-r:");
                            appendFile(dll, assemblyPath.Replace(projectDir, "Assets"));
                        }
#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0
                    }
#endif
                }
                catch { }
            }

            cmd.AppendLine(compileTemplate);
            cmd.Append(dll.ToString());
            cmd.Append(src.ToString());
            cmd.AppendLine("-sdk:2");
            cmd.Append("-out:");
            appendFile(cmd, output);

            return cmd.ToString();
        }

        //1、解析编译参数(处理元文件之外的编译参数)
        //2、搜索工程的c#源码,加上编译参数编译
        //3、编译Assembly-CSharp.dll
        //TODO: 只支持Assembly-CSharp.dll,但较新版本Unity已经支持多dll拆分
        //TODO: 目前的做法挺繁琐的,需要用户去获取Unity的编译命令文件,更好的做法应该是直接
        public static void Compile(string compileArgFile)
        {
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
            var monoPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName),
                "../MonoBleedingEdge/bin/mono");
            var mcsPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName),
                "../MonoBleedingEdge/lib/mono/4.5/mcs.exe");
            if(!File.Exists(monoPath))
            {
                monoPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName),
                    "../../MonoBleedingEdge/bin/mono");
                mcsPath = Path.Combine(Path.GetDirectoryName(typeof(UnityEngine.Debug).Module.FullyQualifiedName),
                    "../../MonoBleedingEdge/lib/mono/4.5/mcs.exe");
            }
#elif UNITY_EDITOR_WIN
            var monoPath = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName),
                "Data/MonoBleedingEdge/bin/mono.exe");
            var mcsPath = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName),
                "Data/MonoBleedingEdge/lib/mono/4.5/mcs.exe");
#endif
            if (!File.Exists(monoPath))
            {
                UnityEngine.Debug.LogError("can not find mono!");
            }

            Process compileProcess = new Process();
            compileProcess.StartInfo.FileName = monoPath;
            compileProcess.StartInfo.Arguments = "\"" + mcsPath + "\" " + "@" + compileArgFile;
            compileProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            compileProcess.StartInfo.RedirectStandardOutput = true;
            compileProcess.StartInfo.RedirectStandardError = true;
            compileProcess.StartInfo.UseShellExecute = false;
            compileProcess.StartInfo.CreateNoWindow = true;
            compileProcess.Start();

            //UnityEngine.Debug.Log(hotfix_injection.StartInfo.FileName);
            //UnityEngine.Debug.Log(hotfix_injection.StartInfo.Arguments);

            while (!compileProcess.StandardError.EndOfStream)
            {
                UnityEngine.Debug.LogError(compileProcess.StandardError.ReadLine());
            }

            while (!compileProcess.StandardOutput.EndOfStream)
            {
                UnityEngine.Debug.Log(compileProcess.StandardOutput.ReadLine());
            }

            compileProcess.WaitForExit();
        }

        //生成特定平台的patch
        public static void GenPlatformPatch(Platform platform, string patchOutputDir,
            string corePath = "./Assets/Plugins/IFix.Core.dll")
        {
            var outputDir = "Temp/ifix";
            Directory.CreateDirectory("Temp");
            Directory.CreateDirectory(outputDir);
#if UNITY_2018_3_OR_NEWER
            ScriptCompilationSettings scriptCompilationSettings = new ScriptCompilationSettings();
            if (platform == Platform.android)
            {
                scriptCompilationSettings.group = BuildTargetGroup.Android;
                scriptCompilationSettings.target = BuildTarget.Android;
            }
            else if (platform == Platform.ios)
            {
                scriptCompilationSettings.group = BuildTargetGroup.iOS;
                scriptCompilationSettings.target = BuildTarget.iOS;
            }
            else
            {
                scriptCompilationSettings.group = BuildTargetGroup.Standalone;
                scriptCompilationSettings.target = BuildTarget.StandaloneWindows;
            }

            ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, outputDir);

            foreach (var assembly in injectAssemblys)
            {
                GenPatch(assembly, string.Format("{0}/{1}.dll", outputDir, assembly),
                    "./Assets/Plugins/IFix.Core.dll", string.Format("{0}{1}.patch.bytes", patchOutputDir, assembly));
            }
#else
            throw new NotImplementedException();
            //var compileArgFile = "Temp/ifix/unity_" + platform + "_compile_argument";
            //var tmpDllPath = "Temp/ifix/Assembly-CSharp.dll";
            //File.WriteAllText(compileArgFile, getCompileArguments(platform, tmpDllPath));
            //先编译dll到Temp目录下
            //Compile(compileArgFile);
            //对编译后的dll生成补丁
            //GenPatch("Assembly-CSharp", tmpDllPath, corePath, patchPath);

            //File.Delete(compileArgFile);
            //File.Delete(tmpDllPath);
            //File.Delete(tmpDllPath + ".mdb");
#endif
        }

        //把方法签名写入文件
        //由于目前不支持泛型函数的patch,所以函数签名为方法名+参数类型
        static void writeMethods(BinaryWriter writer, List<MethodInfo> methods)
        {
            var methodGroups = methods.GroupBy(m => m.DeclaringType).ToList();
            writer.Write(methodGroups.Count);
            foreach (var methodGroup in methodGroups)
            {
                writer.Write(GetCecilTypeName(methodGroup.Key));
                writer.Write(methodGroup.Count());
                foreach (var method in methodGroup)
                {
                    writer.Write(method.Name);
                    writer.Write(GetCecilTypeName(method.ReturnType));
                    writer.Write(method.GetParameters().Length);
                    foreach (var parameter in method.GetParameters())
                    {
                        writer.Write(parameter.IsOut);
                        writer.Write(GetCecilTypeName(parameter.ParameterType));
                    }
                }
            }
        }

        static void writeFields(BinaryWriter writer, List<FieldInfo> fields)
        {
            var fieldGroups = fields.GroupBy(m => m.DeclaringType).ToList();
            writer.Write(fieldGroups.Count);
            foreach (var fieldGroup in fieldGroups)
            {
                writer.Write(GetCecilTypeName(fieldGroup.Key));
                writer.Write(fieldGroup.Count());
                foreach (var field in fieldGroup)
                {
                    writer.Write(field.Name);
                    writer.Write(GetCecilTypeName(field.FieldType));
                }
            }
        }

        static void writeProperties(BinaryWriter writer, List<PropertyInfo> properties)
        {
            var PropertyGroups = properties.GroupBy(m => m.DeclaringType).ToList();
            writer.Write(PropertyGroups.Count);
            foreach (var PropertyGroup in PropertyGroups)
            {
                writer.Write(GetCecilTypeName(PropertyGroup.Key));
                writer.Write(PropertyGroup.Count());
                foreach (var Property in PropertyGroup)
                {
                    writer.Write(Property.Name);
                    writer.Write(GetCecilTypeName(Property.PropertyType));
                }
            }
        }

        static void writeClasses(BinaryWriter writer, List<Type> classes)
        {
            writer.Write(classes.Count);
            foreach (var classGroup in classes)
            {
                writer.Write(GetCecilTypeName(classGroup));
            }
        }

        static bool hasGenericParameter(Type type)
        {
            if (type.IsByRef || type.IsArray)
            {
                return hasGenericParameter(type.GetElementType());
            }
            if (type.IsGenericType)
            {
                foreach (var typeArg in type.GetGenericArguments())
                {
                    if (hasGenericParameter(typeArg))
                    {
                        return true;
                    }
                }
                return false;
            }
            return type.IsGenericParameter;
        }

        static bool hasGenericParameter(MethodBase method)
        {
            if (method.IsGenericMethodDefinition || method.IsGenericMethod) return true;
            if (!method.IsConstructor && hasGenericParameter((method as MethodInfo).ReturnType)) return true;

            foreach (var param in method.GetParameters())
            {
                if (hasGenericParameter(param.ParameterType))
                {
                    return true;
                }
            }
            return false;

        }

        /// <summary>
        /// 生成patch
        /// </summary>
        /// <param name="assembly">程序集名,用来过滤配置</param>
        /// <param name="assemblyCSharpPath">程序集路径</param>
        /// <param name="corePath">IFix.Core.dll所在路径</param>
        /// <param name="patchPath">生成的patch的保存路径</param>
        public static void GenPatch(string assembly, string assemblyCSharpPath
            = "./Library/ScriptAssemblies/Assembly-CSharp.dll",
            string corePath = "./Assets/Plugins/IFix.Core.dll", string patchPath = "Assembly-CSharp.patch.bytes")
        {
            var patchMethods = Configure.GetTagMethods(typeof(PatchAttribute), assembly).ToList();
            var genericMethod = patchMethods.FirstOrDefault(m => hasGenericParameter(m));
            if (genericMethod != null)
            {
                throw new InvalidDataException("not support generic method: " + genericMethod);
            }

            if (patchMethods.Count == 0)
            {
                return;
            }

            var newMethods = Configure.GetTagMethods(typeof(InterpretAttribute), assembly).ToList();
            var newFields = Configure.GetTagFields(typeof(InterpretAttribute), assembly).ToList();
            var newProperties = Configure.GetTagProperties(typeof(InterpretAttribute), assembly).ToList();
            var newClasses = Configure.GetTagClasses(typeof(InterpretAttribute), assembly).ToList();
            genericMethod = newMethods.FirstOrDefault(m => hasGenericParameter(m));
            if (genericMethod != null)
            {
                throw new InvalidDataException("not support generic method: " + genericMethod);
            }

            var processCfgPath = "./process_cfg";

            using (BinaryWriter writer = new BinaryWriter(new FileStream(processCfgPath, FileMode.Create,
                FileAccess.Write)))
            {
                writeMethods(writer, patchMethods);
                writeMethods(writer, newMethods);
                writeFields(writer, newFields);
                writeProperties(writer, newProperties);
                writeClasses(writer, newClasses);
            }

            List<string> args = new List<string>() { "-patch", corePath, assemblyCSharpPath, "null",
                processCfgPath, patchPath };

            foreach (var path in
                (from asm in AppDomain.CurrentDomain.GetAssemblies()
                 select Path.GetDirectoryName(asm.ManifestModule.FullyQualifiedName)).Distinct())
            {
                try
                {
                    //UnityEngine.Debug.Log("searchPath:" + path);
                    args.Add(path);
                }
                catch { }
            }

            CallIFix(args);

            File.Delete(processCfgPath);

            AssetDatabase.Refresh();
        }

        [MenuItem("InjectFix/Fix", false, 2)]
        public static void Patch()
        {
            EditorUtility.DisplayProgressBar("Generate Patch for Edtior", "patching...", 0);
            try
            {
                foreach (var assembly in injectAssemblys)
                {
                    var assembly_path = string.Format("./Library/{0}/{1}.dll", GetScriptAssembliesFolder(), assembly);
                    GenPatch(assembly, assembly_path, "./Assets/Plugins/IFix.Core.dll",
                        string.Format("{0}.patch.bytes", assembly));
                }
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError(e);
            }
            EditorUtility.ClearProgressBar();
        }

#if UNITY_2018_3_OR_NEWER
        [MenuItem("InjectFix/Fix(Android)", false, 3)]
        public static void CompileToAndroid()
        {
            EditorUtility.DisplayProgressBar("Generate Patch for Android", "patching...", 0);
            try
            {
                GenPlatformPatch(Platform.android, "");
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError(e);
            }
            EditorUtility.ClearProgressBar();
        }

        [MenuItem("InjectFix/Fix(IOS)", false, 4)]
        public static void CompileToIOS()
        {
            EditorUtility.DisplayProgressBar("Generate Patch for IOS", "patching...", 0);
            try
            {
                GenPlatformPatch(Platform.ios, "");
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError(e);
            }
            EditorUtility.ClearProgressBar();
        }
#endif
    }
}

@Dante0927
Copy link
Author

其他版本的Unity未进行测试,有需要的可以尝试下,按道理来说,使用Bee进行打包的应该都可以用这个方法解决

@lingfengsuxue
Copy link

赞一个,解决了我们的问题。感谢!

@9aither
Copy link

9aither commented Apr 29, 2024

好兄弟我应该早点上来看issues的,折腾了老半天和你得出了一样的结论

@hhwhb20
Copy link

hhwhb20 commented May 28, 2024

你好,用这个方案在jenkins打包时依然没有注入,自己editor打包没问题,有人遇到过这种问题吗

@Dante0927
Copy link
Author

你好,用这个方案在jenkins打包时依然没有注入,自己editor打包没问题,有人遇到过这种问题吗

你用il2cpp解一下这个目录里的dll看看有没有插装成功:
工程目录/Temp/StagingArea/Data/Managed,插装成功的话应该没问题

@hhwhb20
Copy link

hhwhb20 commented May 28, 2024

你好,用这个方案在jenkins打包时依然没有注入,自己editor打包没问题,有人遇到过这种问题吗

你用il2cpp解一下这个目录里的dll看看有没有插装成功: 工程目录/Temp/StagingArea/Data/Managed,插装成功的话应该没问题

不好意思,我的描述可能让您产生了误解,我遇到的问题是jenkins打包后真机测试是assembly may be not injected yet,而自己用editor buildplayer是可以的

@Dante0927
Copy link
Author

你好,用这个方案在jenkins打包时依然没有注入,自己editor打包没问题,有人遇到过这种问题吗

你用il2cpp解一下这个目录里的dll看看有没有插装成功: 工程目录/Temp/StagingArea/Data/Managed,插装成功的话应该没问题

不好意思,我的描述可能让您产生了误解,我遇到的问题是jenkins打包后真机测试是assembly may be not injected yet,而自己用editor buildplayer是可以的

jenkins我就不了解了 😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants