Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of balrond constructions v1.5.2
plugins/BalrondConstructions.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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)