using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using UnityEngine; using Object = UnityEngine.Object; namespace COM3D2.NeighUncensor.Patcher { public static class NeighUncensorPatcher { public static readonly string[] TargetAssemblyNames = { "Assembly-CSharp.dll", "UnityEngine.dll" }; private static readonly Dictionary> patches = new Dictionary> { ["Assembly-CSharp"] = PatchAssemblyCSharp, ["UnityEngine"] = PatchUnityEngine }; public static void Patch(AssemblyDefinition ad) { var hookAd = AssemblyDefinition.ReadAssembly(Assembly.GetExecutingAssembly().Location); var hooks = hookAd.MainModule.GetType("COM3D2.NeighUncensor.Patcher.Hooks"); if (patches.TryGetValue(ad.Name.Name, out var patch)) patch(ad, hooks); } private static void PatchPostfix(MethodDefinition method, MethodReference postfix, bool passInstance = false) { var il = method.Body.GetILProcessor(); var mRef = method.Module.ImportReference(postfix); foreach (var ins in method.Body.Instructions.ToList()) { if (ins.OpCode != OpCodes.Ret) continue; ins.OpCode = OpCodes.Nop; il.InsertAfter(ins, il.Create(OpCodes.Ret)); il.InsertAfter(ins, il.Create(OpCodes.Call, mRef)); if (passInstance) il.InsertAfter(ins, il.Create(OpCodes.Ldarg_0)); } } private static void PatchAssemblyCSharp(AssemblyDefinition ad, TypeDefinition hooks) { var bgMgr = ad.MainModule.GetType("BgMgr"); var importCM = ad.MainModule.GetType("ImportCM"); var createAssetBundle = bgMgr.Methods.FirstOrDefault(m => m.Name == "CreateAssetBundle"); PatchPostfix(createAssetBundle, hooks.Methods.FirstOrDefault(m => m.Name == nameof(Hooks.PostCreateAssetBundle))); var readMaterial = importCM.Methods.FirstOrDefault(m => m.Name == "ReadMaterial"); PatchPostfix(readMaterial, hooks.Methods.FirstOrDefault(m => m.Name == nameof(Hooks.PostReadMaterial))); } private static void PatchUnityEngine(AssemblyDefinition ad, TypeDefinition hooks) { var resources = ad.MainModule.GetType("UnityEngine.Resources"); var load = resources.Methods.FirstOrDefault( m => m.Name == "Load" && m.Parameters.Count == 1 && m.ReturnType.FullName == "UnityEngine.Object"); PatchPostfix(load, hooks.Methods.FirstOrDefault(m => m.Name == nameof(Hooks.OnResourceLoad))); } } public static class Hooks { private static object replacement; public static Material PostReadMaterial(Material result) { if (result.shader?.name.ToLowerInvariant().Contains("mosaic") ?? false) { if (replacement == null) replacement = Shader.Find("Unlit/Transparent"); result.shader = (Shader)replacement; result.renderQueue = 0; } return result; } public static GameObject PostCreateAssetBundle(GameObject result) { TryUncensor(result); return result; } public static Object OnResourceLoad(Object result) { if (result is GameObject go) TryUncensor(go); return result; } private static void TryUncensor(GameObject go) { if (go == null) return; var smr = go.GetComponentInChildren(); if (smr == null || smr.materials == null) return; foreach (var mm in smr.materials) PostReadMaterial(mm); } } }