|  | @@ -1,115 +1,107 @@
 | 
	
		
			
				|  |  |  using System;
 | 
	
		
			
				|  |  |  using System.Collections.Generic;
 | 
	
		
			
				|  |  | +using System.Diagnostics;
 | 
	
		
			
				|  |  | +using System.IO;
 | 
	
		
			
				|  |  |  using System.Linq;
 | 
	
		
			
				|  |  |  using System.Reflection;
 | 
	
		
			
				|  |  | +using System.Runtime.CompilerServices;
 | 
	
		
			
				|  |  | +using System.Runtime.InteropServices;
 | 
	
		
			
				|  |  |  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<string, Action<AssemblyDefinition, TypeDefinition>> patches =
 | 
	
		
			
				|  |  | -            new Dictionary<string, Action<AssemblyDefinition, TypeDefinition>>
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                ["Assembly-CSharp"] = PatchAssemblyCSharp, ["UnityEngine"] = PatchUnityEngine
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | +        public static readonly string[] TargetAssemblyNames = { "Assembly-CSharp.dll" };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          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);
 | 
	
		
			
				|  |  | +            var gameMain = ad.MainModule.GetType("GameMain");
 | 
	
		
			
				|  |  | +            var onInit = gameMain.Methods.FirstOrDefault(m => m.Name == "OnInitialize");
 | 
	
		
			
				|  |  | +            var ins = onInit.Body.Instructions.First();
 | 
	
		
			
				|  |  | +            var il = onInit.Body.GetILProcessor();
 | 
	
		
			
				|  |  | +            il.InsertBefore(
 | 
	
		
			
				|  |  | +                ins,
 | 
	
		
			
				|  |  | +                il.Create(OpCodes.Call,
 | 
	
		
			
				|  |  | +                          ad.MainModule.ImportReference(
 | 
	
		
			
				|  |  | +                              hooks.Methods.FirstOrDefault(m => m.Name == nameof(Hooks.FixShaders)))));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +    public static class Hooks
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        // List of shaders the RQ of which to zero
 | 
	
		
			
				|  |  | +        private static readonly string[] ShadersToCleanup = { "CM3D2/Mosaic", "CM3D2/Mosaic_en" };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                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));
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +        // We need to keep at least one instance of the shader alive so that it won't get unloaded
 | 
	
		
			
				|  |  | +        private static readonly List<object> ShadersCache = new List<object>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private static void PatchAssemblyCSharp(AssemblyDefinition ad, TypeDefinition hooks)
 | 
	
		
			
				|  |  | +        public static void FixShaders()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var bgMgr = ad.MainModule.GetType("BgMgr");
 | 
	
		
			
				|  |  | -            var importCM = ad.MainModule.GetType("ImportCM");
 | 
	
		
			
				|  |  | +            try
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var modules = Process.GetCurrentProcess().Modules;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var createAssetBundle = bgMgr.Methods.FirstOrDefault(m => m.Name == "CreateAssetBundle");
 | 
	
		
			
				|  |  | -            PatchPostfix(createAssetBundle,
 | 
	
		
			
				|  |  | -                         hooks.Methods.FirstOrDefault(m => m.Name == nameof(Hooks.PostCreateAssetBundle)));
 | 
	
		
			
				|  |  | +                ProcessModule mono = null;
 | 
	
		
			
				|  |  | +                foreach (ProcessModule module in modules)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (!module.ModuleName.ToLowerInvariant().Contains("mono"))
 | 
	
		
			
				|  |  | +                        continue;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var readMaterial = importCM.Methods.FirstOrDefault(m => m.Name == "ReadMaterial");
 | 
	
		
			
				|  |  | -            PatchPostfix(readMaterial, hooks.Methods.FirstOrDefault(m => m.Name == nameof(Hooks.PostReadMaterial)));
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +                    mono = module;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private static void PatchUnityEngine(AssemblyDefinition ad, TypeDefinition hooks)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            var resources = ad.MainModule.GetType("UnityEngine.Resources");
 | 
	
		
			
				|  |  | +                if (mono == null)
 | 
	
		
			
				|  |  | +                    return;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            var load = resources.Methods.FirstOrDefault(
 | 
	
		
			
				|  |  | -                m => m.Name == "Load" && m.Parameters.Count == 1 &&
 | 
	
		
			
				|  |  | -                     m.ReturnType.FullName == "UnityEngine.Object");
 | 
	
		
			
				|  |  | +                var addICall =
 | 
	
		
			
				|  |  | +                    Marshal.GetDelegateForFunctionPointer(GetProcAddress(mono.BaseAddress, "mono_add_internal_call"),
 | 
	
		
			
				|  |  | +                                                          typeof(AddICallDelegate)) as AddICallDelegate;
 | 
	
		
			
				|  |  | +                addICall("COM3D2.NeighUncensor.Patcher.Hooks::FixShader",
 | 
	
		
			
				|  |  | +                         Marshal.GetFunctionPointerForDelegate(new FixShaderDelegate(FixShaderImpl)));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            PatchPostfix(load, hooks.Methods.FirstOrDefault(m => m.Name == nameof(Hooks.OnResourceLoad)));
 | 
	
		
			
				|  |  | +                CleanupMosaicShaders();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            catch (Exception e)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                File.WriteAllText("neigh_uncensor_error.log", e.ToString());
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    public static class Hooks
 | 
	
		
			
				|  |  | -    {
 | 
	
		
			
				|  |  | -        private static object replacement;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public static Material PostReadMaterial(Material result)
 | 
	
		
			
				|  |  | +        private static void CleanupMosaicShaders()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            if (result.shader?.name.ToLowerInvariant().Contains("mosaic") ?? false)
 | 
	
		
			
				|  |  | +            foreach (string shaderName in ShadersToCleanup)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                if (replacement == null)
 | 
	
		
			
				|  |  | -                    replacement = Shader.Find("Unlit/Transparent");
 | 
	
		
			
				|  |  | -                result.shader = (Shader)replacement;
 | 
	
		
			
				|  |  | -                result.renderQueue = 0;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +                var s = Shader.Find(shaderName);
 | 
	
		
			
				|  |  | +                if (s == null)
 | 
	
		
			
				|  |  | +                    continue;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            return result;
 | 
	
		
			
				|  |  | +                ShadersCache.Add(s);
 | 
	
		
			
				|  |  | +                FixShader(s);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public static GameObject PostCreateAssetBundle(GameObject result)
 | 
	
		
			
				|  |  | +        public static unsafe void FixShaderImpl(IntPtr shaderObj)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            TryUncensor(result);
 | 
	
		
			
				|  |  | -            return result;
 | 
	
		
			
				|  |  | +            var s = (byte*)shaderObj.ToPointer();
 | 
	
		
			
				|  |  | +            var a = *(byte**)(s + 0x10);
 | 
	
		
			
				|  |  | +            var b = *(byte**)(a + 0x38);
 | 
	
		
			
				|  |  | +            var rq = (uint*)(b + 0x5C);
 | 
	
		
			
				|  |  | +            *rq = 0u;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        public static Object OnResourceLoad(Object result)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            if (result is GameObject go)
 | 
	
		
			
				|  |  | -                TryUncensor(go);
 | 
	
		
			
				|  |  | -            return result;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +        [MethodImpl(MethodImplOptions.InternalCall)]
 | 
	
		
			
				|  |  | +        private static extern void FixShader(Shader s);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private static void TryUncensor(GameObject go)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            if (go == null)
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            var smr = go.GetComponentInChildren<Renderer>();
 | 
	
		
			
				|  |  | -            if (smr == null || smr.materials == null)
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            foreach (var mm in smr.materials)
 | 
	
		
			
				|  |  | -                PostReadMaterial(mm);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
 | 
	
		
			
				|  |  | +        private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private delegate void FixShaderDelegate(IntPtr shader);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private delegate void AddICallDelegate([MarshalAs(UnmanagedType.LPStr)] string name, IntPtr addr);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |