/*
|
* 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
|
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();
|
}
|
|
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
|
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);
|
|
if (Directory.Exists(patchOutputDir))
|
Directory.Delete(patchOutputDir, true);
|
Directory.CreateDirectory(patchOutputDir);
|
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 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 = ResourcesPath.PATCH_EDITOR)
|
{
|
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 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);
|
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
|
{
|
if (Directory.Exists(ResourcesPath.PATCH_EDITOR))
|
Directory.Delete(ResourcesPath.PATCH_EDITOR, true);
|
Directory.CreateDirectory(ResourcesPath.PATCH_EDITOR);
|
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}{1}.patch.bytes", ResourcesPath.PATCH_EDITOR, 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, ResourcesPath.PATCH_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, ResourcesPath.PATCH_IOS);
|
}
|
catch (Exception e)
|
{
|
UnityEngine.Debug.LogError(e);
|
}
|
EditorUtility.ClearProgressBar();
|
}
|
#endif
|
}
|
}
|