Decompiled source of balrond constructions v1.5.2

plugins/BalrondConstructions.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LitJson2;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("BalrondConstructions")]
[assembly: AssemblyDescription("Balrond construction pieces for Valheim")]
[assembly: AssemblyCompany("Balrond")]
[assembly: AssemblyProduct("BalrondConstructions")]
[assembly: ComVisible(false)]
[assembly: AssemblyFileVersion("1.5.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.5.0.0")]
[module: UnverifiableCode]
public class BalrondDrawBridge : MonoBehaviour, Hoverable, Interactable
{
	public enum DrawBridgeMode
	{
		Single,
		Double
	}

	public enum DrawBridgeState
	{
		Closed,
		Opening,
		Open,
		Closing
	}

	private const string RpcToggle = "RPC_BalrondDrawBridge_Toggle";

	private const string ZdoState = "balrond_drawbridge_state";

	private const string ZdoAnimStartTime = "balrond_drawbridge_anim_start_time";

	private ZNetView _nview;

	private float _lastVisualProgress = float.NaN;

	private float _lastOpenAngle = float.NaN;

	private float _lastGearRatio = float.NaN;

	private DrawBridgeMode _lastVisualMode;

	private Vector3 _lastLeftAxis;

	private Vector3 _lastRightAxis;

	private Vector3 _lastGearAxis;

	[Header("Mode")]
	public DrawBridgeMode m_mode = DrawBridgeMode.Double;

	[Header("Interaction")]
	public string m_name = "Drawbridge";

	public float m_hoverOffset = 0f;

	public bool m_requireWardAccess = true;

	public bool m_blockInteractionWhileMoving = true;

	[Header("Animation")]
	public float m_animationSeconds = 3f;

	public float m_openAngle = 70f;

	[Header("Bridge Leaves")]
	public Transform m_leftPivot;

	public Transform m_rightPivot;

	[Header("Leaf Rotation Axis")]
	public Vector3 m_leftLeafAxis = Vector3.right;

	public Vector3 m_rightLeafAxis = Vector3.right;

	[Header("Gears")]
	public Transform m_leftStaticGear;

	public Transform m_leftBridgeGear;

	public Transform m_rightStaticGear;

	public Transform m_rightBridgeGear;

	public Vector3 m_gearAxis = Vector3.forward;

	public float m_gearDegreesPerBridgeDegree = 4f;

	[Header("Animation Audio Object")]
	public GameObject m_animationActiveObject;

	[Header("Effects")]
	public EffectList m_openStartEffects = new EffectList();

	public EffectList m_closeStartEffects = new EffectList();

	[Header("Debug")]
	public bool m_debugLogs = false;

	private void Awake()
	{
		_nview = ((Component)this).GetComponent<ZNetView>();
		if ((Object)(object)_nview != (Object)null)
		{
			_nview.Register("RPC_BalrondDrawBridge_Toggle", (Action<long>)RPC_Toggle);
		}
		UpdateAnimationAndState();
	}

	private void Update()
	{
		UpdateAnimationAndState();
	}

	public string GetHoverText()
	{
		if (!IsValid())
		{
			return string.Empty;
		}
		DrawBridgeState state = GetState();
		string text = (IsOpenOrOpening(state) ? "Close" : "Open");
		string name = m_name;
		name = name + "\n[<color=yellow><b>$KEY_Use</b></color>] " + text;
		if (IsAnimating(state))
		{
			name += "\n<color=orange>Moving</color>";
		}
		return (Localization.instance != null) ? Localization.instance.Localize(name) : name;
	}

	public string GetHoverName()
	{
		return m_name;
	}

	public float GetHoverOffset()
	{
		return m_hoverOffset;
	}

	public bool Interact(Humanoid user, bool hold, bool alt)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		if (hold)
		{
			return false;
		}
		if (!IsValid())
		{
			return false;
		}
		if (m_requireWardAccess && !PrivateArea.CheckAccess(((Component)this).transform.position, 0f, true, false))
		{
			return true;
		}
		DrawBridgeState state = GetState();
		if (m_blockInteractionWhileMoving && IsAnimating(state))
		{
			Player val = (Player)(object)((user is Player) ? user : null);
			if ((Object)(object)val != (Object)null)
			{
				((Character)val).Message((MessageType)2, "Bridge is moving", 0, (Sprite)null, false);
			}
			return true;
		}
		_nview.InvokeRPC("RPC_BalrondDrawBridge_Toggle", Array.Empty<object>());
		return true;
	}

	public bool UseItem(Humanoid user, ItemData item)
	{
		return false;
	}

	private void RPC_Toggle(long sender)
	{
		if (!IsValid())
		{
			return;
		}
		DrawBridgeState state = GetState();
		if (m_blockInteractionWhileMoving && IsAnimating(state))
		{
			Log("Toggle rejected: bridge is moving");
			return;
		}
		ClaimOwnershipIfNeeded();
		if (state == DrawBridgeState.Closed || state == DrawBridgeState.Closing)
		{
			SetStateOwned(DrawBridgeState.Opening);
			CreateEffects(m_openStartEffects);
			Log("Opening");
		}
		else if (state == DrawBridgeState.Open || state == DrawBridgeState.Opening)
		{
			SetStateOwned(DrawBridgeState.Closing);
			CreateEffects(m_closeStartEffects);
			Log("Closing");
		}
	}

	private void UpdateAnimationAndState()
	{
		if (!IsValid())
		{
			UpdateAnimationActiveObject(active: false);
			return;
		}
		DrawBridgeState state = GetState();
		float num = Mathf.Max(0.01f, SanitizeFloat(m_animationSeconds, 3f));
		float animStartTime = GetAnimStartTime();
		float networkTimeSecondsFloat = GetNetworkTimeSecondsFloat();
		float num2 = Mathf.Clamp01((networkTimeSecondsFloat - animStartTime) / num);
		float progress = 0f;
		switch (state)
		{
		case DrawBridgeState.Closed:
			progress = 0f;
			break;
		case DrawBridgeState.Open:
			progress = 1f;
			break;
		case DrawBridgeState.Opening:
			progress = num2;
			if (num2 >= 1f && IsOwner())
			{
				SetStateNoRestartOwned(DrawBridgeState.Open);
			}
			break;
		case DrawBridgeState.Closing:
			progress = 1f - num2;
			if (num2 >= 1f && IsOwner())
			{
				SetStateNoRestartOwned(DrawBridgeState.Closed);
			}
			break;
		}
		UpdateAnimationActiveObject(IsAnimating(state));
		ApplyVisual(progress);
	}

	private void ApplyVisual(float progress)
	{
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0103: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: 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_0145: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		float num = Mathf.Clamp01(progress);
		if (_lastVisualProgress != num || _lastOpenAngle != m_openAngle || _lastGearRatio != m_gearDegreesPerBridgeDegree || _lastVisualMode != m_mode || !(_lastLeftAxis == m_leftLeafAxis) || !(_lastRightAxis == m_rightLeafAxis) || !(_lastGearAxis == m_gearAxis))
		{
			_lastVisualProgress = num;
			_lastOpenAngle = m_openAngle;
			_lastGearRatio = m_gearDegreesPerBridgeDegree;
			_lastVisualMode = m_mode;
			_lastLeftAxis = m_leftLeafAxis;
			_lastRightAxis = m_rightLeafAxis;
			_lastGearAxis = m_gearAxis;
			float num2 = num * SanitizeFloat(m_openAngle, 70f);
			if ((Object)(object)m_leftPivot != (Object)null)
			{
				Vector3 safeAxis = GetSafeAxis(m_leftLeafAxis, Vector3.right);
				m_leftPivot.localRotation = Quaternion.AngleAxis(0f - num2, safeAxis);
			}
			if (m_mode == DrawBridgeMode.Double && (Object)(object)m_rightPivot != (Object)null)
			{
				Vector3 safeAxis2 = GetSafeAxis(m_rightLeafAxis, Vector3.right);
				m_rightPivot.localRotation = Quaternion.AngleAxis(num2, safeAxis2);
			}
			float num3 = num2 * SanitizeFloat(m_gearDegreesPerBridgeDegree, 4f);
			ApplyGear(m_leftStaticGear, num3);
			ApplyGear(m_leftBridgeGear, 0f - num3);
			if (m_mode == DrawBridgeMode.Double)
			{
				ApplyGear(m_rightStaticGear, 0f - num3);
				ApplyGear(m_rightBridgeGear, num3);
			}
		}
	}

	private void ApplyGear(Transform gear, float angle)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)gear == (Object)null))
		{
			Vector3 safeAxis = GetSafeAxis(m_gearAxis, Vector3.forward);
			gear.localRotation = Quaternion.AngleAxis(angle, safeAxis);
		}
	}

	private void UpdateAnimationActiveObject(bool active)
	{
		if (!((Object)(object)m_animationActiveObject == (Object)null) && m_animationActiveObject.activeSelf != active)
		{
			m_animationActiveObject.SetActive(active);
		}
	}

	private void CreateEffects(EffectList effects)
	{
		//IL_0027: 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)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		if (effects != null && effects.m_effectPrefabs != null && effects.m_effectPrefabs.Length != 0)
		{
			effects.Create(((Component)this).transform.position, ((Component)this).transform.rotation, (Transform)null, 1f, -1, default(ZDOID));
		}
	}

	private void SetStateOwned(DrawBridgeState state)
	{
		ZDO zdo = GetZdo();
		if (zdo != null)
		{
			ClaimOwnershipIfNeeded();
			zdo.Set("balrond_drawbridge_state", (int)state);
			zdo.Set("balrond_drawbridge_anim_start_time", GetNetworkTimeSecondsFloat());
		}
	}

	private void SetStateNoRestartOwned(DrawBridgeState state)
	{
		ZDO zdo = GetZdo();
		if (zdo != null)
		{
			ClaimOwnershipIfNeeded();
			zdo.Set("balrond_drawbridge_state", (int)state);
		}
	}

	public DrawBridgeState GetState()
	{
		ZDO zdo = GetZdo();
		if (zdo == null)
		{
			return DrawBridgeState.Closed;
		}
		return zdo.GetInt("balrond_drawbridge_state", 0) switch
		{
			1 => DrawBridgeState.Opening, 
			2 => DrawBridgeState.Open, 
			3 => DrawBridgeState.Closing, 
			_ => DrawBridgeState.Closed, 
		};
	}

	private float GetAnimStartTime()
	{
		ZDO zdo = GetZdo();
		if (zdo == null)
		{
			return GetNetworkTimeSecondsFloat();
		}
		return zdo.GetFloat("balrond_drawbridge_anim_start_time", GetNetworkTimeSecondsFloat());
	}

	private bool IsOpenOrOpening(DrawBridgeState state)
	{
		return state == DrawBridgeState.Open || state == DrawBridgeState.Opening;
	}

	private bool IsAnimating(DrawBridgeState state)
	{
		return state == DrawBridgeState.Opening || state == DrawBridgeState.Closing;
	}

	private bool IsValid()
	{
		return (Object)(object)_nview != (Object)null && _nview.IsValid() && _nview.GetZDO() != null;
	}

	private ZDO GetZdo()
	{
		if ((Object)(object)_nview == (Object)null || !_nview.IsValid())
		{
			return null;
		}
		return _nview.GetZDO();
	}

	private bool IsOwner()
	{
		return (Object)(object)_nview != (Object)null && _nview.IsValid() && _nview.IsOwner();
	}

	private void ClaimOwnershipIfNeeded()
	{
		if ((Object)(object)_nview != (Object)null && _nview.IsValid() && !_nview.IsOwner())
		{
			_nview.ClaimOwnership();
		}
	}

	private static Vector3 GetSafeAxis(Vector3 axis, Vector3 fallback)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		if (((Vector3)(ref axis)).sqrMagnitude <= 0.0001f)
		{
			return ((Vector3)(ref fallback)).normalized;
		}
		return ((Vector3)(ref axis)).normalized;
	}

	private static float SanitizeFloat(float value, float fallback)
	{
		if (float.IsNaN(value) || float.IsInfinity(value))
		{
			return fallback;
		}
		return value;
	}

	private static float GetNetworkTimeSecondsFloat()
	{
		if ((Object)(object)ZNet.instance != (Object)null)
		{
			return (float)ZNet.instance.GetTimeSeconds();
		}
		return Time.time;
	}

	private void Log(string msg)
	{
		if (m_debugLogs)
		{
			Debug.Log((object)("[BalrondDrawBridge][" + ((Object)((Component)this).gameObject).name + "] " + msg));
		}
	}
}
namespace BalrondConstructions
{
	internal static class AssetLoader
	{
		internal const string BundleName = "balrondconstructions";

