Tencent/InjectFix

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

Opened this issue · 8 comments

  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
    }
}

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

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

Go to the Library folder and search for the file "Assembly-CSharp.dll." You will find more than one file. Remove all these files but keep the one in the ScriptAssemblies folder.

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

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

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

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

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

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

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

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

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

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

jenkins我就不了解了 😂