killapp/Assets/IFix/Editor/ILFixEditor.cs
“虞渠成” 471f4c8962 init
2025-11-18 09:18:48 +08:00

908 lines
34 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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;
#if UNITY_2018_3_OR_NEWER
using UnityEditor.Build.Player;
#endif
namespace IFix.Editor
{
//版本选择窗口
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;
}
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 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("./Library/{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();
}
private static string GetScriptAssembliesFolder()
{
var assembliesFolder = "PlayerScriptAssemblies";
if (!Directory.Exists(string.Format("./Library/{0}/", assembliesFolder)))
{
assembliesFolder = "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("./Library/{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("./Library/{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
}
}