using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using AssetsTools.NET; using AssetsTools.NET.Extra; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mono.Cecil; using ShaderHelperForMac.Commands; using ShaderHelperForMac.Config; using ShaderHelperForMac.Diagnostics; using ShaderHelperForMac.Provenance; using ShaderHelperForMac.Replacement; using ShaderHelperForMac.Shaders; using ShaderHelperForMac.Sweep; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ShaderHelperForMac")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("3.2.0.0")] [assembly: AssemblyInformationalVersion("3.2.0+ed52adef9958d16b08ea973bab179130d4c353bc")] [assembly: AssemblyProduct("ShaderHelperForMac")] [assembly: AssemblyTitle("ShaderHelperForMac")] [assembly: AssemblyVersion("3.2.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ShaderHelperForMac { [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class PatchZNetSceneAwake { private static void Prefix() { Plugin.RetroactiveBundleSweep("ZNetScene.Awake prefix"); } private static void Postfix() { Plugin.Log.LogDebug((object)"[ZNetScene.Awake] Postfix — running post-ZNetScene sweep."); Plugin._zNetSceneAwakeFired = true; Plugin.TryUpgradeFallbackShader(); Plugin.TryUpgradeTransparentShader(); Plugin.RefreshCanonicalShaderIdsFromLoadedAssets(); Plugin.CheckCachedVanillaCacheVersionDrift(); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(Plugin.Instance.SweepMaterialsCoroutine("ZNetScene.Awake")); } } } [HarmonyPatch(typeof(Location), "Awake")] internal static class PatchLocationAwake { private static void Postfix() { Plugin.Log.LogDebug((object)"[Location.Awake] Postfix — sweeping materials after location/dungeon spawn."); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(Plugin.Instance.SweepMaterialsCoroutine("Location.Awake")); } } } [HarmonyPatch] internal static class PatchMeleeWeaponTrailStartProbe { private static readonly FieldInfo? _materialField = AccessTools.Field(AccessTools.TypeByName("MeleeWeaponTrail"), "_material"); private static readonly FieldInfo? _trailObjectField = AccessTools.Field(AccessTools.TypeByName("MeleeWeaponTrail"), "m_trailObject"); private static readonly FieldInfo[] _colorFields = ResolveColorFields(); private static readonly FieldInfo? _colorsArrayField = ResolveColorsArrayField(); private static FieldInfo? ResolveColorsArrayField() { FieldInfo[] colorFields = _colorFields; foreach (FieldInfo fieldInfo in colorFields) { if (fieldInfo.Name == "_colors" && fieldInfo.FieldType == typeof(Color[])) { return fieldInfo; } } return null; } private static FieldInfo[] ResolveColorFields() { Type type = AccessTools.TypeByName("MeleeWeaponTrail"); if (type == null) { return Array.Empty(); } List list = new List(); FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { Type fieldType = fieldInfo.FieldType; if (fieldType == typeof(Color) || fieldType == typeof(Gradient) || fieldType == typeof(Color[])) { list.Add(fieldInfo); } } return list.ToArray(); } private static string FormatColorField(object? value) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) if (value != null) { if (!(value is Color val)) { if (!(value is Color[] array)) { Gradient val2 = (Gradient)((value is Gradient) ? value : null); if (val2 != null) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Gradient{"); GradientColorKey[] colorKeys = val2.colorKeys; for (int i = 0; i < colorKeys.Length; i++) { if (i > 0) { stringBuilder.Append(','); } Color color = colorKeys[i].color; stringBuilder.Append($"@{colorKeys[i].time:F2}=({color.r:F2},{color.g:F2},{color.b:F2})"); } stringBuilder.Append("}"); return stringBuilder.ToString(); } return "<" + value.GetType().Name + ">"; } if (array.Length == 0) { return "Color[0]"; } StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.Append("Color[").Append(array.Length).Append("]{"); int num = Math.Min(array.Length, 4); for (int j = 0; j < num; j++) { if (j > 0) { stringBuilder2.Append(','); } Color val3 = array[j]; stringBuilder2.Append($"({val3.r:F2},{val3.g:F2},{val3.b:F2},{val3.a:F2})"); } if (array.Length > num) { stringBuilder2.Append(",…"); } stringBuilder2.Append('}'); return stringBuilder2.ToString(); } return $"Color({val.r:F3},{val.g:F3},{val.b:F3},{val.a:F3})"; } return ""; } private static bool IsAdditiveParticleShaderName(string? shaderName) { if (string.IsNullOrEmpty(shaderName)) { return false; } switch (shaderName) { case "Legacy Shaders/Particles/Additive": case "Legacy Shaders/Particles/Additive (Soft)": case "Legacy Shaders/Particles/~Additive-Multiply": case "Mobile/Particles/Additive": case "Particles/Additive": case "Particles/Additive (Soft)": return true; default: return false; } } private static IEnumerable TargetMethods() { Type t = AccessTools.TypeByName("MeleeWeaponTrail"); if (!(t == null)) { MethodInfo m = AccessTools.Method(t, "Start", Type.EmptyTypes, (Type[])null); if (m != null) { yield return m; } } } private static void Postfix(object __instance) { //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Unknown result type (might be due to invalid IL or missing references) //IL_0458: Unknown result type (might be due to invalid IL or missing references) //IL_045a: Unknown result type (might be due to invalid IL or missing references) //IL_0461: Unknown result type (might be due to invalid IL or missing references) //IL_0475: Unknown result type (might be due to invalid IL or missing references) //IL_047c: Unknown result type (might be due to invalid IL or missing references) //IL_0490: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_04e1: Unknown result type (might be due to invalid IL or missing references) //IL_0527: Unknown result type (might be due to invalid IL or missing references) //IL_0533: Unknown result type (might be due to invalid IL or missing references) //IL_054c: Unknown result type (might be due to invalid IL or missing references) //IL_0558: Unknown result type (might be due to invalid IL or missing references) //IL_04ab: Unknown result type (might be due to invalid IL or missing references) //IL_04b2: Unknown result type (might be due to invalid IL or missing references) try { Component val = (Component)((__instance is Component) ? __instance : null); string text = (((Object)(object)val != (Object)null) ? TransformPath(val.transform) : ""); string text2 = ""; string text3 = ""; if (_materialField != null) { object? value = _materialField.GetValue(__instance); Material val2 = (Material)((value is Material) ? value : null); if ((Object)(object)val2 == (Object)null) { text2 = ""; text3 = ""; } else { text2 = Plugin.StripCloneSuffix(((Object)val2).name); text3 = (((Object)(object)val2.shader != (Object)null) ? ((Object)val2.shader).name : ""); } } string arg = ""; if (_colorFields.Length != 0) { StringBuilder stringBuilder = new StringBuilder(); FieldInfo[] colorFields = _colorFields; foreach (FieldInfo fieldInfo in colorFields) { object value2 = null; try { value2 = fieldInfo.GetValue(__instance); } catch { } if (stringBuilder.Length > 0) { stringBuilder.Append(" "); } stringBuilder.Append(fieldInfo.Name).Append('=').Append(FormatColorField(value2)); } arg = stringBuilder.ToString(); } string text4 = ""; if (_trailObjectField != null) { object? value3 = _trailObjectField.GetValue(__instance); GameObject val3 = (GameObject)((value3 is GameObject) ? value3 : null); if ((Object)(object)val3 == (Object)null) { text4 = ""; } else { Renderer component = val3.GetComponent(); Material val4 = (((Object)(object)component != (Object)null) ? component.sharedMaterial : null); if ((Object)(object)val4 == (Object)null) { text4 = ""; } else { string text5 = (((Object)(object)val4.shader != (Object)null) ? ((Object)val4.shader).name : ""); string text6 = (val4.HasProperty("_TintColor") ? FormatColorField(val4.GetColor("_TintColor")) : ""); string text7 = (val4.HasProperty("_EmissionColor") ? FormatColorField(val4.GetColor("_EmissionColor")) : ""); string text8 = (val4.HasProperty("_Color") ? FormatColorField(val4.color) : ""); string[] shaderKeywords = val4.shaderKeywords; string text9 = ((shaderKeywords == null || shaderKeywords.Length == 0) ? "" : string.Join("|", shaderKeywords)); text4 = "name='" + Plugin.StripCloneSuffix(((Object)val4).name) + "' shader='" + text5 + "'" + $" matId={((Object)val4).GetInstanceID()}" + " _TintColor=" + text6 + " _EmissionColor=" + text7 + " _Color=" + text8 + " keywords=" + text9; if ((Object)(object)val4.shader != (Object)null && !val4.shader.isSupported && IsAdditiveParticleShaderName(text5)) { Shader val5 = Plugin.FindShaderByNameInternal(text5); if ((Object)(object)val5 != (Object)null && ((Object)val5).GetInstanceID() != ((Object)val4.shader).GetInstanceID()) { int instanceID = ((Object)val4.shader).GetInstanceID(); val4.shader = val5; Plugin.Log.LogInfo((object)("[TrailRebind] '" + text5 + "' on mat '" + Plugin.StripCloneSuffix(((Object)val4).name) + "'" + $" rebound from broken instance id={instanceID} (supported=False)" + $" → canonical id={((Object)val5).GetInstanceID()} (supported=True).")); } if (val4.HasProperty("_TintColor")) { Color val6 = default(Color); ((Color)(ref val6))..ctor(0.5f, 0.5f, 0.5f, 0.5f); Color color = val4.GetColor("_TintColor"); if (!(Math.Abs(color.r - val6.r) < 0.01f) || !(Math.Abs(color.g - val6.g) < 0.01f) || !(Math.Abs(color.b - val6.b) < 0.01f) || !(Math.Abs(color.a - val6.a) < 0.01f)) { val4.SetColor("_TintColor", val6); Plugin.Log.LogInfo((object)("[TrailRebind] '" + text5 + "' on mat '" + Plugin.StripCloneSuffix(((Object)val4).name) + "'" + $" had _TintColor=({color.r:F3},{color.g:F3}," + $"{color.b:F3},{color.a:F3}) → neutralized to" + " vanilla grey (0.5,0.5,0.5,0.5) so the trail matches vanilla with no edge color (Option B: full vanilla match).")); } } } } } } if (Plugin.TrailProbe) { Plugin.Log.LogInfo((object)("[TrailProbe] MeleeWeaponTrail.Start go='" + text + "' trailMaterial='" + text2 + "' shader='" + text3 + "'" + $" colorFields[{_colorFields.Length}]: {arg}" + " liveClone: " + text4)); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("[TrailProbe] swallowed exception: " + ex.GetType().Name + ": " + ex.Message)); } } private static string TransformPath(Transform? t) { if ((Object)(object)t == (Object)null) { return ""; } List list = new List(); Transform val = t; int num = 0; while ((Object)(object)val != (Object)null && num++ < 64) { list.Add(Plugin.StripCloneSuffix(((Object)val).name)); val = val.parent; } StringBuilder stringBuilder = new StringBuilder(); for (int num2 = list.Count - 1; num2 >= 0; num2--) { stringBuilder.Append('/').Append(list[num2]); } return stringBuilder.ToString(); } } [HarmonyPatch(typeof(VisEquipment), "AttachItem")] internal static class PatchVisEquipmentAttachItem { private static readonly HashSet _autoPolishedEquippedMatIds = new HashSet(); private static void Postfix(VisEquipment __instance) { if ((Object)(object)Plugin.Instance == (Object)null) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[AttachItem] " + ((Object)__instance).name + " renderers:"); Renderer[] componentsInChildren = ((Component)__instance).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { Material[] sharedMaterials = val.sharedMaterials; foreach (Material val2 in sharedMaterials) { if (!((Object)(object)val2 == (Object)null)) { string[] obj = new string[7] { " [", ((object)val).GetType().Name, "] mat='", ((Object)val2).name, "' shader='", null, null }; Shader shader = val2.shader; obj[5] = ((shader != null) ? ((Object)shader).name : null); obj[6] = "'"; stringBuilder.Append(string.Concat(obj)); if (!Plugin.IsMaterialProcessedPublic(val2)) { Plugin.DumpMatPropertiesPublic(val2, "AttachItem"); Plugin.TryReplaceMaterialPublic(val2); } } } } if (Plugin.AutoPolishEquippedMetal) { Renderer[] componentsInChildren2 = ((Component)__instance).GetComponentsInChildren(true); foreach (Renderer val3 in componentsInChildren2) { if ((Object)(object)val3 == (Object)null) { continue; } Material[] sharedMaterials2 = val3.sharedMaterials; foreach (Material val4 in sharedMaterials2) { if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4.shader == (Object)null) && !(((Object)val4.shader).name != "Custom/Creature") && val4.HasProperty("_MetallicGlossMap") && !((Object)(object)val4.GetTexture("_MetallicGlossMap") == (Object)null)) { int instanceID = ((Object)val4).GetInstanceID(); if (_autoPolishedEquippedMatIds.Add(instanceID)) { val4.SetFloat("_UseGlossmap", Plugin.AutoPolishUseGlossmap ? 1f : 0f); val4.SetFloat("_MetalGloss", Plugin.AutoPolishMetalGloss); val4.SetFloat("_Glossiness", Plugin.AutoPolishGlossiness); val4.SetFloat("_Metallic", Plugin.AutoPolishMetalAmount); ManualLogSource log = Plugin.Log; string[] obj2 = new string[9] { $"[AutoPolishEquippedMetal] polished mat='{((Object)val4).name}' matId={instanceID}", " shader='", ((Object)val4.shader).name, "' metallicGlossMap='", null, null, null, null, null }; Texture texture = val4.GetTexture("_MetallicGlossMap"); obj2[4] = ((texture != null) ? ((Object)texture).name : null); obj2[5] = "'"; obj2[6] = $" → _UseGlossmap={(Plugin.AutoPolishUseGlossmap ? 1 : 0)}"; obj2[7] = $" _MetalGloss={Plugin.AutoPolishMetalGloss:F2} _Glossiness={Plugin.AutoPolishGlossiness:F2}"; obj2[8] = $" _Metallic={Plugin.AutoPolishMetalAmount:F2}"; log.LogDebug((object)string.Concat(obj2)); } } } } } Plugin.Log.LogDebug((object)stringBuilder.ToString()); } } [HarmonyPatch] internal static class PatchAssetBundleLoadFromFile { private static IEnumerable TargetMethods() { Type t = typeof(AssetBundle); MethodInfo sync = AccessTools.Method(t, "LoadFromFile", new Type[1] { typeof(string) }, (Type[])null); if (sync != null) { yield return sync; } MethodInfo crc = AccessTools.Method(t, "LoadFromFile", new Type[2] { typeof(string), typeof(uint) }, (Type[])null); if (crc != null) { yield return crc; } MethodInfo off = AccessTools.Method(t, "LoadFromFile", new Type[3] { typeof(string), typeof(uint), typeof(ulong) }, (Type[])null); if (off != null) { yield return off; } } private static void Postfix(AssetBundle __result, string path) { Plugin.OnBundleLoadedFromFile(__result, path); } } [HarmonyPatch] internal static class PatchAssetBundleLoadFromMemory { private static IEnumerable TargetMethods() { Type t = typeof(AssetBundle); MethodInfo one = AccessTools.Method(t, "LoadFromMemory", new Type[1] { typeof(byte[]) }, (Type[])null); if (one != null) { yield return one; } MethodInfo two = AccessTools.Method(t, "LoadFromMemory", new Type[2] { typeof(byte[]), typeof(uint) }, (Type[])null); if (two != null) { yield return two; } } private static void Postfix(AssetBundle __result) { Plugin.OnBundleLoadedFromMemoryOrStream(__result); } } [HarmonyPatch] internal static class PatchAssetBundleLoadFromStream { private static IEnumerable TargetMethods() { Type t = typeof(AssetBundle); MethodInfo one = AccessTools.Method(t, "LoadFromStream", new Type[1] { typeof(Stream) }, (Type[])null); if (one != null) { yield return one; } MethodInfo two = AccessTools.Method(t, "LoadFromStream", new Type[2] { typeof(Stream), typeof(uint) }, (Type[])null); if (two != null) { yield return two; } MethodInfo three = AccessTools.Method(t, "LoadFromStream", new Type[3] { typeof(Stream), typeof(uint), typeof(uint) }, (Type[])null); if (three != null) { yield return three; } } private static void Postfix(AssetBundle __result) { Plugin.OnBundleLoadedFromMemoryOrStream(__result); } } [HarmonyPatch] internal static class PatchAssetBundleLoadFromFileAsync { private static IEnumerable TargetMethods() { Type t = typeof(AssetBundle); MethodInfo one = AccessTools.Method(t, "LoadFromFileAsync", new Type[1] { typeof(string) }, (Type[])null); if (one != null) { yield return one; } MethodInfo two = AccessTools.Method(t, "LoadFromFileAsync", new Type[2] { typeof(string), typeof(uint) }, (Type[])null); if (two != null) { yield return two; } MethodInfo three = AccessTools.Method(t, "LoadFromFileAsync", new Type[3] { typeof(string), typeof(uint), typeof(ulong) }, (Type[])null); if (three != null) { yield return three; } } private static void Postfix(AssetBundleCreateRequest __result, string path) { if (__result != null) { ((AsyncOperation)__result).completed += delegate { Plugin.OnBundleLoadedFromFile(__result.assetBundle, path); }; } } } [HarmonyPatch] internal static class PatchAssetBundleLoadFromMemoryAsync { private static IEnumerable TargetMethods() { Type t = typeof(AssetBundle); MethodInfo one = AccessTools.Method(t, "LoadFromMemoryAsync", new Type[1] { typeof(byte[]) }, (Type[])null); if (one != null) { yield return one; } MethodInfo two = AccessTools.Method(t, "LoadFromMemoryAsync", new Type[2] { typeof(byte[]), typeof(uint) }, (Type[])null); if (two != null) { yield return two; } } private static void Postfix(AssetBundleCreateRequest __result) { if (__result != null) { ((AsyncOperation)__result).completed += delegate { Plugin.OnBundleLoadedFromMemoryOrStream(__result.assetBundle); }; } } } [HarmonyPatch] internal static class PatchAssetBundleLoadFromStreamAsync { private static IEnumerable TargetMethods() { Type t = typeof(AssetBundle); MethodInfo one = AccessTools.Method(t, "LoadFromStreamAsync", new Type[1] { typeof(Stream) }, (Type[])null); if (one != null) { yield return one; } MethodInfo two = AccessTools.Method(t, "LoadFromStreamAsync", new Type[2] { typeof(Stream), typeof(uint) }, (Type[])null); if (two != null) { yield return two; } MethodInfo three = AccessTools.Method(t, "LoadFromStreamAsync", new Type[3] { typeof(Stream), typeof(uint), typeof(uint) }, (Type[])null); if (three != null) { yield return three; } } private static void Postfix(AssetBundleCreateRequest __result) { if (__result != null) { ((AsyncOperation)__result).completed += delegate { Plugin.OnBundleLoadedFromMemoryOrStream(__result.assetBundle); }; } } } internal static class AssetBundleMethodResolver { public static MethodInfo? FindNonGeneric(string name, Type[] paramTypes) { MethodInfo[] methods = typeof(AssetBundle).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.IsGenericMethodDefinition || methodInfo.Name != name) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length != paramTypes.Length) { continue; } bool flag = true; for (int j = 0; j < parameters.Length; j++) { if (parameters[j].ParameterType != paramTypes[j]) { flag = false; break; } } if (flag) { return methodInfo; } } return null; } } [HarmonyPatch] internal static class PatchAssetBundleLoadAssetNoType { private static IEnumerable TargetMethods() { MethodInfo m = AssetBundleMethodResolver.FindNonGeneric("LoadAsset", new Type[1] { typeof(string) }); if (m != null) { yield return m; } } private static void Postfix(AssetBundle __instance, Object __result) { if (!(__result == (Object)null)) { Plugin.OnLoadAssetResult(__instance, __result); Material val = (Material)(object)((__result is Material) ? __result : null); if (val != null) { Plugin.TryReplaceMaterialPublic(val); } } } } [HarmonyPatch] internal static class PatchAssetBundleLoadAsset { private static IEnumerable TargetMethods() { MethodInfo m = AssetBundleMethodResolver.FindNonGeneric("LoadAsset", new Type[2] { typeof(string), typeof(Type) }); if (m != null) { yield return m; } } private static void Postfix(AssetBundle __instance, Object __result) { if (!(__result == (Object)null)) { Plugin.OnLoadAssetResult(__instance, __result); Material val = (Material)(object)((__result is Material) ? __result : null); if (val != null) { Plugin.TryReplaceMaterialPublic(val); } } } } [HarmonyPatch] internal static class PatchAssetBundleLoadAllAssets { private static IEnumerable TargetMethods() { MethodInfo none = AssetBundleMethodResolver.FindNonGeneric("LoadAllAssets", new Type[0]); if (none != null) { yield return none; } MethodInfo typed = AssetBundleMethodResolver.FindNonGeneric("LoadAllAssets", new Type[1] { typeof(Type) }); if (typed != null) { yield return typed; } } private static void Postfix(AssetBundle __instance, Object[] __result) { if (__result == null) { return; } foreach (Object val in __result) { if (!(val == (Object)null)) { Plugin.OnLoadAssetResult(__instance, val); Material val2 = (Material)(object)((val is Material) ? val : null); if (val2 != null) { Plugin.TryReplaceMaterialPublic(val2); } } } } } [HarmonyPatch] internal static class PatchAssetBundleLoadAssetAsync { private static IEnumerable TargetMethods() { MethodInfo one = AssetBundleMethodResolver.FindNonGeneric("LoadAssetAsync", new Type[1] { typeof(string) }); if (one != null) { yield return one; } MethodInfo two = AssetBundleMethodResolver.FindNonGeneric("LoadAssetAsync", new Type[2] { typeof(string), typeof(Type) }); if (two != null) { yield return two; } } private static void Postfix(AssetBundle __instance, AssetBundleRequest __result) { if (__result == null) { return; } ((AsyncOperation)__result).completed += delegate { Object asset = __result.asset; if (!(asset == (Object)null)) { Plugin.OnLoadAssetResult(__instance, asset); Material val = (Material)(object)((asset is Material) ? asset : null); if (val != null) { Plugin.TryReplaceMaterialPublic(val); } } }; } } [HarmonyPatch] internal static class PatchAssetBundleLoadAllAssetsAsync { private static IEnumerable TargetMethods() { MethodInfo none = AssetBundleMethodResolver.FindNonGeneric("LoadAllAssetsAsync", new Type[0]); if (none != null) { yield return none; } MethodInfo typed = AssetBundleMethodResolver.FindNonGeneric("LoadAllAssetsAsync", new Type[1] { typeof(Type) }); if (typed != null) { yield return typed; } } private static void Postfix(AssetBundle __instance, AssetBundleRequest __result) { if (__result == null) { return; } ((AsyncOperation)__result).completed += delegate { Object[] allAssets = __result.allAssets; if (allAssets != null) { Object[] array = allAssets; foreach (Object val in array) { if (!(val == (Object)null)) { Plugin.OnLoadAssetResult(__instance, val); Material val2 = (Material)(object)((val is Material) ? val : null); if (val2 != null) { Plugin.TryReplaceMaterialPublic(val2); } } } } }; } } internal static class PatchEffectListCreateSwingProbe { private static IEnumerable TargetMethods() { MethodInfo m = AccessTools.Method(typeof(EffectList), "Create", new Type[5] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int) }, (Type[])null); if (m != null) { yield return m; } } private static void Postfix(GameObject[] __result) { try { if (__result == null) { return; } foreach (GameObject val in __result) { if ((Object)(object)val == (Object)null) { continue; } Renderer[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Renderer val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null) { continue; } Material[] sharedMaterials = val2.sharedMaterials; foreach (Material val3 in sharedMaterials) { if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.shader == (Object)null) && ((Object)val3.shader).name.StartsWith("Hidden/InternalErrorShader", StringComparison.OrdinalIgnoreCase)) { Plugin.Log.LogInfo((object)("[SwingProbe] EffectList.Create spawned error-shader material: mat='" + ((Object)val3).name + "' shader='" + ((Object)val3.shader).name + "'" + $" matId={((Object)val3).GetInstanceID()}" + " rendererType=" + ((object)val2).GetType().Name + " go='" + ((Object)((Component)val2).gameObject).name + "' rootGo='" + ((Object)val).name + "'" + $" alreadyProcessed={Plugin.IsMaterialProcessedPublic(val3)}" + $" isErrorPink={Plugin.IsErrorPinkPublic(val3)}")); } } } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("[SwingProbe] swallowed exception: " + ex.GetType().Name + ": " + ex.Message)); } } } internal static class PatchAttackSwingProbe2 { private const int HeartbeatCap = 8; private static int _heartbeatCount; private const int SceneScanCap = 4; private static int _sceneScanCount; private const int WindowCap = 2; private const int WindowFrames = 30; private static int _windowCount; private static bool _windowRunning; private static IEnumerable TargetMethods() { MethodInfo trigger = AccessTools.Method(typeof(Attack), "OnAttackTrigger", Type.EmptyTypes, (Type[])null); if (trigger != null) { yield return trigger; } MethodInfo melee = AccessTools.Method(typeof(Attack), "DoMeleeAttack", Type.EmptyTypes, (Type[])null); if (melee != null) { yield return melee; } } private static string TransformPath(Transform t) { StringBuilder stringBuilder = new StringBuilder(); List list = new List(); Transform val = t; int num = 0; while ((Object)(object)val != (Object)null && num++ < 64) { list.Add(Plugin.StripCloneSuffix(((Object)val).name)); val = val.parent; } for (int num2 = list.Count - 1; num2 >= 0; num2--) { stringBuilder.Append('/').Append(list[num2]); } return stringBuilder.ToString(); } private static void Postfix(MethodBase __originalMethod) { try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } string text = __originalMethod?.Name ?? "Attack"; int num = 0; int num2 = 0; Renderer[] componentsInChildren = ((Component)localPlayer).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } num++; Material[] sharedMaterials = val.sharedMaterials; foreach (Material val2 in sharedMaterials) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.shader == (Object)null) && ((Object)val2.shader).name.StartsWith("Hidden/InternalErrorShader", StringComparison.OrdinalIgnoreCase)) { num2++; Plugin.Log.LogInfo((object)("[SwingProbe2] hook=" + text + " error-shader material under local player: mat='" + ((Object)val2).name + "' shader='" + ((Object)val2.shader).name + "'" + $" matId={((Object)val2).GetInstanceID()}" + " rendererType=" + ((object)val).GetType().Name + " go='" + ((Object)((Component)val).gameObject).name + "'" + $" alreadyProcessed={Plugin.IsMaterialProcessedPublic(val2)}" + $" isErrorPink={Plugin.IsErrorPinkPublic(val2)}")); } } } if (_sceneScanCount < 4) { _sceneScanCount++; int num3 = 0; int num4 = 0; Renderer[] array = Resources.FindObjectsOfTypeAll(); foreach (Renderer val3 in array) { if ((Object)(object)val3 == (Object)null) { continue; } num3++; Material[] sharedMaterials2 = val3.sharedMaterials; foreach (Material val4 in sharedMaterials2) { if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4.shader == (Object)null) && ((Object)val4.shader).name.StartsWith("Hidden/InternalErrorShader", StringComparison.OrdinalIgnoreCase)) { num4++; Plugin.Log.LogInfo((object)($"[SwingProbe2-Scene] scan #{_sceneScanCount}/{4} hook={text}" + " mat='" + ((Object)val4).name + "' shader='" + ((Object)val4.shader).name + "'" + $" matId={((Object)val4).GetInstanceID()}" + " rendererType=" + ((object)val3).GetType().Name + " path='" + TransformPath(((Component)val3).transform) + "'" + $" activeInHierarchy={((Component)val3).gameObject.activeInHierarchy}" + $" rendererEnabled={val3.enabled}" + $" alreadyProcessed={Plugin.IsMaterialProcessedPublic(val4)}" + $" isErrorPink={Plugin.IsErrorPinkPublic(val4)}")); } } } Plugin.Log.LogInfo((object)($"[SwingProbe2-Scene] scan #{_sceneScanCount}/{4} hook={text}" + $" sceneRenderers={num3} sceneErrorShaderMatches={num4}")); } if (_windowCount < 2 && !_windowRunning && (Object)(object)Plugin.Instance != (Object)null) { _windowCount++; _windowRunning = true; ((MonoBehaviour)Plugin.Instance).StartCoroutine(PerFrameWindowScan(text, _windowCount)); } if (_heartbeatCount < 8) { _heartbeatCount++; Plugin.Log.LogInfo((object)($"[SwingProbe2] heartbeat #{_heartbeatCount}/{8} hook={text}" + $" renderersScanned={num} errorShaderMatches={num2}")); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("[SwingProbe2] swallowed exception: " + ex.GetType().Name + ": " + ex.Message)); } } private static IEnumerator PerFrameWindowScan(string hook, int windowIndex) { Plugin.Log.LogInfo((object)($"[SwingProbe2-Window] window #{windowIndex}/{2} OPEN hook={hook}" + " — DE-ASSUMED per-frame scan (error-pink-by-color OR trail/line/particle" + $" renderer; reads sharedMaterials AND materials) for up to {30} frames")); HashSet loggedKeys = new HashSet(StringComparer.Ordinal); int distinctLogged = 0; MaterialPropertyBlock mpbScratch = new MaterialPropertyBlock(); HashSet pinkSourceKeys = new HashSet(StringComparer.Ordinal); int pinkSourceLogged = 0; for (int frame = 0; frame < 30; frame++) { yield return (object)new WaitForEndOfFrame(); Renderer[] all; try { all = Resources.FindObjectsOfTypeAll(); } catch (Exception ex) { Plugin.Log.LogDebug((object)$"[SwingProbe2-Window] enumerate failed frame={frame}: {ex.Message}"); continue; } Renderer[] array = all; foreach (Renderer r in array) { if ((Object)(object)r == (Object)null) { continue; } bool isTrailLikeRenderer = r is TrailRenderer || r is LineRenderer || r is ParticleSystemRenderer; Material[] shared; try { shared = r.sharedMaterials; } catch { shared = Array.Empty(); } bool sharedHasPink = false; Material[] array2 = shared; foreach (Material mat in array2) { if (!((Object)(object)mat == (Object)null)) { bool pink = false; try { pink = Plugin.IsErrorPinkPublic(mat); } catch { } if (pink) { sharedHasPink = true; } if (pink || isTrailLikeRenderer) { EmitWindowMatch(windowIndex, frame, "shared", r, mat, pink, loggedKeys, ref distinctLogged); } } } if (isTrailLikeRenderer || sharedHasPink) { Material[] inst; try { inst = r.materials; } catch { inst = Array.Empty(); } Material[] array3 = inst; foreach (Material mat2 in array3) { if (!((Object)(object)mat2 == (Object)null)) { bool pink2 = false; try { pink2 = Plugin.IsErrorPinkPublic(mat2); } catch { } if (pink2 || isTrailLikeRenderer) { EmitWindowMatch(windowIndex, frame, "instance", r, mat2, pink2, loggedKeys, ref distinctLogged); } } } } if (isTrailLikeRenderer) { try { EmitPinkSourceMatch(windowIndex, frame, r, mpbScratch, pinkSourceKeys, ref pinkSourceLogged); } catch (Exception ex2) { Exception ex3 = ex2; Plugin.Log.LogDebug((object)("[PinkSourceProbe] swallowed exception: " + ex3.GetType().Name + ": " + ex3.Message)); } } } } Plugin.Log.LogInfo((object)($"[SwingProbe2-Window] window #{windowIndex} CLOSED at frame cap ({30})" + $" — distinctLogged={distinctLogged} (each line is a candidate for what the pink" + " swing arc actually is; 0 means neither error-pink-by-color nor any trail/line/ particle material appeared in the window — next probe widens render technique).")); _windowRunning = false; } private static void EmitWindowMatch(int windowIndex, int frame, string slot, Renderer r, Material mat, bool pink, HashSet loggedKeys, ref int distinctLogged) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) string text = (((Object)(object)mat.shader != (Object)null) ? ((Object)mat.shader).name : ""); string text2; try { text2 = TransformPath(((Component)r).transform); } catch { text2 = ""; } string item = $"{((object)r).GetType().Name}|{text}|{text2}|{pink}|{slot}"; if (!loggedKeys.Add(item)) { return; } distinctLogged++; string arg = "n/a"; try { if (mat.HasProperty("_Color")) { arg = ((object)mat.color/*cast due to .constrained prefix*/).ToString(); } } catch { } Plugin.Log.LogInfo((object)($"[SwingProbe2-Window] window #{windowIndex} frame=+{frame} CANDIDATE slot={slot}" + " mat='" + ((Object)mat).name + "' shader='" + text + "'" + $" matId={((Object)mat).GetInstanceID()}" + " rendererType=" + ((object)r).GetType().Name + " path='" + text2 + "'" + $" isErrorPinkByColor={pink} color={arg}" + $" activeInHierarchy={((Component)r).gameObject.activeInHierarchy}" + $" rendererEnabled={r.enabled}" + $" alreadyProcessed={Plugin.IsMaterialProcessedPublic(mat)}")); } private static bool IsMagenta(Color c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return MaterialClassifier.IsMagenta(c); } private static void EmitPinkSourceMatch(int windowIndex, int frame, Renderer r, MaterialPropertyBlock mpbScratch, HashSet pinkSourceKeys, ref int pinkSourceLogged) { //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) List<(string, Color)> list = new List<(string, Color)>(4); Material val = null; string text = ""; string text2 = ""; try { Material[] sharedMaterials = r.sharedMaterials; if (sharedMaterials != null) { Material[] array = sharedMaterials; foreach (Material val2 in array) { if ((Object)(object)val2 == (Object)null) { continue; } val = val2; text = ((Object)val2).name; text2 = (((Object)(object)val2.shader != (Object)null) ? ((Object)val2.shader).name : ""); if (val2.HasProperty("_Color")) { Color color = val2.color; if (IsMagenta(color)) { list.Add(("mat._Color", color)); } } if (val2.HasProperty("_TintColor")) { Color color2 = val2.GetColor("_TintColor"); if (IsMagenta(color2)) { list.Add(("mat._TintColor", color2)); } } if (val2.HasProperty("_EmissionColor")) { Color color3 = val2.GetColor("_EmissionColor"); if (IsMagenta(color3)) { list.Add(("mat._EmissionColor", color3)); } } break; } } } catch { } try { if (r.HasPropertyBlock()) { mpbScratch.Clear(); r.GetPropertyBlock(mpbScratch); Color color4 = mpbScratch.GetColor("_Color"); Color color5 = mpbScratch.GetColor("_MainColor"); Color color6 = mpbScratch.GetColor("_TintColor"); if (IsMagenta(color4)) { list.Add(("mpb._Color", color4)); } if (IsMagenta(color5)) { list.Add(("mpb._MainColor", color5)); } if (IsMagenta(color6)) { list.Add(("mpb._TintColor", color6)); } } } catch { } try { ParticleSystem component = ((Component)r).gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { MainModule main = component.main; MinMaxGradient startColor = ((MainModule)(ref main)).startColor; Color color7 = ((MinMaxGradient)(ref startColor)).color; if (IsMagenta(color7)) { list.Add(("ps.startColor", color7)); } } } catch { } if (list.Count == 0) { return; } string text3; try { text3 = TransformPath(((Component)r).transform); } catch { text3 = ""; } string text4 = string.Join("+", list.ConvertAll(((string src, Color col) h) => h.src)); string item = ((object)r).GetType().Name + "|" + text2 + "|" + text3 + "|" + text4; if (pinkSourceKeys.Add(item)) { pinkSourceLogged++; bool flag = list.Exists(((string src, Color col) h) => ((Color)(ref h.col)).maxColorComponent > 1f); string arg = string.Join(" ", list.ConvertAll(((string src, Color col) h) => $"{h.src}=RGBA({h.col.r:F2},{h.col.g:F2},{h.col.b:F2},{h.col.a:F2})|HDR={((Color)(ref h.col)).maxColorComponent > 1f}")); Plugin.Log.LogInfo((object)($"[PinkSourceProbe] window #{windowIndex} frame=+{frame} MAGENTA-SOURCE={text4}" + $" anyHDR={flag} values=[{arg}]" + " mat='" + text + "' shader='" + text2 + "'" + $" matId={(((Object)(object)val != (Object)null) ? ((Object)val).GetInstanceID() : 0)}" + " rendererType=" + ((object)r).GetType().Name + " path='" + text3 + "'" + $" activeInHierarchy={((Component)r).gameObject.activeInHierarchy}" + $" rendererEnabled={r.enabled}")); } } } internal static class PatchShaderFindBindProbe { private static readonly HashSet _shaderFindProbeFiredNames = new HashSet(StringComparer.Ordinal); private static void Postfix(string name, Shader __result) { //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__result == (Object)null) { return; } string text = ((Object)__result).name ?? string.Empty; if (string.IsNullOrEmpty(text) || !BundleAttribution.IsVanillaShaderName(text) || !_shaderFindProbeFiredNames.Add(text)) { return; } Shader[] array = Resources.FindObjectsOfTypeAll(); int num = 0; List list = new List(); int instanceID = ((Object)__result).GetInstanceID(); Shader[] array2 = array; foreach (Shader val in array2) { if (!((Object)(object)val == (Object)null) && !(((Object)val).name != text) && ((Object)val).GetInstanceID() != instanceID) { num++; if (list.Count < 8) { list.Add(((Object)val).GetInstanceID()); } } } if (num >= 1) { string text2 = string.Join(",", list); string text3 = ((num > list.Count) ? ("+" + (num - list.Count) + "more") : string.Empty); string text4 = "n/a"; try { int num2 = __result.FindPropertyIndex("_Color"); text4 = ((num2 < 0) ? "no-_Color" : ((object)__result.GetPropertyType(num2)/*cast due to .constrained prefix*/).ToString()); } catch (Exception) { } Plugin.Log.LogInfo((object)("[BindProbe-ShaderFind] requestedName='" + name + "' returnedName='" + text + "'" + $" returnedId={instanceID} peerCount={num} peerIds=[{text2}{text3}]" + $" returnedRQ={__result.renderQueue} returnedIsSupported={__result.isSupported}" + " schema=" + text4 + " vanillaName=true")); } } catch (Exception ex2) { Plugin.Log.LogDebug((object)("[BindProbe-ShaderFind] swallowed exception for Find request '" + (name ?? "") + "': " + ex2.GetType().Name + ": " + ex2.Message)); } } } [DefaultExecutionOrder(30000)] internal sealed class KeepRendererDisabled : MonoBehaviour { private Renderer? _renderer; private void Awake() { _renderer = ((Component)this).GetComponent(); } private void LateUpdate() { if ((Object)(object)_renderer != (Object)null && _renderer.enabled) { _renderer.enabled = false; } } } internal enum ShaderAction { Transparent, Fallback, Original, UseShader } [BepInPlugin("aaa.drummercraig.shaderhelperformac", "ShaderHelperForMac", "3.2.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "aaa.drummercraig.shaderhelperformac"; public const string PluginName = "ShaderHelperForMac"; public const string PluginVersion = "3.2.0"; internal static readonly string UseTransparentShaderFile = "usetransparentshader.txt"; internal static readonly string UseFallbackShaderFile = "usefallbackshader.txt"; internal static readonly string UseOriginalShaderFile = "useoriginalshader.txt"; internal static HashSet _useTransparentDlls = new HashSet(StringComparer.OrdinalIgnoreCase); internal static HashSet _useFallbackDlls = new HashSet(StringComparer.OrdinalIgnoreCase); internal static HashSet _useOriginalDlls = new HashSet(StringComparer.OrdinalIgnoreCase); internal static HashSet _transparentMatNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static HashSet _fallbackMatNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static HashSet _originalMatNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static List _transparentMatSuffixes = new List(); internal static List _fallbackMatSuffixes = new List(); internal static HashSet _forceFallbackMatNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static List _forceFallbackMatSuffixes = new List(); internal static HashSet _forceTransparentMatNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static List _forceTransparentMatSuffixes = new List(); internal static HashSet _forceFallbackPrefixSet = new HashSet(StringComparer.OrdinalIgnoreCase); internal static HashSet _forceTransparentPrefixSet = new HashSet(StringComparer.OrdinalIgnoreCase); internal static Dictionary _matShaderOverrides = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static Dictionary> _matShaderPropertyOverrides = new Dictionary>(StringComparer.OrdinalIgnoreCase); internal static Dictionary> _matShaderKeywordOverrides = new Dictionary>(StringComparer.OrdinalIgnoreCase); internal static HashSet _hideRendererMatNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static List _hideRendererMatSuffixes = new List(); private FileSystemWatcher? _watcher; private ConfigEntry _cfgPeriodicSweepInterval = null; private ConfigEntry _cfgBridgeTraceMatNames = null; private ConfigEntry _cfgSweepProgressTrace = null; private ConfigEntry _cfgVanillaRebindEnabled = null; private ConfigEntry _cfgPreserveAdditiveParticles = null; private ConfigEntry _cfgHunFXAutoDefault = null; private ConfigEntry _cfgRepairInternalErrorShader = null; private ConfigEntry _cfgTrailProbe = null; private ConfigEntry _cfgDiagnosticProbes = null; private ConfigEntry _cfgAutoPolishMetallic = null; private ConfigEntry _cfgAutoPolishEquippedMetal = null; private ConfigEntry _cfgAutoPolishUseGlossmap = null; private ConfigEntry _cfgAutoPolishMetalGloss = null; private ConfigEntry _cfgAutoPolishGlossiness = null; private ConfigEntry _cfgAutoPolishMetalAmount = null; private static Plugin? _instance; internal static readonly Dictionary ModShaderDlls = new Dictionary(); internal static readonly Dictionary _bundleToDll = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet _tocAttributedBundles = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly Dictionary _prefabRootToDll = new Dictionary(StringComparer.OrdinalIgnoreCase); internal static readonly string[] FallbackChain = new string[2] { "Custom/Creature", "Custom/Piece" }; internal static readonly string[] TransparentChain = new string[5] { "Particles/Standard Unlit", "Legacy Shaders/Particles/Alpha Blended", "Particles/Standard Surface2", "Sprites/Default", "UI/Default" }; internal static readonly HashSet CanonicalShaderIds = new HashSet(); internal static readonly HashSet _vanillaBundleNames = new HashSet(StringComparer.Ordinal); internal static bool _awakeVanillaSnapshotEmpty; internal static bool _zNetSceneAwakeFired; internal static readonly List _deferredMemoryStreamBundles = new List(); internal static string? _valheimDataPath; internal static readonly HashSet _modPrefabRootNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet _prefabScopedModMatIds = new HashSet(); internal static readonly HashSet _particleRendererMatIdHints = new HashSet(); internal static readonly HashSet _forceReplacedMaterialIds = new HashSet(); internal static readonly Dictionary _originalEmissionByMat = new Dictionary(); internal static readonly HashSet _baselineMatNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly HashSet _vanillaShaderNames = new HashSet(StringComparer.OrdinalIgnoreCase); internal static readonly string[] ValheimShaderNames = new string[55] { "Custom/Piece", "Custom/Creature", "Custom/Vegetation", "Custom/StaticRock", "Custom/Lit", "Custom/LitParticles", "Custom/FlowOpaque", "Custom/Billboard", "Custom/DistantLod", "Custom/WaterLily", "Custom/Trilinear", "Custom/Decal", "Custom/Cliff", "Custom/Skybox", "Custom/Distortion", "Custom/Player", "Custom/Blob", "Custom/Bonemass", "Custom/Grass", "Custom/Heightmap", "Custom/Rug", "Custom/ShadowBlob", "Custom/Tar", "Custom/Trilinearmap", "Custom/Yggdrasil", "Custom/Yggdrasil_root", "Custom/Water", "Custom/WaterBottom", "Custom/WaterMask", "Custom/Clouds", "Custom/Flow", "Custom/SkyboxProcedural", "Custom/SkyObject", "Custom/icon", "Custom/LitGui", "Custom/mapshader", "Custom/UI/AntiAliasedCircle", "Custom/UI_BGBlur", "Custom/AlphaParticle", "Custom/Gradient Mapped Particle (Unlit)", "Custom/Mesh Flipbook Particle", "Custom/Particle (Unlit)", "Custom/ParticleDecal", "Particles/Standard Unlit2", "Particles/Standard Surface2", "Standard TwoSided", "FX/Glass/Stained BumpDistort", "FX/Glass/Stained", "Unlit/Lighting", "ToonDeferredShading", "ToonDeferredShading2017", "Earthquake/Tree", "Earthquake/Leaf", "Hovl/Particles/Additive", "Hovl/Particles/AlphaBlend" }; internal static readonly HashSet ValheimShaderNameSet = new HashSet(ValheimShaderNames, StringComparer.OrdinalIgnoreCase); internal static readonly string[] UnityBuiltInShaderNames = new string[36] { "Standard", "Standard (Specular setup)", "Standard (Roughness setup)", "Sprites/Default", "Sprites/Mask", "UI/Default", "UI/DefaultFont", "UI/Unlit/Text", "UI/Unlit/Transparent", "Unlit/Color", "Unlit/Texture", "Unlit/Transparent", "Unlit/Transparent Cutout", "Diffuse", "Mobile/Diffuse", "Transparent/Diffuse", "VertexLit", "Mobile/VertexLit", "Legacy Shaders/Particles/Alpha Blended", "Legacy Shaders/Particles/Additive", "Legacy Shaders/Particles/Additive (Soft)", "Legacy Shaders/Particles/Multiply", "Legacy Shaders/Particles/VertexLit Blended", "Particles/Standard Unlit", "Particles/Standard Surface", "TextMeshPro/Distance Field", "TextMeshPro/Distance Field (Surface)", "TextMeshPro/Distance Field Overlay", "TextMeshPro/Distance Field SSD", "TextMeshPro/Mobile/Distance Field", "TextMeshPro/Mobile/Distance Field - Masking", "TextMeshPro/Mobile/Distance Field Overlay", "TextMeshPro/Bitmap", "TextMeshPro/Mobile/Bitmap", "TextMeshPro/Sprite", "GUI/Text Shader" }; internal static readonly HashSet UnityBuiltInShaderNameSet = new HashSet(UnityBuiltInShaderNames, StringComparer.OrdinalIgnoreCase); internal static Material? _transparentMat; internal static Material? _fallbackMat; internal bool _initialized; internal bool _sweepRunning; internal static bool _sweepDisabled; internal static string? _cachedValheimVersionForDriftCheck; internal static int _consecutiveSweepSkips; internal static bool _forceNextPeriodicSweep; internal static ManualLogSource Log { get; private set; } = null; internal static Plugin Instance { get; private set; } = null; internal static string ConfigDir => Path.Combine(Paths.ConfigPath, "ShaderHelperForMac"); internal static float PeriodicSweepInterval => _instance?._cfgPeriodicSweepInterval.Value ?? 30f; internal static HashSet BridgeTraceMatNames { get { string text = _instance?._cfgBridgeTraceMatNames.Value ?? string.Empty; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(text)) { return hashSet; } string[] array = text.Split(new char[1] { ',' }); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length > 0) { hashSet.Add(text3); } } return hashSet; } } internal static bool SweepProgressTrace => _instance?._cfgSweepProgressTrace?.Value == true; internal static bool VanillaRebindEnabled => _instance?._cfgVanillaRebindEnabled?.Value ?? true; internal static bool PreserveAdditiveParticles => _instance?._cfgPreserveAdditiveParticles?.Value == true; internal static bool HunFXAutoDefault => _instance?._cfgHunFXAutoDefault?.Value ?? true; internal static bool AutoPolishMetallic => _instance?._cfgAutoPolishMetallic?.Value == true; internal static bool AutoPolishEquippedMetal => _instance?._cfgAutoPolishEquippedMetal?.Value ?? true; internal static float AutoPolishMetalAmount => _instance?._cfgAutoPolishMetalAmount?.Value ?? 1f; internal static bool AutoPolishUseGlossmap => _instance?._cfgAutoPolishUseGlossmap?.Value ?? true; internal static float AutoPolishMetalGloss => _instance?._cfgAutoPolishMetalGloss?.Value ?? 0.7f; internal static float AutoPolishGlossiness => _instance?._cfgAutoPolishGlossiness?.Value ?? 0.9f; internal static bool RepairInternalErrorShader => _instance?._cfgRepairInternalErrorShader?.Value ?? true; internal static bool TrailProbe => _instance?._cfgTrailProbe?.Value == true; internal static bool DiagnosticProbes => _instance?._cfgDiagnosticProbes?.Value == true; internal static bool _transparentShaderIsParticleCapable { get; set; } internal static Shader? FallbackShader { get; set; } internal static string StripCloneSuffix(string name) { while (name.EndsWith("(Clone)", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - "(Clone)".Length); } return name; } internal static void PopulateLane5FromSceneWalk() { _prefabScopedModMatIds.Clear(); if (_modPrefabRootNames.Count == 0) { return; } int num = 0; int num2 = 0; Renderer[] array = Resources.FindObjectsOfTypeAll(); Renderer[] array2 = array; foreach (Renderer val in array2) { if ((Object)(object)val == (Object)null) { continue; } bool flag = false; Transform val2 = ((Component)val).transform; while ((Object)(object)val2 != (Object)null) { string item = StripCloneSuffix(((Object)val2).name); if (_modPrefabRootNames.Contains(item)) { flag = true; break; } val2 = val2.parent; } if (!flag) { continue; } num++; Material[] sharedMaterials = val.sharedMaterials; foreach (Material val3 in sharedMaterials) { if (!((Object)(object)val3 == (Object)null) && _prefabScopedModMatIds.Add(((Object)val3).GetInstanceID())) { num2++; } } } if (num > 0) { Log.LogDebug((object)$"[Provenance] Lane-5 prefab scope: {num} renderer(s) under declared [prefab] ancestor(s), admitting {num2} material instance(s)"); } } internal static void CheckCachedVanillaCacheVersionDrift() { if (_cachedValheimVersionForDriftCheck != null) { string valheimVersionViaReflection = VanillaBaselineCache.GetValheimVersionViaReflection(); if (!string.Equals(_cachedValheimVersionForDriftCheck, valheimVersionViaReflection, StringComparison.Ordinal)) { Log.LogWarning((object)("[VanillaCache] version drift: cache was produced for Valheim '" + _cachedValheimVersionForDriftCheck + "' but this install reports '" + valheimVersionViaReflection + "'. Keeping the cache loaded; if you observe vanilla materials being replaced, run `shaderhelper capturevanilla` from a clean install to refresh.")); } _cachedValheimVersionForDriftCheck = null; } } private void Awake() { Instance = this; _instance = this; Log = ((BaseUnityPlugin)this).Logger; BundleAttribution.EmitBaselineSnapshot("Awake-Start"); _cfgPeriodicSweepInterval = ((BaseUnityPlugin)this).Config.Bind("General", "Periodic Sweep Interval", 600f, "How often (in seconds) to re-sweep all materials. The periodic sweep is now a safety net for unobserved bundle-load / prefab-spawn events; the primary mechanisms are Harmony postfixes on ZNetScene.Awake, Location.Awake, AssetBundle.Load*, VisEquipment.AttachItem, and the scene-load handler. Default raised to 600 s in v3.0.63 because the v2.1.17 admission-epoch skip-gate already short-circuits every periodic sweep on a stable scene; the cadence only matters when the safety cap (10 consecutive skips) forces a full sweep. Lower this (e.g. 120 or 30) if you run VFX-heavy mods that spawn materials frequently and you observe pink materials between sweeps. Set to 0 to disable the periodic timer entirely."); _cfgBridgeTraceMatNames = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Bridge Trace Mat Names", "", "Diagnostic-only. Comma-separated list of material base names (case-insensitive, after StripInstance normalization). When a material's name is in this list, BridgeMaterialProperties emits one [BridgeTrace] log line per bridge call with the source _EmissionColor, IsHdrEmission result, _EMISSION keyword state, and HasEmissiveIntent result. Empty disables the trace (default). Used to gather evidence for the v3.0.7 emission-bridge timing investigation; see LESSONS_LEARNED.md and CHANGELOG v3.0.7. Pure read-only observation — does not affect any gate, sweep, or resolver decision."); _cfgSweepProgressTrace = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Sweep Progress Trace", false, "Diagnostic-only. When true, SweepMaterialsCoroutine emits one [SweepProgress] log line per SweepBatchSize (=100) renderer batch, reporting per-batch wall-clock, cumulative sweep time, renderer index, mats processed, and replacement count. Used to identify where periodic-sweep wall-clock cost is concentrated when investigating frame-time stutter. Default false; enable for one diagnostic session, then disable. Pure read-only observation — does not affect any gate, sweep decision, or resolver outcome. See CHANGELOG v3.0.16."); _cfgVanillaRebindEnabled = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Vanilla Rebind Enabled", true, "When true (DEFAULT), TryReplaceMaterial rebinds any material to the canonical Unity instance of its vanilla-named shader, ONCE per material per session, AND ONLY when the material is on a ParticleSystemRenderer this sweep. Fixes the dropped-item particle indicator (item_particle on Magic_Supremacy drops) without re-introducing the v2.1.27-class weapon flicker that v3.0.27's unscoped rebind caused. Set to false to disable the rebind entirely (restoring v3.0.28 behaviour: item_particle renders pink on dropped items, but no weapon flicker risk). The flag is provided as a kill-switch only — flipping to false is intended for users who observe regressions on materials this rebind class shouldn't have touched. See CHANGELOG v3.0.30 for the v3.0.29 evidence and v3.0.30 design."); _cfgPreserveAdditiveParticles = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Preserve Additive Particles", true, "Default ON (v3.1.33). When true, a gate-admitted mod material whose current shader is already a supported additive / alpha-blended particle shader (e.g. 'Legacy Shaders/Particles/Additive', 'Particles/Standard Unlit') AND has no hard mat:/suffix: rule is LEFT ON ITS OWN SHADER instead of being routed through the aggressive-family Fallback bind to Custom/Creature (which drops the additive _TintColor/_InvFade controls — the flame_TW 'good look' loss). This reproduces, without a rule, what '[useshader] Legacy Shaders/Particles/Additive' achieves. Any user [forcefallback]/[forcetransparent]/[useshader] rule still wins (the branch is gated on no-hard-rule). Vanilla materials are unaffected (the provenance veto runs first). The v3.1.32 default-OFF opt-in was validated in-game (2165 [PreserveAdditive] fires across 36 distinct VFX materials on 4 shader families, zero solid geometry caught) and flipped to default-ON in v3.1.33. Set to false to disable the preservation branch and route additive particles through the aggressive-family Fallback. See CHANGELOG v3.1.32 + v3.1.33."); _cfgHunFXAutoDefault = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "HunFX Auto Default", true, "Default ON (v3.1.54, validated in-game). When true, a gate-admitted mod material whose current shader is in the 'HunFX/' family (the HunFX VFX shader pack — bursts / fire / embers / slashes, shader names 'HunFX/SH_HunFX_common', 'HunFX/SH_HunFX_simple', etc.) is automatically bridged to 'Legacy Shaders/Particles/Additive' — the same target users hand-write '[useshader]' rules for on these materials, applied automatically. This branch runs BEFORE the [useshader] consumption block, so it decides purely from the material's shader identity and ignores any [useshader] rule for HunFX materials by construction. Vanilla materials are unaffected (the provenance veto runs first). The v3.1.53 default-OFF opt-in was validated in-game (449 [HunFXAutoDefault] fires across 51 distinct VFX materials on 3 HunFX shaders, all landing Additive, zero solid geometry caught) and flipped to default-ON in v3.1.54. Set to false to disable the auto-default and route HunFX materials through the aggressive-family Fallback (or your own [useshader] rules). See CHANGELOG v3.1.53 + v3.1.54."); _cfgRepairInternalErrorShader = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Repair Internal Error Shader", true, "Default ON. When true, a material whose runtime shader is Unity's 'Hidden/InternalErrorShader' placeholder (the magenta error shader Unity substitutes when a real shader fails to compile or load on Metal) is admitted to the sweep and bound to the Fallback shader (Custom/Creature) so it renders visibly instead of staying pink. Set to false to leave such materials magenta — the opt-in 'show me the pink items that were on this shader' diagnostic: both recognition sites (MaterialClassifier.NeedsReplacement and ActionResolver.ResolveByShaderName) fall through, and because 'Hidden/' is on the safe-shader prefix list NeedsReplacement returns false, leaving the material on its error-shader binding. This is a sweep-side filter toggle, NOT a provenance-gate change — vanilla materials rendering correctly are never affected (their shader is not Hidden/InternalErrorShader). See CHANGELOG v3.1.34."); _cfgTrailProbe = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Trail Probe", false, "Diagnostic-only (default OFF). When true, a read-only Harmony postfix on Valheim's MeleeWeaponTrail.Start emits one [TrailProbe] log line per weapon-trail component as it is created, reporting the trail material's name + shader + the owning GameObject path. The vanilla weapon swing/sweep trail is a runtime-generated mesh on a detached GameObject('Trail') with a MeshRenderer — NOT a TrailRenderer, NOT a particle — so it cannot be named by `nearby` / `capture` / `watchr` or by the offline bundle tools. Enable this, swing a weapon, then read the [TrailProbe] lines in LogOutput.log to get the material name. Pure read-only observation — mutates no material, renderer, gate, sweep, resolver, or cache. Disable when done. See CHANGELOG v3.1.56."); _cfgDiagnosticProbes = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Diagnostic Probes", false, "Diagnostic-only (default OFF). When true, the [BindProbe-*] shader-collision diagnostics in the bundle-load / Awake paths (OnLoadAsset, MaterialLoad, GameObjectMaterial, ShaderLoad, Baseline, RetroactiveSweep) emit [BindProbe-*] LogInfo lines and run their Resources.FindObjectsOfTypeAll() peer-count scans. These are pure read-only observation used to localize vanilla-shader-name collisions during development; they mutate no material, gate, sweep, resolver, baseline, or cache state. Left OFF, the plugin skips the scan + log work entirely on every bundle load and Awake snapshot — recommended for normal play. Enable only for one diagnostic session when asked to capture [BindProbe-*] evidence, then disable. See CHANGELOG v3.1.65."); _cfgAutoPolishMetallic = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Auto Polish Metallic Materials", false, "Default OFF (opt-in). When true, a gate-admitted mod material whose source _Metallic >= 0.5 AND that has a metallic-gloss map bound (_MetallicGlossMap / _MetallicMap) has its smoothness slots written (_UseGlossmap=1, _MetalGloss=0.7, _Glossiness=0.9, _Smoothness=0.9) after the Fallback bridge so the matte-metal blade (e.g. BronzeClaymore_mat_TW: _Metallic=1 + metallic-roughness map + smoothness 0 = dark diffuse metal on Metal) reads polished instead of dark. This automates, without a per-sword rule, the fix the user validated by hand via '[useshader] mat: Custom/Creature _UseGlossmap=1 _MetalGloss=0.7 _Glossiness=0.9'. The write lands at the POST-BRIDGE Frame-B site the v3.1.49 probe proved has the writable slots (the reverted v3.1.45–47 wrote inside the bridge against a different, slot-less Custom/Creature instance and no-op'd three times). Non-metals (antler/leather/wood, all _Metallic=0) never enter the branch. Any per-mod [useshader] rule still wins (a ruled material returns before reaching this branch). Vanilla materials are unaffected (the provenance veto runs first). Sweep-side property write, NOT a provenance-gate change. Set to true to enable; validate that the intended blades polish and no non-metal changes, then a future release may flip the default ON. See CHANGELOG v3.1.49 + v3.1.50."); _cfgAutoPolishEquippedMetal = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Auto Polish Equipped Metal", true, "Default ON (v3.1.52, validated in-game). When true, an equipped-item material the plugin has already bridged to Custom/Creature that carries an ASSIGNED metallic-gloss texture (_MetallicGlossMap has a real texture, not just the empty property slot) has its smoothness slots written so mod-metal weapons (e.g. Warfare IronSledge_mat_TW / bronzedark_mat_TW) read polished without a per-weapon [useshader] rule. The assigned-texture gate cleanly separates metal from equipped wood/leather (whose _MetallicGlossMap is null), which the v3.1.50 'Auto Polish Metallic Materials' _Metallic>=0.5 gate could not (mod authors ship metal weapons at _Metallic 0–0.2). The written values are the tunable 'Auto Polish Use Glossmap' / 'Auto Polish MetalGloss' / 'Auto Polish Glossiness' entries below. Does NOT raise _Metallic and does NOT touch emission (a glowing mod weapon needs a per-mod [useshader] _EmissionColor rule). Only materials already on Custom/Creature (gate-admitted, mod-owned, plugin-bridged) are touched — a correctly-rendering vanilla item is never affected. Sweep-side property write at the equip hook, NOT a provenance-gate change. See CHANGELOG v3.1.51."); _cfgAutoPolishMetalAmount = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Auto Polish Metallic", 1f, "Tuning for 'Auto Polish Equipped Metal'. The _Metallic value written to polished equipped-metal materials (0 = dielectric/plastic, 1 = fully metallic). Default 1.0. Mods routinely ship metal weapons at _Metallic 0–0.2 (the validated iron-knife knifeironblade_mat_TW = 0), which on a PBR lit shader renders as a glossy dielectric rather than metal even after the gloss writes land — so raising metalness is needed in addition to the smoothness values. Edit, then re-equip the weapon (or reload + sweep) to see the change. Only has effect when 'Auto Polish Equipped Metal' is enabled. See CHANGELOG v3.1.52."); _cfgAutoPolishUseGlossmap = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Auto Polish Use Glossmap", true, "Tuning for 'Auto Polish Equipped Metal'. When true (default), the auto-polish writes _UseGlossmap=1 so the bound metallic-gloss map's alpha drives per-texel smoothness; false writes _UseGlossmap=0 (uniform smoothness from the MetalGloss/Glossiness values). Only has effect when 'Auto Polish Equipped Metal' is enabled. See CHANGELOG v3.1.51."); _cfgAutoPolishMetalGloss = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Auto Polish MetalGloss", 0.7f, "Tuning for 'Auto Polish Equipped Metal'. The _MetalGloss value written to polished equipped-metal materials (0 = matte, 1 = mirror). Default 0.7. Edit, then re-equip the weapon (or reload + sweep) to see the change. Only has effect when 'Auto Polish Equipped Metal' is enabled. See CHANGELOG v3.1.51."); _cfgAutoPolishGlossiness = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "Auto Polish Glossiness", 0.9f, "Tuning for 'Auto Polish Equipped Metal'. The _Glossiness value written to polished equipped-metal materials (0 = matte, 1 = mirror). Default 0.9. Edit, then re-equip the weapon (or reload + sweep) to see the change. Only has effect when 'Auto Polish Equipped Metal' is enabled. See CHANGELOG v3.1.51."); foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { if (!((Object)(object)allLoadedAssetBundle == (Object)null)) { _vanillaBundleNames.Add(((Object)allLoadedAssetBundle).name); } } VanillaBaselineCache.TryExtractEmbeddedBaseline(ConfigDir, Log); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); HashSet hashSet3 = new HashSet(StringComparer.OrdinalIgnoreCase); if (VanillaBaselineCache.TryLoad(ConfigDir, Log, hashSet, hashSet2, hashSet3, out string cachedValheimVersion)) { foreach (string item in hashSet) { _baselineMatNames.Add(item); } foreach (string item2 in hashSet2) { _vanillaBundleNames.Add(item2); } foreach (string item3 in hashSet3) { _vanillaShaderNames.Add(item3); } Log.LogDebug((object)$"[VanillaCache] loaded as authoritative baseline: {_baselineMatNames.Count} material name(s), {_vanillaShaderNames.Count} shader name(s), {_vanillaBundleNames.Count} bundle name(s)."); _cachedValheimVersionForDriftCheck = cachedValheimVersion; ReplacementCore.BumpAdmissionEpoch("Awake.VanillaCacheLoaded"); } else { Log.LogWarning((object)"[VanillaCache] WARNING: no on-disk vanilla baseline found. The vanilla-name veto is DISARMED for this session — mod-name collisions with Valheim materials may be replaced unsafely. The plugin ships a pre-populated baseline as an embedded resource; if it failed to extract, run `shaderhelper capturevanilla` from a clean install:"); Log.LogWarning((object)"[VanillaCache] 1. Move all non-ShaderHelperForMac DLLs out of BepInEx/plugins/."); Log.LogWarning((object)"[VanillaCache] 2. Launch Valheim and load any world."); Log.LogWarning((object)"[VanillaCache] 3. F5 console: shaderhelper capturevanilla"); Log.LogWarning((object)"[VanillaCache] 4. Quit, restore your mod DLLs, relaunch."); } _sweepDisabled = _baselineMatNames.Count == 0 && _vanillaShaderNames.Count == 0; if (_sweepDisabled) { Log.LogWarning((object)"[v3.0] SWEEP DISABLED — vanilla truth file is empty (zero baseline mat names AND zero vanilla shader names). The option-3 admission gate cannot run safely without truth data; admitting everything would include vanilla materials. The replacement sweep will refuse on every trigger this session. Diagnostic commands remain functional. Run `shaderhelper capturevanilla` from a clean install to populate the cache (see [VanillaCache] WARNING above for the 4-step remediation), then relaunch."); } ReplacementCore.BumpAdmissionEpoch("Awake.PostCacheFlip"); _awakeVanillaSnapshotEmpty = _vanillaBundleNames.Count == 0; Log.LogDebug((object)($"Vanilla bundles snapshotted: {_vanillaBundleNames.Count}" + (_awakeVanillaSnapshotEmpty ? " (empty ⇒ memory/stream bundle tagging deferred to ZNetScene.Awake retroactive sweep)" : ""))); _valheimDataPath = Application.dataPath.Replace('\\', '/').TrimEnd(new char[1] { '/' }); Log.LogDebug((object)("Valheim data path: " + _valheimDataPath)); int num = ShaderScanner.ScanPlugins(Paths.PluginPath, ModShaderDlls, Log); Log.LogDebug((object)$"Scanned plugins: {num} mod shader(s) recorded across {ModShaderDlls.Values.Distinct().Count()} DLL(s)."); foreach (string item4 in ModShaderDlls.Values.Distinct()) { Log.LogDebug((object)(" Shaders found in: '" + item4 + "'")); } ConfigLoader.EnsureConfigFiles(); ReloadConfigFiles(); StartConfigWatcher(); BundleAttribution.EmitBaselineSnapshot("Awake-End"); SceneManager.sceneLoaded += OnSceneLoaded; } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; _watcher?.Dispose(); } internal static int IndexOfInlineComment(string line) { return ConfigLoader.IndexOfInlineComment(line); } internal static void ReloadConfigFiles() { ConfigLoader.ReloadConfigFiles(); } private void StartConfigWatcher() { try { _watcher = new FileSystemWatcher(ConfigDir, "*.txt") { NotifyFilter = NotifyFilters.LastWrite, EnableRaisingEvents = true }; _watcher.Changed += delegate { ReloadConfigFiles(); }; } catch { } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!_initialized) { ((MonoBehaviour)this).StartCoroutine(SweepCoordinator.InitAfterFrame(this)); } else { ((MonoBehaviour)this).StartCoroutine(SweepCoordinator.SweepAfterFrame(this)); } } private void RegisterCommands() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand("shaderhelper", "[patch|patches|undo|sweep|nearby|status|…] ShaderHelperForMac — run `shaderhelper` alone for full usage.", (ConsoleEvent)delegate(ConsoleEventArgs args) { switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "help") { case "reload": BasicCommands.HandleReload(this, args); break; case "capturevanilla": CaptureVanillaCommand.Handle(this, args); break; case "sweep": BasicCommands.HandleSweep(this, args); break; case "dumpmod": BasicCommands.HandleDumpMod(this, args); break; case "scan": DllFilteredCommands.HandleScan(this, args); break; case "pink": GateAuditCommands.HandlePink(this, args); break; case "shaders": BasicCommands.HandleShaders(this, args); break; case "renderers": BasicCommands.HandleRenderers(this, args); break; case "all": BasicCommands.HandleAll(this, args); break; case "nearby": GateAuditCommands.HandleNearby(this, args); break; case "transparent": DllFilteredCommands.HandleTransparent(this, args); break; case "loaded": DllFilteredCommands.HandleLoaded(this, args); break; case "checkvanilla": GateAuditCommands.HandleCheckVanilla(this, args); break; case "whymat": WhymatCommand.Handle(this, args); break; case "explain": GateAuditCommands.HandleExplain(this, args); break; case "status": StatusCommand.Handle(this, args); break; case "fallbackchain": FallbackChainCommand.Handle(this, args); break; case "resolveshader": ResolveShaderCommand.Handle(this, args); break; case "bindprobe": BindProbeCommand.Handle(this, args); break; case "patch": ConfigAuthoringCommands.HandlePatch(this, args); break; case "patches": ConfigAuthoringCommands.HandlePatches(this, args); break; case "undo": ConfigAuthoringCommands.HandleUndo(this, args); break; case "dlls": DllFilteredCommands.HandleDlls(this, args); break; case "classify": InfoCommands.HandleClassify(this, args); break; case "dumpprefabmap": InfoCommands.HandleDumpPrefabMap(this, args); break; case "suggest": InfoCommands.HandleSuggest(this, args); break; case "prefab": InfoCommands.HandlePrefab(this, args); break; case "modprefabs": InfoCommands.HandleModPrefabs(this, args); break; case "bundles": InfoCommands.HandleBundles(this, args); break; case "watch": ObserverCommands.HandleWatch(this, args); break; case "watchr": ObserverCommands.HandleWatchr(this, args); break; case "matdump": MatDumpCommand.Handle(this, args); break; case "checkredesign": GateAuditCommands.HandleCheckRedesign(this, args); break; case "psprobe": PsProbeCommand.Handle(this, args); break; case "capture": { string text = ((args.Length > 2) ? args[2].ToLowerInvariant() : string.Empty); if (text == "stop") { CaptureCommand.HandleStop(this, args); } else if (text == "start") { CaptureCommand.HandleStart(this, args); } else { args.Context.AddString("Usage: shaderhelper capture start [radius] — begin continuous nearby-material capture"); args.Context.AddString(" shaderhelper capture stop — end capture and emit per-material summary"); } break; } default: args.Context.AddString("Usage: shaderhelper patch [radius] [DllName] — stand near pink object; auto-adds a rule to the owning mod's .txt (default radius 15). Pass DllName for large-DLL mods."); args.Context.AddString(" shaderhelper patches — list every batch previously added by `patch`"); args.Context.AddString(" shaderhelper undo [id|all|last] — revert the most recent patch batch (or a specific id, or all)"); args.Context.AddString(" shaderhelper sweep — run a material sweep immediately (reloads config first)"); args.Context.AddString(" shaderhelper reload — reload all config files without running a sweep"); args.Context.AddString(" shaderhelper nearby [radius] [DllName|mod] — log materials near the player; shows patch=yes when patch would add a rule"); args.Context.AddString(" shaderhelper transparent — log materials made (or to be made) transparent for a mod"); args.Context.AddString(" shaderhelper loaded [DllName] — log all in-memory materials incl. prefabs; optionally filter by mod DLL name"); args.Context.AddString(" shaderhelper scan — log all materials from a mod with shader/support info"); args.Context.AddString(" shaderhelper pink — log all unhandled pink/unsupported materials"); args.Context.AddString(" shaderhelper renderers — log materials from active scene renderers"); args.Context.AddString(" shaderhelper all — log ALL loaded materials regardless of action (diagnostics)"); args.Context.AddString(" shaderhelper explain — audit why a material lands on Fallback/Transparent/Original"); args.Context.AddString(" shaderhelper checkvanilla [Name2] ... — report which vanilla / mod-attribution sets contain each name"); args.Context.AddString(" shaderhelper whymat — report which lane(s) admitted a material (legacy diagnostic)"); args.Context.AddString(" shaderhelper watch [seconds] — log per-frame material state for N seconds (default 5, max 30); diagnoses flicker invisible to `explain`"); args.Context.AddString(" shaderhelper watchr [seconds] — log per-frame renderer state for N seconds; diagnoses renderer.enabled toggling"); args.Context.AddString(" shaderhelper matdump [--save=