		internal const string BuildPieceAssetRoot = "assets/custom/balrondconstructions/pieces/";

		private static readonly string[] OtherPrefabNames = new string[1] { "sfx_queendoor_open1" };

		private static AssetBundle _bundle;

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

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

		internal static bool LoadAll()
		{
			AssetRegistry.Reset();
			_bundle = LoadEmbedded("balrondconstructions");
			if ((Object)(object)_bundle == (Object)null)
			{
				return false;
			}
			BuildAssetIndex();
			LoadPrefabCollection(BuildPieceList.buildPieces, AssetRegistry.BuildPieces, "build piece", ResolveBuildPiecePath, "assets/custom/balrondconstructions/pieces/");
			LoadPrefabCollection(OtherPrefabNames, AssetRegistry.Other, "support prefab", ResolveSupportPrefabPath, null);
			ModLog.Info("Assets loaded: buildPieces=" + AssetRegistry.BuildPieces.Count + ", other=" + AssetRegistry.Other.Count + ".");
			return true;
		}

		internal static IEnumerable<GameObject> EnumerateOwnedPrefabs()
		{
			int i = 0;
			while (i < AssetRegistry.BuildPieces.Count)
			{
				if ((Object)(object)AssetRegistry.BuildPieces[i] != (Object)null)
				{
					yield return AssetRegistry.BuildPieces[i];
				}
				int num = i + 1;
				i = num;
			}
			int i2 = 0;
			while (i2 < AssetRegistry.Other.Count)
			{
				if ((Object)(object)AssetRegistry.Other[i2] != (Object)null)
				{
					yield return AssetRegistry.Other[i2];
				}
				int num = i2 + 1;
				i2 = num;
			}
		}

		private static void LoadPrefabCollection(string[] names, List<GameObject> destination, string kind, Func<string, string> resolveAssetPath, string requiredRoot)
		{
			if (names == null || destination == null || (Object)(object)_bundle == (Object)null || resolveAssetPath == null)
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			List<string> list = new List<string>();
			foreach (string text in names)
			{
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				if (!hashSet.Add(text))
				{
					ModLog.DebugOnce("duplicate-declared:" + text, "Assets", "Duplicate declared " + kind + " ID ignored: " + text);
					continue;
				}
				string text2 = resolveAssetPath(text);
				if (string.IsNullOrEmpty(text2))
				{
					list.Add(string.IsNullOrEmpty(requiredRoot) ? text : (text + "\t" + requiredRoot + text + ".prefab"));
					continue;
				}
				GameObject val = null;
				try
				{
					val = _bundle.LoadAsset<GameObject>(text2);
				}
				catch (Exception ex)
				{
					ModLog.Error("Failed to load " + kind + " '" + text + "' from '" + text2 + "': " + ex.Message);
				}
				if ((Object)(object)val == (Object)null)
				{
					list.Add(string.IsNullOrEmpty(requiredRoot) ? text : (text + "\t" + requiredRoot + text + ".prefab"));
				}
				else if (!string.Equals(((Object)val).name, text, StringComparison.Ordinal))
				{
					ModLog.Error("SAVE-SAFETY: bundle asset path for '" + text + "' produced GameObject name '" + ((Object)val).name + "'. Asset skipped; runtime rename is intentionally forbidden.");
				}
				else
				{
					ShaderReplacment.Replace(val);
					destination.Add(val);
				}
			}
			if (list.Count > 0)
			{
				string text3 = (string.IsNullOrEmpty(requiredRoot) ? string.Empty : " under required path 'Assets/Custom/BalrondConstructions/Pieces/'");
				ModLog.Warning("AssetBundle missing " + list.Count + " declared " + kind + " prefab(s)" + text3 + ". Detailed list is available when diagnostics are enabled.");
				DiagnosticsWriter.WriteList("MissingAssets_" + kind.Replace(' ', '_') + ".txt", list);
			}
		}

		private static string ResolveBuildPiecePath(string id)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				return null;
			}
			string key = NormalizeAssetPath("assets/custom/balrondconstructions/pieces/" + id + ".prefab");
			string value;
			return ExactPrefabPaths.TryGetValue(key, out value) ? value : null;
		}

		private static string ResolveSupportPrefabPath(string id)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				return null;
			}
			string value;
			return SupportPrefabPaths.TryGetValue(id, out value) ? value : null;
		}

		private static void BuildAssetIndex()
		{
			ExactPrefabPaths.Clear();
			SupportPrefabPaths.Clear();
			string[] array = (((Object)(object)_bundle != (Object)null) ? _bundle.GetAllAssetNames() : null);
			if (array == null)
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>(OtherPrefabNames, StringComparer.OrdinalIgnoreCase);
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (string text in array)
			{
				if (string.IsNullOrEmpty(text) || !text.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
				{
					continue;
				}
				string text2 = NormalizeAssetPath(text);
				if (!ExactPrefabPaths.ContainsKey(text2))
				{
					ExactPrefabPaths.Add(text2, text);
				}
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2);
				if (!string.IsNullOrEmpty(fileNameWithoutExtension) && hashSet.Contains(fileNameWithoutExtension))
				{
					if (!SupportPrefabPaths.TryGetValue(fileNameWithoutExtension, out var value))
					{
						SupportPrefabPaths.Add(fileNameWithoutExtension, text);
					}
					else if (!string.Equals(NormalizeAssetPath(value), text2, StringComparison.OrdinalIgnoreCase))
					{
						hashSet2.Add(fileNameWithoutExtension);
					}
				}
			}
			foreach (string item in hashSet2)
			{
				SupportPrefabPaths.Remove(item);
				ModLog.DebugOnce("ambiguous-support-prefab:" + item, "Assets", "Support prefab ID '" + item + "' exists at multiple bundle paths and was left unresolved.");
			}
			ModLog.Debug("Assets", "AssetBundle prefab paths=" + ExactPrefabPaths.Count + ", canonical build root='assets/custom/balrondconstructions/pieces/', support entries=" + SupportPrefabPaths.Count + ".");
		}

		private static string NormalizeAssetPath(string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				return string.Empty;
			}
			return path.Replace('\\', '/').TrimStart(new char[1] { '/' });
		}

		private static AssetBundle LoadEmbedded(string filename)
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			string text = null;
			string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
			for (int i = 0; i < manifestResourceNames.Length; i++)
			{
				if (manifestResourceNames[i].EndsWith(filename, StringComparison.OrdinalIgnoreCase))
				{
					text = manifestResourceNames[i];
					break;
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				ModLog.Error("Embedded AssetBundle resource not found: " + filename);
				return null;
			}
			using Stream stream = executingAssembly.GetManifestResourceStream(text);
			if (stream == null)
			{
				ModLog.Error("Could not open embedded AssetBundle resource: " + text);
				return null;
			}
			AssetBundle val = AssetBundle.LoadFromStream(stream);
			if ((Object)(object)val == (Object)null)
			{
				ModLog.Error("AssetBundle.LoadFromStream returned null for: " + text);
			}
			return val;
		}
	}
	internal static class AssetRegistry
	{
		internal static readonly List<GameObject> BuildPieces = new List<GameObject>();

		internal static readonly List<GameObject> Other = new List<GameObject>();

		internal static readonly List<GameObject> RegisteredBuildPieces = new List<GameObject>();

		internal static void Reset()
		{
			BuildPieces.Clear();
			Other.Clear();
			RegisteredBuildPieces.Clear();
		}
	}
	public class FxReplacment
	{
		private readonly Dictionary<string, GameObject> _prefabLookup = new Dictionary<string, GameObject>(StringComparer.Ordinal);

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

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

		private int _effectReferencesInspected;

		private int _effectReferencesResolved;

		private ZNetScene _cachedScene;

		private int _cachedPrefabCount = -1;

		public void Prepare(ZNetScene zNetScene)
		{
			if (!((Object)(object)zNetScene == (Object)null))
			{
				EnsurePrefabLookup(zNetScene);
			}
		}

		public void ReplaceOnObject(GameObject gameObject, ZNetScene zNetScene)
		{
			if (!((Object)(object)gameObject == (Object)null) && !((Object)(object)zNetScene == (Object)null))
			{
				EnsurePrefabLookup(zNetScene);
				SpawnArea component = gameObject.GetComponent<SpawnArea>();
				if ((Object)(object)component != (Object)null)
				{
					ReplaceEffectList(component.m_spawnEffects, zNetScene);
				}
				Destructible component2 = gameObject.GetComponent<Destructible>();
				if ((Object)(object)component2 != (Object)null)
				{
					ReplaceEffectList(component2.m_hitEffect, zNetScene);
					ReplaceEffectList(component2.m_destroyedEffect, zNetScene);
				}
				Projectile component3 = gameObject.GetComponent<Projectile>();
				if ((Object)(object)component3 != (Object)null)
				{
					ReplaceEffectList(component3.m_hitEffects, zNetScene);
					ReplaceEffectList(component3.m_hitWaterEffects, zNetScene);
					ReplaceEffectList(component3.m_spawnOnHitEffects, zNetScene);
				}
			}
		}

		public void ReplaceOnVegetation(GameObject gameObject, ZNetScene zNetScene)
		{
			if (!((Object)(object)gameObject == (Object)null) && !((Object)(object)zNetScene == (Object)null))
			{
				EnsurePrefabLookup(zNetScene);
				Pickable component = gameObject.GetComponent<Pickable>();
				if ((Object)(object)component != (Object)null)
				{
					ReplaceEffectList(component.m_pickEffector, zNetScene);
				}
				Destructible component2 = gameObject.GetComponent<Destructible>();
				if ((Object)(object)component2 != (Object)null)
				{
					ReplaceEffectList(component2.m_hitEffect, zNetScene);
					ReplaceEffectList(component2.m_destroyedEffect, zNetScene);
				}
				MineRock5 component3 = gameObject.GetComponent<MineRock5>();
				if ((Object)(object)component3 != (Object)null)
				{
					ReplaceEffectList(component3.m_hitEffect, zNetScene);
					ReplaceEffectList(component3.m_destroyedEffect, zNetScene);
				}
				MineRock component4 = gameObject.GetComponent<MineRock>();
				if ((Object)(object)component4 != (Object)null)
				{
					ReplaceEffectList(component4.m_hitEffect, zNetScene);
					ReplaceEffectList(component4.m_destroyedEffect, zNetScene);
				}
			}
		}

		public void ReplaceOnMonster(GameObject gameObject, ZNetScene zNetScene)
		{
			if (!((Object)(object)gameObject == (Object)null) && !((Object)(object)zNetScene == (Object)null))
			{
				EnsurePrefabLookup(zNetScene);
				Humanoid component = gameObject.GetComponent<Humanoid>();
				if ((Object)(object)component != (Object)null)
				{
					ReplaceEffectList(component.m_dropEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_backstabHitEffects, zNetScene);
					ReplaceEffectList(component.m_consumeItemEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_critHitEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_deathEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_hitEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_jumpEffects, zNetScene);
					ReplaceEffectList(component.m_perfectBlockEffect, zNetScene);
					ReplaceEffectList(component.m_pickupEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_slideEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_tarEffects, zNetScene);
					ReplaceEffectList(((Character)component).m_waterEffects, zNetScene);
				}
				FootStep component2 = gameObject.GetComponent<FootStep>();
				if ((Object)(object)component2 != (Object)null)
				{
					ReplaceFootStepEffects(component2, zNetScene);
				}
			}
		}

		public void ReplaceOnItem(GameObject gameObject, ZNetScene zNetScene)
		{
			if (!((Object)(object)gameObject == (Object)null) && !((Object)(object)zNetScene == (Object)null))
			{
				EnsurePrefabLookup(zNetScene);
				ItemDrop component = gameObject.GetComponent<ItemDrop>();
				if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null)
				{
					ReplaceEffectList(component.m_itemData.m_shared.m_hitEffect, zNetScene);
					ReplaceEffectList(component.m_itemData.m_shared.m_hitTerrainEffect, zNetScene);
					ReplaceEffectList(component.m_itemData.m_shared.m_holdStartEffect, zNetScene);
					ReplaceEffectList(component.m_itemData.m_shared.m_trailStartEffect, zNetScene);
					ReplaceEffectList(component.m_itemData.m_shared.m_blockEffect, zNetScene);
				}
			}
		}

		public void ReplaceFxOnPiece(GameObject gameObject, ZNetScene zNetScene)
		{
			if (!((Object)(object)gameObject == (Object)null) && !((Object)(object)zNetScene == (Object)null))
			{
				EnsurePrefabLookup(zNetScene);
				Piece component = gameObject.GetComponent<Piece>();
				if ((Object)(object)component != (Object)null)
				{
					ReplaceEffectList(component.m_placeEffect, zNetScene);
				}
				WearNTear component2 = gameObject.GetComponent<WearNTear>();
				if ((Object)(object)component2 != (Object)null)
				{
					ReplaceEffectList(component2.m_hitEffect, zNetScene);
					ReplaceEffectList(component2.m_destroyedEffect, zNetScene);
					ReplaceEffectList(component2.m_switchEffect, zNetScene);
				}
				ReplaceSerializedPieceEffects(gameObject, zNetScene);
			}
		}

		private void ReplaceSerializedPieceEffects(GameObject root, ZNetScene scene)
		{
			MonoBehaviour[] componentsInChildren = root.GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				FieldInfo[] serializedEffectFields = GetSerializedEffectFields(((object)val).GetType());
				for (int j = 0; j < serializedEffectFields.Length; j++)
				{
					EffectList val2 = null;
					try
					{
						object? value = serializedEffectFields[j].GetValue(val);
						val2 = (EffectList)((value is EffectList) ? value : null);
					}
					catch
					{
					}
					if (val2 != null)
					{
						ReplaceEffectList(val2, scene);
					}
				}
			}
		}

		private static FieldInfo[] GetSerializedEffectFields(Type componentType)
		{
			if (SerializedEffectFields.TryGetValue(componentType, out var value))
			{
				return value;
			}
			List<FieldInfo> list = new List<FieldInfo>();
			Type type = componentType;
			while (type != null && type != typeof(MonoBehaviour) && type != typeof(Behaviour) && type != typeof(Component))
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (!fieldInfo.IsStatic && !(fieldInfo.FieldType != typeof(EffectList)) && (fieldInfo.IsPublic || Attribute.IsDefined(fieldInfo, typeof(SerializeField))))
					{
						list.Add(fieldInfo);
					}
				}
				type = type.BaseType;
			}
			value = list.ToArray();
			SerializedEffectFields.Add(componentType, value);
			return value;
		}

		public void ReplaceOnConstructionCustom(GameObject gameObject, ZNetScene zNetScene)
		{
			if (!((Object)(object)gameObject == (Object)null) && !((Object)(object)zNetScene == (Object)null))
			{
				BalrondDrawBridge component = gameObject.GetComponent<BalrondDrawBridge>();
				if (!((Object)(object)component == (Object)null))
				{
					EnsurePrefabLookup(zNetScene);
					ReplaceEffectList(component.m_openStartEffects, zNetScene);
					ReplaceEffectList(component.m_closeStartEffects, zNetScene);
				}
			}
		}

		public void InvalidateCache()
		{
			_cachedScene = null;
			_cachedPrefabCount = -1;
			_prefabLookup.Clear();
			_missingEffectWarnings.Clear();
			_effectReferencesInspected = 0;
			_effectReferencesResolved = 0;
		}

		private void ReplaceEffectList(EffectList effectList, ZNetScene zNetScene)
		{
			if (effectList != null)
			{
				ReplaceEffectData(effectList.m_effectPrefabs, zNetScene);
			}
		}

		private void ReplaceEffectData(EffectData[] effects, ZNetScene zNetScene)
		{
			if (effects == null || effects.Length == 0)
			{
				return;
			}
			EnsurePrefabLookup(zNetScene);
			foreach (EffectData val in effects)
			{
				if (val == null || (Object)(object)val.m_prefab == (Object)null)
				{
					continue;
				}
				_effectReferencesInspected++;
				GameObject val2 = ResolvePrefab(((Object)val.m_prefab).name);
				if ((Object)(object)val2 != (Object)null)
				{
					_effectReferencesResolved++;
					if (val.m_prefab != val2)
					{
						val.m_prefab = val2;
					}
				}
				else
				{
					LogMissingEffectOnce(((Object)val.m_prefab).name);
				}
			}
		}

		private void ReplaceFootStepEffects(FootStep footStep, ZNetScene zNetScene)
		{
			if ((Object)(object)footStep == (Object)null || footStep.m_effects == null)
			{
				return;
			}
			EnsurePrefabLookup(zNetScene);
			for (int i = 0; i < footStep.m_effects.Count; i++)
			{
				StepEffect val = footStep.m_effects[i];
				if (val == null || val.m_effectPrefabs == null || val.m_effectPrefabs.Length == 0)
				{
					continue;
				}
				for (int j = 0; j < val.m_effectPrefabs.Length; j++)
				{
					GameObject val2 = val.m_effectPrefabs[j];
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					_effectReferencesInspected++;
					GameObject val3 = ResolvePrefab(((Object)val2).name);
					if ((Object)(object)val3 != (Object)null)
					{
						_effectReferencesResolved++;
						if (val2 != val3)
						{
							val.m_effectPrefabs[j] = val3;
						}
					}
					else
					{
						LogMissingEffectOnce(((Object)val2).name);
					}
				}
			}
		}

		private void EnsurePrefabLookup(ZNetScene zNetScene)
		{
			if ((Object)(object)zNetScene == (Object)null || zNetScene.m_prefabs == null)
			{
				InvalidateCache();
				return;
			}
			int num = zNetScene.m_prefabs.Count + ((zNetScene.m_namedPrefabs != null) ? zNetScene.m_namedPrefabs.Count : 0);
			if (_cachedScene == zNetScene && _cachedPrefabCount == num)
			{
				return;
			}
			_cachedScene = zNetScene;
			_cachedPrefabCount = num;
			_prefabLookup.Clear();
			_missingEffectWarnings.Clear();
			for (int i = 0; i < zNetScene.m_prefabs.Count; i++)
			{
				GameObject val = zNetScene.m_prefabs[i];
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !_prefabLookup.ContainsKey(((Object)val).name))
				{
					_prefabLookup.Add(((Object)val).name, val);
				}
			}
			if (zNetScene.m_namedPrefabs == null)
			{
				return;
			}
			foreach (GameObject value in zNetScene.m_namedPrefabs.Values)
			{
				if ((Object)(object)value != (Object)null && !string.IsNullOrEmpty(((Object)value).name) && !_prefabLookup.ContainsKey(((Object)value).name))
				{
					_prefabLookup.Add(((Object)value).name, value);
				}
			}
		}

		private GameObject ResolvePrefab(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			_prefabLookup.TryGetValue(prefabName, out var value);
			return value;
		}

		public void FlushMissingEffectSummary()
		{
			if (ConstructionConfig.FullDiagnosticsEnabled)
			{
				List<string> list = new List<string>(_missingEffectWarnings);
				list.Sort(StringComparer.Ordinal);
				WriteDiagnosticReport(list);
				ModLog.Debug("FX", "inspected=" + _effectReferencesInspected + ", matched=" + _effectReferencesResolved + ", optionalUnresolved=" + list.Count + "; original references retained.");
			}
		}

		private void WriteDiagnosticReport(List<string> names)
		{
			try
			{
				DiagnosticReportPaths.EnsureDirectory();
				List<string> list = new List<string>
				{
					RuntimeBuildIdentity.Describe(),
					"References inspected: " + _effectReferencesInspected,
					"References resolved: " + _effectReferencesResolved,
					"Unique unresolved references: " + (names?.Count ?? 0),
					string.Empty
				};
				if (names == null || names.Count == 0)
				{
					list.Add("None.");
				}
				else
				{
					list.AddRange(names);
				}
				File.WriteAllLines(DiagnosticReportPaths.GetPath("BalrondConstructions_FxReplacement.txt"), list.ToArray());
			}
			catch (Exception ex)
			{
				ModLog.Warning("[BalrondConstructions Diagnostics] Failed to write FX replacement report: " + ex.Message);
			}
		}

		private void LogMissingEffectOnce(string prefabName)
		{
			if (!string.IsNullOrEmpty(prefabName) && ConstructionConfig.FullDiagnosticsEnabled)
			{
				_missingEffectWarnings.Add(prefabName);
			}
		}
	}
	public static class ShaderReplacment
	{
		private sealed class MaterialRegistration
		{
			public string OriginalDummyShaderName;

			public MaterialSnapshot OriginalSnapshot;
		}

		private sealed class MaterialSnapshot
		{
			public string MaterialName;

			public string ShaderName;

			public int RenderQueue;

			public bool HadCustomRenderQueue;

			public MaterialGlobalIlluminationFlags GlobalIlluminationFlags;

			public bool DoubleSidedGI;

			public string[] DiagnosticOriginalKeywords;

			public readonly Dictionary<string, PropertySnapshot> Properties = new Dictionary<string, PropertySnapshot>(StringComparer.Ordinal);
		}

		private sealed class PropertySnapshot
		{
			public string Name;

			public ShaderPropertyType Type;

			public Texture TextureValue;

			public Vector2 TextureScale;

			public Vector2 TextureOffset;

			public float FloatValue;

			public Color ColorValue;

			public Vector4 VectorValue;
		}

		private sealed class ShaderEvidence
		{
			public Shader Shader;

			public int MaterialReferenceCount;

			public int LiveRendererReferenceCount;

			public bool ReturnedByShaderFind;

			public bool IsSupported;

			public int PassCount;

			public int PropertyCount;

			public bool HasRuntimeEvidence => MaterialReferenceCount > 0 || LiveRendererReferenceCount > 0;

			public long Score
			{
				get
				{
					long num = 0L;
					num += (long)LiveRendererReferenceCount * 1000000000L;
					num += (long)MaterialReferenceCount * 1000000L;
					num += (long)PropertyCount * 100L;
					num += (long)PassCount * 10L;
					if (ReturnedByShaderFind)
					{
						num++;
					}
					return num;
				}
			}
		}

		private sealed class ShaderEvidenceSnapshot
		{
			public readonly Dictionary<string, Dictionary<Shader, ShaderEvidence>> ByName = new Dictionary<string, Dictionary<Shader, ShaderEvidence>>(StringComparer.Ordinal);

			public ShaderEvidence GetOrCreate(string shaderName, Shader shader)
			{
				if ((Object)(object)shader == (Object)null || string.IsNullOrEmpty(shaderName))
				{
					return null;
				}
				if (!ByName.TryGetValue(shaderName, out var value))
				{
					value = new Dictionary<Shader, ShaderEvidence>(ShaderReferenceComparer.Instance);
					ByName.Add(shaderName, value);
				}
				if (!value.TryGetValue(shader, out var value2))
				{
					value2 = new ShaderEvidence
					{
						Shader = shader,
						IsSupported = SafeGetIsSupported(shader),
						PassCount = SafeGetPassCount(shader),
						PropertyCount = SafeGetPropertyCount(shader)
					};
					value.Add(shader, value2);
				}
				return value2;
			}
		}

		private sealed class MaterialReferenceComparer : IEqualityComparer<Material>
		{
			public static readonly MaterialReferenceComparer Instance = new MaterialReferenceComparer();

			public bool Equals(Material x, Material y)
			{
				return x == y;
			}

			public int GetHashCode(Material obj)
			{
				return (!((Object)(object)obj == (Object)null)) ? RuntimeHelpers.GetHashCode(obj) : 0;
			}
		}

		private sealed class ShaderReferenceComparer : IEqualityComparer<Shader>
		{
			public static readonly ShaderReferenceComparer Instance = new ShaderReferenceComparer();

			public bool Equals(Shader x, Shader y)
			{
				return x == y;
			}

			public int GetHashCode(Shader obj)
			{
				return (!((Object)(object)obj == (Object)null)) ? RuntimeHelpers.GetHashCode(obj) : 0;
			}
		}

		public static readonly List<GameObject> prefabsToReplaceShader = new List<GameObject>();

		public static readonly List<Material> materialsInPrefabs = new List<Material>();

		public static readonly List<Shader> shaders = new List<Shader>();

		public static bool debug = false;

		public static bool debugShaderDifferences = false;

		public static bool debugTargetOnlyProperties = false;

		public static bool debugShaderCollisions = false;

		public static int maxDifferenceLogsPerShaderPair = 20;

		public static bool optimizeRepairCandidateTracking = true;

		public static float fallbackCutoff = 0.5f;

		private const float MeaningfulFloatEpsilon = 0.0001f;

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

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

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

		private static readonly HashSet<Material> RegisteredMaterials = new HashSet<Material>(MaterialReferenceComparer.Instance);

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

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

		private static readonly Dictionary<Material, MaterialRegistration> MaterialRegistrations = new Dictionary<Material, MaterialRegistration>(MaterialReferenceComparer.Instance);

		private static readonly HashSet<Material> SuccessfullyProcessedMaterials = new HashSet<Material>(MaterialReferenceComparer.Instance);

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

		private static int MaterialRegistrationVersion;

		private static int LastCompletedRegistrationVersion = -1;

		private static bool HasUnresolvedRegisteredMaterials = true;

		private static bool ReportMissingShaderWarnings;

		private static ShaderEvidenceSnapshot CurrentEvidenceSnapshot;

		private static long MaterialCollectionStopwatchTicks;

		private static readonly HashSet<string> StrictCutoutShaderNames = new HashSet<string>(StringComparer.Ordinal) { "Custom/Grass", "Custom/Vegetation" };

		private static readonly HashSet<string> ProtectedCutoutStateProperties = new HashSet<string>(StringComparer.Ordinal) { "_Mode", "_Surface", "_Blend", "_SrcBlend", "_DstBlend", "_ZWrite", "_AlphaClip", "_AlphaClipThreshold" };

		public static void Replace(GameObject gameObject)
		{
			if (IsDedicatedServer() || (Object)(object)gameObject == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)gameObject).GetInstanceID();
			if (!RegisteredPrefabInstanceIds.Add(instanceID))
			{
				return;
			}
			prefabsToReplaceShader.Add(gameObject);
			long timestamp = Stopwatch.GetTimestamp();
			try
			{
				CollectMaterialsFromPrefab(gameObject);
			}
			finally
			{
				MaterialCollectionStopwatchTicks += Stopwatch.GetTimestamp() - timestamp;
			}
		}

		internal static void ResetRegistrationTiming()
		{
			MaterialCollectionStopwatchTicks = 0L;
		}

		internal static long GetRegistrationTimingMilliseconds()
		{
			if (MaterialCollectionStopwatchTicks <= 0)
			{
				return 0L;
			}
			return (long)((double)MaterialCollectionStopwatchTicks * 1000.0 / (double)Stopwatch.Frequency);
		}

		internal static void SetRepairCandidateOptimization(bool enabled)
		{
			if (optimizeRepairCandidateTracking != enabled)
			{
				optimizeRepairCandidateTracking = enabled;
				MaterialRegistrationVersion++;
				LastCompletedRegistrationVersion = -1;
				HasUnresolvedRegisteredMaterials = true;
			}
		}

		public static void GetMaterialsInPrefab(GameObject gameObject)
		{
			CollectMaterialsFromPrefab(gameObject);
		}

		public static void ForceEnableGpuInstancing(GameObject gameObject)
		{
			CollectMaterialsFromPrefab(gameObject);
		}

		public static void LoadShadersFromBundles()
		{
			RefreshShaderCache();
		}

		public static void getMeShaders()
		{
			LoadShadersFromBundles();
		}

		public static void RefreshShaderCache()
		{
			ShaderCache.Clear();
			shaders.Clear();
			LastShaderEvidence.Clear();
			foreach (KeyValuePair<string, Shader> trustedShaderOverride in TrustedShaderOverrides)
			{
				if (!((Object)(object)trustedShaderOverride.Value == (Object)null))
				{
					ShaderCache[trustedShaderOverride.Key] = trustedShaderOverride.Value;
					AddPublicShaderReference(trustedShaderOverride.Value);
				}
			}
			HashSet<string> hashSet = CollectRegisteredTargetShaderNames();
			CurrentEvidenceSnapshot = BuildEvidenceSnapshot(hashSet);
			foreach (string item in hashSet)
			{
				if (!TrustedShaderOverrides.ContainsKey(item))
				{
					Shader val = ResolveShaderByEvidence(item, CurrentEvidenceSnapshot, forceDiagnosticLog: false);
					if (!((Object)(object)val == (Object)null))
					{
						ShaderCache[item] = val;
						AddPublicShaderReference(val);
					}
				}
			}
		}

		public static void RegisterTrustedShader(Shader shader)
		{
			if (!((Object)(object)shader == (Object)null) && !string.IsNullOrWhiteSpace(((Object)shader).name))
			{
				RegisterTrustedShader(((Object)shader).name, shader);
			}
		}

		public static void RegisterTrustedShader(string shaderName, Shader shader)
		{
			if (!((Object)(object)shader == (Object)null) && !string.IsNullOrWhiteSpace(shaderName))
			{
				string text = shaderName.Trim();
				TrustedShaderOverrides[text] = shader;
				ShaderCache[text] = shader;
				AddPublicShaderReference(shader);
				if (debug)
				{
					ModLog.Debug("Shader", "Explicit trusted shader '" + text + "', instanceID=" + SafeGetInstanceId((Object)(object)shader) + ", passes=" + SafeGetPassCount(shader) + ", properties=" + SafeGetPropertyCount(shader));
				}
			}
		}

		public static void RemoveTrustedShader(string shaderName)
		{
			if (!string.IsNullOrWhiteSpace(shaderName))
			{
				string key = shaderName.Trim();
				TrustedShaderOverrides.Remove(key);
				ShaderCache.Remove(key);
			}
		}

		public static Shader GetShaderByName(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			string text = name.Trim();
			if (TrustedShaderOverrides.TryGetValue(text, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			if (ShaderCache.TryGetValue(text, out var value2) && (Object)(object)value2 != (Object)null)
			{
				return value2;
			}
			HashSet<string> targetNames = new HashSet<string>(StringComparer.Ordinal) { text };
			ShaderEvidenceSnapshot snapshot = BuildEvidenceSnapshot(targetNames);
			Shader val = ResolveShaderByEvidence(text, snapshot, forceDiagnosticLog: false);
			if ((Object)(object)val != (Object)null)
			{
				ShaderCache[text] = val;
				AddPublicShaderReference(val);
			}
			return val;
		}

		public static Shader findShader(string name)
		{
			return GetShaderByName(name);
		}

		public static void RunMaterialFix()
		{
			RunMaterialFix(reportUnresolvedWarnings: false);
		}

		public static void RunMaterialFix(bool reportUnresolvedWarnings)
		{
			if (IsDedicatedServer() || (LastCompletedRegistrationVersion == MaterialRegistrationVersion && !HasUnresolvedRegisteredMaterials))
			{
				return;
			}
			ReportMissingShaderWarnings = reportUnresolvedWarnings;
			RefreshShaderCache();
			IList<Material> list2;
			if (!optimizeRepairCandidateTracking)
			{
				IList<Material> list = materialsInPrefabs;
				list2 = list;
			}
			else
			{
				IList<Material> list = RepairCandidateMaterials;
				list2 = list;
			}
			IList<Material> list3 = list2;
			int count = list3.Count;
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			for (int i = 0; i < count; i++)
			{
				Material val = list3[i];
				if ((Object)(object)val == (Object)null)
				{
					num5++;
					continue;
				}
				val.enableInstancing = true;
				Shader targetShader;
				string selectedTargetName;
				bool cutoutRepaired;
				if (!TryGetOrCreateMaterialRegistration(val, out var registration))
				{
					num4++;
				}
				else if (!TryResolveTargetShader(registration.OriginalDummyShaderName, out targetShader, out selectedTargetName))
				{
					SuccessfullyProcessedMaterials.Remove(val);
					num3++;
				}
				else if (val.shader == targetShader)
				{
					if (RepairStrictCutoutState(val, registration.OriginalSnapshot, targetShader))
					{
						num6++;
					}
					val.enableInstancing = true;
					SuccessfullyProcessedMaterials.Add(val);
					num2++;
				}
				else if (TryApplyTargetShader(val, registration, targetShader, selectedTargetName, out cutoutRepaired))
				{
					if (cutoutRepaired)
					{
						num6++;
					}
					SuccessfullyProcessedMaterials.Add(val);
					num++;
				}
				else
				{
					SuccessfullyProcessedMaterials.Remove(val);
					num3++;
				}
			}
			HasUnresolvedRegisteredMaterials = num3 > 0 || num5 > 0;
			if (!HasUnresolvedRegisteredMaterials)
			{
				LastCompletedRegistrationVersion = MaterialRegistrationVersion;
			}
			if (reportUnresolvedWarnings && num3 > 0)
			{
				ReportFinalUnresolvedShaders(list3);
			}
			if (debug)
			{
				WriteDiagnosticReport(list3, count, num, num2, num6, num3, num4, num5, reportUnresolvedWarnings);
			}
			ReportMissingShaderWarnings = false;
		}

		private static void ReportFinalUnresolvedShaders(IList<Material> repairMaterials)
		{
			Dictionary<string, List<string>> dictionary = BuildUnresolvedShaderMap(repairMaterials);
			foreach (KeyValuePair<string, List<string>> item in dictionary)
			{
				List<string> targetShaderCandidates = GetTargetShaderCandidates(item.Key);
				item.Value.Sort(StringComparer.Ordinal);
				ModLog.WarningOnce("shader-final-unresolved:" + item.Key, "[BalrondShaderReplacement] FINAL unresolved shader mapping: dummy='" + item.Key + "', checked=[" + string.Join(", ", targetShaderCandidates.ToArray()) + "], affectedMaterials=[" + string.Join(", ", item.Value.ToArray()) + "]. This is a real unresolved runtime shader mapping and requires an explicit target/fix; no guessed shader was applied.");
			}
		}

		private static Dictionary<string, List<string>> BuildUnresolvedShaderMap(IList<Material> repairMaterials)
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.Ordinal);
			bool reportMissingShaderWarnings = ReportMissingShaderWarnings;
			ReportMissingShaderWarnings = false;
			try
			{
				if (repairMaterials == null)
				{
					return dictionary;
				}
				for (int i = 0; i < repairMaterials.Count; i++)
				{
					Material val = repairMaterials[i];
					if (!((Object)(object)val == (Object)null) && MaterialRegistrations.TryGetValue(val, out var value) && value != null && !string.IsNullOrEmpty(value.OriginalDummyShaderName) && !TryResolveTargetShader(value.OriginalDummyShaderName, out var _, out var _))
					{
						if (!dictionary.TryGetValue(value.OriginalDummyShaderName, out var value2))
						{
							value2 = new List<string>();
							dictionary.Add(value.OriginalDummyShaderName, value2);
						}
						if (!value2.Contains(((Object)val).name))
						{
							value2.Add(((Object)val).name);
						}
					}
				}
			}
			finally
			{
				ReportMissingShaderWarnings = reportMissingShaderWarnings;
			}
			return dictionary;
		}

		private static void WriteDiagnosticReport(IList<Material> repairMaterials, int repairWork, int replaced, int alreadyExact, int cutoutRepairs, int missingShader, int unmanaged, int invalid, bool finalStrictPass)
		{
			try
			{
				Dictionary<string, List<string>> dictionary = BuildUnresolvedShaderMap(repairMaterials);
				HashSet<string> hashSet = CollectRegisteredTargetShaderNames();
				StringBuilder stringBuilder = new StringBuilder(8192);
				stringBuilder.AppendLine("BalrondConstructions - Shader Replacement");
				stringBuilder.AppendLine("GeneratedUtc=" + DateTime.UtcNow.ToString("O"));
				stringBuilder.AppendLine(RuntimeBuildIdentity.Describe());
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("FinalStrictPass=" + finalStrictPass);
				stringBuilder.AppendLine("TrackedMaterials=" + materialsInPrefabs.Count);
				stringBuilder.AppendLine("RepairWork=" + repairWork);
				stringBuilder.AppendLine("TargetShaderFamilies=" + hashSet.Count);
				stringBuilder.AppendLine("ResolvedTargetShaders=" + ShaderCache.Count);
				stringBuilder.AppendLine("TrustedOverrides=" + TrustedShaderOverrides.Count);
				stringBuilder.AppendLine("Replaced=" + replaced);
				stringBuilder.AppendLine("AlreadyExact=" + alreadyExact);
				stringBuilder.AppendLine("CutoutRepairs=" + cutoutRepairs);
				stringBuilder.AppendLine("MissingShaderMaterials=" + missingShader);
				stringBuilder.AppendLine("Unmanaged=" + unmanaged);
				stringBuilder.AppendLine("Invalid=" + invalid);
				stringBuilder.AppendLine("UnresolvedShaderFamilies=" + dictionary.Count);
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("=== UNRESOLVED SHADER FAMILIES ===");
				if (dictionary.Count == 0)
				{
					stringBuilder.AppendLine("NONE");
				}
				else
				{
					List<string> list = new List<string>(dictionary.Keys);
					list.Sort(StringComparer.Ordinal);
					for (int i = 0; i < list.Count; i++)
					{
						string text = list[i];
						List<string> list2 = dictionary[text];
						list2.Sort(StringComparer.Ordinal);
						List<string> targetShaderCandidates = GetTargetShaderCandidates(text);
						stringBuilder.AppendLine();
						stringBuilder.AppendLine("Dummy=" + text);
						stringBuilder.AppendLine("CheckedTargets=" + string.Join(", ", targetShaderCandidates.ToArray()));
						stringBuilder.AppendLine("AffectedMaterialCount=" + list2.Count);
						stringBuilder.AppendLine("AffectedMaterials=" + string.Join(", ", list2.ToArray()));
					}
				}
				DiagnosticReportPaths.EnsureDirectory();
				File.WriteAllText(DiagnosticReportPaths.GetPath("BalrondConstructions_ShaderReplacement.txt"), stringBuilder.ToString(), Encoding.UTF8);
			}
			catch (Exception ex)
			{
				ModLog.WarningOnce("shader-diagnostic-report-write", "[BalrondShaderReplacement] Failed to write BalrondConstructions_ShaderReplacement.txt: " + ex.Message);
			}
		}

		public static void runMaterialFix()
		{
			RunMaterialFix();
		}

		public static void ForceRefreshAndRunMaterialFix()
		{
			SuccessfullyProcessedMaterials.Clear();
			AnalysedShaderPairs.Clear();
			ShaderCache.Clear();
			CurrentEvidenceSnapshot = null;
			LastCompletedRegistrationVersion = -1;
			HasUnresolvedRegisteredMaterials = true;
			RunMaterialFix(reportUnresolvedWarnings: true);
		}

		public static void RepairAllRegisteredMaterials()
		{
			ForceRefreshAndRunMaterialFix();
		}

		internal static void RepairMossRoofVegetationCutouts(IEnumerable<GameObject> pieces)
		{
			if (IsDedicatedServer() || pieces == null)
			{
				return;
			}
			string[] array = new string[4] { "bush_roof_bal", "bush_roof_icorner_bal", "bush_roof_ocorner_bal", "bush_roof_top_bal" };
			HashSet<Material> hashSet = new HashSet<Material>(MaterialReferenceComparer.Instance);
			List<Material> list = new List<Material>(16);
			List<string> list2 = new List<string>();
			int num = 0;
			int num2 = 0;
			foreach (GameObject piece in pieces)
			{
				if ((Object)(object)piece == (Object)null || Array.IndexOf(array, ((Object)piece).name) < 0)
				{
					continue;
				}
				list.Clear();
				Renderer[] componentsInChildren = piece.GetComponentsInChildren<Renderer>(true);
				foreach (Renderer val in componentsInChildren)
				{
					if (!((Object)(object)val == (Object)null))
					{
						Material[] sharedMaterials = val.sharedMaterials;
						if (sharedMaterials != null)
						{
							list.AddRange(sharedMaterials);
						}
					}
				}
				InstanceRenderer[] componentsInChildren2 = piece.GetComponentsInChildren<InstanceRenderer>(true);
				foreach (InstanceRenderer val2 in componentsInChildren2)
				{
					if ((Object)(object)val2 != (Object)null)
					{
						list.Add(val2.m_material);
					}
				}
				for (int k = 0; k < list.Count; k++)
				{
					Material val3 = list[k];
					if ((Object)(object)val3 == (Object)null)
					{
						continue;
					}
					Shader shader = val3.shader;
					if (ConstructionConfig.FullDiagnosticsEnabled)
					{
						string text = (val3.HasProperty("_Cutoff") ? val3.GetFloat("_Cutoff").ToString("R") : "<not exposed>");
						list2.Add(((Object)piece).name + " / " + ((Object)val3).name + " | shader=" + (((Object)(object)shader != (Object)null) ? ((Object)shader).name : "<missing>") + " | cutoff_before=" + text);
					}
					if (!hashSet.Add(val3) || (Object)(object)shader == (Object)null || !string.Equals(((Object)shader).name, "Custom/Vegetation", StringComparison.Ordinal))
					{
						continue;
					}
					num++;
					MaterialRegistrations.TryGetValue(val3, out var value);
					if (RepairStrictCutoutState(val3, value?.OriginalSnapshot, shader))
					{
						num2++;
						if (val3.HasProperty("_Cutoff"))
						{
							ModLog.Debug("Shader", "Moss roof vegetation cutoff restored: " + ((Object)piece).name + "/" + ((Object)val3).name + ", cutoff=" + val3.GetFloat("_Cutoff") + ".");
						}
					}
				}
			}
			ModLog.Debug("Shader", "Moss roof vegetation materials=" + num + ", cutoff repairs=" + num2 + ".");
			DiagnosticsWriter.WriteList("MossRoof_Materials.txt", list2);
		}

		public static void ClearRegisteredMaterials()
		{
			prefabsToReplaceShader.Clear();
			materialsInPrefabs.Clear();
			RepairCandidateMaterials.Clear();
			RegisteredPrefabInstanceIds.Clear();
			RegisteredMaterials.Clear();
			MaterialRegistrations.Clear();
			SuccessfullyProcessedMaterials.Clear();
			ShaderCache.Clear();
			LastShaderEvidence.Clear();
			AnalysedShaderPairs.Clear();
			CurrentEvidenceSnapshot = null;
			MaterialRegistrationVersion = 0;
			MaterialCollectionStopwatchTicks = 0L;
			LastCompletedRegistrationVersion = -1;
			HasUnresolvedRegisteredMaterials = true;
		}

		public static void ResetProcessedMaterials()
		{
			SuccessfullyProcessedMaterials.Clear();
			AnalysedShaderPairs.Clear();
			LastCompletedRegistrationVersion = -1;
			HasUnresolvedRegisteredMaterials = true;
		}

		public static void DiagnoseRegisteredShaderCollisions()
		{
			HashSet<string> hashSet = CollectRegisteredTargetShaderNames();
			ShaderEvidenceSnapshot snapshot = BuildEvidenceSnapshot(hashSet);
			foreach (string item in hashSet)
			{
				ResolveShaderByEvidence(item, snapshot, forceDiagnosticLog: true);
			}
		}

		private static void CollectMaterialsFromPrefab(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return;
			}
			Renderer[] componentsInChildren = gameObject.GetComponentsInChildren<Renderer>(true);
			if (componentsInChildren != null)
			{
				foreach (Renderer val in componentsInChildren)
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					Material[] sharedMaterials = val.sharedMaterials;
					if (sharedMaterials != null)
					{
						for (int j = 0; j < sharedMaterials.Length; j++)
						{
							RegisterMaterial(sharedMaterials[j]);
						}
					}
				}
			}
			InstanceRenderer[] componentsInChildren2 = gameObject.GetComponentsInChildren<InstanceRenderer>(true);
			if (componentsInChildren2 == null)
			{
				return;
			}
			foreach (InstanceRenderer val2 in componentsInChildren2)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					RegisterMaterial(val2.m_material);
				}
			}
		}

		private static void RegisterMaterial(Material material)
		{
			if ((Object)(object)material == (Object)null)
			{
				return;
			}
			material.enableInstancing = true;
			bool flag = RegisteredMaterials.Add(material);
			MaterialRegistration registration;
			bool flag2 = TryGetOrCreateMaterialRegistration(material, out registration);
			if (flag)
			{
				materialsInPrefabs.Add(material);
				if (flag2)
				{
					RepairCandidateMaterials.Add(material);
				}
				if (!optimizeRepairCandidateTracking || flag2)
				{
					MaterialRegistrationVersion++;
					LastCompletedRegistrationVersion = -1;
				}
			}
		}

		private static bool TryGetOrCreateMaterialRegistration(Material material, out MaterialRegistration registration)
		{
			registration = null;
			if ((Object)(object)material == (Object)null)
			{
				return false;
			}
			if (MaterialRegistrations.TryGetValue(material, out registration) && registration != null && !string.IsNullOrEmpty(registration.OriginalDummyShaderName))
			{
				return true;
			}
			Shader shader = material.shader;
			if ((Object)(object)shader == (Object)null || !IsDummyShader(((Object)shader).name))
			{
				return false;
			}
			registration = new MaterialRegistration
			{
				OriginalDummyShaderName = ((Object)shader).name,
				OriginalSnapshot = CaptureMaterial(material)
			};
			MaterialRegistrations[material] = registration;
			return true;
		}

		private static bool IsDummyShader(string shaderName)
		{
			if (string.IsNullOrEmpty(shaderName))
			{
				return false;
			}
			if (string.Equals(shaderName, "Balrond/ScrollingTex", StringComparison.Ordinal))
			{
				return false;
			}
			return shaderName.StartsWith("Balrond/", StringComparison.Ordinal);
		}

		private static List<string> GetTargetShaderCandidates(string oldShaderName)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(oldShaderName))
			{
				return list;
			}
			string text = oldShaderName;
			if (text.StartsWith("Balrond/", StringComparison.Ordinal))
			{
				text = text.Substring("Balrond/".Length);
			}
			string text2 = RemovePrefix(text, "Particles/");
			if (ContainsOrdinalIgnoreCase(text, "Tess Bumped"))
			{
				AddCandidate(list, "Lux Lit Particles/ Tess Bumped");
				AddCandidate(list, "Lux Lit Particles/Tess Bumped");
				AddCandidate(list, "Lux Lit Particles/" + text2);
			}
			if (ContainsOrdinalIgnoreCase(text, "Bumped"))
			{
				AddCandidate(list, "Lux Lit Particles/ Bumped");
				AddCandidate(list, "Lux Lit Particles/Bumped");
				AddCandidate(list, "Lux Lit Particles/" + text2);
			}
			if (ContainsOrdinalIgnoreCase(text, "Standard Surface"))
			{
				AddCandidate(list, "Particles/Standard Surface");
				AddCandidate(list, "Particles/Standard Surface2");
				string text3 = text2;
				if (ContainsOrdinalIgnoreCase(text3, "Standard Surface2"))
				{
					text3 = ReplaceOrdinalIgnoreCase(text3, "Standard Surface2", "Standard Surface");
				}
				AddCandidate(list, "Particles/" + text3);
			}
			if (ContainsOrdinalIgnoreCase(text, "Standard Unlit"))
			{
				AddCandidate(list, "Particles/Standard Unlit2");
				AddCandidate(list, "Particles/Standard Unlit");
				string text4 = text2;
				if (!ContainsOrdinalIgnoreCase(text4, "Standard Unlit2"))
				{
					text4 = ReplaceOrdinalIgnoreCase(text4, "Standard Unlit", "Standard Unlit2");
				}
				AddCandidate(list, "Particles/" + text4);
			}
			AddCandidate(list, "Custom/" + text);
			return list;
		}

		private static void AddCandidate(List<string> candidates, string shaderName)
		{
			if (!string.IsNullOrWhiteSpace(shaderName))
			{
				string item = shaderName.Trim();
				if (!candidates.Contains(item))
				{
					candidates.Add(item);
				}
			}
		}

		private static bool TryResolveTargetShader(string oldShaderName, out Shader targetShader, out string selectedTargetName)
		{
			targetShader = null;
			selectedTargetName = null;
			List<string> targetShaderCandidates = GetTargetShaderCandidates(oldShaderName);
			for (int i = 0; i < targetShaderCandidates.Count; i++)
			{
				string text = targetShaderCandidates[i];
				Shader shaderByName = GetShaderByName(text);
				if (IsUsableTargetShader(shaderByName, text))
				{
					targetShader = shaderByName;
					selectedTargetName = text;
					return true;
				}
			}
			if (debug && ReportMissingShaderWarnings)
			{
				ModLog.WarningOnce("shader-target-missing:" + oldShaderName, "[BalrondShaderReplacement] No target shader for dummy '" + oldShaderName + "'. Checked: " + string.Join(", ", targetShaderCandidates));
			}
			return false;
		}

		private static bool IsUsableTargetShader(Shader shader, string expectedName)
		{
			if ((Object)(object)shader == (Object)null)
			{
				return false;
			}
			if (!string.Equals(((Object)shader).name, expectedName, StringComparison.Ordinal))
			{
				return false;
			}
			if (!SafeGetIsSupported(shader))
			{
				if (debug)
				{
					ModLog.WarningOnce("shader-unsupported:" + expectedName, "[BalrondShaderReplacement] Rejected unsupported shader '" + expectedName + "', instanceID=" + SafeGetInstanceId((Object)(object)shader));
				}
				return false;
			}
			if (SafeGetPassCount(shader) <= 0)
			{
				if (debug)
				{
					ModLog.WarningOnce("shader-zero-pass:" + expectedName, "[BalrondShaderReplacement] Rejected zero-pass shader '" + expectedName + "', instanceID=" + SafeGetInstanceId((Object)(object)shader));
				}
				return false;
			}
			return true;
		}

		private static HashSet<string> CollectRegisteredTargetShaderNames()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (KeyValuePair<Material, MaterialRegistration> materialRegistration in MaterialRegistrations)
			{
				MaterialRegistration value = materialRegistration.Value;
				if (value != null && !string.IsNullOrEmpty(value.OriginalDummyShaderName))
				{
					List<string> targetShaderCandidates = GetTargetShaderCandidates(value.OriginalDummyShaderName);
					for (int i = 0; i < targetShaderCandidates.Count; i++)
					{
						hashSet.Add(targetShaderCandidates[i]);
					}
				}
			}
			return hashSet;
		}

		private static ShaderEvidenceSnapshot BuildEvidenceSnapshot(HashSet<string> targetNames)
		{
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			ShaderEvidenceSnapshot shaderEvidenceSnapshot = new ShaderEvidenceSnapshot();
			if (targetNames == null || targetNames.Count == 0)
			{
				return shaderEvidenceSnapshot;
			}
			Material[] array = Resources.FindObjectsOfTypeAll<Material>();
			if (array != null)
			{
				foreach (Material val in array)
				{
					if ((Object)(object)val == (Object)null || RegisteredMaterials.Contains(val) || (Object)(object)val.shader == (Object)null)
					{
						continue;
					}
					Shader shader = val.shader;
					string name = ((Object)shader).name;
					if (!string.IsNullOrEmpty(name) && targetNames.Contains(name))
					{
						ShaderEvidence orCreate = shaderEvidenceSnapshot.GetOrCreate(name, shader);
						if (orCreate != null)
						{
							orCreate.MaterialReferenceCount++;
						}
					}
				}
			}
			Renderer[] array2 = Resources.FindObjectsOfTypeAll<Renderer>();
			if (array2 != null)
			{
				foreach (Renderer val2 in array2)
				{
					if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).gameObject == (Object)null)
					{
						continue;
					}
					Scene scene = ((Component)val2).gameObject.scene;
					if (!((Scene)(ref scene)).IsValid())
					{
						continue;
					}
					scene = ((Component)val2).gameObject.scene;
					if (!((Scene)(ref scene)).isLoaded)
					{
						continue;
					}
					Material[] sharedMaterials = val2.sharedMaterials;
					if (sharedMaterials == null)
					{
						continue;
					}
					foreach (Material val3 in sharedMaterials)
					{
						if ((Object)(object)val3 == (Object)null || RegisteredMaterials.Contains(val3) || (Object)(object)val3.shader == (Object)null)
						{
							continue;
						}
						Shader shader2 = val3.shader;
						string name2 = ((Object)shader2).name;
						if (!string.IsNullOrEmpty(name2) && targetNames.Contains(name2))
						{
							ShaderEvidence orCreate2 = shaderEvidenceSnapshot.GetOrCreate(name2, shader2);
							if (orCreate2 != null)
							{
								orCreate2.LiveRendererReferenceCount++;
							}
						}
					}
				}
			}
			Shader[] array3 = Resources.FindObjectsOfTypeAll<Shader>();
			if (array3 != null)
			{
				foreach (Shader val4 in array3)
				{
					if (!((Object)(object)val4 == (Object)null) && !string.IsNullOrEmpty(((Object)val4).name) && targetNames.Contains(((Object)val4).name))
					{
						shaderEvidenceSnapshot.GetOrCreate(((Object)val4).name, val4);
					}
				}
			}
			foreach (string targetName in targetNames)
			{
				Shader val5 = null;
				try
				{
					val5 = Shader.Find(targetName);
				}
				catch (Exception ex)
				{
					if (debug)
					{
						ModLog.Warning("[BalrondShaderReplacement] Shader.Find failed for '" + targetName + "': " + ex.Message);
					}
				}
				if (!((Object)(object)val5 == (Object)null) && string.Equals(((Object)val5).name, targetName, StringComparison.Ordinal))
				{
					ShaderEvidence orCreate3 = shaderEvidenceSnapshot.GetOrCreate(targetName, val5);
					if (orCreate3 != null)
					{
						orCreate3.ReturnedByShaderFind = true;
					}
				}
			}
			return shaderEvidenceSnapshot;
		}

		private static Shader ResolveShaderByEvidence(string shaderName, ShaderEvidenceSnapshot snapshot, bool forceDiagnosticLog)
		{
			if (string.IsNullOrWhiteSpace(shaderName))
			{
				return null;
			}
			string text = shaderName.Trim();
			if (TrustedShaderOverrides.TryGetValue(text, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			if (snapshot == null || !snapshot.ByName.TryGetValue(text, out var value2))
			{
				if (debug && ReportMissingShaderWarnings)
				{
					ModLog.WarningOnce("shader-candidates-missing:" + text, "[BalrondShaderReplacement] No loaded shader candidates for '" + text + "'.");
				}
				return null;
			}
			bool flag = false;
			foreach (ShaderEvidence value3 in value2.Values)
			{
				if (value3 == null || (Object)(object)value3.Shader == (Object)null || !value3.IsSupported || value3.PassCount <= 0 || !value3.HasRuntimeEvidence)
				{
					continue;
				}
				flag = true;
				break;
			}
			ShaderEvidence shaderEvidence = null;
			foreach (ShaderEvidence value4 in value2.Values)
			{
				if (value4 != null && !((Object)(object)value4.Shader == (Object)null) && value4.IsSupported && value4.PassCount > 0 && (!flag || value4.HasRuntimeEvidence) && (shaderEvidence == null || value4.Score > shaderEvidence.Score))
				{
					shaderEvidence = value4;
				}
			}
			List<ShaderEvidence> list = new List<ShaderEvidence>(value2.Values);
			list.Sort(delegate(ShaderEvidence left, ShaderEvidence right)
			{
				if (left == right)
				{
					return 0;
				}
				return (left == null) ? 1 : (right?.Score.CompareTo(left.Score) ?? (-1));
			});
			LastShaderEvidence[text] = list;
			bool flag2 = list.Count > 1;
			if (forceDiagnosticLog || (debugShaderCollisions && flag2))
			{
				ModLog.Warning("[BalrondShaderReplacement] Shader candidates for '" + text + "': count=" + list.Count + ", runtimeEvidenceMode=" + flag + ", selected=" + ((shaderEvidence != null) ? SafeGetInstanceId((Object)(object)shaderEvidence.Shader).ToString() : "<none>"));
				for (int num = 0; num < list.Count; num++)
				{
					ShaderEvidence shaderEvidence2 = list[num];
					if (shaderEvidence2 != null)
					{
						bool flag3 = shaderEvidence != null && shaderEvidence2.Shader == shaderEvidence.Shader;
						ModLog.Warning("[BalrondShaderReplacement]   " + (flag3 ? "[SELECTED] " : string.Empty) + "instanceID=" + SafeGetInstanceId((Object)(object)shaderEvidence2.Shader) + ", supported=" + shaderEvidence2.IsSupported + ", passes=" + shaderEvidence2.PassCount + ", properties=" + shaderEvidence2.PropertyCount + ", externalMaterialRefs=" + shaderEvidence2.MaterialReferenceCount + ", externalLiveRendererRefs=" + shaderEvidence2.LiveRendererReferenceCount + ", Shader.Find=" + shaderEvidence2.ReturnedByShaderFind + ", score=" + shaderEvidence2.Score);
					}
				}
			}
			return shaderEvidence?.Shader;
		}

		private static bool TryApplyTargetShader(Material material, MaterialRegistration registration, Shader targetShader, string selectedTargetName, out bool cutoutRepaired)
		{
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			cutoutRepaired = false;
			if ((Object)(object)material == (Object)null || registration == null || (Object)(object)targetShader == (Object)null)
			{
				return false;
			}
			Shader shader = material.shader;
			string text = (((Object)(object)shader != (Object)null) ? ((Object)shader).name : "<null>");
			MaterialSnapshot materialSnapshot = registration.OriginalSnapshot ?? CaptureMaterial(material);
			if (debugShaderDifferences)
			{
				AnalyseShaderDifferences(material, materialSnapshot, targetShader, registration.OriginalDummyShaderName, selectedTargetName);
			}
			try
			{
				material.shader = targetShader;
				bool flag = IsStrictCutoutTarget(targetShader);
				RestoreCompatibleProperties(material, materialSnapshot, flag);
				if (flag)
				{
					material.renderQueue = -1;
				}
				else if (materialSnapshot != null && materialSnapshot.HadCustomRenderQueue)
				{
					material.renderQueue = materialSnapshot.RenderQueue;
				}
				if (materialSnapshot != null)
				{
					material.globalIlluminationFlags = materialSnapshot.GlobalIlluminationFlags;
					material.doubleSidedGI = materialSnapshot.DoubleSidedGI;
				}
				material.enableInstancing = true;
				cutoutRepaired = RepairStrictCutoutState(material, materialSnapshot, targetShader);
				return true;
			}
			catch (Exception ex)
			{
				ModLog.Error("[BalrondShaderReplacement] Failed replacing shader on material '" + ((Object)material).name + "'. dummy='" + registration.OriginalDummyShaderName + "', current='" + text + "', target='" + ((Object)targetShader).name + "'. " + ex);
				return false;
			}
		}

		private static bool IsStrictCutoutTarget(Shader shader)
		{
			return (Object)(object)shader != (Object)null && StrictCutoutShaderNames.Contains(((Object)shader).name);
		}

		private static bool RepairStrictCutoutState(Material material, MaterialSnapshot originalSnapshot, Shader targetShader)
		{
			if ((Object)(object)material == (Object)null || (Object)(object)targetShader == (Object)null || !IsStrictCutoutTarget(targetShader))
			{
				return false;
			}
			bool result = false;
			if (material.HasProperty("_Cutoff"))
			{
				float num = material.GetFloat("_Cutoff");
				float snapshotFloat = GetSnapshotFloat(originalSnapshot, "_Cutoff", float.NaN);
				float num2 = ((float.IsNaN(snapshotFloat) || !(snapshotFloat > 0.0001f)) ? Mathf.Clamp01(fallbackCutoff) : Mathf.Clamp01(snapshotFloat));
				if (num2 > 0.0001f && Mathf.Abs(num - num2) > 0.0001f)
				{
					material.SetFloat("_Cutoff", num2);
					result = true;
				}
			}
			if (material.renderQueue != -1)
			{
				material.renderQueue = -1;
				result = true;
			}
			return result;
		}

		private static MaterialSnapshot CaptureMaterial(Material material)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Expected I4, but got Unknown
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			MaterialSnapshot materialSnapshot = new MaterialSnapshot();
			if ((Object)(object)material == (Object)null)
			{
				return materialSnapshot;
			}
			Shader shader = material.shader;
			materialSnapshot.MaterialName = ((Object)material).name;
			materialSnapshot.ShaderName = (((Object)(object)shader != (Object)null) ? ((Object)shader).name : string.Empty);
			materialSnapshot.RenderQueue = material.renderQueue;
			materialSnapshot.HadCustomRenderQueue = (Object)(object)shader != (Object)null && material.renderQueue != shader.renderQueue;
			materialSnapshot.GlobalIlluminationFlags = material.globalIlluminationFlags;
			materialSnapshot.DoubleSidedGI = material.doubleSidedGI;
			try
			{
				string[] shaderKeywords = material.shaderKeywords;
				materialSnapshot.DiagnosticOriginalKeywords = ((shaderKeywords != null) ? ((string[])shaderKeywords.Clone()) : new string[0]);
			}
			catch
			{
				materialSnapshot.DiagnosticOriginalKeywords = new string[0];
			}
			if ((Object)(object)shader == (Object)null)
			{
				return materialSnapshot;
			}
			int num = SafeGetPropertyCount(shader);
			for (int i = 0; i < num; i++)
			{
				string propertyName;
				ShaderPropertyType propertyType;
				try
				{
					propertyName = shader.GetPropertyName(i);
					propertyType = shader.GetPropertyType(i);
				}
				catch (Exception ex)
				{
					if (debug)
					{
						ModLog.Warning("[BalrondShaderReplacement] Could not read shader property metadata from '" + ((Object)shader).name + "': " + ex.Message);
					}
					continue;
				}
				if (string.IsNullOrEmpty(propertyName) || !material.HasProperty(propertyName))
				{
					continue;
				}
				PropertySnapshot propertySnapshot = new PropertySnapshot
				{
					Name = propertyName,
					Type = propertyType
				};
				try
				{
					ShaderPropertyType val = propertyType;
					ShaderPropertyType val2 = val;
					switch ((int)val2)
					{
					default:
						goto end_IL_0179;
					case 4:
						propertySnapshot.TextureValue = material.GetTexture(propertyName);
						propertySnapshot.TextureScale = material.GetTextureScale(propertyName);
						propertySnapshot.TextureOffset = material.GetTextureOffset(propertyName);
						break;
					case 0:
						propertySnapshot.ColorValue = material.GetColor(propertyName);
						break;
					case 1:
						propertySnapshot.VectorValue = material.GetVector(propertyName);
						break;
					case 2:
					case 3:
						propertySnapshot.FloatValue = material.GetFloat(propertyName);
						break;
					}
					materialSnapshot.Properties[propertyName] = propertySnapshot;
					end_IL_0179:;
				}
				catch (Exception ex2)
				{
					if (debug)
					{
						ModLog.Warning("[BalrondShaderReplacement] Could not capture property '" + propertyName + "' from material '" + ((Object)material).name + "': " + ex2.Message);
					}
				}
			}
			return materialSnapshot;
		}

		private static int RestoreCompatibleProperties(Material material, MaterialSnapshot snapshot, bool protectCutoutRenderState)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected I4, but got Unknown
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)material == (Object)null || (Object)(object)material.shader == (Object)null || snapshot == null)
			{
				return 0;
			}
			Dictionary<string, ShaderPropertyType> shaderPropertyTypes = GetShaderPropertyTypes(material.shader);
			int num = 0;
			foreach (KeyValuePair<string, PropertySnapshot> property in snapshot.Properties)
			{
				string key = property.Key;
				PropertySnapshot value = property.Value;
				if ((protectCutoutRenderState && ProtectedCutoutStateProperties.Contains(key)) || !shaderPropertyTypes.TryGetValue(key, out var value2) || !ArePropertyTypesCompatible(value.Type, value2) || !material.HasProperty(key))
				{
					continue;
				}
				try
				{
					ShaderPropertyType val = value2;
					ShaderPropertyType val2 = val;
					switch ((int)val2)
					{
					default:
						goto end_IL_00ca;
					case 4:
						material.SetTexture(key, value.TextureValue);
						material.SetTextureScale(key, value.TextureScale);
						material.SetTextureOffset(key, value.TextureOffset);
						break;
					case 0:
						material.SetColor(key, value.ColorValue);
						break;
					case 1:
						material.SetVector(key, value.VectorValue);
						break;
					case 2:
					case 3:
						material.SetFloat(key, value.FloatValue);
						break;
					}
					num++;
					end_IL_00ca:;
				}
				catch (Exception ex)
				{
					if (debug)
					{
						ModLog.Warning("[BalrondShaderReplacement] Could not restore property '" + key + "' on material '" + ((Object)material).name + "': " + ex.Message);
					}
				}
			}
			return num;
		}

		private static Dictionary<string, ShaderPropertyType> GetShaderPropertyTypes(Shader shader)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, ShaderPropertyType> dictionary = new Dictionary<string, ShaderPropertyType>(StringComparer.Ordinal);
			if ((Object)(object)shader == (Object)null)
			{
				return dictionary;
			}
			int num = SafeGetPropertyCount(shader);
			for (int i = 0; i < num; i++)
			{
				try
				{
					string propertyName = shader.GetPropertyName(i);
					ShaderPropertyType propertyType = shader.GetPropertyType(i);
					if (!string.IsNullOrEmpty(propertyName))
					{
						dictionary[propertyName] = propertyType;
					}
				}
				catch
				{
				}
			}
			return dictionary;
		}

		private static bool ArePropertyTypesCompatible(ShaderPropertyType sourceType, ShaderPropertyType targetType)
		{
			//IL_0001: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			if (sourceType == targetType)
			{
				return true;
			}
			bool flag = (int)sourceType == 2 || (int)sourceType == 3;
			bool flag2 = (int)targetType == 2 || (int)targetType == 3;
			return flag && flag2;
		}

		private static float GetSnapshotFloat(MaterialSnapshot snapshot, string propertyName, float fallback)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Invalid comparison between Unknown and I4
			if (snapshot == null || string.IsNullOrEmpty(propertyName) || !snapshot.Properties.TryGetValue(propertyName, out var value))
			{
				return fallback;
			}
			if ((int)value.Type != 2 && (int)value.Type != 3)
			{
				return fallback;
			}
			return value.FloatValue;
		}

		private unsafe static void AnalyseShaderDifferences(Material material, MaterialSnapshot sourceSnapshot, Shader targetShader, string oldShaderName, string targetShaderName)
		{
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			if (sourceSnapshot == null || (Object)(object)targetShader == (Object)null)
			{
				return;
			}
			string text = oldShaderName + " -> " + targetShaderName;
			if (!AnalysedShaderPairs.Add(text))
			{
				return;
			}
			Dictionary<string, ShaderPropertyType> shaderPropertyTypes = GetShaderPropertyTypes(targetShader);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int currentCount = 0;
			foreach (KeyValuePair<string, PropertySnapshot> property in sourceSnapshot.Properties)
			{
				string key = property.Key;
				PropertySnapshot value = property.Value;
				if (!shaderPropertyTypes.TryGetValue(key, out var value2))
				{
					if (IsMeaningfulValue(value))
					{
						num2++;
						if (CanLogAnotherDifference(ref currentCount))
						{
							ModLog.Warning("[BalrondShaderReplacement] Target '" + targetShaderName + "' is missing source property '" + key + "' (" + ((object)Unsafe.As<ShaderPropertyType, ShaderPropertyType>(ref value.Type)/*cast due to .constrained prefix*/).ToString() + ") from material '" + ((Object)material).name + "', value=" + FormatPropertyValue(value));
						}
					}
				}
				else if (!ArePropertyTypesCompatible(value.Type, value2))
				{
					num3++;
					if (CanLogAnotherDifference(ref currentCount))
					{
						ModLog.Warning("[BalrondShaderReplacement] Property type mismatch for '" + text + "', property='" + key + "', dummy=" + ((object)Unsafe.As<ShaderPropertyType, ShaderPropertyType>(ref value.Type)/*cast due to .constrained prefix*/).ToString() + ", target=" + ((object)(*(ShaderPropertyType*)(&value2))/*cast due to .constrained prefix*/).ToString());
					}
				}
				else
				{
					num++;
				}
			}
			foreach (string key2 in shaderPropertyTypes.Keys)
			{
				if (!sourceSnapshot.Properties.ContainsKey(key2))
				{
					num4++;
					if (debugTargetOnlyProperties && CanLogAnotherDifference(ref currentCount))
					{
						ModLog.Debug("Shader", "Target-only property for '" + text + "': '" + key2 + "'.");
					}
				}
			}
			ModLog.Debug("Shader", "Shader analysis '" + text + "': dummyProperties=" + sourceSnapshot.Properties.Count + ", targetProperties=" + shaderPropertyTypes.Count + ", compatible=" + num + ", meaningfulMissing=" + num2 + ", typeMismatch=" + num3 + ", targetOnly=" + num4);
		}

		private static bool IsMeaningfulValue(PropertySnapshot property)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected I4, but got Unknown
			if (property == null)
			{
				return false;
			}
			ShaderPropertyType type = property.Type;
			ShaderPropertyType val = type;
			switch ((int)val)
			{
			case 4:
				return (Object)(object)property.TextureValue != (Object)null;
			case 2:
			case 3:
				return Mathf.Abs(property.FloatValue) > 0.0001f;
			case 0:
				return Mathf.Abs(property.ColorValue.r) > 0.0001f || Mathf.Abs(property.ColorValue.g) > 0.0001f || Mathf.Abs(property.ColorValue.b) > 0.0001f || Mathf.Abs(property.ColorValue.a) > 0.0001f;
			case 1:
				return ((Vector4)(ref property.VectorValue)).sqrMagnitude > 9.999999E-09f;
			default:
				return false;
			}
		}

		private static string FormatPropertyValue(PropertySnapshot property)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected I4, but got Unknown
			if (property == null)
			{
				return "null";
			}
			ShaderPropertyType type = property.Type;
			ShaderPropertyType val = type;
			switch ((int)val)
			{
			case 4:
				return ((Object)(object)property.TextureValue != (Object)null) ? ((Object)property.TextureValue).name : "null";
			case 2:
			case 3:
				return property.FloatValue.ToString("0.####");
			case 0:
				return ((object)Unsafe.As<Color, Color>(ref property.ColorValue)/*cast due to .constrained prefix*/).ToString();
			case 1:
				return ((object)Unsafe.As<Vector4, Vector4>(ref property.VectorValue)/*cast due to .constrained prefix*/).ToString();
			default:
				return "unsupported";
			}
		}

		private static bool CanLogAnotherDifference(ref int currentCount)
		{
			if (maxDifferenceLogsPerShaderPair <= 0 || currentCount >= maxDifferenceLogsPerShaderPair)
			{
				return false;
			}
			currentCount++;
			return true;
		}

		private static void AddPublicShaderReference(Shader shader)
		{
			if ((Object)(object)shader == (Object)null)
			{
				return;
			}
			for (int i = 0; i < shaders.Count; i++)
			{
				if (shaders[i] == shader)
				{
					return;
				}
			}
			shaders.Add(shader);
		}

		private static int SafeGetPropertyCount(Shader shader)
		{
			if ((Object)(object)shader == (Object)null)
			{
				return 0;
			}
			try
			{
				return shader.GetPropertyCount();
			}
			catch (Exception ex)
			{
				if (debug)
				{
					ModLog.Warning("[BalrondShaderReplacement] GetPropertyCount failed for '" + ((Object)shader).name + "': " + ex.Message);
				}
				return 0;
			}
		}

		private static int SafeGetPassCount(Shader shader)
		{
			if ((Object)(object)shader == (Object)null)
			{
				return 0;
			}
			try
			{
				return shader.passCount;
			}
			catch (Exception ex)
			{
				if (debug)
				{
					ModLog.Warning("[BalrondShaderReplacement] passCount failed for '" + ((Object)shader).name + "': " + ex.Message);
				}
				return 0;
			}
		}

		private static bool SafeGetIsSupported(Shader shader)
		{
			if ((Object)(object)shader == (Object)null)
			{
				return false;
			}
			try
			{
				return shader.isSupported;
			}
			catch (Exception ex)
			{
				if (debug)
				{
					ModLog.Warning("[BalrondShaderReplacement] isSupported failed for '" + ((Object)shader).name + "': " + ex.Message);
				}
				return false;
			}
		}

		private static int SafeGetInstanceId(Object unityObject)
		{
			if (unityObject == (Object)null)
			{
				return 0;
			}
			try
			{
				return unityObject.GetInstanceID();
			}
			catch
			{
				return 0;
			}
		}

		private static bool IsDedicatedServer()
		{
			try
			{
				return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated();
			}
			catch
			{
				return false;
			}
		}

		private static string RemovePrefix(string value, string prefix)
		{
			if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(prefix))
			{
				return value;
			}
			return value.StartsWith(prefix, StringComparison.Ordinal) ? value.Substring(prefix.Length) : value;
		}

		private static bool ContainsOrdinalIgnoreCase(string value, string fragment)
		{
			return !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(fragment) && value.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static string ReplaceOrdinalIgnoreCase(string source, string oldValue, string newValue)
		{
			if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(oldValue))
			{
				return source;
			}
			int num = source.IndexOf(oldValue, StringComparison.OrdinalIgnoreCase);
			if (num < 0)
			{
				return source;
			}
			return source.Substring(0, num) + newValue + source.Substring(num + oldValue.Length);
		}
	}
	[BepInPlugin("balrond.astafaraios.BalrondConstructions", "BalrondConstructions", "1.5.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Launch : BaseUnityPlugin
	{
		public const string PluginGUID = "balrond.astafaraios.BalrondConstructions";

		public const string PluginName = "BalrondConstructions";

		public const string PluginVersion = "1.5.2";

		internal static readonly BuildPieceBuilder BuildPieceBuilder = new BuildPieceBuilder();

		internal static readonly FxReplacment FxReplacment = new FxReplacment();

		internal static readonly JsonLoader JsonLoader = new JsonLoader();

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			ModLog.Initialize(((BaseUnityPlugin)this).Logger);
			ConstructionConfig.Bind(((BaseUnityPlugin)this).Config);
			ShaderReplacment.debug = ConstructionConfig.FullDiagnosticsEnabled;
			RuntimeBuildIdentity.WriteDiagnosticStamp();
			try
			{
				JsonLoader.loadJson();
				if (!AssetLoader.LoadAll())
				{
					throw new InvalidOperationException("Required embedded AssetBundle could not be loaded.");
				}
				_harmony = new Harmony("balrond.astafaraios.BalrondConstructions");
				PatchHarmonyTypesSafely();
				ModLog.Info(RuntimeBuildIdentity.Describe() + " initialized. Build-piece prefab IDs are preserved exactly; BAO integration is soft/optional.");
			}
			catch (Exception ex)
			{
				ModLog.Error("Initialization failed: " + ex);
				SafeShutdown();
				throw;
			}
		}

		private void PatchHarmonyTypesSafely()
		{
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Launch).Assembly);
			int num = 0;
			int num2 = 0;
			foreach (Type type in typesFromAssembly)
			{
				if (type == null)
				{
					continue;
				}
				try
				{
					_harmony.CreateClassProcessor(type).Patch();
					num++;
				}
				catch (Exception ex)
				{
					if (Attribute.IsDefined(type, typeof(OptionalHarmonyPatchAttribute), inherit: false))
					{
						num2++;
						Exception baseException = ex.GetBaseException();
						ModLog.WarningOnce("optional-patch:" + type.FullName, "Optional Harmony patch disabled because its target is unavailable/incompatible: " + type.FullName + " :: " + baseException.Message);
						continue;
					}
					ModLog.Error("Required Harmony patch failed: " + type.FullName + "\\n" + ex);
					throw new InvalidOperationException("Harmony patch failed: " + type.FullName, ex);
				}
			}
			ModLog.Debug("Harmony", "Patch types processed=" + num + ", optionalDisabled=" + num2 + ".");
		}

		private void OnDestroy()
		{
			SafeShutdown();
		}

		private void SafeShutdown()
		{
			try
			{
				if (_harmony != null)
				{
					_harmony.UnpatchSelf();
				}
			}
			catch (Exception ex)
			{
				ModLog.Warning("Harmony shutdown failed: " + ex.Message);
			}
			_harmony = null;
			LifecycleCoordinator.Reset();
			ShaderReplacment.ClearRegisteredMaterials();
			RuntimeRegistry.Reset();
			PrefabRegistrationService.Reset();
			ContentResolver.Reset();
			TableMapper.Reset();
			ModLog.Reset();
		}
	}
	internal static class BalrondHashCompat
	{
		public static int StableHash(string value)
		{
			if (value == null)
			{
				return 0;
			}
			return StringExtensionMethods.GetStableHashCode(value);
		}
	}
	internal static class BaoCompatibilityService
	{
		internal const string BaoGuid = "balrond.astafaraios.BalrondAbsoluteOverhaul";

		internal static bool IsPresent => Chainloader.PluginInfos != null && Chainloader.PluginInfos.ContainsKey("balrond.astafaraios.BalrondAbsoluteOverhaul");

		internal static string Describe()
		{
			if (!IsPresent)
			{
				return "absent (standalone mode)";
			}
			try
			{
				PluginInfo val = Chainloader.PluginInfos["balrond.astafaraios.BalrondAbsoluteOverhaul"];
				return "present " + ((val != null && val.Metadata != null) ? val.Metadata.Version.ToString() : "unknown-version");
			}
			catch
			{
				return "present";
			}
		}
	}
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	internal sealed class OptionalHarmonyPatchAttribute : Attribute
	{
	}
	internal static class PrefabIdCompatibilityService
	{
		private static readonly Dictionary<string, string> CanonicalIds = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{ "BellMetal_bal", "Brass_bal" },
			{ "BellMetalScrap_bal", "BrassScrap_bal" },
			{ "BellMetalChain_bal", "BrassChain_bal" },
			{ "BlueBasalt_bal", "WhiteMarble_bal" }
		};

		private static readonly Dictionary<string, string> StandaloneFallbacks = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{ "Brass_bal", "Bronze" },
			{ "BrassScrap_bal", "CopperScrap" },
			{ "BrassChain_bal", "Chain" },
			{ "WhiteMarble_bal", "Grausten" },
			{ "DarksteelNails_bal", "IronNails" },
			{ "WoodNails_bal", "Wood" },
			{ "HardWood_bal", "FineWood" },
			{ "Clay_bal", "Stone" },
			{ "ClayBrick_bal", "Stone" },
			{ "Gabro_bal", "Stone" },
			{ "StrawThread_bal", "LinenThread" },
			{ "Straw_bal", "Wood" },
			{ "Moss_bal", "Dandelion" },
			{ "Cheese_bal", "CookedMeat" }
		};

		internal static string GetCanonicalId(string id)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				return id;
			}
			id = id.Trim();
			string value;
			return CanonicalIds.TryGetValue(id, out value) ? value : id;
		}

		internal static string GetStandaloneFallback(string id)
		{
			if (string.IsNullOrWhiteSpace(id))
			{
				return null;
			}
			string canonicalId = GetCanonicalId(id);
			string value;
			return StandaloneFallbacks.TryGetValue(canonicalId, out value) ? value : null;
		}

		internal static IEnumerable<string> EnumerateCandidates(string requestedId, string explicitFallback)
		{
			HashSet<string> emitted = new HashSet<string>(StringComparer.Ordinal);
			string canonical = GetCanonicalId(requestedId);
			if (!string.IsNullOrWhiteSpace(canonical) && emitted.Add(canonical))
			{
				yield return canonical;
			}
			if (!string.IsNullOrWhiteSpace(requestedId))
			{
				string original = requestedId.Trim();
				if (emitted.Add(original))
				{
					yield return original;
				}
			}
			if (!string.IsNullOrWhiteSpace(explicitFallback))
			{
				string explicitId = GetCanonicalId(explicitFallback);
				if (emitted.Add(explicitId))
				{
					yield return explicitId;
				}
				string originalExplicit = explicitFallback.Trim();
				if (emitted.Add(originalExplicit))
				{
					yield return originalExplicit;
				}
			}
			string standalone = GetStandaloneFallback(canonical);
			if (!string.IsNullOrWhiteSpace(standalone) && emitted.Add(standalone))
			{
				yield return standalone;
			}
			if (emitted.Add("Wood"))
			{
				yield return "Wood";
			}
		}
	}
	internal static class ConstructionConfig
	{
		private static ConfigEntry<bool> _fullDiagnostics;

		internal static bool FullDiagnosticsEnabled => _fullDiagnostics != null && _fullDiagnostics.Value;

		internal static void Bind(ConfigFile config)
		{
			if (config != null)
			{
				_fullDiagnostics = config.Bind<bool>("Diagnostics", "Enable detailed diagnostics", false, "Writes detailed BalrondConstructions diagnostic reports and enables debug-level lifecycle logging. Local-only; does not affect gameplay or save data.");
			}
		}
	}
	internal static class CompatibilityDiagnosticService
	{
		internal static void WriteRuntimeSummary()
		{
			if (ConstructionConfig.FullDiagnosticsEnabled)
			{
				List<string> values = new List<string>
				{
					"BAO=" + BaoCompatibilityService.Describe(),
					"LoadedBuildPieces=" + AssetRegistry.BuildPieces.Count,
					"RegisteredBuildPieces=" + AssetRegistry.RegisteredBuildPieces.Count,
					"LoadedOtherPrefabs=" + AssetRegistry.Other.Count,
					"RuntimePrefabIndex=" + RuntimeRegistry.Prefabs.Count,
					"Station.workbench=" + Describe(TableMapper.workbench),
					"Station.heavyWorkbench=" + Describe(TableMapper.heavyWorkbench),
					"Station.forge=" + Describe(TableMapper.forge),
					"Station.blackforge=" + Describe(TableMapper.blackforge),
					"Station.stoneCutter=" + Describe(TableMapper.stoneCutter),
					"Alias.BellMetal_bal=" + PrefabIdCompatibilityService.GetCanonicalId("BellMetal_bal"),
					"Alias.BlueBasalt_bal=" + PrefabIdCompatibilityService.GetCanonicalId("BlueBasalt_bal")
				};
				DiagnosticsWriter.WriteList("CompatibilitySummary.txt", values);
			}
		}

		private static string Describe(CraftingStation station)
		{
			return ((Object)(object)station != (Object)null) ? ((Object)station).name : "<missing>";
		}
	}
	internal static class DiagnosticReportPaths
	{
		internal const string DirectoryName = "BalrondConstructions-diagnostics";

		internal static string DirectoryPath => Path.Combine(Paths.ConfigPath, "BalrondConstructions-diagnostics");

		internal static string GetPath(string fileName)
		{
			return Path.Combine(DirectoryPath, fileName);
		}

		internal static string EnsureDirectory()
		{
			Directory.CreateDirectory(DirectoryPath);
			return DirectoryPath;
		}
	}
	internal static class DiagnosticsWriter
	{
		internal static void WriteList(string fileName, IEnumerable<string> values)
		{
			if (!ConstructionConfig.FullDiagnosticsEnabled)
			{
				return;
			}
			try
			{
				DiagnosticReportPaths.EnsureDirectory();
				List<string> list = new List<string>
				{
					RuntimeBuildIdentity.Describe(),
					"UTC=" + DateTime.UtcNow.ToString("O"),
					string.Empty
				};
				if (values != null)
				{
					list.AddRange(values);
				}
				File.WriteAllLines(DiagnosticReportPaths.GetPath(fileName), list.ToArray(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			}
			catch (Exception ex)
			{
				ModLog.WarningOnce("diag-write:" + fileName, "Could not write diagnostic report '" + fileName + "': " + ex.Message);
			}
		}
	}
	internal static class ModLog
	{
		private static readonly HashSet<string> OnceKeys = new HashSet<string>(StringComparer.Ordinal);

		private static readonly object OnceLock = new object();

		private static ManualLogSource _logger;

		internal static void Initialize(ManualLogSource logger)
		{
			if (logger != null)
			{
				_logger = logger;
			}
		}

		internal static void Reset()
		{
			lock (OnceLock)
			{
				OnceKeys.Clear();
			}
			_logger = null;
		}

		internal static void Info(string message)
		{
			Write((LogLevel)16, message);
		}

		internal static void Warning(string message)
		{
			Write((LogLevel)4, message);
		}

		internal static void Error(string message)
		{
			Write((LogLevel)2, message);
		}

		internal static void Debug(string category, string message)
		{
			if (ConstructionConfig.FullDiagnosticsEnabled)
			{
				Write((LogLevel)32, string.IsNullOrEmpty(category)