Decompiled source of ShaderHelperForMac v4.0.1

plugins/ShaderHelperForMac.dll

Decompiled 3 weeks ago
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.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using ShaderHelper.Apply;
using ShaderHelper.Decide;
using ShaderHelper.Diagnostics;
using ShaderHelper.Facts;
using ShaderHelper.Patches;
using ShaderHelper.Scan;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ShaderHelperForMac")]
[assembly: AssemblyProduct("ShaderHelperForMac")]
[assembly: AssemblyFileVersion("4.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("4.0.1.0")]
namespace ShaderHelper
{
	[BepInPlugin("aaa.drummercraig.shaderhelperformac", "ShaderHelperForMac", "4.0.1")]
	public sealed class ShaderHelperPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "aaa.drummercraig.shaderhelperformac";

		public const string PluginName = "ShaderHelperForMac";

		public const string PluginVersion = "4.0.1";

		internal static ManualLogSource Log;

		internal static ShaderHelperPlugin Instance;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			VanillaBaseline.Load(Log);
			_harmony = new Harmony("aaa.drummercraig.shaderhelperformac");
			ResilientPatcher.ApplyAll(_harmony, typeof(ShaderHelperPlugin).Assembly, Log);
		}

		private void Update()
		{
			try
			{
				HitchWatchdog.Tick();
			}
			catch (Exception)
			{
			}
		}
	}
}
namespace ShaderHelper.Scan
{
	internal sealed class CatchUp
	{
		private const int ExampleCap = 6;

		private readonly Dictionary<int, bool> _pink = new Dictionary<int, bool>();

		private readonly List<string> _examples = new List<string>();

		internal int Count { get; private set; }

		internal void Note(Material m)
		{
			if ((Object)(object)m == (Object)null)
			{
				return;
			}
			try
			{
				if (Coverage.Seen(((Object)m).GetInstanceID()))
				{
					return;
				}
				Shader shader = m.shader;
				if ((Object)(object)shader == (Object)null)
				{
					return;
				}
				int instanceID = ((Object)shader).GetInstanceID();
				if (!_pink.TryGetValue(instanceID, out var value))
				{
					value = (_pink[instanceID] = IsPinkModInstance(shader, instanceID));
				}
				if (value)
				{
					Count++;
					if (_examples.Count < 6)
					{
						_examples.Add($"'{((Object)m).name}' on '{Names.Shader(((Object)shader).name)}'#{instanceID}");
					}
				}
			}
			catch (Exception)
			{
			}
		}

		private static bool IsPinkModInstance(Shader sh, int sid)
		{
			string name = ((Object)sh).name;
			if (string.IsNullOrEmpty(name) || string.Equals(name, "Hidden/InternalErrorShader", StringComparison.Ordinal))
			{
				return false;
			}
			if (VanillaShaders.IsHarvested(sid) || ValheimPrefabShaders.IsProven(sid))
			{
				return false;
			}
			if (Targets.TryGet(Names.Shader(name), out var target) && target.InstanceId == sid)
			{
				return false;
			}
			return ShaderStructure.IsPlaceholder(sh);
		}

		internal string Summary(int scanned, double ms, int frames, bool queued)
		{
			return $"[Eval] catch-up check: {scanned} loaded material(s); {Count} unseen by the pass on a placeholder-shaped mod instance" + ((_examples.Count > 0) ? (" (" + string.Join(" ", _examples) + ((Count > _examples.Count) ? $" +{Count - _examples.Count} more" : "") + ")") : "") + $"; {ms:F1} ms over {frames} frame(s); " + (queued ? "one catch-up pass queued." : ((Count > 0) ? "no catch-up (a pass is already queued, or the world closed)." : "no catch-up needed."));
		}
	}
	internal static class Coverage
	{
		private const int TopNames = 10;

		private const int ExamplesPerGroup = 6;

		private static readonly HashSet<int> _seen = new HashSet<int>();

		private static readonly Dictionary<int, Verdict> _verdicts = new Dictionary<int, Verdict>();

		private static bool _passed;

		internal static void BeginPass()
		{
			_seen.Clear();
			_verdicts.Clear();
			_passed = true;
		}

		internal static void NoteSeen(int materialId)
		{
			_seen.Add(materialId);
		}

		internal static bool Seen(int materialId)
		{
			return _seen.Contains(materialId);
		}

		internal static void NoteDecided(int materialId, Verdict v)
		{
			_verdicts[materialId] = v;
		}

		internal static void AtWorldClose()
		{
			try
			{
				Count();
			}
			finally
			{
				_seen.Clear();
				_verdicts.Clear();
				_passed = false;
			}
		}

		private static void Count()
		{
			if (!_passed)
			{
				ShaderHelperPlugin.Log.LogInfo((object)"[Coverage] world closed before any world-load pass; nothing to compare.");
				return;
			}
			long timestamp = Stopwatch.GetTimestamp();
			Material[] array;
			try
			{
				array = Resources.FindObjectsOfTypeAll<Material>();
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogWarning((object)("[Coverage] material scan FAILED (" + ex.GetType().Name + "); no coverage count."));
				return;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			Material[] array2 = array;
			foreach (Material val in array2)
			{
				if ((Object)(object)val == (Object)null || _seen.Contains(((Object)val).GetInstanceID()))
				{
					continue;
				}
				num++;
				try
				{
					if (VanillaBaseline.IsVanillaMaterial(Names.MaterialBase(((Object)val).name ?? "", out var _)))
					{
						num2++;
						continue;
					}
					Shader shader = val.shader;
					string text = (((Object)(object)shader != (Object)null) ? ((Object)shader).name : null);
					if (string.IsNullOrEmpty(text))
					{
						num5++;
						continue;
					}
					string text2 = Names.Shader(text);
					if (Targets.TryGet(text2, out var target) && target.InstanceId == ((Object)shader).GetInstanceID())
					{
						num3++;
						continue;
					}
					num4++;
					dictionary.TryGetValue(text2, out var value);
					dictionary[text2] = value + 1;
				}
				catch (Exception)
				{
					num5++;
				}
			}
			long ticks = Stopwatch.GetTimestamp() - timestamp;
			WorkMeter.Add(ticks);
			IEnumerable<string> values = from kv in dictionary.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).ThenBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> kv) => kv.Key, StringComparer.Ordinal).Take(10)
				select $"'{kv.Key}' x{kv.Value}";
			ShaderHelperPlugin.Log.LogInfo((object)($"[Coverage] world close: {array.Length} loaded material(s), {num} not seen by the world-load pass " + $"(lower bound: teardown may have destroyed some): {num2} cache-named, {num3} non-cache (mod, or engine/UI) on a Valheim target " + string.Format("instance, {0} non-cache NOT on one, {1} unreadable{2}; ", num4, num5, Evaluator.IsRunning ? "; the pass was still running" : "") + $"{WorkMeter.Ms(ticks):F1} ms." + ((dictionary.Count > 0) ? (" Not on one, by bound shader: " + string.Join("; ", values)) : "")));
			Placeholders(array);
		}

		private static void Placeholders(Material[] all)
		{
			if (!ShaderStructure.PlaceholderKnown)
			{
				ShaderHelperPlugin.Log.LogInfo((object)"[Coverage] placeholder-shaped now: not measured (the placeholder shape was not read this session).");
				return;
			}
			long timestamp = Stopwatch.GetTimestamp();
			Dictionary<int, bool> dictionary = new Dictionary<int, bool>();
			Dictionary<string, (int, List<string>, HashSet<string>)> dictionary2 = new Dictionary<string, (int, List<string>, HashSet<string>)>(StringComparer.Ordinal);
			int num = 0;
			int num2 = 0;
			foreach (Material val in all)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				try
				{
					Shader shader = val.shader;
					if ((Object)(object)shader == (Object)null)
					{
						continue;
					}
					string name = ((Object)shader).name;
					if (string.IsNullOrEmpty(name) || string.Equals(name, "Hidden/InternalErrorShader", StringComparison.Ordinal))
					{
						continue;
					}
					int instanceID = ((Object)shader).GetInstanceID();
					if (!dictionary.TryGetValue(instanceID, out var value))
					{
						value = (dictionary[instanceID] = ShaderStructure.IsPlaceholder(shader));
					}
					if (value)
					{
						num++;
						string key = Status(((Object)val).GetInstanceID()) + ": '" + Names.Shader(name) + "'";
						if (!dictionary2.TryGetValue(key, out var value2))
						{
							value2 = (0, new List<string>(), new HashSet<string>(StringComparer.Ordinal));
						}
						string text = ((Object)val).name ?? "";
						if (value2.Item2.Count < 6 && value2.Item3.Add(text))
						{
							value2.Item2.Add($"'{text}'#{instanceID}");
						}
						dictionary2[key] = (value2.Item1 + 1, value2.Item2, value2.Item3);
					}
				}
				catch (Exception)
				{
					num2++;
				}
			}
			long ticks = Stopwatch.GetTimestamp() - timestamp;
			WorkMeter.Add(ticks);
			List<KeyValuePair<string, (int, List<string>, HashSet<string>)>> list = dictionary2.OrderByDescending<KeyValuePair<string, (int, List<string>, HashSet<string>)>, int>((KeyValuePair<string, (int Count, List<string> Names, HashSet<string> Seen)> kv) => kv.Value.Count).ThenBy<KeyValuePair<string, (int, List<string>, HashSet<string>)>, string>((KeyValuePair<string, (int Count, List<string> Names, HashSet<string> Seen)> kv) => kv.Key, StringComparer.Ordinal).ToList();
			ShaderHelperPlugin.Log.LogInfo((object)($"[Coverage] placeholder-shaped now: {num} loaded material(s) bound to a shader shaped like the error " + $"shader (most likely pink), {num2} unreadable, by status (world-load verdict, decided before any write) and bound shader: " + ((list.Count == 0) ? "none" : string.Join("; ", list.Select<KeyValuePair<string, (int, List<string>, HashSet<string>)>, string>((KeyValuePair<string, (int Count, List<string> Names, HashSet<string> Seen)> kv) => $"{kv.Key} x{kv.Value.Count}"))) + string.Format("{0}; {1:F1} ms.", Evaluator.IsRunning ? "; the pass was still running" : "", WorkMeter.Ms(ticks))));
			foreach (KeyValuePair<string, (int, List<string>, HashSet<string>)> item in list)
			{
				int num3 = item.Value.Item1 - item.Value.Item2.Count;
				ShaderHelperPlugin.Log.LogInfo((object)("[Coverage]   " + item.Key + ": " + string.Join(" ", item.Value.Item2) + ((num3 > 0) ? $" +{num3} more" : "")));
			}
		}

		private static string Status(int id)
		{
			if (_verdicts.TryGetValue(id, out var value))
			{
				if (!Verdicts.WouldWrite(value))
				{
					return value.ToString();
				}
				return string.Format("{0}{1}", value, ShaderWriter.WasWritten(id) ? "+written" : "+notWritten");
			}
			if (!_seen.Contains(id))
			{
				return "unseen";
			}
			return "seen-undecided";
		}
	}
	internal static class Evaluator
	{
		private static bool _running;

		private static bool _again;

		private static string _againLabel;

		private const string CatchUpLabel = "catch-up";

		internal static bool IsRunning => _running;

		internal static void RequestPass(string label)
		{
			if (_running)
			{
				_again = true;
				_againLabel = label;
			}
			else if (!((Object)(object)ShaderHelperPlugin.Instance == (Object)null))
			{
				((MonoBehaviour)ShaderHelperPlugin.Instance).StartCoroutine(Pass(label));
			}
		}

		private static IEnumerator Pass(string label)
		{
			_running = true;
			try
			{
				yield return null;
				if (!VanillaBaseline.Loaded)
				{
					ShaderHelperPlugin.Log.LogError((object)"[Eval] baseline not loaded; nothing is decided.");
					yield break;
				}
				Slicer s = new Slicer();
				s.Resume();
				Targets.Build();
				PropertyMatch.BeginPass();
				ShaderWriter.BeginPass();
				EquipmentMaterials.BeginSeed();
				ArmorMaterials.Begin();
				ValheimPrefabShaders.Begin();
				foreach (GameObject go in Copy(((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.m_items : null))
				{
					if (s.Due(1))
					{
						s.Pause();
						yield return null;
						s.Resume();
					}
					EquipmentMaterials.SeedFrom(go);
					ArmorMaterials.AddFrom(go);
					ValheimPrefabShaders.AddFrom(go);
				}
				if ((Object)(object)ObjectDB.instance == (Object)null)
				{
					ShaderHelperPlugin.Log.LogWarning((object)"[Equip] ObjectDB not available at world load; the equipment seed is empty this pass.");
				}
				EquipmentMaterials.EndSeed();
				PieceMaterials.Begin();
				foreach (GameObject go in Copy(((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.m_prefabs : null))
				{
					if (s.Due(1))
					{
						s.Pause();
						yield return null;
						s.Resume();
					}
					PieceMaterials.AddFrom(go);
					ValheimPrefabShaders.AddFrom(go);
				}
				Targets.AddPrefabProven();
				RendererKinds.Begin();
				Renderer[] array = FindAll<Renderer>();
				foreach (Renderer r in array)
				{
					if (s.Due())
					{
						s.Pause();
						yield return null;
						s.Resume();
					}
					RendererKinds.Record(r);
				}
				CameraMaterials.Walk();
				Material[] materials = FindAll<Material>();
				List<MaterialRow> rows = new List<MaterialRow>(materials.Length);
				Report report = new Report();
				Coverage.BeginPass();
				CopySource.BeginPass();
				Material[] array2 = materials;
				foreach (Material m in array2)
				{
					if (s.Due())
					{
						s.Pause();
						yield return null;
						s.Resume();
					}
					if ((Object)(object)m == (Object)null)
					{
						report.CountGone();
						continue;
					}
					Coverage.NoteSeen(((Object)m).GetInstanceID());
					if (MaterialRows.TryBuild(m, report, out var row))
					{
						rows.Add(row);
						MaterialRows.CaptureAuthored(in row);
					}
				}
				List<(MaterialRow Row, Decision D)> candidates = new List<(MaterialRow, Decision)>();
				foreach (MaterialRow item2 in rows)
				{
					MaterialRow row2 = item2;
					if (s.Due())
					{
						s.Pause();
						yield return null;
						s.Resume();
					}
					if ((Object)(object)row2.Mat == (Object)null || (Object)(object)row2.Bound == (Object)null)
					{
						report.CountGone();
						continue;
					}
					Decision item = MaterialRows.Record(report, in row2);
					Coverage.NoteDecided(row2.Id, item.Verdict);
					if (ShaderWriter.InScope(item.Verdict))
					{
						candidates.Add((row2, item));
					}
				}
				rows.Clear();
				foreach (var item3 in candidates)
				{
					(MaterialRow Row, Decision D) c = item3;
					if (s.Due())
					{
						s.Pause();
						yield return null;
						s.Resume();
					}
					if (MaterialRows.Write(in c.Row, c.D) && s.Due(1))
					{
						s.Pause();
						yield return null;
						s.Resume();
					}
				}
				candidates.Clear();
				s.Pause();
				long timestamp = Stopwatch.GetTimestamp();
				MaterialRows.Emit(report, label, materials.Length, s);
				WorkMeter.Add(Stopwatch.GetTimestamp() - timestamp);
				if (string.Equals(label, "catch-up", StringComparison.Ordinal))
				{
					yield break;
				}
				Slicer c2 = new Slicer();
				c2.Resume();
				array2 = FindAll<Material>();
				CatchUp check = new CatchUp();
				Material[] array3 = array2;
				foreach (Material m in array3)
				{
					if (c2.Due())
					{
						c2.Pause();
						yield return null;
						c2.Resume();
						if ((Object)(object)ZNetScene.instance == (Object)null)
						{
							break;
						}
					}
					check.Note(m);
				}
				c2.Pause();
				bool flag = check.Count > 0 && !_again && (Object)(object)ZNetScene.instance != (Object)null;
				ShaderHelperPlugin.Log.LogInfo((object)check.Summary(array2.Length, c2.WorkMs, c2.Frames, flag));
				if (flag)
				{
					RequestPass("catch-up");
				}
			}
			finally
			{
				_running = false;
				ValheimMaterials.End();
				if (_again)
				{
					_again = false;
					RequestPass(_againLabel);
				}
			}
		}

		private static List<GameObject> Copy(List<GameObject> src)
		{
			if (src == null)
			{
				return new List<GameObject>();
			}
			return new List<GameObject>(src);
		}

		private static T[] FindAll<T>() where T : Object
		{
			try
			{
				return Resources.FindObjectsOfTypeAll<T>();
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogWarning((object)("[Eval] scan of " + typeof(T).Name + " FAILED (" + ex.GetType().Name + "); that fact is empty this pass."));
				return Array.Empty<T>();
			}
		}
	}
	internal static class MaterialRows
	{
		internal static bool TryBuild(Material m, Report report, out MaterialRow row)
		{
			row = default(MaterialRow);
			try
			{
				string name = ((Object)m).name ?? "";
				bool copy;
				string baseName = Names.MaterialBase(name, out copy);
				Shader shader = m.shader;
				string text = (((Object)(object)shader != (Object)null) ? ((Object)shader).name : null);
				if (string.IsNullOrEmpty(text))
				{
					report.CountUnreadable();
					return false;
				}
				if (string.Equals(text, "Hidden/InternalErrorShader", StringComparison.Ordinal))
				{
					report.CountErrorShader();
					return false;
				}
				row = new MaterialRow(m, ((Object)m).GetInstanceID(), name, baseName, copy, shader, Names.Shader(text), Names.IsMock(text));
				return true;
			}
			catch (Exception)
			{
				report.CountUnreadable();
				return false;
			}
		}

		internal static void CaptureAuthored(in MaterialRow row)
		{
			if (!VanillaBaseline.IsVanillaMaterial(row.Base))
			{
				AuthoredShader.Capture(row.Id, row.Base, row.Copy, row.BoundName, ((Object)row.Bound).GetInstanceID());
			}
			if (VanillaBaseline.IsVanillaMaterial(row.Base))
			{
				return;
			}
			try
			{
				if (row.Copy)
				{
					CopySource.NoteCopy(row.Mat, row.Id, row.Base, row.Bound);
				}
				else
				{
					CopySource.NoteOriginal(row.Mat, row.Base, row.Bound);
				}
			}
			catch (Exception)
			{
			}
		}

		internal static Decision DecideSafely(in MaterialRow row)
		{
			try
			{
				return Decider.Decide(in row);
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Eval] decide threw for '" + row.Name + "': " + ex.GetType().Name + ": " + ex.Message));
				return new Decision(Verdict.Unreadable, null, row.BoundName, default(Target));
			}
		}

		internal static Decision Record(Report report, in MaterialRow row)
		{
			Decision decision = DecideSafely(in row);
			try
			{
				report.Record(in row, decision);
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Eval] record threw for '" + row.Name + "': " + ex.GetType().Name + ": " + ex.Message));
			}
			if (row.Copy && !VanillaBaseline.IsVanillaMaterial(row.Base))
			{
				try
				{
					CopySource.Judge(row.Id, row.Base, decision.Verdict.ToString());
				}
				catch (Exception)
				{
				}
			}
			return decision;
		}

		internal static bool Write(in MaterialRow row, Decision d)
		{
			try
			{
				return ShaderWriter.Consider(in row, d) == WriteOutcome.Written;
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Apply] consider threw for '" + row.Name + "': " + ex.GetType().Name + ": " + ex.Message));
				return false;
			}
		}

		internal static void Emit(Report report, string label, int loaded, Slicer s)
		{
			try
			{
				report.Emit(label, loaded, s);
			}
			catch (Exception arg)
			{
				ShaderHelperPlugin.Log.LogError((object)$"[Eval] report failed: {arg}");
			}
		}
	}
	internal sealed class Slicer
	{
		internal const double FrameBudgetMs = 1.0;

		internal const int CheapItems = 64;

		private readonly long _budget = (long)(1.0 * (double)Stopwatch.Frequency / 1000.0);

		private long _start;

		private long _work;

		private int _n;

		internal int Frames { get; private set; }

		internal double WorkMs => WorkMeter.Ms(_work);

		internal void Resume()
		{
			_start = Stopwatch.GetTimestamp();
			_n = 0;
			Frames++;
		}

		internal bool Due(int every = 64)
		{
			if (++_n < every)
			{
				return false;
			}
			_n = 0;
			return Stopwatch.GetTimestamp() - _start >= _budget;
		}

		internal void Pause()
		{
			long num = Stopwatch.GetTimestamp() - _start;
			_work += num;
			WorkMeter.Add(num);
		}
	}
}
namespace ShaderHelper.Patches
{
	internal static class JotunnOverlayPatches
	{
		private const string ManagerType = "Jotunn.Managers.MinimapManager";

		private static bool _armed;

		private static bool _hooked;

		private static bool _disabled;

		private static bool _deferPass;

		private static FieldInfo _composeField;

		private static int _decidedId;

		private static bool _decidedBroken;

		private static bool _loggedTakeover;

		private static bool _ensureTried;

		internal static void Arm()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Expected O, but got Unknown
			if (_armed)
			{
				return;
			}
			_armed = true;
			try
			{
				Type type = FindManager();
				if (type == null)
				{
					ShaderHelperPlugin.Log.LogDebug((object)"[Overlay] Jotunn not loaded: map-overlay compose left alone.");
					return;
				}
				MethodInfo methodInfo = AccessTools.Method(type, "SetupOverlays", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					ShaderHelperPlugin.Log.LogWarning((object)"[Overlay] Jotunn has no MinimapManager.SetupOverlays: overlays stay as Jotunn draws them.");
					return;
				}
				new Harmony("aaa.drummercraig.shaderhelperformac").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(JotunnOverlayPatches), "AfterSetupOverlays", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				ShaderHelperPlugin.Log.LogDebug((object)"[Overlay] armed on Jotunn's SetupOverlays.");
				object obj = AccessTools.Field(type, "_instance")?.GetValue(null);
				if (obj != null && AccessTools.Field(type, "Overlays")?.GetValue(obj) is ICollection { Count: >0 })
				{
					AfterSetupOverlays(obj);
				}
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogWarning((object)("[Overlay] arming failed (Jotunn unaffected): " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static Type FindManager()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (string.Equals(assembly.GetName().Name, "Jotunn", StringComparison.Ordinal))
				{
					return assembly.GetType("Jotunn.Managers.MinimapManager", throwOnError: false);
				}
			}
			return null;
		}

		private static void AfterSetupOverlays(object __instance)
		{
			//IL_010c: 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_012d: 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_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Expected O, but got Unknown
			//IL_015f: Expected O, but got Unknown
			//IL_015f: Expected O, but got Unknown
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Expected O, but got Unknown
			if (_hooked || _disabled || __instance == null)
			{
				return;
			}
			try
			{
				Type type = __instance.GetType();
				MethodInfo methodInfo = AccessTools.Method(type, "DrawLayer", new Type[4]
				{
					typeof(Texture2D),
					typeof(Texture2D),
					typeof(Material),
					typeof(RenderTextureFormat)
				}, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(type, "DrawOverlay", (Type[])null, (Type[])null);
				MethodInfo methodInfo3 = ((methodInfo2 != null) ? AccessTools.EnumeratorMoveNext((MethodBase)methodInfo2) : null);
				_composeField = AccessTools.Field(type, "ComposeOverlayMaterial");
				if (methodInfo == null || methodInfo3 == null || _composeField == null)
				{
					_disabled = true;
					ShaderHelperPlugin.Log.LogWarning((object)($"[Overlay] Jotunn's compose hooks not found (DrawLayer={methodInfo != null} DrawOverlay.MoveNext={methodInfo3 != null} " + $"ComposeOverlayMaterial={_composeField != null}): overlays stay as Jotunn draws them."));
					return;
				}
				Harmony val = new Harmony("aaa.drummercraig.shaderhelperformac");
				Type typeFromHandle = typeof(JotunnOverlayPatches);
				val.Patch((MethodBase)methodInfo3, new HarmonyMethod(AccessTools.Method(typeFromHandle, "RedrawStart", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeFromHandle, "RedrawEnd", (Type[])null, (Type[])null)), (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeFromHandle, "RedrawFinally", (Type[])null, (Type[])null)), (HarmonyMethod)null);
				val.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeFromHandle, "BeforeDrawLayer", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_hooked = true;
				ShaderHelperPlugin.Log.LogInfo((object)"[Overlay] Jotunn map overlays: compose hooks installed (each takes over only while the compose shader reads placeholder-shaped).");
			}
			catch (Exception ex)
			{
				_disabled = true;
				ShaderHelperPlugin.Log.LogWarning((object)("[Overlay] hook install failed; Jotunn's own path stands: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void RedrawStart()
		{
			if (!_disabled)
			{
				_deferPass = false;
				_ensureTried = false;
				OverlayCompose.BeginPass();
			}
		}

		private static void RedrawEnd()
		{
			if (_disabled)
			{
				OverlayCompose.Abort();
				return;
			}
			try
			{
				string text = OverlayCompose.Flush();
				if (text != null)
				{
					ShaderHelperPlugin.Log.LogDebug((object)("[Overlay] redraw: " + text + "."));
				}
			}
			catch (Exception ex)
			{
				Disable("upload", ex);
			}
		}

		private static void RedrawFinally()
		{
			OverlayCompose.Abort();
		}

		private static bool BeforeDrawLayer(object __instance, Texture2D __0, Texture2D __1, Material __2)
		{
			if (_disabled || _deferPass)
			{
				return true;
			}
			try
			{
				if ((Object)(object)__0 == (Object)null || (Object)(object)__1 == (Object)null || (Object)(object)__2 == (Object)null)
				{
					return true;
				}
				object? value = _composeField.GetValue(__instance);
				if (__2 != ((value is Material) ? value : null))
				{
					return true;
				}
				if (!Broken(__2))
				{
					return true;
				}
				if (!OverlayCompose.PassActive)
				{
					Disable("redraw brackets not firing", null);
					return true;
				}
				string text = OverlayCompose.Blend(__0, __1);
				if (text == null)
				{
					if (!_loggedTakeover)
					{
						_loggedTakeover = true;
						ShaderHelperPlugin.Log.LogInfo((object)("[Overlay] compose shader '" + ((Object)__2.shader).name + "' is pink: overlays composited on the CPU."));
					}
					return false;
				}
				if (!OverlayCompose.Dirty)
				{
					_deferPass = true;
					ShaderHelperPlugin.Log.LogWarning((object)("[Overlay] " + text + ": this redraw is left to Jotunn."));
					return true;
				}
				ShaderHelperPlugin.Log.LogWarning((object)("[Overlay] " + text + ": this overlay is skipped (the redraw is already partly composited)."));
				return false;
			}
			catch (Exception ex)
			{
				Disable("compose", ex);
				return true;
			}
		}

		private static bool Broken(Material mat)
		{
			int instanceID = ((Object)mat).GetInstanceID();
			if (instanceID == _decidedId && instanceID != 0)
			{
				return _decidedBroken;
			}
			if (!ShaderStructure.PlaceholderKnown && !_ensureTried)
			{
				_ensureTried = true;
				ShaderStructure.EnsurePlaceholder();
			}
			if (!ShaderStructure.PlaceholderKnown)
			{
				return false;
			}
			_decidedId = instanceID;
			_decidedBroken = ShaderStructure.IsPlaceholder(mat.shader);
			ShaderHelperPlugin.Log.LogInfo((object)(string.Format("[Overlay] compose material #{0} on '{1}': ", instanceID, ((Object)(object)mat.shader != (Object)null) ? ((Object)mat.shader).name : "?") + (_decidedBroken ? "placeholder-shaped, CPU compose takes over." : "not placeholder-shaped, Jotunn's own path.")));
			return _decidedBroken;
		}

		private static void Disable(string where, Exception ex)
		{
			_disabled = true;
			OverlayCompose.Abort();
			ShaderHelperPlugin.Log.LogError((object)("[Overlay] " + where + " failed; CPU compose off for this session, Jotunn's own path resumes" + ((ex != null) ? (": " + ex.GetType().Name + ": " + ex.Message) : ".")));
		}
	}
	internal static class ResilientPatcher
	{
		internal static void ApplyAll(Harmony harmony, Assembly assembly, ManualLogSource log)
		{
			if (harmony == null || assembly == null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			List<string> list = null;
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					List<MethodInfo> list2 = harmony.CreateClassProcessor(type).Patch();
					if (list2 != null && list2.Count > 0)
					{
						num++;
						if (log != null)
						{
							log.LogDebug((object)$"[ShaderHelper] patched {type.Name} ({list2.Count} method(s)).");
						}
					}
				}
				catch (Exception ex)
				{
					num2++;
					(list ?? (list = new List<string>())).Add(type.Name);
					if (log != null)
					{
						log.LogWarning((object)("[ShaderHelper] SKIP patch class " + type.Name + ": " + ex.Message));
					}
				}
			}
			if (num2 == 0)
			{
				if (log != null)
				{
					log.LogInfo((object)$"[ShaderHelper] patches: {num} applied, 0 skipped.");
				}
			}
			else if (log != null)
			{
				log.LogWarning((object)(string.Format("[ShaderHelper] patches: {0} applied, {1} skipped ({2}). ", num, num2, string.Join(", ", list)) + "A skipped patch usually means a Valheim update changed a hooked method; that feature is disabled but the mod is still running."));
			}
		}
	}
	[HarmonyPatch(typeof(VisEquipment), "AttachItem", new Type[]
	{
		typeof(int),
		typeof(int),
		typeof(Transform),
		typeof(bool),
		typeof(bool),
		typeof(int)
	})]
	internal static class PatchVisEquipmentAttachItem
	{
		private static readonly List<GameObject> _one = new List<GameObject>(1);

		private static void Postfix(int __0, GameObject __result)
		{
			try
			{
				if (!((Object)(object)__result == (Object)null))
				{
					_one.Clear();
					_one.Add(__result);
					EquipmentMaterials.RecordEquip(_one, __0, "AttachItem");
				}
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Equip] AttachItem postfix: " + ex.GetType().Name + ": " + ex.Message));
			}
			finally
			{
				_one.Clear();
			}
		}
	}
	[HarmonyPatch(typeof(VisEquipment), "AttachArmor", new Type[]
	{
		typeof(int),
		typeof(int),
		typeof(int)
	})]
	internal static class PatchVisEquipmentAttachArmor
	{
		private static void Postfix(int __0, List<GameObject> __result)
		{
			try
			{
				if (__result != null && __result.Count > 0)
				{
					EquipmentMaterials.RecordEquip(__result, __0, "AttachArmor");
				}
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Equip] AttachArmor postfix: " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class PatchZNetSceneAwake
	{
		private static void Postfix()
		{
			try
			{
				if (!VanillaBaseline.Loaded)
				{
					ShaderHelperPlugin.Log.LogError((object)"[Eval] baseline not loaded; nothing is decided.");
					return;
				}
				if (!Evaluator.IsRunning)
				{
					EquipmentMaterials.BeginSeed();
				}
				VanillaShaders.Harvest("world load");
				JotunnOverlayPatches.Arm();
				Evaluator.RequestPass("world load");
			}
			catch (Exception arg)
			{
				ShaderHelperPlugin.Log.LogError((object)$"[Eval] world-load hook failed (Valheim unaffected): {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "OnDestroy")]
	internal static class PatchZNetSceneOnDestroy
	{
		private static void Prefix()
		{
			try
			{
				Coverage.AtWorldClose();
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Coverage] failed (Valheim unaffected): " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void Postfix()
		{
			try
			{
				ShaderHelperPlugin.Log.LogInfo((object)$"[Eval] world closed. {EquipmentMaterials.Summary()}; total plugin work {WorkMeter.TotalMs:F0} ms.");
			}
			catch (Exception)
			{
			}
		}
	}
}
namespace ShaderHelper.Facts
{
	internal static class ArmorMaterials
	{
		private static readonly HashSet<int> _ids = new HashSet<int>();

		private static readonly HashSet<string> _names = new HashSet<string>(StringComparer.Ordinal);

		internal static int Count => _ids.Count;

		internal static void Begin()
		{
			_ids.Clear();
			_names.Clear();
		}

		internal static void AddFrom(GameObject prefab)
		{
			try
			{
				ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null);
				Material val2 = ((!((Object)(object)val != (Object)null)) ? null : val.m_itemData?.m_shared?.m_armorMaterial);
				if (!((Object)(object)val2 == (Object)null))
				{
					_ids.Add(((Object)val2).GetInstanceID());
					_names.Add(Names.MaterialBase(((Object)val2).name, out var _));
				}
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Equip] armour material skipped a prefab: " + ex.GetType().Name));
			}
		}

		internal static bool Contains(int id, string baseName, bool copy)
		{
			if (!_ids.Contains(id))
			{
				if (copy)
				{
					return _names.Contains(baseName);
				}
				return false;
			}
			return true;
		}
	}
	internal enum AuthoredStatus
	{
		Unknown,
		Known,
		AmbiguousName
	}
	internal static class AuthoredShader
	{
		private readonly struct Entry
		{
			internal readonly string MaterialName;

			internal readonly string Shader;

			internal Entry(string materialName, string shader)
			{
				MaterialName = materialName;
				Shader = shader;
			}
		}

		private static readonly Dictionary<int, Entry> _byId = new Dictionary<int, Entry>();

		private static readonly Dictionary<string, string> _byName = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly HashSet<string> _ambiguousNames = new HashSet<string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, Dictionary<int, string>> _assignedByName = new Dictionary<string, Dictionary<int, string>>(StringComparer.Ordinal);

		private static readonly Dictionary<int, Entry> _copyById = new Dictionary<int, Entry>();

		internal static int AmbiguousNameCount => _ambiguousNames.Count;

		internal static bool IsAssigned(string baseName, int shaderId)
		{
			if (baseName != null && _assignedByName.TryGetValue(baseName, out var value))
			{
				return value.ContainsKey(shaderId);
			}
			return false;
		}

		internal static bool TryGetAssigned(string baseName, int shaderId, out string authored)
		{
			authored = null;
			if (baseName != null && _assignedByName.TryGetValue(baseName, out var value))
			{
				return value.TryGetValue(shaderId, out authored);
			}
			return false;
		}

		internal static void NoteAssigned(string baseName, int shaderId, string authored)
		{
			if (baseName != null && !string.IsNullOrEmpty(authored))
			{
				if (!_assignedByName.TryGetValue(baseName, out var value))
				{
					value = (_assignedByName[baseName] = new Dictionary<int, string>());
				}
				if (!value.ContainsKey(shaderId))
				{
					value[shaderId] = authored;
				}
			}
		}

		internal static void Capture(int id, string baseName, bool copy, string boundName, int boundShaderId)
		{
			if (string.IsNullOrEmpty(boundName))
			{
				return;
			}
			Dictionary<int, Entry> dictionary = (copy ? _copyById : _byId);
			if (baseName != null && _assignedByName.TryGetValue(baseName, out var value) && value.TryGetValue(boundShaderId, out var value2))
			{
				if (!dictionary.ContainsKey(id))
				{
					dictionary[id] = new Entry(baseName, value2);
				}
				return;
			}
			if (copy)
			{
				CaptureCopy(id, baseName, boundName);
				return;
			}
			if (!_byId.ContainsKey(id))
			{
				_byId[id] = new Entry(baseName, boundName);
			}
			if (!_byName.TryGetValue(baseName, out var value3))
			{
				_byName[baseName] = boundName;
			}
			else if (!string.Equals(value3, boundName, StringComparison.OrdinalIgnoreCase))
			{
				_ambiguousNames.Add(baseName);
			}
		}

		private static void CaptureCopy(int id, string baseName, string boundName)
		{
			if (baseName != null && !_copyById.ContainsKey(id))
			{
				_copyById[id] = new Entry(baseName, boundName);
			}
		}

		internal static AuthoredStatus Get(int id, string baseName, bool copy, out string authored)
		{
			authored = null;
			if (!copy)
			{
				if (!_byId.TryGetValue(id, out var value) || !string.Equals(value.MaterialName, baseName, StringComparison.Ordinal))
				{
					return AuthoredStatus.Unknown;
				}
				authored = value.Shader;
				return AuthoredStatus.Known;
			}
			if (_ambiguousNames.Contains(baseName))
			{
				return AuthoredStatus.AmbiguousName;
			}
			if (_byName.TryGetValue(baseName, out authored) && !CopySource.OwnTexture(id, baseName))
			{
				return AuthoredStatus.Known;
			}
			authored = null;
			if (!_copyById.TryGetValue(id, out var value2) || !string.Equals(value2.MaterialName, baseName, StringComparison.Ordinal))
			{
				return AuthoredStatus.Unknown;
			}
			authored = value2.Shader;
			return AuthoredStatus.Known;
		}

		internal static bool ResolvesByName(string baseName, out string authored)
		{
			authored = null;
			if (baseName != null && !_ambiguousNames.Contains(baseName))
			{
				return _byName.TryGetValue(baseName, out authored);
			}
			return false;
		}

		internal static bool TryGetCopyFirstSight(int id, string baseName, out string shader)
		{
			shader = null;
			if (!_copyById.TryGetValue(id, out var value) || !string.Equals(value.MaterialName, baseName, StringComparison.Ordinal))
			{
				return false;
			}
			shader = value.Shader;
			return true;
		}

		internal static int ReadBoundId(Material m)
		{
			try
			{
				return ((Object)(object)m != (Object)null && (Object)(object)m.shader != (Object)null) ? ((Object)m.shader).GetInstanceID() : 0;
			}
			catch (Exception)
			{
				return 0;
			}
		}

		internal static string ReadBound(Material m)
		{
			if ((Object)(object)m == (Object)null)
			{
				return null;
			}
			Shader shader;
			try
			{
				shader = m.shader;
			}
			catch (Exception)
			{
				return null;
			}
			if ((Object)(object)shader == (Object)null)
			{
				return null;
			}
			string name;
			try
			{
				name = ((Object)shader).name;
			}
			catch (Exception)
			{
				return null;
			}
			if (string.IsNullOrEmpty(name) || string.Equals(name, "Hidden/InternalErrorShader", StringComparison.Ordinal))
			{
				return null;
			}
			return Names.Shader(name);
		}
	}
	internal static class CameraMaterials
	{
		private const int LineCap = 24;

		private static readonly Dictionary<Type, FieldInfo[]> _fieldsByType = new Dictionary<Type, FieldInfo[]>();

		private static readonly Dictionary<Type, bool> _blitsByType = new Dictionary<Type, bool>();

		private static readonly HashSet<int> _ids = new HashSet<int>();

		private static readonly HashSet<int> _blitIds = new HashSet<int>();

		private static readonly List<Component> _comps = new List<Component>();

		private static int _cameras;

		private static int _components;

		private static int _lines;

		private static int _errorShader;

		internal static bool Contains(int materialId)
		{
			return _ids.Contains(materialId);
		}

		internal static bool IsBlit(int materialId)
		{
			return _blitIds.Contains(materialId);
		}

		internal static void Walk()
		{
			_ids.Clear();
			_blitIds.Clear();
			_cameras = (_components = (_lines = (_errorShader = 0)));
			try
			{
				Camera[] allCameras = Camera.allCameras;
				foreach (Camera val in allCameras)
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					_cameras++;
					try
					{
						string name = ((Object)val).name;
						((Component)val).GetComponents<Component>(_comps);
						foreach (Component comp in _comps)
						{
							if (!((Object)(object)comp == (Object)null) && !(comp is Renderer))
							{
								_components++;
								try
								{
									ReadComponent(name, comp);
								}
								catch (Exception ex)
								{
									ShaderHelperPlugin.Log.LogDebug((object)("[Eval] camera walk skipped one component: " + ex.GetType().Name));
								}
							}
						}
					}
					catch (Exception ex2)
					{
						ShaderHelperPlugin.Log.LogDebug((object)("[Eval] camera walk skipped one camera: " + ex2.GetType().Name));
					}
					finally
					{
						_comps.Clear();
					}
				}
				ShaderHelperPlugin.Log.LogInfo((object)($"[Eval] camera-held: {_cameras} camera(s), {_components} component(s), {_ids.Count} material(s) " + $"({Math.Max(0, _ids.Count - _lines)} not listed, {_errorShader} on the error shader skipped). {PassThroughLine()}"));
			}
			catch (Exception ex3)
			{
				ShaderHelperPlugin.Log.LogWarning((object)("[Eval] camera walk FAILED (" + ex3.GetType().Name + "); no camera-held facts this pass."));
			}
		}

		private static void ReadComponent(string camName, Component c)
		{
			FieldInfo[] array = FieldsOf(((object)c).GetType());
			if (array.Length == 0)
			{
				return;
			}
			Behaviour val = (Behaviour)(object)((c is Behaviour) ? c : null);
			bool enabled = val == null || val.enabled;
			FieldInfo[] array2 = array;
			foreach (FieldInfo fieldInfo in array2)
			{
				object value;
				try
				{
					value = fieldInfo.GetValue(c);
				}
				catch (Exception)
				{
					continue;
				}
				Material val2 = (Material)((value is Material) ? value : null);
				if (val2 != null)
				{
					Note(camName, c, enabled, fieldInfo.Name, val2);
				}
				else if (value is Material[] array3)
				{
					for (int j = 0; j < array3.Length; j++)
					{
						Note(camName, c, enabled, $"{fieldInfo.Name}[{j}]", array3[j]);
					}
				}
			}
		}

		private static void Note(string camName, Component c, bool enabled, string field, Material m)
		{
			if ((Object)(object)m == (Object)null)
			{
				return;
			}
			Shader shader = m.shader;
			string text = (((Object)(object)shader != (Object)null) ? ((Object)shader).name : null);
			if (string.Equals(text, "Hidden/InternalErrorShader", StringComparison.Ordinal))
			{
				_errorShader++;
				return;
			}
			bool flag = Blits(((object)c).GetType());
			if (flag)
			{
				_blitIds.Add(((Object)m).GetInstanceID());
			}
			if (_ids.Add(((Object)m).GetInstanceID()) && _lines < 24)
			{
				_lines++;
				bool copy;
				string canonicalName = Names.MaterialBase(((Object)m).name ?? "", out copy);
				string item = Names.Shader(text ?? "");
				int num = (((Object)(object)shader != (Object)null) ? ((Object)shader).GetInstanceID() : 0);
				bool b = (Object)(object)shader != (Object)null && (VanillaShaders.IsHarvested(num) || ValheimPrefabShaders.IsProven(num));
				ShaderHelperPlugin.Log.LogInfo((object)($"[Eval]   camera-held: camera='{camName}' component='{((object)c).GetType().Name}' enabled={enabled} blits={YesNo(flag)} field='{field}' " + $"mat='{((Object)m).name}'#{((Object)m).GetInstanceID()} kind={RendererKinds.Get(((Object)m).GetInstanceID())} shader='{text}'#{num} " + "placeholder=" + YesNo(ShaderStructure.IsPlaceholder(shader)) + " cacheMaterial=" + YesNo(VanillaBaseline.IsVanillaMaterial(canonicalName)) + " shippedShader=" + YesNo(VanillaBaseline.ShaderNames.Contains(item) || VanillaBaseline.ShippedShaderNames.Contains(item)) + " " + $"builtin={YesNo(UnityBuiltIns.ShaderNames.Contains(item))} valheimInstance={YesNo(b)} queue={m.renderQueue}"));
			}
		}

		private static string PassThroughLine()
		{
			Shader val;
			try
			{
				val = Shader.Find("Hidden/BlitCopy");
			}
			catch (Exception ex)
			{
				return "pass-through 'Hidden/BlitCopy': Find threw " + ex.GetType().Name;
			}
			if ((Object)(object)val == (Object)null)
			{
				return "pass-through 'Hidden/BlitCopy': NOT found";
			}
			ShaderShape shaderShape = ShaderStructure.Read(val);
			return string.Format("pass-through '{0}': found #{1} {2} readable={3} ", "Hidden/BlitCopy", ((Object)val).GetInstanceID(), shaderShape, YesNo(ShaderStructure.IsReadable(shaderShape))) + "placeholder=" + YesNo(ShaderStructure.IsPlaceholder(val)) + " placeholderKnown=" + YesNo(ShaderStructure.PlaceholderKnown);
		}

		private static FieldInfo[] FieldsOf(Type type)
		{
			if (_fieldsByType.TryGetValue(type, out var value))
			{
				return value;
			}
			List<FieldInfo> list = new List<FieldInfo>();
			try
			{
				Type type2 = type;
				while (type2 != null && type2 != typeof(MonoBehaviour) && type2 != typeof(Behaviour) && type2 != typeof(Component))
				{
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						try
						{
							if (fieldInfo.FieldType == typeof(Material) || fieldInfo.FieldType == typeof(Material[]))
							{
								list.Add(fieldInfo);
							}
						}
						catch (Exception)
						{
						}
					}
					type2 = type2.BaseType;
				}
			}
			catch (Exception)
			{
				list.Clear();
			}
			FieldInfo[] array = list.ToArray();
			_fieldsByType[type] = array;
			return array;
		}

		private static bool Blits(Type type)
		{
			if (_blitsByType.TryGetValue(type, out var value))
			{
				return value;
			}
			bool flag = false;
			try
			{
				Type type2 = type;
				while (!flag && type2 != null && type2 != typeof(MonoBehaviour) && type2 != typeof(Behaviour) && type2 != typeof(Component))
				{
					MethodInfo[] methods = type2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					for (int i = 0; i < methods.Length; i++)
					{
						if (methods[i].Name == "OnRenderImage")
						{
							flag = true;
							break;
						}
					}
					if (flag)
					{
						break;
					}
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						try
						{
							if (fieldInfo.FieldType == typeof(CommandBuffer))
							{
								flag = true;
								break;
							}
						}
						catch (Exception)
						{
						}
					}
					type2 = type2.BaseType;
				}
			}
			catch (Exception)
			{
				flag = false;
			}
			_blitsByType[type] = flag;
			return flag;
		}

		private static string YesNo(bool b)
		{
			if (!b)
			{
				return "no";
			}
			return "yes";
		}
	}
	internal static class CopySource
	{
		private readonly struct Seen
		{
			internal readonly string Base;

			internal readonly string Slot;

			internal readonly string Name;

			internal readonly int Width;

			internal readonly int Height;

			internal Seen(string baseName, string slot, string name, int w, int h)
			{
				Base = baseName;
				Slot = slot;
				Name = name;
				Width = w;
				Height = h;
			}

			public override string ToString()
			{
				if (Name != null)
				{
					return $"{Slot} '{Name}' {Width}x{Height}";
				}
				return Slot + " none";
			}
		}

		private const int ExampleCap = 40;

		private static readonly Dictionary<string, Seen> _originals = new Dictionary<string, Seen>(StringComparer.Ordinal);

		private static readonly Dictionary<int, Seen> _copies = new Dictionary<int, Seen>();

		private static readonly Dictionary<string, int> _examples = new Dictionary<string, int>(StringComparer.Ordinal);

		private static int _byName;

		private static int _same;

		private static int _bothEmpty;

		private static int _sizeOnly;

		private static int _own;

		private static int _notComparable;

		private static int _overflow;

		internal static IEnumerable<string> Examples => from kv in _examples.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).ThenBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> kv) => kv.Key, StringComparer.Ordinal)
			select $"x{kv.Value} {kv.Key}";

		internal static void BeginPass()
		{
			_examples.Clear();
			_byName = (_same = (_bothEmpty = (_sizeOnly = (_own = (_notComparable = (_overflow = 0))))));
		}

		internal static void NoteOriginal(Material m, string baseName, Shader bound)
		{
			if (!((Object)(object)m == (Object)null) && !((Object)(object)bound == (Object)null) && !string.IsNullOrEmpty(baseName) && !_originals.ContainsKey(baseName) && !AuthoredShader.IsAssigned(baseName, ((Object)bound).GetInstanceID()) && TryRead(m, baseName, bound, out var seen))
			{
				_originals[baseName] = seen;
			}
		}

		internal static void NoteCopy(Material m, int id, string baseName, Shader bound)
		{
			if (!((Object)(object)m == (Object)null) && !((Object)(object)bound == (Object)null) && !string.IsNullOrEmpty(baseName) && !_copies.ContainsKey(id) && !AuthoredShader.IsAssigned(baseName, ((Object)bound).GetInstanceID()) && TryRead(m, baseName, bound, out var seen))
			{
				_copies[id] = seen;
			}
		}

		internal static bool OwnTexture(int id, string baseName)
		{
			if (Compare(id, baseName, out var original, out var copy) && original.Name != null && copy.Name != null)
			{
				return !string.Equals(original.Name, copy.Name, StringComparison.Ordinal);
			}
			return false;
		}

		internal static void Judge(int id, string baseName, string verdict)
		{
			if (!AuthoredShader.ResolvesByName(baseName, out var authored))
			{
				return;
			}
			_byName++;
			if (!Compare(id, baseName, out var original, out var copy))
			{
				_notComparable++;
				return;
			}
			if (original.Name == null && copy.Name == null)
			{
				_bothEmpty++;
				return;
			}
			if (original.Name == null || copy.Name == null)
			{
				_notComparable++;
				return;
			}
			if (string.Equals(original.Name, copy.Name, StringComparison.Ordinal))
			{
				if (original.Width == copy.Width && original.Height == copy.Height)
				{
					_same++;
				}
				else
				{
					_sizeOnly++;
				}
				return;
			}
			_own++;
			AuthoredShader.TryGetCopyFirstSight(id, baseName, out var shader);
			string key = $"'{baseName}' copy {copy} vs source {original}: own first sight '{shader}' (name gives '{authored}') -> {verdict}";
			if (_examples.TryGetValue(key, out var value))
			{
				_examples[key] = value + 1;
			}
			else if (_examples.Count < 40)
			{
				_examples[key] = 1;
			}
			else
			{
				_overflow++;
			}
		}

		internal static string Summary()
		{
			return $"copy source by texture: {_byName} cop(ies) of a captured original's name; same texture {_same}, size only {_sizeOnly}, " + $"both empty {_bothEmpty}, not comparable {_notComparable}; texture name differs {_own} (decided from their own first sight); " + $"{_examples.Count} distinct case(s) listed" + ((_overflow > 0) ? $", {_overflow} cop(ies) in cases past the first {40}." : ".");
		}

		private static bool Compare(int id, string baseName, out Seen original, out Seen copy)
		{
			original = default(Seen);
			copy = default(Seen);
			if (baseName != null && _originals.TryGetValue(baseName, out original) && _copies.TryGetValue(id, out copy) && string.Equals(copy.Base, baseName, StringComparison.Ordinal))
			{
				return string.Equals(original.Slot, copy.Slot, StringComparison.Ordinal);
			}
			return false;
		}

		private static bool TryRead(Material m, string baseName, Shader bound, out Seen seen)
		{
			seen = default(Seen);
			try
			{
				string text = MainSlot(bound);
				if (text == null)
				{
					return false;
				}
				Texture texture = m.GetTexture(text);
				if ((Object)(object)texture == (Object)null)
				{
					seen = new Seen(baseName, text, null, 0, 0);
					return true;
				}
				string text2 = ((Object)texture).name;
				if (string.IsNullOrEmpty(text2))
				{
					return false;
				}
				if (text2.StartsWith("JVLmock_", StringComparison.Ordinal))
				{
					text2 = text2.Substring(8);
				}
				seen = new Seen(baseName, text, text2, texture.width, texture.height);
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static string MainSlot(Shader sh)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Invalid comparison between Unknown and I4
			if (sh.FindPropertyIndex("_MainTex") >= 0)
			{
				return "_MainTex";
			}
			int propertyCount = sh.GetPropertyCount();
			for (int i = 0; i < propertyCount; i++)
			{
				if ((int)sh.GetPropertyType(i) == 4)
				{
					return sh.GetPropertyName(i);
				}
			}
			return null;
		}
	}
	internal static class EquipmentMaterials
	{
		private const int LinesPerMinute = 20;

		private const int NamesPerLine = 8;

		private static readonly MaterialSet _seeded = new MaterialSet();

		private static readonly MaterialSet _attached = new MaterialSet();

		private static readonly Dictionary<ItemType, int> _seedPrefabsByType = new Dictionary<ItemType, int>();

		private static readonly List<Material> _seen = new List<Material>();

		private static readonly HashSet<int> _seenIds = new HashSet<int>();

		private static readonly HashSet<int> _hookOnlyIds = new HashSet<int>();

		private static int _seedPrefabs;

		private static int _hookItem;

		private static int _hookArmor;

		private static int _hookSkipped;

		private static int _linesInWindow;

		private static float _windowStart;

		internal static bool SeedDone { get; private set; }

		internal static bool Contains(int id, string baseName, bool copy)
		{
			if (!_seeded.Contains(id, baseName, copy))
			{
				return _attached.Contains(id, baseName, copy);
			}
			return true;
		}

		internal static void BeginSeed()
		{
			_seeded.Clear();
			_seedPrefabsByType.Clear();
			_seedPrefabs = 0;
			SeedDone = false;
		}

		internal static void SeedFrom(GameObject prefab)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (EquipmentTypes.TryGetType(prefab, out var type) && EquipmentTypes.IsEquipment(type))
				{
					_seeded.AddFrom(prefab);
					_seedPrefabs++;
					_seedPrefabsByType.TryGetValue(type, out var value);
					_seedPrefabsByType[type] = value + 1;
				}
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Equip] seed skipped a prefab: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal static void EndSeed()
		{
			SeedDone = true;
		}

		internal static void RecordEquip(IReadOnlyList<GameObject> instances, int itemHash, string via)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			long timestamp = Stopwatch.GetTimestamp();
			try
			{
				GameObject val = null;
				try
				{
					val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(itemHash) : null);
				}
				catch (Exception)
				{
				}
				if (!EquipmentTypes.TryGetType(val, out var type) || !EquipmentTypes.IsEquipment(type))
				{
					_hookSkipped++;
					return;
				}
				if (via == "AttachArmor")
				{
					_hookArmor++;
				}
				else
				{
					_hookItem++;
				}
				_seen.Clear();
				foreach (GameObject instance in instances)
				{
					try
					{
						if ((Object)(object)instance != (Object)null)
						{
							_attached.AddFrom(instance, _seen);
							RendererKinds.RecordUnder(instance);
						}
					}
					catch (Exception ex2)
					{
						ShaderHelperPlugin.Log.LogDebug((object)("[Equip] " + via + " skipped an instance: " + ex2.GetType().Name + ": " + ex2.Message));
					}
				}
				Distinct(_seen);
				foreach (Material item in _seen)
				{
					string text = AuthoredShader.ReadBound(item);
					bool copy;
					string text2 = Names.MaterialBase(((Object)item).name, out copy);
					if (text != null && !VanillaBaseline.IsVanillaMaterial(text2))
					{
						AuthoredShader.Capture(((Object)item).GetInstanceID(), text2, copy, text, AuthoredShader.ReadBoundId(item));
					}
				}
				int num = 0;
				foreach (Material item2 in _seen)
				{
					bool copy2;
					string baseName = Names.MaterialBase(((Object)item2).name, out copy2);
					if (!_seeded.Contains(((Object)item2).GetInstanceID(), baseName, copy2))
					{
						num++;
						if (SeedDone)
						{
							_hookOnlyIds.Add(((Object)item2).GetInstanceID());
						}
					}
				}
				LogEquip(via, ((Object)val).name, type, num);
			}
			finally
			{
				_seen.Clear();
				WorkMeter.Add(Stopwatch.GetTimestamp() - timestamp);
			}
		}

		private static void Distinct(List<Material> list)
		{
			_seenIds.Clear();
			list.RemoveAll((Material m) => (Object)(object)m == (Object)null || !_seenIds.Add(((Object)m).GetInstanceID()));
			_seenIds.Clear();
		}

		private static void LogEquip(string via, string item, ItemType type, int notSeeded)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup - _windowStart > 60f)
			{
				_windowStart = realtimeSinceStartup;
				_linesInWindow = 0;
			}
			if (_linesInWindow++ >= 20)
			{
				return;
			}
			List<string> list = new List<string>(_seen.Count);
			foreach (Material item2 in _seen)
			{
				list.Add(Names.MaterialBase(((Object)item2).name, out var _));
			}
			ManualLogSource log = ShaderHelperPlugin.Log;
			log.LogInfo((object)($"[Equip] {via} '{item}' ({type}): {_seen.Count} material(s), " + (SeedDone ? (notSeeded + " not in the world-load seed") : "seed not built yet") + "; " + MaterialLine.PrefabCheck(item, list)));
			int num = 0;
			foreach (Material item3 in _seen)
			{
				if (num >= 8)
				{
					log.LogInfo((object)$"[Equip]   ... {_seen.Count - num} more");
					break;
				}
				string text;
				try
				{
					text = MaterialLine.Describe(item3);
				}
				catch (Exception ex)
				{
					text = "'" + ((Object)item3).name + "' not describable: " + ex.GetType().Name;
				}
				if (text != null)
				{
					num++;
					log.LogInfo((object)("[Equip]   " + text));
				}
			}
		}

		internal static string Summary()
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder($"equipment: seed {_seedPrefabs} prefab(s) -> {_seeded.Count} material(s) [");
			bool flag = true;
			foreach (KeyValuePair<ItemType, int> item in _seedPrefabsByType)
			{
				stringBuilder.Append(flag ? "" : " ").Append(item.Key).Append('=')
					.Append(item.Value);
				flag = false;
			}
			stringBuilder.Append($"]; equip hooks (calls): AttachItem={_hookItem} AttachArmor={_hookArmor} non-equipment skipped={_hookSkipped}, " + $"{_attached.Count} distinct material(s) recorded, {_hookOnlyIds.Count} distinct not in the seed");
			return stringBuilder.ToString();
		}
	}
	internal static class EquipmentTypes
	{
		internal static bool IsEquipment(ItemType t)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected I4, but got Unknown
			switch (t - 3)
			{
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 8:
			case 9:
			case 11:
			case 12:
			case 14:
			case 15:
			case 16:
			case 19:
			case 21:
				return true;
			default:
				return false;
			}
		}

		internal static bool TryGetType(GameObject prefab, out ItemType type)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected I4, but got Unknown
			type = (ItemType)0;
			if ((Object)(object)prefab == (Object)null)
			{
				return false;
			}
			try
			{
				ItemDrop component = prefab.GetComponent<ItemDrop>();
				SharedData val = ((!((Object)(object)component != (Object)null)) ? null : component.m_itemData?.m_shared);
				if (val == null)
				{
					return false;
				}
				type = (ItemType)(int)val.m_itemType;
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
	internal sealed class MaterialSet
	{
		private static readonly List<Renderer> _renderers = new List<Renderer>();

		private static readonly List<Material> _materials = new List<Material>();

		private readonly HashSet<int> _ids = new HashSet<int>();

		private readonly HashSet<string> _names = new HashSet<string>(StringComparer.Ordinal);

		internal int Count => _ids.Count;

		internal int AddFrom(GameObject root, List<Material> seen = null)
		{
			if ((Object)(object)root == (Object)null)
			{
				return 0;
			}
			int num = 0;
			_renderers.Clear();
			root.GetComponentsInChildren<Renderer>(true, _renderers);
			foreach (Renderer renderer in _renderers)
			{
				if ((Object)(object)renderer == (Object)null)
				{
					continue;
				}
				_materials.Clear();
				renderer.GetSharedMaterials(_materials);
				foreach (Material material in _materials)
				{
					if (!((Object)(object)material == (Object)null))
					{
						if (_ids.Add(((Object)material).GetInstanceID()))
						{
							num++;
						}
						_names.Add(Names.MaterialBase(((Object)material).name, out var _));
						seen?.Add(material);
					}
				}
			}
			_renderers.Clear();
			_materials.Clear();
			return num;
		}

		internal bool Contains(int id, string baseName, bool copy)
		{
			if (!_ids.Contains(id))
			{
				if (copy)
				{
					return _names.Contains(baseName);
				}
				return false;
			}
			return true;
		}

		internal void Clear()
		{
			_ids.Clear();
			_names.Clear();
		}
	}
	internal static class Names
	{
		private const string InstanceSuffix = " (Instance)";

		private const string CloneSuffix = "(Clone)";

		private const string JotunnMockPrefix = "JVLmock_";

		internal static string MaterialBase(string name, out bool copy)
		{
			copy = false;
			if (string.IsNullOrEmpty(name))
			{
				return name ?? "";
			}
			string text = name;
			while (true)
			{
				if (text.EndsWith(" (Instance)", StringComparison.Ordinal))
				{
					text = text.Substring(0, text.Length - " (Instance)".Length);
				}
				else
				{
					if (!text.EndsWith("(Clone)", StringComparison.Ordinal))
					{
						break;
					}
					text = text.Substring(0, text.Length - "(Clone)".Length).TrimEnd(Array.Empty<char>());
				}
				copy = true;
			}
			return text;
		}

		internal static string Shader(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return name;
			}
			if (!name.StartsWith("JVLmock_", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring("JVLmock_".Length);
		}

		internal static bool IsMock(string shaderName)
		{
			return shaderName?.StartsWith("JVLmock_", StringComparison.Ordinal) ?? false;
		}
	}
	internal static class PassThroughTarget
	{
		private static Target _target;

		private static int _loaded;

		internal static bool TryGet(out Target target)
		{
			target = _target;
			return (Object)(object)target.Shader != (Object)null;
		}

		internal static void Clear()
		{
			_target = default(Target);
		}

		internal static void Resolve(int loaded)
		{
			_target = default(Target);
			_loaded = loaded;
			Shader val;
			try
			{
				val = Shader.Find("Hidden/BlitCopy");
			}
			catch (Exception)
			{
				val = null;
			}
			if (!((Object)(object)val == (Object)null) && ShaderStructure.PlaceholderKnown)
			{
				ShaderShape shaderShape = ShaderStructure.Read(val);
				if (ShaderStructure.IsReadable(shaderShape) && !ShaderStructure.Equal(shaderShape, ShaderStructure.Placeholder))
				{
					_target = new Target(val, ((Object)val).GetInstanceID(), TargetTier.Engine, 1);
				}
			}
		}

		internal static string Summary()
		{
			object arg;
			if (!((Object)(object)_target.Shader != (Object)null))
			{
				arg = "none";
			}
			else
			{
				int instanceId = _target.InstanceId;
				arg = "#" + instanceId;
			}
			return $"passThrough={arg} (loaded x{_loaded})";
		}
	}
	internal static class PieceMaterials
	{
		private static readonly MaterialSet _set = new MaterialSet();

		private static int _prefabs;

		internal static bool Contains(int id, string baseName, bool copy)
		{
			return _set.Contains(id, baseName, copy);
		}

		internal static void Begin()
		{
			_set.Clear();
			_prefabs = 0;
		}

		internal static void AddFrom(GameObject prefab)
		{
			try
			{
				if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent<Piece>() == (Object)null))
				{
					_set.AddFrom(prefab);
					_prefabs++;
				}
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Pieces] skipped a prefab: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal static string Summary()
		{
			return $"pieces: {_prefabs} prefab(s) -> {_set.Count} material(s)";
		}
	}
	internal static class PropertyMatch
	{
		private static readonly HashSet<string> _neverReturned = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Custom/Creature", "Custom/Piece" };

		private static readonly string[] _neverPrefixes = new string[5] { "Hidden/", "UI/", "GUI/", "TextMeshPro/", "Skybox/" };

		private const int MinProperties = 2;

		private static readonly Dictionary<string, List<string>> _valheimBySignature = new Dictionary<string, List<string>>(StringComparer.Ordinal);

		private static readonly Dictionary<int, string> _signatureById = new Dictionary<int, string>();

		private static bool _built;

		internal static void BeginPass()
		{
			_valheimBySignature.Clear();
			_signatureById.Clear();
			_built = false;
		}

		internal static string Find(Shader bound, string boundName)
		{
			if ((Object)(object)bound == (Object)null)
			{
				return null;
			}
			if (!_built)
			{
				Build();
			}
			string text = Signature(bound);
			if (text == null || !_valheimBySignature.TryGetValue(text, out var value))
			{
				return null;
			}
			string text2 = ((value.Count == 1) ? value[0] : TieBreak(value, boundName));
			if (text2 != null && !_neverReturned.Contains(text2))
			{
				return text2;
			}
			return null;
		}

		private static string TieBreak(List<string> names, string boundName)
		{
			string text = Normal(boundName);
			if (text.Length == 0)
			{
				return null;
			}
			string result = null;
			string result2 = null;
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			foreach (string name in names)
			{
				string text2 = Normal(name);
				if (text2.Length == 0)
				{
					continue;
				}
				if (text2 == text)
				{
					result = name;
					num++;
				}
				else if (text.Contains(text2))
				{
					if (text2.Length > num2)
					{
						num2 = text2.Length;
						result2 = name;
						num3 = 1;
					}
					else if (text2.Length == num2)
					{
						num3++;
					}
				}
			}
			switch (num)
			{
			case 1:
				return result;
			case 0:
				if (num3 == 1)
				{
					return result2;
				}
				break;
			}
			return null;
		}

		private static string Normal(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length);
			foreach (char c in s)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}

		private static void Build()
		{
			_built = true;
			foreach (string name in VanillaBaseline.ShaderNames)
			{
				if (IsNeverCandidate(name) || !Targets.TryGet(name, out var target) || (Object)(object)target.Shader == (Object)null)
				{
					continue;
				}
				string text = Signature(target.Shader);
				if (text != null)
				{
					if (!_valheimBySignature.TryGetValue(text, out var value))
					{
						value = (_valheimBySignature[text] = new List<string>(1));
					}
					if (!value.Exists((string n) => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)))
					{
						value.Add(name);
					}
				}
			}
			foreach (List<string> value2 in _valheimBySignature.Values)
			{
				value2.Sort(StringComparer.Ordinal);
			}
		}

		private static bool IsNeverCandidate(string name)
		{
			string[] neverPrefixes = _neverPrefixes;
			foreach (string value in neverPrefixes)
			{
				if (name.StartsWith(value, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static string Signature(Shader sh)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected I4, but got Unknown
			int instanceID = ((Object)sh).GetInstanceID();
			if (_signatureById.TryGetValue(instanceID, out var value))
			{
				return value;
			}
			string text = null;
			try
			{
				int propertyCount = sh.GetPropertyCount();
				if (propertyCount >= 2)
				{
					string[] array = new string[propertyCount];
					for (int i = 0; i < propertyCount; i++)
					{
						array[i] = sh.GetPropertyName(i) + ":" + (int)sh.GetPropertyType(i);
					}
					Array.Sort(array, (IComparer<string>?)StringComparer.Ordinal);
					StringBuilder stringBuilder = new StringBuilder();
					string[] array2 = array;
					foreach (string value2 in array2)
					{
						stringBuilder.Append(value2).Append(';');
					}
					text = stringBuilder.ToString();
				}
			}
			catch (Exception)
			{
				text = null;
			}
			_signatureById[instanceID] = text;
			return text;
		}
	}
	[Flags]
	internal enum RendererKind
	{
		None = 0,
		Mesh = 1,
		Particle = 2,
		Other = 4
	}
	internal static class RendererKinds
	{
		private static readonly Dictionary<int, RendererKind> _byId = new Dictionary<int, RendererKind>();

		private static readonly List<Material> _buf = new List<Material>();

		private static readonly List<Renderer> _renderersBuf = new List<Renderer>();

		private static int _renderers;

		internal static void RecordUnder(GameObject root)
		{
			if ((Object)(object)root == (Object)null)
			{
				return;
			}
			_renderersBuf.Clear();
			try
			{
				root.GetComponentsInChildren<Renderer>(true, _renderersBuf);
				foreach (Renderer item in _renderersBuf)
				{
					Record(item);
				}
			}
			catch (Exception)
			{
			}
			finally
			{
				_renderersBuf.Clear();
			}
		}

		internal static void Begin()
		{
			_byId.Clear();
			_renderers = 0;
		}

		internal static void Record(Renderer r)
		{
			try
			{
				if ((Object)(object)r == (Object)null)
				{
					return;
				}
				RendererKind rendererKind = KindOf(r);
				_renderers++;
				_buf.Clear();
				r.GetSharedMaterials(_buf);
				foreach (Material item in _buf)
				{
					if (!((Object)(object)item == (Object)null))
					{
						int instanceID = ((Object)item).GetInstanceID();
						_byId.TryGetValue(instanceID, out var value);
						_byId[instanceID] = value | rendererKind;
					}
				}
			}
			catch (Exception)
			{
			}
			finally
			{
				_buf.Clear();
			}
		}

		internal static RendererKind Get(int id)
		{
			if (!_byId.TryGetValue(id, out var value))
			{
				return RendererKind.None;
			}
			return value;
		}

		private static RendererKind KindOf(Renderer r)
		{
			if (r is MeshRenderer || r is SkinnedMeshRenderer)
			{
				return RendererKind.Mesh;
			}
			if (r is ParticleSystemRenderer || r is TrailRenderer || r is LineRenderer)
			{
				return RendererKind.Particle;
			}
			return RendererKind.Other;
		}

		internal static string Summary()
		{
			return $"renderers: {_renderers} -> {_byId.Count} material(s) classified";
		}
	}
	internal readonly struct ShaderShape
	{
		internal readonly int Subshaders;

		internal readonly int ActivePasses;

		internal readonly int[] PassesPerSubshader;

		internal ShaderShape(int subshaders, int activePasses, int[] passesPerSubshader)
		{
			Subshaders = subshaders;
			ActivePasses = activePasses;
			PassesPerSubshader = passesPerSubshader ?? Array.Empty<int>();
		}

		public override string ToString()
		{
			return string.Format("sub={0} passes={1} [{2}]", Subshaders, ActivePasses, string.Join(",", PassesPerSubshader));
		}
	}
	internal static class ShaderStructure
	{
		private const int MaxEnsureScans = 3;

		private static int _ensureScans;

		internal static ShaderShape Placeholder { get; private set; }

		internal static bool PlaceholderKnown { get; private set; }

		internal static void SetPlaceholder(ShaderShape shape, bool known)
		{
			Placeholder = shape;
			PlaceholderKnown = known && IsReadable(shape);
		}

		internal static ShaderShape Read(Shader sh)
		{
			int num;
			try
			{
				num = sh.subshaderCount;
			}
			catch (Exception)
			{
				num = -1;
			}
			int activePasses;
			try
			{
				activePasses = sh.passCount;
			}
			catch (Exception)
			{
				activePasses = -1;
			}
			if (num <= 0)
			{
				return new ShaderShape(num, activePasses, Array.Empty<int>());
			}
			int[] array = new int[num];
			for (int i = 0; i < num; i++)
			{
				try
				{
					array[i] = sh.GetPassCountInSubshader(i);
				}
				catch (Exception)
				{
					array[i] = -1;
				}
			}
			return new ShaderShape(num, activePasses, array);
		}

		internal static bool IsReadable(ShaderShape s)
		{
			if (s.Subshaders < 0 || s.ActivePasses < 0)
			{
				return false;
			}
			int[] passesPerSubshader = s.PassesPerSubshader;
			for (int i = 0; i < passesPerSubshader.Length; i++)
			{
				if (passesPerSubshader[i] < 0)
				{
					return false;
				}
			}
			return true;
		}

		internal static bool Equal(ShaderShape a, ShaderShape b)
		{
			if (a.Subshaders < 0 || b.Subshaders < 0 || a.ActivePasses < 0 || b.ActivePasses < 0)
			{
				return false;
			}
			if (a.Subshaders != b.Subshaders || a.ActivePasses != b.ActivePasses)
			{
				return false;
			}
			if (a.PassesPerSubshader.Length != b.PassesPerSubshader.Length)
			{
				return false;
			}
			for (int i = 0; i < a.PassesPerSubshader.Length; i++)
			{
				int num = a.PassesPerSubshader[i];
				int num2 = b.PassesPerSubshader[i];
				if (num < 0 || num2 < 0 || num != num2)
				{
					return false;
				}
			}
			return true;
		}

		internal static bool IsPlaceholder(Shader sh)
		{
			if ((Object)(object)sh != (Object)null && PlaceholderKnown)
			{
				return Equal(Read(sh), Placeholder);
			}
			return false;
		}

		internal static void EnsurePlaceholder()
		{
			if (PlaceholderKnown || _ensureScans >= 3)
			{
				return;
			}
			_ensureScans++;
			try
			{
				Shader[] array = Resources.FindObjectsOfTypeAll<Shader>();
				foreach (Shader val in array)
				{
					if (!((Object)(object)val == (Object)null) && string.Equals(((Object)val).name, "Hidden/InternalErrorShader", StringComparison.Ordinal))
					{
						SetPlaceholder(Read(val), known: true);
						break;
					}
				}
			}
			catch (Exception)
			{
			}
		}
	}
	internal sealed class ShippedRow
	{
		internal string Shader { get; private set; }

		internal string Source { get; private set; }

		internal int Queue { get; private set; }

		internal string[] Keywords { get; private set; }

		internal Dictionary<string, float> Blend { get; private set; }

		internal string SourceBundle { get; private set; }

		internal static ShippedRow Parse(string[] cols)
		{
			if (cols.Length < 6 || cols[1].Length == 0 || cols[2].Length == 0)
			{
				return null;
			}
			if (!int.TryParse(cols[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return null;
			}
			string[] array = ((cols[4] == "-") ? Array.Empty<string>() : cols[4].Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
			Array.Sort(array, (IComparer<string>?)StringComparer.Ordinal);
			Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.Ordinal);
			if (cols[5] != "-")
			{
				string[] array2 = cols[5].Split(new char[1] { ';' });
				foreach (string text in array2)
				{
					int num = text.IndexOf('=');
					if (num <= 0)
					{
						return null;
					}
					if (!float.TryParse(text.Substring(num + 1), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
					{
						return null;
					}
					dictionary[text.Substring(0, num)] = result2;
				}
			}
			string sourceBundle = (cols[2].StartsWith("bundle:", StringComparison.Ordinal) ? cols[2].Substring(7) : null);
			return new ShippedRow
			{
				Shader = cols[1],
				Source = cols[2],
				SourceBundle = sourceBundle,
				Queue = result,
				Keywords = array,
				Blend = dictionary
			};
		}
	}
	internal enum TargetTier
	{
		None,
		Bundle,
		Engine,
		Structural,
		Source,
		Prefab
	}
	internal readonly struct Target
	{
		internal readonly Shader Shader;

		internal readonly int InstanceId;

		internal readonly TargetTier Tier;

		internal readonly int RivalCount;

		internal Target(Shader shader, int instanceId, TargetTier tier, int rivalCount)
		{
			Shader = shader;
			InstanceId = instanceId;
			Tier = tier;
			RivalCount = rivalCount;
		}
	}
	internal static class Targets
	{
		private const int NameCap = 24;

		private static readonly Dictionary<string, Target> _byName = new Dictionary<string, Target>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, HashSet<int>> _loadedIds = new Dictionary<string, HashSet<int>>(StringComparer.OrdinalIgnoreCase);

		private static readonly int[] _tierCounts = new int[4];

		private static readonly List<string> _refused = new List<string>();

		private static readonly Dictionary<string, List<Shader>> _unresolved = new Dictionary<string, List<Shader>>(StringComparer.OrdinalIgnoreCase);

		private static int _prefabTier;

		private static int _bundleRefused;

		private static int _noVanillaAuthor;

		private static int _ambiguous;

		private static int _noneUsable;

		private static int _unreadable;

		private static long _buildTicks;

		internal static bool TryGetForSource(string name, string sourceBundle, out Target target)
		{
			target = default(Target);
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			if (sourceBundle != null && _loadedIds.TryGetValue(name, out var value))
			{
				Shader val = VanillaShaders.GetFrom(sourceBundle, name);
				if ((Object)(object)val != (Object)null && value.Contains(((Object)val).GetInstanceID()))
				{
					target = new Target(val, ((Object)val).GetInstanceID(), TargetTier.Source, value.Count);
					return true;
				}
			}
			return TryGet(name, out target);
		}

		internal static bool TryGet(string name, out Target target)
		{
			target = default(Target);
			if (!string.IsNullOrEmpty(name))
			{
				return _byName.TryGetValue(name, out target);
			}
			return false;
		}

		internal static void Build()
		{
			long timestamp = Stopwatch.GetTimestamp();
			ResetCounts();
			Dictionary<string, List<Shader>> dictionary = LoadedByName();
			_loadedIds.Clear();
			if (dictionary == null)
			{
				return;
			}
			foreach (KeyValuePair<string, List<Shader>> item in dictionary)
			{
				HashSet<int> hashSet = new HashSet<int>();
				foreach (Shader item2 in item.Value)
				{
					if ((Object)(object)item2 != (Object)null)
					{
						hashSet.Add(((Object)item2).GetInstanceID());
					}
				}
				_loadedIds[item.Key] = hashSet;
			}
			ReadPlaceholder(dictionary);
			PassThroughTarget.Resolve(dictionary.TryGetValue("Hidden/BlitCopy", out var value) ? value.Count : 0);
			foreach (string shaderName in VanillaBaseline.ShaderNames)
			{
				dictionary.TryGetValue(shaderName, out var value2);
				if (value2 != null && value2.Count != 0)
				{
					TargetTier tier = TargetTier.None;
					Shader val = TryBundle(shaderName, value2, ref tier) ?? TryEngine(shaderName, value2, ref tier) ?? TryStructural(shaderName, value2, ref tier);
					if ((Object)(object)val == (Object)null)
					{
						_unresolved[shaderName] = value2;
						continue;
					}
					_byName[shaderName] = new Target(val, ((Object)val).GetInstanceID(), tier, value2.Count);
					_tierCounts[(int)tier]++;
				}
			}
			_buildTicks = Stopwatch.GetTimestamp() - timestamp;
		}

		internal static void AddPrefabProven()
		{
			try
			{
				if (!ShaderStructure.PlaceholderKnown)
				{
					return;
				}
				foreach (KeyValuePair<string, List<Shader>> item in _unresolved)
				{
					if (!ValheimPrefabShaders.TryGetSole(item.Key, out var instanceId))
					{
						continue;
					}
					foreach (Shader item2 in item.Value)
					{
						if (!((Object)(object)item2 == (Object)null) && ((Object)item2).GetInstanceID() == instanceId)
						{
							ShaderShape shaderShape = ShaderStructure.Read(item2);
							if (ShaderStructure.IsReadable(shaderShape) && !ShaderStructure.Equal(shaderShape, ShaderStructure.Placeholder))
							{
								_byName[item.Key] = new Target(item2, instanceId, TargetTier.Prefab, item.Value.Count);
								_prefabTier++;
							}
							break;
						}
					}
				}
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogDebug((object)("[Targets] prefab tier skipped: " + ex.GetType().Name + ": " + ex.Message));
			}
			finally
			{
				_unresolved.Clear();
			}
		}

		internal static string Summary()
		{
			return $"targets={_byName.Count} (bundle={_tierCounts[1]} engine={_tierCounts[2]} structural={_tierCounts[3]} prefab={_prefabTier}); " + PassThroughTarget.Summary() + "; " + $"refused: bundleNotLoaded={_bundleRefused} noVanillaAuthor={_noVanillaAuthor} ambiguous={_ambiguous} " + string.Format("noneUsable={0} unreadable={1}; placeholder={2}; ", _noneUsable, _unreadable, ShaderStructure.PlaceholderKnown ? ShaderStructure.Placeholder.ToString() : "UNREAD") + $"{WorkMeter.Ms(_buildTicks):F1} ms" + ((_refused.Count > 0) ? ("; refused names: " + string.Join(", ", _refused)) : "");
		}

		private static Shader TryBundle(string name, List<Shader> rivals, ref TargetTier tier)
		{
			Shader val = VanillaShaders.Get(name);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			if (Contains(rivals, val))
			{
				tier = TargetTier.Bundle;
				return val;
			}
			_bundleRefused++;
			Note(name + "(bundleNotLoaded)");
			return null;
		}

		private static Shader TryEngine(string name, List<Shader> rivals, ref TargetTier tier)
		{
			if (!UnityBuiltIns.ShaderNames.Contains(name))
			{
				return null;
			}
			Shader val;
			try
			{
				val = Shader.Find(name);
			}
			catch (Exception)
			{
				val = null;
			}
			if ((Object)(object)val == (Object)null || !Contains(rivals, val))
			{
				return null;
			}
			tier = TargetTier.Engine;
			return val;
		}

		private static Shader TryStructural(string name, List<Shader> rivals, ref TargetTier tier)
		{
			if (!ShaderStructure.PlaceholderKnown)
			{
				return null;
			}
			if (!VanillaBaseline.AuthoredShaderNames.Contains(name))
			{
				_noVanillaAuthor++;
				Note(name + "(noVanillaAuthor)");
				return null;
			}
			Shader val = null;
			int num = 0;
			foreach (Shader rival in rivals)
			{
				ShaderShape shaderShape = ShaderStructure.Read(rival);
				if (!ShaderStructure.IsReadable(shaderShape))
				{
					_unreadable++;
					Note(name + "(unreadableRival)");
					return null;
				}
				if (!ShaderStructure.Equal(shaderShape, ShaderStructure.Placeholder))
				{
					num++;
					if ((Object)(object)val == (Object)null)
					{
						val = rival;
					}
				}
			}
			switch (num)
			{
			case 1:
				tier = TargetTier.Structural;
				return val;
			case 0:
				_noneUsable++;
				break;
			default:
				_ambiguous++;
				break;
			}
			Note(name + "(" + ((num == 0) ? "noneUsable" : (num + "usable")) + ")");
			return null;
		}

		private static Dictionary<string, List<Shader>> LoadedByName()
		{
			Shader[] array;
			try
			{
				array = Resources.FindObjectsOfTypeAll<Shader>();
			}
			catch (Exception ex)
			{
				ShaderHelperPlugin.Log.LogWarning((object)("[Targets] shader scan FAILED (" + ex.GetType().Name + "); no targets this pass (read failure, not a measurement)."));
				return null;
			}
			Dictionary<string, List<Shader>> dictionary = new Dictionary<string, List<Shader>>(StringComparer.OrdinalIgnoreCase);
			Shader[] array2 = array;
			foreach (Shader val in array2)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				string name;
				try
				{
					name = ((Object)val).name;
				}
				catch (Exception)
				{
					continue;
				}
				if (!string.IsNullOrEmpty(name))
				{
					if (!dictionary.TryGetValue(name, out var value))
					{
						value = (dictionary[name] = new List<Shader>());
					}
					if (!Contains(value, val))
					{
						value.Add(val);
					}
				}
			}
			return dictionary;
		}

		private static void ReadPlaceholder(Dictionary<string, List<Shader>> byName)
		{
			if (byName.TryGetValue("Hidden/InternalErrorShader", out var value) && value.Count > 0)
			{
				ShaderStructure.SetPlaceholder(ShaderStructure.Read(value[0]), known: true);
				if (ShaderStructure.PlaceholderKnown)
				{
					return;
				}
			}
			ShaderStructure.SetPlaceholder(default(ShaderShape), known: false);
			ShaderHelperPlugin.Log.LogWarning((object)"[Targets] 'Hidden/InternalErrorShader' not loaded or not fully readable; structural tier disabled this pass.");
		}

		private static bool Contains(List<Shader> list, Shader sh)
		{
			int instanceID = ((Object)sh).GetInstanceID();
			foreach (Shader item in list)
			{
				if ((Object)(object)item != (Object)null && ((Object)item).GetInstanceID() == instanceID)
				{
					return true;
				}
			}
			return false;
		}

		private static void Note(string s)
		{
			if (_refused.Count < 24)
			{
				_refused.Add(s);
			}
		}

		private static void ResetCounts()
		{
			_byName.Clear();
			_refused.Clear();
			_unresolved.Clear();
			PassThroughTarget.Clear();
			Array.Clear(_tierCounts, 0, _tierCounts.Length);
			_bundleRefused = (_noVanillaAuthor = (_ambiguous = (_noneUsable = (_unreadable = (_prefabTier = 0)))));
		}
	}
	internal static class UnityBuiltIns
	{
		internal const string ErrorShaderName = "Hidden/InternalErrorShader";

		internal const string PassThroughShaderName = "Hidden/BlitCopy";

		internal static readonly HashSet<string> ShaderNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			"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<string> DefaultMaterialNames = new HashSet<string>(StringComparer.Ordinal) { "Default-Line", "Default-Material", "Default-Particle", "Default-ParticleSystem", "Sprites-Default", "Font Material" };
	}
	internal static class ValheimMaterials
	{
		private static readonly Dictionary<string, Material> _byName = new Dictionary<string, Material>(StringComparer.Ordinal);

		internal static int Count => _byName.Count;

		internal static void Begin()
		{
			_byName.Clear();
		}

		internal static void End()
		{
			_byName.Clear();
		}

		internal static void Add(string baseName, Material m)
		{
			if (!((Object)(object)m == (Object)null) && !string.IsNullOrEmpty(baseName))
			{
				if (!_byName.TryGetValue(baseName, out var value))
				{
					_byName[baseName] = m;
				}
				else if ((Object)(object)value != (Object)null && ((Object)value).GetInstanceID() != ((Object)m).GetInstanceID())
				{
					_byName[baseName] = null;
				}
			}
		}

		internal static bool TryGet(string baseName, out Material m)
		{
			m = null;
			if (!string.IsNullOrEmpty(baseName) && _byName.TryGetValue(baseName, out m) && (Object)(object)m != (Object)null)
			{
				return (Object)(object)m.shader != (Object)null;
			}
			return false;
		}
	}
	internal static class ValheimPrefabShaders
	{
		private static readonly List<Renderer> _renderers = new List<Renderer>();

		private static readonly List<Material> _materials = new List<Material>();

		private static readonly HashSet<string> _listed = new HashSet<string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, Dictionary<int, HashSet<string>>> _byName = new Dictionary<string, Dictionary<int, HashSet<string>>>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<int> _ids = new HashSet<int>();

		private static readonly Dictionary<int, (string Name, int Id)?> _judged = new Dictionary<int, (string, int)?>();

		private static readonly HashSet<int> _valheimMaterialIds = new HashSet<int>();

		private static readonly HashSet<string> _offPairNames = new HashSet<string>(StringComparer.Ordinal);

		private static int _prefabs;

		private static int _accepted;

		private static bool _faulted;

		private static long _ticks;

		private const int MinRoots = 2;

		internal static void Begin()
		{
			_byName.Clear();
			_ids.Clear();
			_judged.Clear();
			_valheimMaterialIds.Clear();
			_offPairNames.Clear();
			_prefabs = (_accepted = 0);
			_faulted = false;
			_ticks = 0L;
			ValheimMaterials.Begin();
		}

		internal static void AddFrom(GameObject prefab)
		{
			long timestamp = Stopwatch.GetTimestamp();
			try
			{
				if ((Object)(object)prefab == (Object)null || !VanillaPrefabs.TryGet(((Object)prefab).name, out var prefabs))
				{
					return;
				}
				_listed.Clear();
				foreach (VanillaPrefab item2 in prefabs)
				{
					foreach (var material in item2.Materials)
					{
						string item = material.Material;
						if (VanillaBaseline.IsVanillaMaterial(item))
						{
							_listed.Add(item);
						}
					}
				}
				if (_listed.Count == 0)
				{
					return;
				}
				_prefabs++;
				_renderers.Clear();
				prefab.GetComponentsInChildren<Renderer>(true, _renderers);
				foreach (Renderer renderer in _renderers)
				{
					if ((Object)(object)renderer == (Object)null)
					{
						continue;
					}
					_materials.Clear();
					renderer.GetSharedMaterials(_materials);
					foreach (Material material2 in _materials)
					{
						Note(material2, ((Object)prefab).name);
					}
				}
			}
			catch (Exception ex)
			{
				_faulted = true;
				ShaderHelperPlugin.Log.LogDebug((object)("[Targets] prefab proof skipped a prefab: " + ex.GetType().Name + ": " + ex.Message));
			}
			finally
			{
				_renderers.Clear();
				_materials.Clear();
				_ticks += Stopwatch.GetTimestamp() - timestamp;
			}
		}

		private static void Note(Material m, string root)
		{
			if ((Object)(object)m == (Object)null)
			{
				return;
			}
			bool copy;
			string text = Names.MaterialBase(((Object)m).name, out copy);
			if (copy || !_listed.Contains(text))
			{
				return;
			}
			int instanceID = ((Object)m).GetInstanceID();
			_valheimMaterialIds.Add(instanceID);
			if (!_judged.TryGetValue(instanceID, out (string, int)? value))
			{
				value = (_judged[instanceID] = Judge(m, text));
			}
			if (!value.HasValue)
			{
				string text2 = Names.Shader(((Object)(object)m.shader != (Object)null) ? ((Object)m.shader).name : "");
				bool num;
				if (!VanillaBaseline.TryGetPair(text, out var shader))
				{
					if (!VanillaBaseline.IsAmbiguous(text))
					{
						return;
					}
					num = !VanillaBaseline.TryGetRowsOn(text, text2, out var _);
				}
				else
				{
					num = !string.Equals(text2, shader, StringComparison.OrdinalIgnoreCase);
				}
				if (num)
				{
					_offPairNames.Add(text);
				}
			}
			else
			{
				var (key, key2) = value.Value;
				if (!_byName.TryGetValue(key, out var value2))
				{
					value2 = (_byName[key] = new Dictionary<int, HashSet<string>>());
				}
				if (!value2.TryGetValue(key2, out var value3))
				{
					value3 = (value2[key2] = new HashSet<string>(StringComparer.Ordinal));
				}
				value3.Add(root);
			}
		}

		private static (string Name, int Id)? Judge(Material m, string baseName)
		{
			Shader shader = m.shader;
			string text = (((Object)(object)shader != (Object)null) ? ((Object)shader).name : null);
			if (string.IsNullOrEmpty(text) || string.Equals(text, "Hidden/InternalErrorShader", StringComparison.Ordinal))
			{
				return null;
			}
			string text2 = Names.Shader(text);
			bool num;
			if (!VanillaBaseline.TryGetPair(baseName, out var shader2))
			{
				if (!VanillaBaseline.IsAmbiguous(baseName))
				{
					goto IL_006c;
				}
				num = VanillaBaseline.TryGetRowsOn(baseName, text2, out var _);
			}
			else
			{
				num = string.Equals(text2, shader2, StringComparison.OrdinalIgnoreCase);
			}
			if (num)
			{
				int instanceID = ((Object)shader).GetInstanceID();
				if (ShaderWriter.WasWritten(((Object)m).GetInstanceID()))
				{
					return null;
				}
				_ids.Add(instanceID);
				_accepted++;
				ValheimMaterials.Add(baseName, m);
				return (text2, instanceID);
			}
			goto IL_006c;
			IL_006c:
			return null;
		}

		internal static bool IsProven(int shaderId)
		{
			return _ids.Contains(shaderId);
		}

		internal static bool IsValheimMaterial(int materialId, string baseName, bool copy)
		{
			if (!_valheimMaterialIds.Contains(materialId))
			{
				if (copy)
				{
					return _offPairNames.Contains(baseName);
				}
				return false;
			}
			return true;
		}

		internal static bool ProvesTarget(string shaderName, int targetId)
		{
			if (TryGetSole(shaderName, out var instanceId))
			{
				return instanceId == targetId;
			}
			return false;
		}

		internal static bool TryGetSole(string shaderName, out int instanceId)
		{
			instanceId = 0;
			if (_faulted || string.IsNullOrEmpty(shaderName) || !_byName.TryGetValue(shaderName, out var value) || value.Count != 1)
			{
				return false;
			}
			foreach (KeyValuePair<int, HashSet<string>> item in value)
			{
				if (item.Value.Count < 2)
				{
					return false;
				}
				instanceId = item.Key;
			}
			return true;
		}

		internal static string Summary()
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Dictionary<int, HashSet<string>>> item in _byName)
			{
				if (item.Value.Count < 2)
				{
					continue;
				}
				List<string> list2 = new List<string>();
				foreach (KeyValuePair<int, HashSet<string>> item2 in item.Value)
				{
					list2.Add($"#{item2.Key}x{item2.Value.Count}");
				}
				list.Add("'" + item.Key + "' " + string.Join("/", list2));
			}
			return $"prefab proof: {_prefabs} Valheim prefab(s) -> {_accepted} material(s) on {_byName.Count} shader name(s); " + string.Format("{0} name(s) on 2+ instances (refused){1}; ", list.Count, _faulted ? "; a prefab walk FAULTED: no target proven this pass" : "") + $"{WorkMeter.Ms(_ticks):F1} ms" + ((list.Count > 0) ? (": " + string.Join("; ", list)) : "");
		}
	}
	internal static class VanillaBaseline
	{
		private const string ResourceName = "ShaderHelper.vanilla_baseline.cache";

		private const string Magic = "# shaderhelper-vanilla-baseline";

		private const int SchemaVersion = 6;

		internal static readonly HashSet<string> BundleNames = new HashSet<string>(StringComparer.Ordinal);

		internal static readonly HashSet<string> ShaderNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		internal static readonly HashSet<string> ShippedShaderNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		internal static readonly HashSet<string> AuthoredShaderNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> _materialNames = new HashSet<string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, List<ShippedRow>> _rows = new Dictionary<string, List<ShippedRow>>(StringComparer.Ordinal);

		private static readonly HashSet<string> _ambiguous = new HashSet<string>(StringComparer.Ordinal);

		internal static bool Loaded { get; private set; }

		internal static string ValheimVersion { get; private set; } = "";

		internal static bool IsVanillaMaterial(string canonicalName)
		{
			if (!string.IsNullOrEmpty(canonicalName))
			{
				if (!_materialNames.Contains(canonicalName))
				{
					return _rows.ContainsKey(canonicalName);
				}
				return true;
			}
			return false;
		}

		internal static bool TryGetPair(string canonicalName, out string shader)
		{
			shader = null;
			if (string.IsNullOrEmpty(canonicalName) || _ambiguous.Contains(canonicalName) || !_rows.TryGetValue(canonicalName, out var value))
			{
				return false;
			}
			shader = value[0].Shader;
			return true;
		}

		internal static bool TryGetRows(string canonicalName, out List<ShippedRow> rows)
		{
			rows = null;
			if (!string.IsNullOrEmpty(canonicalName))
			{
				return _rows.TryGetValue(canonicalName, out rows);
			}
			return false;
		}

		internal static bool IsAmbiguous(string canonicalName)
		{
			if (!string.IsNullOrEmpty(canonicalName))
			{
				return _ambiguous.Contains(canonicalName);
			}
			return false;
		}

		internal static bool TryGetRowsOn(string canonicalName, string shader, out List<ShippedRow> rows)
		{
			rows = null;
			if (string.IsNullOrEmpty(shader) || !TryGetRows(canonicalName, out var rows2))
			{
				return false;
			}
			foreach (ShippedRow item in rows2)
			{
				if (string.Equals(item.Shader, shader, StringComparison.OrdinalIgnoreCase))
				{
					(rows ?? (rows = new List<ShippedRow>(1))).Add(item);
				}
			}
			return rows != null;
		}

		internal static void Load(ManualLogSource log)
		{
			Clear();
			long timestamp = Stopwatch.GetTimestamp();
			try
			{
				using Stream stream = typeof(VanillaBaseline).Assembly.GetManifestResourceStream("ShaderHelper.vanilla_baseline.cache");
				if (stream == null)
				{
					log.LogError((object)"[Baseline] embedded resource 'ShaderHelper.vanilla_baseline.cache' missing; NOTHING will be decided.");
					return;
				}
				using StreamReader r = new StreamReader(stream);
				Parse(r, log, timestamp);
			}
			catch (Exception ex)
			{
				Clear();
				log.LogError((object)("[Baseline] read failed (" + ex.GetType().Name + ": " + ex.Message + "); NOTHING will be decided."));
			}
		}

		private static void Parse(StreamReader r, ManualLogSource log, long t0)
		{
			string text = r.ReadLine();
			if (text == null || !text.StartsWith("# shaderhelper-vanilla-baseline", StringComparison.Ordinal))
			{
				log.LogError((object)"[Baseline] bad header.");
				return;
			}
			if (HeaderField(text, "schema=") != 6.ToString())
			{
				log.LogError((object)$"[Baseline] schema mismatch (want {6}): '{text}'.");
				return;
			}
			ValheimVersion = HeaderField(text, "valheim-version=");
			if (ValheimVersion.Length == 0)
			{
				log.LogError((object)("[Baseline] header has no valheim-version: '" + text + "'."));
				return;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			string text2 = "";
			string text3;
			while ((text3 = r.ReadLine()) != null)
			{
				if (text3.Length > 0 && text3[text3.Length - 1] == '\r')
				{
					text3 = text3.Substring(0, text3.Length - 1);
				}
				if (text3.Length == 0 || text3[0] == '#')
				{
					continue;
				}
				if (text3[0] == '[' && text3[text3.Length - 1] == ']')
				{
					text2 = text3.Substring(1, text3.Length - 2);
					continue;
				}
				switch (text2)
				{
				case "bundles":
					BundleNames.Add(text3);
					break;
				case "mats":
					_materialNames.Add(text3);
					break;
				case "shaders":
					ShaderNames.Add(text3);
					break;
				case "matshaders":
					if (AddRow(text3.Split(new char[1] { '\t' })))
					{
						num3++;
					}
					else
					{
						num++;
					}
					break;
				case "shadersrc":
					num4++;
					AddShipped(text3);
					break;
				case "prefabs":
					if (!VanillaPrefabs.Add(text3.Split(new char[1] { '\t' })))
					{
						num2++;
					}
					break;
				}
			}
			foreach (KeyValuePair<string, List<ShippedRow>> row in _rows)
			{
				if (!_ambiguous.Contains(row.Key) && !UnityBuiltIns.DefaultMaterialNames.Contains(row.Key))
				{
					AuthoredShaderNames.Add(row.Value[0].Shader);
				}
			}
			Loaded = BundleNames.Count > 0 && ShaderNames.Count > 0 && _rows.Count > 0;
			double num5 = (double)(Stopwatch.GetTimestamp() - t0) * 1000.0 / (double)Stopwatch.Frequency;
			log.LogInfo((object)($"[Baseline] schema {6}, valheim-version={ValheimVersion}: {BundleNames.Count} bundles, {_materialNames.Count} materials, " + $"{ShaderNames.Count} shaders, {num3} shipped rows over {_rows.Count - _ambiguous.Count} usable names ({_ambiguous.Count} ambiguous refused, " + $"{num} malformed; exact-case names), {num4} shader sources, {VanillaPrefabs.Count} prefabs ({num2} malformed); parsed in {num5:F0} ms."));
		}

		private static bool AddRow(string[] cols)
		{
			ShippedRow shippedRow = ShippedRow.Parse(cols);
			if (shippedRow == null || cols[0].Length == 0)
			{
				return false;
			}
			if (!_rows.TryGetValue(cols[0], out var value))
			{
				value = (_rows[cols[0]] = new List<ShippedRow>(1));
			}
			else if (!string.Equals(value[0].Shader, shippedRow.Shader, StringComparison.OrdinalIgnoreCase))
			{
				_ambiguous.Add(cols[0]);
			}
			value.Add(shippedRow);
			return true;
		}

		private static void AddShipped(string line)
		{
			int num = line.IndexOf('\t');
			string text = ((num < 0) ? line : line.Substring(0, num));
			if (text.Length > 0)
			{
				ShippedShaderNames.Add(text);
			}
		}

		private static string HeaderField(string header, string key)
		{
			int num = header.IndexOf(key, StringComparison.Ordinal);
			if (num < 0)
			{
				return "";
			}
			int num2 = num + key.Length;
			int num3 = header.IndexOf(' ', num2);
			return header.Substring(num2, ((num3 < 0) ? header.Length : num3) - num2).Trim();
		}

		private static void Clear()
		{
			BundleNames.Clear();
			ShaderNames.Clear();
			ShippedShaderNames.Clear();
			AuthoredShaderNames.Clear();
			_materialNames.Clear();
			_rows.Clear();
			_ambiguous.Clear();
			VanillaPrefabs.Clear();
			Loaded = false;
			ValheimVer