Created
June 10, 2022 11:48
-
-
Save gotmachine/791b1da0067dc234b141cdca5f31796b to your computer and use it in GitHub Desktop.
Harmony - Replace a method call in KSP and all loaded plugins
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
using HarmonyLib; | |
using Mono.Cecil; | |
using Mono.Cecil.Cil; | |
using Mono.Cecil.Rocks; | |
using MonoMod.Utils; | |
using UnityEngine; | |
using Code = Mono.Cecil.Cil.Code; | |
namespace ShaderReplacer | |
{ | |
[KSPAddon(KSPAddon.Startup.Instantly, true)] | |
class ShaderReplacer : MonoBehaviour | |
{ | |
private static MethodInfo mInfo_ShaderFind_Original; | |
private static MethodInfo mInfo_ShaderFind_Replacement; | |
private void Start() | |
{ | |
string cecilMethodName = "UnityEngine.Shader UnityEngine.Shader::Find(System.String)"; | |
mInfo_ShaderFind_Original = AccessTools.Method(typeof(Shader), nameof(Shader.Find)); | |
mInfo_ShaderFind_Replacement = AccessTools.Method(typeof(ShaderReplacer), nameof(ShaderReplacer.FindShader)); | |
List<MethodBase> callSites = new List<MethodBase>(); | |
// Don't use appdomain, we don't want to accidentally patch Unity itself and this avoid | |
// having to iterate on the BCL and Unity assemblies. | |
foreach (AssemblyLoader.LoadedAssembly kspAssembly in AssemblyLoader.loadedAssemblies) | |
{ | |
if (string.IsNullOrEmpty(kspAssembly.assembly?.Location)) | |
continue; | |
AssemblyDefinition assemblyDef; | |
try | |
{ | |
assemblyDef = AssemblyDefinition.ReadAssembly(kspAssembly.assembly.Location); | |
if (assemblyDef == null) | |
throw new FileLoadException($"Couldn't read assembly \"{kspAssembly.assembly.Location}\""); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogWarning($"[ShaderReplacer] Replace failed for assembly {kspAssembly.name}\n{e}"); | |
continue; | |
} | |
foreach (ModuleDefinition moduleDef in assemblyDef.Modules) | |
{ | |
foreach (TypeDefinition typeDef in moduleDef.GetAllTypes()) | |
{ | |
foreach (MethodDefinition methodDef in typeDef.Methods) | |
{ | |
if (!methodDef.HasBody) | |
continue; | |
foreach (Instruction instruction in methodDef.Body.Instructions) | |
{ | |
if (instruction.OpCode.Code == Code.Call | |
&& instruction.Operand is MethodReference mRef | |
&& mRef.FullName == cecilMethodName) | |
{ | |
MethodBase callSite; | |
try | |
{ | |
callSite = methodDef.ResolveReflection(); | |
if (callSite == null) | |
throw new MemberAccessException(); | |
} | |
catch (Exception e) | |
{ | |
Debug.LogWarning($"[ShaderReplacer] Failed to patch method {assemblyDef.Name}::{typeDef.Name}.{methodDef.Name}"); | |
break; | |
} | |
callSites.Add(callSite); | |
break; | |
} | |
} | |
} | |
} | |
} | |
} | |
Harmony harmony = new Harmony("ShaderReplacer"); | |
MethodInfo callSiteTranspiler = AccessTools.Method(typeof(ShaderReplacer), nameof(ShaderReplacer.CallSiteTranspiler)); | |
foreach (MethodBase callSite in callSites) | |
{ | |
if (callSite == mInfo_ShaderFind_Replacement) | |
continue; | |
Debug.Log($"[ShaderReplacer] Patching call site : {callSite.DeclaringType.Assembly.GetName().Name}::{callSite.DeclaringType}.{callSite.Name}"); | |
harmony.Patch(callSite, null, null, new HarmonyMethod(callSiteTranspiler)); | |
} | |
} | |
static IEnumerable<CodeInstruction> CallSiteTranspiler(IEnumerable<CodeInstruction> instructions) | |
{ | |
foreach (CodeInstruction instruction in instructions) | |
{ | |
if (instruction.opcode == OpCodes.Call && ReferenceEquals(instruction.operand, mInfo_ShaderFind_Original)) | |
instruction.operand = mInfo_ShaderFind_Replacement; | |
yield return instruction; | |
} | |
} | |
public static Shader FindShader(string name) | |
{ | |
Debug.LogError($"[ShaderReplacer] Shader requested : \"{name}\""); | |
return Shader.Find(name); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment