Decompiled source of balrond BannerColorizer v1.2.3

plugins/BalrondBannerColorizer.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Text;
using BalrondBannerColorizer.Assets;
using BalrondBannerColorizer.Colorization;
using BalrondBannerColorizer.Compatibility;
using BalrondBannerColorizer.Config;
using BalrondBannerColorizer.Infrastructure;
using BalrondBannerColorizer.Patches;
using BalrondBannerColorizer.Runtime;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using LitJson2;
using Microsoft.CodeAnalysis;
using ServerSync;
using TMPro;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("BalrondBannerColorizer")]
[assembly: AssemblyDescription("Runtime dye and colorization support for Valheim building pieces")]
[assembly: AssemblyCompany("Balrond")]
[assembly: AssemblyProduct("BalrondBannerColorizer")]
[assembly: ComVisible(false)]
[assembly: Guid("f405ea1c-ac25-47a3-9aa2-a8f56c14bfd6")]
[assembly: AssemblyFileVersion("1.2.3.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.2.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BalrondBannerColorizer
{
	public class BalrondTranslator
	{
		public static Dictionary<string, Dictionary<string, string>> translations = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);

		internal static int LanguageCount => translations.Count;

		public static Dictionary<string, string> getLanguage(string language)
		{
			if (string.IsNullOrWhiteSpace(language))
			{
				return null;
			}
			translations.TryGetValue(language.Trim(), out var value);
			return value;
		}

		internal static void Clear()
		{
			translations.Clear();
		}

		internal static void SetLanguage(string language, Dictionary<string, string> entries)
		{
			if (!string.IsNullOrWhiteSpace(language) && entries != null)
			{
				translations[language.Trim()] = entries;
			}
		}
	}
	public class JsonLoader
	{
		public string defaultPath = string.Empty;

		public void loadJson()
		{
			LoadJson();
		}

		public void justDefaultPath()
		{
			defaultPath = GetTranslationDirectory();
		}

		public void createDefaultPath()
		{
			defaultPath = GetTranslationDirectory();
			EnsureDirectory(defaultPath);
		}

		internal void LoadJson()
		{
			defaultPath = GetTranslationDirectory();
			EnsureDirectory(defaultPath);
			BalrondTranslator.Clear();
			LoadTranslations(defaultPath);
		}

		private static string GetTranslationDirectory()
		{
			return Path.Combine(Paths.ConfigPath, "BalrondBannerColorizer-translation");
		}

		private static void EnsureDirectory(string path)
		{
			if (string.IsNullOrEmpty(path) || Directory.Exists(path))
			{
				return;
			}
			try
			{
				Directory.CreateDirectory(path);
			}
			catch (Exception ex)
			{
				ModLog.Error("BalrondBannerColorizer: could not create translation directory '" + path + "': " + ex.Message);
			}
		}

		private static void LoadTranslations(string directory)
		{
			if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory))
			{
				return;
			}
			string[] files;
			try
			{
				files = Directory.GetFiles(directory, "*.json", SearchOption.TopDirectoryOnly);
			}
			catch (Exception ex)
			{
				ModLog.Error("BalrondBannerColorizer: could not enumerate translation files: " + ex.Message);
				return;
			}
			Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
			foreach (string text in files)
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
				if (string.IsNullOrWhiteSpace(fileNameWithoutExtension))
				{
					continue;
				}
				try
				{
					JsonData jsonData = JsonMapper.ToObject(File.ReadAllText(text));
					if (jsonData == null || !jsonData.IsObject)
					{
						ModLog.Warning("BalrondBannerColorizer: translation root must be a JSON object: " + text);
						continue;
					}
					Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
					foreach (string key in jsonData.Keys)
					{
						if (!string.IsNullOrWhiteSpace(key))
						{
							JsonData jsonData2 = jsonData[key];
							dictionary[key] = ((jsonData2 != null) ? jsonData2.ToString() : string.Empty);
						}
					}
					BalrondTranslator.SetLanguage(fileNameWithoutExtension, dictionary);
				}
				catch (Exception ex2)
				{
					ModLog.Error("BalrondBannerColorizer: failed to load translation file '" + text + "': " + ex2.Message);
				}
			}
		}
	}
	internal static class TranslationPatches
	{
		private static readonly MethodInfo AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", new Type[2]
		{
			typeof(string),
			typeof(string)
		}, (Type[])null);

		internal static void Register(Harmony harmony)
		{
			if (harmony != null)
			{
				PatchNamedPostfixes(harmony, typeof(Localization), "SetupLanguage", "LocalizationLifecyclePostfix");
				PatchNamedPostfixes(harmony, typeof(Localization), "LoadCSV", "LocalizationLifecyclePostfix");
				PatchSinglePostfix(harmony, typeof(FejdStartup), "SetupGui", "FejdStartupSetupGuiPostfix");
			}
		}

		internal static void Clear()
		{
		}

		private static void LocalizationLifecyclePostfix(Localization __instance)
		{
			ApplySelectedLanguage(__instance);
		}

		private static void FejdStartupSetupGuiPostfix()
		{
			ApplySelectedLanguage(Localization.instance);
		}

		private static void ApplySelectedLanguage(Localization localization)
		{
			if (localization == null)
			{
				return;
			}
			string language = null;
			try
			{
				language = localization.GetSelectedLanguage();
			}
			catch
			{
			}
			Dictionary<string, string> dictionary = BuildLanguageOverlay(language);
			if (dictionary == null || dictionary.Count == 0)
			{
				return;
			}
			if (AddWordMethod == null)
			{
				ModLog.Error("BalrondBannerColorizer: Localization.AddWord(string,string) was not found.");
				return;
			}
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (!string.IsNullOrWhiteSpace(item.Key))
				{
					try
					{
						AddWordMethod.Invoke(localization, new object[2]
						{
							item.Key,
							item.Value ?? string.Empty
						});
					}
					catch (Exception ex)
					{
						ModLog.Warning("BalrondBannerColorizer: failed to add translation key '" + item.Key + "': " + ex.Message);
					}
				}
			}
		}

		private static Dictionary<string, string> BuildLanguageOverlay(string language)
		{
			Dictionary<string, string> language2 = BalrondTranslator.getLanguage("English");
			Dictionary<string, string> dictionary = ((!string.IsNullOrWhiteSpace(language)) ? BalrondTranslator.getLanguage(language) : null);
			if (language2 == null && dictionary == null)
			{
				return null;
			}
			Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.Ordinal);
			if (language2 != null)
			{
				foreach (KeyValuePair<string, string> item in language2)
				{
					dictionary2[item.Key] = item.Value;
				}
			}
			if (dictionary != null && dictionary != language2)
			{
				foreach (KeyValuePair<string, string> item2 in dictionary)
				{
					dictionary2[item2.Key] = item2.Value;
				}
			}
			return dictionary2;
		}

		private static void PatchNamedPostfixes(Harmony harmony, Type type, string methodName, string patchName)
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(TranslationPatches), patchName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				ModLog.Warning("BalrondBannerColorizer: translation patch method missing: " + patchName);
				return;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			int num = 0;
			foreach (MethodInfo methodInfo2 in methods)
			{
				if (!(methodInfo2 == null) && string.Equals(methodInfo2.Name, methodName, StringComparison.Ordinal))
				{
					try
					{
						HarmonyMethod val = CreateBaoOrderedPostfix(methodInfo);
						harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex)
					{
						ModLog.Warning("BalrondBannerColorizer: optional translation patch disabled for " + type.Name + "." + methodName + ": " + ex.GetBaseException().Message);
					}
				}
			}
			if (num == 0)
			{
				ModLog.Warning("BalrondBannerColorizer: translation lifecycle target not found: " + type.Name + "." + methodName);
			}
		}

		private static void PatchSinglePostfix(Harmony harmony, Type type, string methodName, string patchName)
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(TranslationPatches), patchName, (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				ModLog.Warning("BalrondBannerColorizer: translation lifecycle target not found: " + type.Name + "." + methodName);
				return;
			}
			try
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, CreateBaoOrderedPostfix(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				ModLog.Warning("BalrondBannerColorizer: optional translation patch disabled for " + type.Name + "." + methodName + ": " + ex.GetBaseException().Message);
			}
		}

		private static HarmonyMethod CreateBaoOrderedPostfix(MethodInfo patch)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			HarmonyMethod val = new HarmonyMethod(patch);
			val.after = new string[1] { "balrond.astafaraios.BalrondAbsoluteOverhaul" };
			return val;
		}
	}
	[BepInPlugin("balrond.astafaraios.BalrondBannerColorizer", "BalrondBannerColorizer", "1.2.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Launch : BaseUnityPlugin
	{
		public const string PluginGUID = "balrond.astafaraios.BalrondBannerColorizer";

		public const string PluginName = "BalrondBannerColorizer";

		public const string PluginVersion = "1.2.3";

		private Harmony _harmony;

		private ModResourceLoader _resourceLoader;

		private ModAssetCatalog _assets;

		private JsonLoader _jsonLoader;

		internal static ContentRuntime Runtime { get; private set; }

		private void Awake()
		{
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			ModLog.Initialize(((BaseUnityPlugin)this).Logger);
			ModLog.Info("BalrondBannerColorizer 1.2.3 | MVID=" + typeof(Launch).Assembly.ManifestModule.ModuleVersionId.ToString() + " loading.");
			BaoCompatibilityBridge.LogStartupStatus();
			ServerSyncBridge.Initialize("balrond.astafaraios.BalrondBannerColorizer", "BalrondBannerColorizer", "1.2.3");
			_jsonLoader = new JsonLoader();
			_jsonLoader.loadJson();
			_assets = new ModAssetCatalog();
			_resourceLoader = new ModResourceLoader();
			if (!_resourceLoader.Load(_assets))
			{
				ModLog.Error("BalrondBannerColorizer: content AssetBundle did not load completely. Runtime hooks will remain failure-isolated.");
			}
			ModConfig.Initialize(this, ResolveSerializedDyeStationCraftingStation());
			ColorizerCatalog.Build(_assets);
			AudioRoutingService.Register(_assets.OwnedPrefabs);
			Runtime = new ContentRuntime(_assets);
			ModConfig.Changed += OnSynchronizedConfigurationChanged;
			_harmony = new Harmony("balrond.astafaraios.BalrondBannerColorizer");
			TranslationPatches.Register(_harmony);
			PatchRegistrar.Register(_harmony);
			ModLog.Info("BalrondBannerColorizer loaded.");
		}

		private void OnSynchronizedConfigurationChanged()
		{
			Runtime?.OnConfigurationChanged();
		}

		private string ResolveSerializedDyeStationCraftingStation()
		{
			Piece val = (((Object)(object)_assets?.DyeStation != (Object)null) ? _assets.DyeStation.GetComponent<Piece>() : null);
			CraftingStation val2 = (((Object)(object)val != (Object)null) ? val.m_craftingStation : null);
			if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).gameObject == (Object)null || string.IsNullOrEmpty(((Object)((Component)val2).gameObject).name))
			{
				return "NONE";
			}
			return ((Object)((Component)val2).gameObject).name;
		}

		private void OnDestroy()
		{
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			ModConfig.Changed -= OnSynchronizedConfigurationChanged;
			Runtime?.Clear();
			Runtime = null;
			ModConfig.Reset();
			ServerSyncBridge.Reset();
			AudioRoutingService.Clear();
			ColorizerCatalog.Clear();
			ShaderReplacement.Clear();
			TranslationPatches.Clear();
			BalrondTranslator.Clear();
			BaoCompatibilityBridge.Reset();
			if (_resourceLoader != null && _assets != null)
			{
				_resourceLoader.Unload(_assets);
			}
		}
	}
}
namespace BalrondBannerColorizer.Runtime
{
	internal sealed class ContentRuntime
	{
		private readonly ModAssetCatalog _assets;

		private readonly PrefabLookupCache _prefabs = new PrefabLookupCache();

		private readonly FxReplacement _fxReplacement = new FxReplacement();

		private readonly DyeRecipeService _dyeRecipes;

		private readonly DyeStationService _dyeStation;

		private ObjectDB _currentObjectDb;

		private ZNetScene _currentScene;

		private bool _frontendReady;

		internal ContentRuntime(ModAssetCatalog assets)
		{
			_assets = assets;
			_dyeRecipes = new DyeRecipeService(assets);
			_dyeStation = new DyeStationService(assets);
		}

		internal void OnObjectDbReady(ObjectDB objectDb)
		{
			_currentObjectDb = objectDb;
			RunStage(delegate
			{
				_dyeRecipes.Apply(objectDb);
			}, "dye item and recipe registration");
			RunStage(delegate
			{
				ColorizerInstaller.InstallOnObjectDb(objectDb);
			}, "Hammer colorizer installation");
			RunStage(delegate
			{
				BaoCompatibilityBridge.ValidateObjectDb(objectDb, _assets);
			}, "BAO ObjectDB validation");
		}

		internal void OnZNetScenePrefix(ZNetScene scene)
		{
			if (!((Object)(object)scene == (Object)null) && scene.m_prefabs != null && _assets != null)
			{
				AppendOwnedPrefabs(scene);
			}
		}

		internal void OnZNetScenePostfix(ZNetScene scene)
		{
			if (!((Object)(object)scene == (Object)null) && scene.m_prefabs != null)
			{
				if (_currentScene != scene)
				{
					ShaderReplacement.BeginScene();
				}
				_currentScene = scene;
				_prefabs.Rebuild(scene.m_prefabs);
				RunStage(RegisterTrustedRuntimeShaders, "runtime shader registration");
				RunStage(delegate
				{
					_dyeStation.ConfigureAndRegister(scene, _prefabs);
				}, "Dye Station registration");
				BaoCompatibilityBridge.SharedBannerPieceSnapshot baoBannerState = null;
				RunStage(delegate
				{
					baoBannerState = BaoCompatibilityBridge.CaptureSharedBannerPieceState(_prefabs);
				}, "BAO shared-banner snapshot");
				RunStage(delegate
				{
					ColorizerInstaller.Install(_prefabs);
				}, "colorizer installation");
				RunStage(delegate
				{
					BaoCompatibilityBridge.ValidateSharedBannerPieceState(_prefabs, baoBannerState);
				}, "BAO shared-banner validation");
				RunStage(delegate
				{
					_fxReplacement.RepairOwnedPrefabs(_assets.OwnedPrefabs, scene);
				}, "FX repair");
				RunStage(AudioRoutingService.Refresh, "audio routing after FX");
				RunStage(delegate
				{
					ShaderReplacement.RunMaterialFix(forceRefresh: true);
				}, "shader repair");
				RunStage(delegate
				{
					BaoCompatibilityBridge.ValidateScene(scene, _prefabs, _assets);
				}, "BAO scene validation");
			}
		}

		internal void OnConfigurationChanged()
		{
			if ((Object)(object)_currentObjectDb != (Object)null)
			{
				_dyeRecipes.Apply(_currentObjectDb);
			}
			if ((Object)(object)_currentScene != (Object)null && _currentScene.m_prefabs != null)
			{
				if (_prefabs.Count == 0)
				{
					_prefabs.Rebuild(_currentScene.m_prefabs);
				}
				RunStage(delegate
				{
					_dyeStation.ConfigureAndRegister(_currentScene, _prefabs);
				}, "Dye Station config refresh");
			}
		}

		internal void OnFrontendReady()
		{
			_frontendReady = true;
			if ((Object)(object)_currentScene != (Object)null)
			{
				ShaderReplacement.RunMaterialFix(forceRefresh: true);
			}
		}

		internal void Clear()
		{
			_currentObjectDb = null;
			_currentScene = null;
			_frontendReady = false;
			_dyeRecipes.Clear();
			_fxReplacement.Clear();
		}

		private static void RunStage(Action action, string stage)
		{
			if (action == null)
			{
				return;
			}
			try
			{
				action();
			}
			catch (Exception ex)
			{
				ModLog.Error("BalrondBannerColorizer: " + stage + " failed: " + ex);
			}
		}

		private void RegisterTrustedRuntimeShaders()
		{
			for (int i = 0; i < ColorizerCatalog.TargetPrefabNames.Length; i++)
			{
				ShaderReplacement.RegisterTrustedShaders(_prefabs.Get(ColorizerCatalog.TargetPrefabNames[i]));
			}
			ShaderReplacement.RegisterTrustedShaders(_prefabs.Get("piece_workbench"));
			ShaderReplacement.RegisterTrustedShaders(_prefabs.Get("Hammer"));
		}

		private void AppendOwnedPrefabs(ZNetScene scene)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			HashSet<int> hashSet2 = new HashSet<int>();
			for (int i = 0; i < scene.m_prefabs.Count; i++)
			{
				GameObject val = scene.m_prefabs[i];
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name))
				{
					hashSet.Add(((Object)val).name);
					hashSet2.Add(StringExtensionMethods.GetStableHashCode(((Object)val).name));
				}
			}
			int num = 0;
			for (int j = 0; j < _assets.OwnedPrefabs.Count; j++)
			{
				GameObject val2 = _assets.OwnedPrefabs[j];
				if ((Object)(object)val2 == (Object)null || string.IsNullOrEmpty(((Object)val2).name))
				{
					continue;
				}
				int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)val2).name);
				if (hashSet.Contains(((Object)val2).name))
				{
					GameObject val3 = FindByName(scene.m_prefabs, ((Object)val2).name);
					if (val3 != val2)
					{
						ModLog.Warning("BalrondBannerColorizer: ZNetScene prefab name already exists; existing prefab wins: " + ((Object)val2).name);
					}
				}
				else if (hashSet2.Contains(stableHashCode))
				{
					ModLog.Warning("BalrondBannerColorizer: ZNetScene stable-hash collision; prefab skipped: " + ((Object)val2).name);
				}
				else
				{
					scene.m_prefabs.Add(val2);
					hashSet.Add(((Object)val2).name);
					hashSet2.Add(stableHashCode);
					num++;
				}
			}
			if (num > 0)
			{
				ModLog.Info("BalrondBannerColorizer" + $": registered {num} owned network prefab(s).");
			}
		}

		private static GameObject FindByName(List<GameObject> prefabs, string name)
		{
			for (int i = 0; i < prefabs.Count; i++)
			{
				GameObject val = prefabs[i];
				if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, name, StringComparison.Ordinal))
				{
					return val;
				}
			}
			return null;
		}
	}
	internal sealed class DyeRecipeService
	{
		private readonly ModAssetCatalog _assets;

		private readonly ObjectDbLookupCache _lookup = new ObjectDbLookupCache();

		private readonly Dictionary<string, Recipe> _recipes = new Dictionary<string, Recipe>(StringComparer.Ordinal);

		internal DyeRecipeService(ModAssetCatalog assets)
		{
			_assets = assets;
		}

		internal void Apply(ObjectDB objectDb)
		{
			if (!((Object)(object)objectDb == (Object)null) && objectDb.m_items != null && objectDb.m_recipes != null && _assets != null && !((Object)(object)FindItemByName(objectDb.m_items, "Amber") == (Object)null))
			{
				RegisterItems(objectDb);
				_lookup.Rebuild(objectDb);
				RegisterRecipes(objectDb);
			}
		}

		internal void Clear()
		{
			_recipes.Clear();
		}

		private void RegisterItems(ObjectDB objectDb)
		{
			foreach (GameObject dyeItem in _assets.DyeItems)
			{
				if ((Object)(object)dyeItem == (Object)null || (Object)(object)dyeItem.GetComponent<ItemDrop>() == (Object)null)
				{
					continue;
				}
				int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)dyeItem).name);
				GameObject val = FindItemByName(objectDb.m_items, ((Object)dyeItem).name);
				if ((Object)(object)val == (Object)null)
				{
					GameObject val2 = FindItemByStableHash(objectDb.m_items, stableHashCode, ((Object)dyeItem).name);
					if ((Object)(object)val2 != (Object)null)
					{
						ModLog.Warning("BalrondBannerColorizer: ObjectDB stable-hash collision; item skipped: " + ((Object)dyeItem).name + " conflicts with " + ((Object)val2).name);
						continue;
					}
					if (ObjectDbCompat.TryGetHashedItem(objectDb, stableHashCode, out var prefab) && (Object)(object)prefab != (Object)null && !string.Equals(((Object)prefab).name, ((Object)dyeItem).name, StringComparison.Ordinal))
					{
						ModLog.Warning("BalrondBannerColorizer: ObjectDB hash dictionary collision; item skipped: " + ((Object)dyeItem).name + " conflicts with " + ((Object)prefab).name);
						continue;
					}
					objectDb.m_items.Add(dyeItem);
					val = dyeItem;
				}
				else if (val != dyeItem)
				{
					ModLog.Warning("BalrondBannerColorizer: ObjectDB item name already exists; existing prefab wins: " + ((Object)dyeItem).name);
				}
				ObjectDbCompat.SetHashedItem(objectDb, stableHashCode, val);
			}
		}

		private void RegisterRecipes(ObjectDB objectDb)
		{
			GameObject dyeStation = _assets.DyeStation;
			CraftingStation val = (((Object)(object)dyeStation != (Object)null) ? dyeStation.GetComponent<CraftingStation>() : null);
			if ((Object)(object)val == (Object)null)
			{
				ModLog.Warning("BalrondBannerColorizer: CraftingStation missing on piece_dyestation; dye recipes were not registered.");
				return;
			}
			for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++)
			{
				string text = ModAssetCatalog.DyePrefabNames[i];
				GameObject val2 = _lookup.Get(text);
				GameObject item = _assets.GetItem(text);
				if ((Object)(object)val2 != (Object)null && (Object)(object)item != (Object)null && val2 != item)
				{
					ModLog.Warning("BalrondBannerColorizer: dye recipe skipped because another ObjectDB item owns prefab ID " + text);
					continue;
				}
				ItemDrop val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val3 == (Object)null)
				{
					ModLog.Warning("BalrondBannerColorizer: dye output missing from ObjectDB: " + text);
					continue;
				}
				string text2 = "Recipe_" + text;
				Recipe orCreateRecipe = GetOrCreateRecipe(text2);
				Requirement[] array = BuildRequirements(text);
				if (array != null)
				{
					((Object)orCreateRecipe).name = text2;
					orCreateRecipe.m_enabled = true;
					orCreateRecipe.m_amount = 1;
					orCreateRecipe.m_minStationLevel = 1;
					orCreateRecipe.m_craftingStation = val;
					orCreateRecipe.m_repairStation = val;
					orCreateRecipe.m_item = val3;
					orCreateRecipe.m_resources = array;
					if (!ContainsRecipe(objectDb.m_recipes, text2))
					{
						objectDb.m_recipes.Add(orCreateRecipe);
					}
				}
			}
		}

		private Requirement[] BuildRequirements(string dyeName)
		{
			string dyeRecipe = ModConfig.GetDyeRecipe(dyeName);
			if (RecipeCostParser.TryBuildRequirements(dyeRecipe, _lookup.GetItemDrop, out var requirements, out var error))
			{
				return requirements;
			}
			string defaultDyeRecipe = ModConfig.GetDefaultDyeRecipe(dyeName);
			ModLog.Warning("BalrondBannerColorizer: invalid synchronized recipe for " + dyeName + " ('" + dyeRecipe + "'): " + error + ". Falling back to '" + defaultDyeRecipe + "'.");
			if (RecipeCostParser.TryBuildRequirements(defaultDyeRecipe, _lookup.GetItemDrop, out requirements, out var error2))
			{
				return requirements;
			}
			ModLog.Error("BalrondBannerColorizer: built-in fallback recipe for " + dyeName + " could not be resolved: " + error2);
			return null;
		}

		private Recipe GetOrCreateRecipe(string recipeName)
		{
			if (_recipes.TryGetValue(recipeName, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			value = ScriptableObject.CreateInstance<Recipe>();
			((Object)value).name = recipeName;
			_recipes[recipeName] = value;
			return value;
		}

		private static bool ContainsRecipe(List<Recipe> recipes, string recipeName)
		{
			for (int i = 0; i < recipes.Count; i++)
			{
				Recipe val = recipes[i];
				if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, recipeName, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static GameObject FindItemByStableHash(List<GameObject> items, int hash, string sameName)
		{
			if (items == null)
			{
				return null;
			}
			for (int i = 0; i < items.Count; i++)
			{
				GameObject val = items[i];
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !string.Equals(((Object)val).name, sameName, StringComparison.Ordinal) && StringExtensionMethods.GetStableHashCode(((Object)val).name) == hash)
				{
					return val;
				}
			}
			return null;
		}

		private static GameObject FindItemByName(List<GameObject> items, string name)
		{
			if (items == null || string.IsNullOrEmpty(name))
			{
				return null;
			}
			for (int i = 0; i < items.Count; i++)
			{
				GameObject val = items[i];
				if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, name, StringComparison.Ordinal))
				{
					return val;
				}
			}
			return null;
		}
	}
	internal sealed class DyeStationService
	{
		private readonly ModAssetCatalog _assets;

		private bool _warnedPieceCollision;

		internal DyeStationService(ModAssetCatalog assets)
		{
			_assets = assets;
		}

		internal void ConfigureAndRegister(ZNetScene scene, PrefabLookupCache prefabs)
		{
			GameObject val = _assets?.DyeStation;
			if (!((Object)(object)val == (Object)null) && !((Object)(object)scene == (Object)null) && prefabs != null)
			{
				ConfigurePiece(val, prefabs);
				ConfigureRavenGuide(val, prefabs);
				AddToHammer(val, prefabs);
			}
		}

		private static void ConfigurePiece(GameObject station, PrefabLookupCache prefabs)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			Piece component = station.GetComponent<Piece>();
			if ((Object)(object)component == (Object)null)
			{
				ModLog.Error("BalrondBannerColorizer: Piece missing on piece_dyestation.");
				return;
			}
			component.m_enabled = true;
			component.m_category = (PieceCategory)1;
			component.m_usage = (UsageTagFlags)2;
			ApplyResourceCost(component, prefabs);
			ApplyCraftingStation(component, prefabs);
		}

		private static void ApplyResourceCost(Piece piece, PrefabLookupCache prefabs)
		{
			string text = ((ModConfig.DyeStationResourceCost != null) ? ModConfig.DyeStationResourceCost.Value : "Wood:5,Resin:5");
			if (TryBuildResourceCost(text, prefabs, out var requirements, out var error))
			{
				piece.m_resources = requirements;
				return;
			}
			ModLog.Warning("BalrondBannerColorizer: invalid Dye Station resource cost ('" + text + "'): " + error + ". Falling back to 'Wood:5,Resin:5'.");
			if (TryBuildResourceCost("Wood:5,Resin:5", prefabs, out requirements, out var error2))
			{
				piece.m_resources = requirements;
			}
			else
			{
				ModLog.Error("BalrondBannerColorizer: built-in Dye Station resource cost could not be resolved: " + error2);
			}
		}

		private static bool TryBuildResourceCost(string specification, PrefabLookupCache prefabs, out Requirement[] requirements, out string error)
		{
			return RecipeCostParser.TryBuildRequirements(specification, delegate(string prefabName)
			{
				GameObject val = prefabs.Get(prefabName);
				return ((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null;
			}, out requirements, out error);
		}

		private static void ApplyCraftingStation(Piece piece, PrefabLookupCache prefabs)
		{
			string text = ((ModConfig.DyeStationCraftingStation != null) ? ModConfig.DyeStationCraftingStation.Value : ModConfig.DefaultDyeStationCraftingStation);
			if (TryResolveCraftingStation(text, prefabs, out var station, out var error))
			{
				piece.m_craftingStation = station;
				return;
			}
			string defaultDyeStationCraftingStation = ModConfig.DefaultDyeStationCraftingStation;
			ModLog.Warning("BalrondBannerColorizer: invalid Dye Station crafting station ('" + text + "'): " + error + ". Falling back to '" + defaultDyeStationCraftingStation + "'.");
			if (TryResolveCraftingStation(defaultDyeStationCraftingStation, prefabs, out station, out var error2))
			{
				piece.m_craftingStation = station;
				return;
			}
			piece.m_craftingStation = null;
			ModLog.Error("BalrondBannerColorizer: built-in Dye Station crafting-station fallback could not be resolved: " + error2);
		}

		private static bool TryResolveCraftingStation(string prefabName, PrefabLookupCache prefabs, out CraftingStation station, out string error)
		{
			station = null;
			error = null;
			string text = (prefabName ?? string.Empty).Trim();
			if (text.Length == 0 || string.Equals(text, "NONE", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			GameObject val = prefabs.Get(text);
			if ((Object)(object)val == (Object)null)
			{
				error = "prefab not found: " + text;
				return false;
			}
			station = val.GetComponent<CraftingStation>();
			if ((Object)(object)station == (Object)null)
			{
				error = "prefab has no CraftingStation component: " + text;
				return false;
			}
			return true;
		}

		private void AddToHammer(GameObject station, PrefabLookupCache prefabs)
		{
			GameObject val = prefabs.Get("Hammer");
			PieceTable val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared?.m_buildPieces;
			if ((Object)(object)val2 == (Object)null)
			{
				ModLog.Warning("BalrondBannerColorizer: Hammer PieceTable was not available.");
				return;
			}
			if (val2.m_pieces == null)
			{
				val2.m_pieces = new List<GameObject>();
			}
			for (int i = 0; i < val2.m_pieces.Count; i++)
			{
				GameObject val3 = val2.m_pieces[i];
				if ((Object)(object)val3 != (Object)null && string.Equals(((Object)val3).name, ((Object)station).name, StringComparison.Ordinal))
				{
					if (val3 != station && !_warnedPieceCollision)
					{
						_warnedPieceCollision = true;
						ModLog.Warning("BalrondBannerColorizer: Hammer already contains a different prefab using the Dye Station ID; preserving its owner.");
					}
					return;
				}
			}
			val2.m_pieces.Add(station);
			InvalidateBuildPieceCache();
		}

		private static void InvalidateBuildPieceCache()
		{
			ObjectDbCompat.InvalidateBuildPieceCache(ObjectDB.instance);
		}

		private static void ConfigureRavenGuide(GameObject station, PrefabLookupCache prefabs)
		{
			Transform val = station.transform.Find("GuidePoint");
			GameObject val2 = prefabs.Get("piece_workbench");
			Transform val3 = (((Object)(object)val2 != (Object)null) ? val2.transform.Find("GuidePoint") : null);
			GuidePoint val4 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<GuidePoint>() : null);
			GuidePoint val5 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).GetComponent<GuidePoint>() : null);
			if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val5 == (Object)null) && !((Object)(object)val5.m_ravenPrefab == (Object)null))
			{
				val4.m_ravenPrefab = val5.m_ravenPrefab;
			}
		}
	}
	internal static class ObjectDbCompat
	{
		private static readonly FieldInfo ItemByHashField = typeof(ObjectDB).GetField("m_itemByHash", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly FieldInfo BuildPiecesField = typeof(ObjectDB).GetField("m_buildPieces", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		private static bool _warnedItemHashUnavailable;

		private static bool _warnedBuildPiecesUnavailable;

		internal static bool TryGetHashedItem(ObjectDB objectDb, int hash, out GameObject prefab)
		{
			prefab = null;
			Dictionary<int, GameObject> itemHashDictionary = GetItemHashDictionary(objectDb);
			return itemHashDictionary != null && itemHashDictionary.TryGetValue(hash, out prefab) && (Object)(object)prefab != (Object)null;
		}

		internal static bool SetHashedItem(ObjectDB objectDb, int hash, GameObject prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return false;
			}
			Dictionary<int, GameObject> itemHashDictionary = GetItemHashDictionary(objectDb);
			if (itemHashDictionary == null)
			{
				return false;
			}
			itemHashDictionary[hash] = prefab;
			return true;
		}

		internal static bool InvalidateBuildPieceCache(ObjectDB objectDb)
		{
			if ((Object)(object)objectDb == (Object)null)
			{
				return false;
			}
			if (BuildPiecesField == null)
			{
				WarnBuildPiecesUnavailableOnce("field metadata was not found");
				return false;
			}
			try
			{
				BuildPiecesField.SetValue(objectDb, null);
				return true;
			}
			catch (Exception ex)
			{
				WarnBuildPiecesUnavailableOnce(ex.Message);
				return false;
			}
		}

		private static Dictionary<int, GameObject> GetItemHashDictionary(ObjectDB objectDb)
		{
			if ((Object)(object)objectDb == (Object)null || ItemByHashField == null)
			{
				WarnItemHashUnavailableOnce();
				return null;
			}
			try
			{
				Dictionary<int, GameObject> dictionary = ItemByHashField.GetValue(objectDb) as Dictionary<int, GameObject>;
				if (dictionary == null)
				{
					WarnItemHashUnavailableOnce();
				}
				return dictionary;
			}
			catch (Exception ex)
			{
				if (!_warnedItemHashUnavailable)
				{
					_warnedItemHashUnavailable = true;
					ModLog.Warning("BalrondBannerColorizer: ObjectDB item hash cache could not be accessed: " + ex.Message);
				}
				return null;
			}
		}

		private static void WarnItemHashUnavailableOnce()
		{
			if (!_warnedItemHashUnavailable)
			{
				_warnedItemHashUnavailable = true;
				ModLog.Warning("BalrondBannerColorizer: ObjectDB.m_itemByHash is unavailable; item hash cache could not be updated.");
			}
		}

		private static void WarnBuildPiecesUnavailableOnce(string reason)
		{
			if (!_warnedBuildPiecesUnavailable)
			{
				_warnedBuildPiecesUnavailable = true;
				ModLog.Warning("BalrondBannerColorizer: ObjectDB.m_buildPieces cache could not be invalidated" + (string.IsNullOrEmpty(reason) ? "." : (": " + reason)));
			}
		}
	}
	internal sealed class ObjectDbLookupCache
	{
		private readonly Dictionary<string, GameObject> _items = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		internal void Rebuild(ObjectDB objectDb)
		{
			_items.Clear();
			if ((Object)(object)objectDb == (Object)null || objectDb.m_items == null)
			{
				return;
			}
			for (int i = 0; i < objectDb.m_items.Count; i++)
			{
				GameObject val = objectDb.m_items[i];
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !_items.ContainsKey(((Object)val).name))
				{
					_items.Add(((Object)val).name, val);
				}
			}
		}

		internal GameObject Get(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			_items.TryGetValue(prefabName, out var value);
			return value;
		}

		internal ItemDrop GetItemDrop(string prefabName)
		{
			GameObject val = Get(prefabName);
			return ((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null;
		}
	}
	internal sealed class PrefabLookupCache
	{
		private readonly Dictionary<string, GameObject> _byName = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		internal int Count => _byName.Count;

		internal void Rebuild(IList<GameObject> prefabs)
		{
			_byName.Clear();
			if (prefabs == null)
			{
				return;
			}
			for (int i = 0; i < prefabs.Count; i++)
			{
				GameObject val = prefabs[i];
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !_byName.ContainsKey(((Object)val).name))
				{
					_byName.Add(((Object)val).name, val);
				}
			}
		}

		internal bool TryGet(string prefabName, out GameObject prefab)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				prefab = null;
				return false;
			}
			return _byName.TryGetValue(prefabName, out prefab) && (Object)(object)prefab != (Object)null;
		}

		internal GameObject Get(string prefabName)
		{
			TryGet(prefabName, out var prefab);
			return prefab;
		}
	}
}
namespace BalrondBannerColorizer.Patches
{
	internal static class PatchRegistrar
	{
		internal static void Register(Harmony harmony)
		{
			if (harmony != null)
			{
				string[] after = new string[1] { "balrond.astafaraios.BalrondAbsoluteOverhaul" };
				Patch(harmony, typeof(ObjectDB), "Awake", null, null, "ObjectDbAwakePostfix", after);
				Patch(harmony, typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, null, "ObjectDbCopyPostfix", after);
				Patch(harmony, typeof(ZNetScene), "Awake", null, "ZNetSceneAwakePrefix", "ZNetSceneAwakePostfix", after);
				Patch(harmony, typeof(AudioMan), "Awake", null, null, "AudioManAwakePostfix", after);
				Patch(harmony, typeof(Piece), "Awake", null, null, "PieceAwakePostfix", after);
				Patch(harmony, typeof(FejdStartup), "SetupGui", null, null, "FejdStartupSetupGuiPostfix", after);
			}
		}

		private static void Patch(Harmony harmony, Type type, string methodName, Type[] argumentTypes, string prefix = null, string postfix = null, string[] after = null)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo methodInfo = ((argumentTypes == null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : AccessTools.Method(type, methodName, argumentTypes, (Type[])null));
			if (methodInfo == null)
			{
				ModLog.Warning("BalrondBannerColorizer: Harmony target not found: " + type.Name + "." + methodName);
				return;
			}
			try
			{
				HarmonyMethod val = ((prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(PatchRegistrar), prefix, (Type[])null));
				HarmonyMethod val2 = ((postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(PatchRegistrar), postfix, (Type[])null));
				if (val != null && after != null)
				{
					val.after = after;
				}
				if (val2 != null && after != null)
				{
					val2.after = after;
				}
				harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				ModLog.Error("BalrondBannerColorizer: failed to patch " + type.Name + "." + methodName + ": " + ex.Message);
			}
		}

		private static void ObjectDbAwakePostfix(ObjectDB __instance)
		{
			Safe(delegate
			{
				Launch.Runtime?.OnObjectDbReady(__instance);
			}, "ObjectDB.Awake postfix");
		}

		private static void ObjectDbCopyPostfix(ObjectDB __instance)
		{
			Safe(delegate
			{
				Launch.Runtime?.OnObjectDbReady(__instance);
			}, "ObjectDB.CopyOtherDB postfix");
		}

		private static void ZNetSceneAwakePrefix(ZNetScene __instance)
		{
			Safe(delegate
			{
				Launch.Runtime?.OnZNetScenePrefix(__instance);
			}, "ZNetScene.Awake prefix");
		}

		private static void ZNetSceneAwakePostfix(ZNetScene __instance)
		{
			Safe(delegate
			{
				Launch.Runtime?.OnZNetScenePostfix(__instance);
			}, "ZNetScene.Awake postfix");
		}

		private static void PieceAwakePostfix(Piece __instance)
		{
			try
			{
				ColorizerInstaller.InstallOnRuntimePiece(__instance);
			}
			catch (Exception ex)
			{
				ModLog.Error("BalrondBannerColorizer: Piece.Awake colorizer install failed: " + ex);
			}
		}

		private static void AudioManAwakePostfix(AudioMan __instance)
		{
			Safe(delegate
			{
				AudioRoutingService.OnAudioManAwake(__instance);
			}, "AudioMan.Awake postfix");
		}

		private static void FejdStartupSetupGuiPostfix()
		{
			Safe(delegate
			{
				Launch.Runtime?.OnFrontendReady();
			}, "FejdStartup.SetupGui shader retry");
		}

		private static void Safe(Action action, string context)
		{
			try
			{
				action?.Invoke();
			}
			catch (Exception ex)
			{
				ModLog.Error("BalrondBannerColorizer: " + context + " failed: " + ex);
			}
		}
	}
}
namespace BalrondBannerColorizer.Infrastructure
{
	internal static class ModLog
	{
		private static ManualLogSource _logger;

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

		internal static void Info(string message)
		{
			if (_logger != null)
			{
				_logger.LogInfo((object)message);
			}
		}

		internal static void Warning(string message)
		{
			if (_logger != null)
			{
				_logger.LogWarning((object)message);
			}
		}

		internal static void Error(string message)
		{
			if (_logger != null)
			{
				_logger.LogError((object)message);
			}
		}
	}
}
namespace BalrondBannerColorizer.Compatibility
{
	public static class BaoCompatibilityBridge
	{
		internal sealed class SharedBannerPieceSnapshot
		{
			internal readonly Dictionary<string, PieceState> States = new Dictionary<string, PieceState>(StringComparer.Ordinal);
		}

		internal sealed class PieceState
		{
			private readonly PieceCategory _category;

			private readonly UsageTagFlags _usage;

			private readonly CraftingStation _craftingStation;

			private readonly string _requirements;

			private PieceState(PieceCategory category, UsageTagFlags usage, CraftingStation craftingStation, string requirements)
			{
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				_category = category;
				_usage = usage;
				_craftingStation = craftingStation;
				_requirements = requirements ?? string.Empty;
			}

			internal static PieceState Capture(Piece piece)
			{
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				return new PieceState(piece.m_category, piece.m_usage, piece.m_craftingStation, BuildRequirementSignature(piece.m_resources));
			}

			public override bool Equals(object obj)
			{
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: 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_0020: Unknown result type (might be due to invalid IL or missing references)
				return obj is PieceState pieceState && _category == pieceState._category && _usage == pieceState._usage && _craftingStation == pieceState._craftingStation && string.Equals(_requirements, pieceState._requirements, StringComparison.Ordinal);
			}

			public override int GetHashCode()
			{
				//IL_0003: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Expected I4, but got Unknown
				//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_0018: Expected I4, but got Unknown
				int num = (int)_category;
				num = (num * 397) ^ _usage;
				num = (num * 397) ^ (((Object)(object)_craftingStation != (Object)null) ? ((Object)_craftingStation).GetInstanceID() : 0);
				return (num * 397) ^ _requirements.GetHashCode();
			}

			private static string BuildRequirementSignature(Requirement[] requirements)
			{
				if (requirements == null)
				{
					return "<null>";
				}
				StringBuilder stringBuilder = new StringBuilder(requirements.Length * 24);
				for (int i = 0; i < requirements.Length; i++)
				{
					Requirement val = requirements[i];
					if (i > 0)
					{
						stringBuilder.Append('|');
					}
					if (val == null)
					{
						stringBuilder.Append("<null>");
						continue;
					}
					ItemDrop resItem = val.m_resItem;
					stringBuilder.Append(((Object)(object)resItem != (Object)null && (Object)(object)((Component)resItem).gameObject != (Object)null) ? ((Object)((Component)resItem).gameObject).name : "<missing>");
					stringBuilder.Append(':').Append(val.m_amount);
					stringBuilder.Append(':').Append(val.m_amountPerLevel);
					stringBuilder.Append(':').Append(val.m_recover ? '1' : '0');
				}
				return stringBuilder.ToString();
			}
		}

		public const string BaoGuid = "balrond.astafaraios.BalrondAbsoluteOverhaul";

		public const string MinimumSupportedBaoVersion = "2.0.0";

		private static readonly string[] BaoSharedBannerPrefabs = new string[11]
		{
			"piece_banner01", "piece_banner02", "piece_banner03", "piece_banner04", "piece_banner05", "piece_banner06", "piece_banner07", "piece_banner08", "piece_banner09", "piece_banner10",
			"piece_banner11"
		};

		private static bool _startupStatusLogged;

		private static bool _objectDbAuditLogged;

		private static bool _sceneAuditLogged;

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

		public static string LoadedBaoVersion
		{
			get
			{
				if (!IsBaoLoaded)
				{
					return string.Empty;
				}
				try
				{
					PluginInfo val = Chainloader.PluginInfos["balrond.astafaraios.BalrondAbsoluteOverhaul"];
					return (val != null && val.Metadata != null && val.Metadata.Version != null) ? val.Metadata.Version.ToString() : string.Empty;
				}
				catch
				{
					return string.Empty;
				}
			}
		}

		public static bool IsSupportedBaoVersion
		{
			get
			{
				if (!IsBaoLoaded)
				{
					return true;
				}
				if (!Version.TryParse(LoadedBaoVersion, out Version result) || !Version.TryParse("2.0.0", out Version result2))
				{
					return false;
				}
				return result.CompareTo(result2) >= 0;
			}
		}

		public static string IntegrationMode
		{
			get
			{
				if (!IsBaoLoaded)
				{
					return "Standalone";
				}
				return IsSupportedBaoVersion ? "BAO integrated" : "BAO compatibility warning";
			}
		}

		public static bool OwnsPrefabId(string prefabId)
		{
			if (string.IsNullOrEmpty(prefabId))
			{
				return false;
			}
			if (string.Equals(prefabId, "piece_dyestation", StringComparison.Ordinal))
			{
				return true;
			}
			for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++)
			{
				if (string.Equals(prefabId, ModAssetCatalog.DyePrefabNames[i], StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		public static bool SupportsColorizationTarget(string prefabId)
		{
			if (string.IsNullOrEmpty(prefabId))
			{
				return false;
			}
			for (int i = 0; i < ColorizerCatalog.TargetPrefabNames.Length; i++)
			{
				if (string.Equals(prefabId, ColorizerCatalog.TargetPrefabNames[i], StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		internal static void LogStartupStatus()
		{
			if (!_startupStatusLogged)
			{
				_startupStatusLogged = true;
				if (!IsBaoLoaded)
				{
					ModLog.Info("BalrondBannerColorizer: BAO compatibility: standalone mode.");
				}
				else if (!IsSupportedBaoVersion)
				{
					ModLog.Warning("BalrondBannerColorizer: BAO compatibility: detected BAO " + (string.IsNullOrEmpty(LoadedBaoVersion) ? "unknown version" : LoadedBaoVersion) + "; tested baseline is >= 2.0.0. Standalone ownership remains active and no BAO internals will be called.");
				}
				else
				{
					ModLog.Info("BalrondBannerColorizer: BAO compatibility: integrated mode with BAO " + LoadedBaoVersion + ".");
				}
			}
		}

		internal static SharedBannerPieceSnapshot CaptureSharedBannerPieceState(PrefabLookupCache prefabs)
		{
			if (!IsBaoLoaded || prefabs == null)
			{
				return null;
			}
			SharedBannerPieceSnapshot sharedBannerPieceSnapshot = new SharedBannerPieceSnapshot();
			for (int i = 0; i < BaoSharedBannerPrefabs.Length; i++)
			{
				string text = BaoSharedBannerPrefabs[i];
				GameObject val = prefabs.Get(text);
				Piece val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Piece>() : null);
				if (!((Object)(object)val2 == (Object)null))
				{
					sharedBannerPieceSnapshot.States[text] = PieceState.Capture(val2);
				}
			}
			return sharedBannerPieceSnapshot;
		}

		internal static void ValidateSharedBannerPieceState(PrefabLookupCache prefabs, SharedBannerPieceSnapshot before)
		{
			if (!IsBaoLoaded || prefabs == null || before == null)
			{
				return;
			}
			foreach (KeyValuePair<string, PieceState> state in before.States)
			{
				GameObject val = prefabs.Get(state.Key);
				Piece val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Piece>() : null);
				if ((Object)(object)val2 == (Object)null)
				{
					ModLog.Warning("BalrondBannerColorizer: BAO compatibility: shared banner disappeared during integration: " + state.Key);
					continue;
				}
				PieceState obj = PieceState.Capture(val2);
				if (!state.Value.Equals(obj))
				{
					ModLog.Warning("BalrondBannerColorizer: BAO compatibility: shared Piece metadata changed while installing colorization on " + state.Key + ". BannerColorizer must not own BAO build costs/station metadata.");
				}
			}
		}

		internal static void ValidateObjectDb(ObjectDB db, ModAssetCatalog assets)
		{
			if (!IsBaoLoaded || _objectDbAuditLogged || (Object)(object)db == (Object)null || assets == null)
			{
				return;
			}
			int num = 0;
			for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++)
			{
				string text = ModAssetCatalog.DyePrefabNames[i];
				GameObject item = assets.GetItem(text);
				GameObject val = FindItem(db, text);
				if (!((Object)(object)val == (Object)null) && val != item)
				{
					num++;
				}
			}
			if (num > 0)
			{
				ModLog.Warning("BalrondBannerColorizer: BAO compatibility: " + num + " dye prefab ID collision(s) detected in ObjectDB; existing registrations remain authoritative.");
			}
			_objectDbAuditLogged = true;
		}

		internal static void ValidateScene(ZNetScene scene, PrefabLookupCache prefabs, ModAssetCatalog assets)
		{
			if (!IsBaoLoaded || _sceneAuditLogged || (Object)(object)scene == (Object)null || prefabs == null || assets == null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < BaoSharedBannerPrefabs.Length; i++)
			{
				GameObject val = prefabs.Get(BaoSharedBannerPrefabs[i]);
				if (!((Object)(object)val == (Object)null))
				{
					num++;
					if ((Object)(object)val.GetComponent<BannerColorizer>() != (Object)null)
					{
						num2++;
					}
				}
			}
			int num3 = 0;
			int num4 = 0;
			for (int j = 0; j < assets.OwnedPrefabs.Count; j++)
			{
				GameObject val2 = assets.OwnedPrefabs[j];
				if (!((Object)(object)val2 == (Object)null) && !string.IsNullOrEmpty(((Object)val2).name))
				{
					GameObject val3 = prefabs.Get(((Object)val2).name);
					if (val3 == val2)
					{
						num3++;
					}
					else if ((Object)(object)val3 != (Object)null)
					{
						num4++;
					}
				}
			}
			if (num2 != num)
			{
				ModLog.Warning("BalrondBannerColorizer: BAO compatibility: colorizer installed on " + num2 + "/" + num + " shared BAO/vanilla banner prefabs.");
			}
			if (num4 > 0)
			{
				ModLog.Warning("BalrondBannerColorizer: BAO compatibility: " + num4 + " BBC-owned network prefab ID collision(s); first registered prefab remains authoritative.");
			}
			if (num2 == num && num4 == 0)
			{
				ModLog.Info("BalrondBannerColorizer: BAO compatibility check passed (shared banners=" + num2 + ", BBC-owned prefabs=" + num3 + ").");
			}
			_sceneAuditLogged = true;
		}

		internal static void Reset()
		{
			_startupStatusLogged = false;
			_objectDbAuditLogged = false;
			_sceneAuditLogged = false;
		}

		private static GameObject FindItem(ObjectDB db, string prefabId)
		{
			if ((Object)(object)db == (Object)null || db.m_items == null || string.IsNullOrEmpty(prefabId))
			{
				return null;
			}
			for (int i = 0; i < db.m_items.Count; i++)
			{
				GameObject val = db.m_items[i];
				if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, prefabId, StringComparison.Ordinal))
				{
					return val;
				}
			}
			return null;
		}
	}
}
namespace BalrondBannerColorizer.Colorization
{
	[Serializable]
	public sealed class BannerColorizer : MonoBehaviour, Hoverable, Interactable
	{
		private const string RpcPaint = "RPC_Paint";

		private const string ZdoColorKey = "colorizerSet";

		private const string ZdoActiveKey = "colorizerIsActive";

		private readonly List<MeshRenderer> _bodyRenderers = new List<MeshRenderer>(8);

		private readonly List<MeshRenderer> _destructionRenderers = new List<MeshRenderer>(8);

		private ZNetView _znetView;

		private Piece _piece;

		private MeshRenderer _mainRenderer;

		private string _prefabName;

		private string _skinName;

		private string _defaultSkinName;

		private bool _renderersReady;

		private bool _paintRpcRegistered;

		private bool _rpcRegistrationWarningLogged;

		private void Awake()
		{
			_prefabName = NormalizePrefabName(((Object)((Component)this).gameObject).name);
			if (ColorizerCatalog.IsTarget(_prefabName))
			{
				_piece = ((Component)this).GetComponent<Piece>();
				_defaultSkinName = ColorizerCatalog.GetDefaultSkin(_prefabName);
				if (!SetupRenderers())
				{
					ModLog.Warning("BalrondBannerColorizer: no supported renderer layout found for " + _prefabName);
					return;
				}
				_renderersReady = true;
				_znetView = ((Component)this).GetComponent<ZNetView>();
				TryRegisterPaintRpc();
				RefreshInitialSkin();
			}
		}

		private void Start()
		{
			if (_renderersReady)
			{
				TryRegisterPaintRpc();
				RefreshInitialSkin();
			}
		}

		private void TryRegisterPaintRpc()
		{
			if (_paintRpcRegistered)
			{
				return;
			}
			if ((Object)(object)_znetView == (Object)null)
			{
				_znetView = ((Component)this).GetComponent<ZNetView>();
			}
			if ((Object)(object)_znetView == (Object)null || !_znetView.IsValid() || _znetView.GetZDO() == null)
			{
				return;
			}
			try
			{
				_znetView.Register<string>("RPC_Paint", (Action<long, string>)RPC_Paint);
				_paintRpcRegistered = true;
				RefreshInitialSkin();
			}
			catch (Exception ex)
			{
				if (!_rpcRegistrationWarningLogged)
				{
					_rpcRegistrationWarningLogged = true;
					ModLog.Warning("BalrondBannerColorizer: paint RPC registration failed for " + _prefabName + ": " + ex.Message);
				}
			}
		}

		private void RefreshInitialSkin()
		{
			ZDO val = (((Object)(object)_znetView != (Object)null && _znetView.IsValid()) ? _znetView.GetZDO() : null);
			string text = ((val != null && val.GetBool("colorizerIsActive", false)) ? val.GetString("colorizerSet", "") : _defaultSkinName);
			if (string.IsNullOrWhiteSpace(text))
			{
				text = _defaultSkinName;
			}
			if (!string.Equals(_skinName, text, StringComparison.Ordinal))
			{
				ApplySkin(text);
			}
		}

		public string GetHoverName()
		{
			return ((Object)(object)_piece != (Object)null) ? _piece.m_name : ((Object)((Component)this).gameObject).name;
		}

		public string GetHoverText()
		{
			if (!_paintRpcRegistered)
			{
				TryRegisterPaintRpc();
			}
			string text = Localize(GetHoverName());
			string text2 = Localize("$bbc_add_dye");
			string text3 = Localize("$bbc_current_dye");
			string text4 = Localize("$bbc_default_dye");
			string text5 = LocalizeColor(_skinName);
			string text6 = LocalizeColor(_defaultSkinName);
			return text + "\n[<color=yellow><b>1-8</b></color>] " + text2 + "\n[" + text3 + "] " + text5 + "\n[<color=yellow>" + text4 + "</color>] " + text6;
		}

		public float GetHoverOffset()
		{
			return 0f;
		}

		public bool Interact(Humanoid character, bool repeat, bool alt)
		{
			return false;
		}

		public bool UseItem(Humanoid user, ItemData item)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)user == (Object)null || item == null)
			{
				return false;
			}
			TryRegisterPaintRpc();
			if ((Object)(object)_znetView != (Object)null && _znetView.IsValid() && !_paintRpcRegistered)
			{
				return false;
			}
			if (!PrivateArea.CheckAccess(((Component)this).transform.position, 0f, true, false))
			{
				return true;
			}
			string dyePrefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? NormalizePrefabName(((Object)item.m_dropPrefab).name) : string.Empty);
			if (!ColorizerCatalog.TryGetByDye(_prefabName, dyePrefabName, out var pack))
			{
				((Character)user).Message((MessageType)2, "$msg_wrongitem", 0, (Sprite)null, false);
				return false;
			}
			if (string.Equals(_skinName, pack.Skin, StringComparison.Ordinal))
			{
				((Character)user).Message((MessageType)2, "$msg_wrongitem", 0, (Sprite)null, false);
				return false;
			}
			((Character)user).Message((MessageType)2, "$msg_added " + item.m_shared.m_name, 0, (Sprite)null, false);
			user.GetInventory().RemoveItem(item.m_shared.m_name, 1, -1, true);
			BroadcastPaint(pack.Skin);
			return true;
		}

		private void BroadcastPaint(string skin)
		{
			if ((Object)(object)_znetView == (Object)null || !_znetView.IsValid())
			{
				ApplySkin(skin);
				return;
			}
			_znetView.InvokeRPC(ZNetView.Everybody, "RPC_Paint", new object[1] { skin });
		}

		private void RPC_Paint(long caller, string skin)
		{
			if (string.IsNullOrWhiteSpace(skin))
			{
				skin = _defaultSkinName;
			}
			if ((Object)(object)_znetView != (Object)null && _znetView.IsValid() && _znetView.IsOwner())
			{
				ZDO zDO = _znetView.GetZDO();
				if (zDO != null)
				{
					zDO.Set("colorizerSet", skin);
					zDO.Set("colorizerIsActive", true);
				}
			}
			ApplySkin(skin);
		}

		private void ApplySkin(string skin)
		{
			if (!ColorizerCatalog.TryGetBySkin(_prefabName, skin, out var pack))
			{
				if (string.Equals(skin, _defaultSkinName, StringComparison.Ordinal) || !ColorizerCatalog.TryGetBySkin(_prefabName, _defaultSkinName, out pack))
				{
					ModLog.Warning("BalrondBannerColorizer: color pack '" + skin + "' not found for " + _prefabName);
					return;
				}
				skin = _defaultSkinName;
			}
			ApplyMaterialList(_bodyRenderers, pack.Body);
			if ((Object)(object)_mainRenderer != (Object)null && (Object)(object)pack.Main != (Object)null)
			{
				ApplyMaterial(_mainRenderer, pack.Main);
			}
			if ((Object)(object)pack.Destruction != (Object)null)
			{
				ApplyMaterialList(_destructionRenderers, pack.Destruction);
			}
			_skinName = pack.Skin;
		}

		private bool SetupRenderers()
		{
			_bodyRenderers.Clear();
			_destructionRenderers.Clear();
			_mainRenderer = null;
			if (ColorizerCatalog.IsRoof(_prefabName))
			{
				Transform parent = ((Component)this).transform.Find("New");
				AddRendererFromChild(parent, 0, _bodyRenderers);
				AddRendererFromChild(parent, 1, _bodyRenderers);
				AddRenderersFromTransform(((Component)this).transform.Find("Worn"), _destructionRenderers);
				AddRenderersFromTransform(((Component)this).transform.Find("Broken"), _destructionRenderers);
				AddRenderersFromTransform(((Component)this).transform.Find("Destruction"), _destructionRenderers);
				return _bodyRenderers.Count > 0;
			}
			switch (_prefabName)
			{
			case "crystal_wall_1x1":
			{
				Transform parent2 = ((Component)this).transform.Find("New");
				AddRendererFromChild(parent2, 0, _bodyRenderers);
				AddRendererFromChild(parent2, 1, _bodyRenderers);
				return _bodyRenderers.Count > 0;
			}
			case "piece_banner01":
			case "piece_banner02":
			case "piece_banner03":
			case "piece_banner04":
			case "piece_banner05":
			case "piece_banner06":
			case "piece_banner07":
			case "piece_banner08":
			case "piece_banner09":
			case "piece_banner10":
			case "piece_banner11":
				AddRendererFromPath("default", _bodyRenderers);
				return _bodyRenderers.Count > 0;
			case "piece_cloth_hanging_door":
				AddRendererFromPath("new/cloth", _bodyRenderers);
				return _bodyRenderers.Count > 0;
			case "piece_cloth_hanging_door_blue":
			{
				Transform parent7 = ((Component)this).transform.Find("new");
				AddRendererFromChild(parent7, 0, _bodyRenderers);
				AddRendererFromChild(parent7, 1, _bodyRenderers);
				return _bodyRenderers.Count > 0;
			}
			case "piece_cloth_hanging_door_blue2":
			{
				Transform parent3 = ((Component)this).transform.Find("new");
				Transform parent4 = ((Component)this).transform.Find("worn");
				Transform parent5 = ((Component)this).transform.Find("broken");
				Transform parent6 = ((Component)this).transform.Find("destruction");
				AddRendererFromChild(parent3, 1, _bodyRenderers);
				AddRendererFromChild(parent3, 2, _bodyRenderers);
				AddRendererFromChild(parent4, 1, _bodyRenderers);
				AddRendererFromChild(parent4, 2, _bodyRenderers);
				AddRendererFromChild(parent5, 1, _bodyRenderers);
				AddRendererFromChild(parent5, 2, _bodyRenderers);
				AddRendererFromChild(parent6, 0, _bodyRenderers);
				return _bodyRenderers.Count > 0;
			}
			case "jute_carpet":
			case "jute_carpet_blue":
				_mainRenderer = GetRendererFromChildIndex(((Component)this).transform, 1);
				AddRendererFromRootChildIndex(2, _bodyRenderers);
				AddRendererFromRootChildIndex(3, _bodyRenderers);
				AddRenderersFromTransform(((Component)this).transform.Find("destruction"), _destructionRenderers);
				return _bodyRenderers.Count > 0 || (Object)(object)_mainRenderer != (Object)null;
			default:
				return false;
			}
		}

		private static void ApplyMaterialList(List<MeshRenderer> renderers, Material material)
		{
			if (renderers != null && !((Object)(object)material == (Object)null))
			{
				for (int i = 0; i < renderers.Count; i++)
				{
					ApplyMaterial(renderers[i], material);
				}
			}
		}

		private static void ApplyMaterial(MeshRenderer renderer, Material material)
		{
			MaterialVariantBinder.Assign(renderer, material);
		}

		private void AddRendererFromPath(string path, List<MeshRenderer> target)
		{
			Transform val = ((Component)this).transform.Find(path);
			if (!((Object)(object)val == (Object)null))
			{
				MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>();
				if ((Object)(object)component != (Object)null)
				{
					target.Add(component);
				}
			}
		}

		private static void AddRendererFromChild(Transform parent, int childIndex, List<MeshRenderer> target)
		{
			if (!((Object)(object)parent == (Object)null) && childIndex >= 0 && childIndex < parent.childCount)
			{
				MeshRenderer component = ((Component)parent.GetChild(childIndex)).GetComponent<MeshRenderer>();
				if ((Object)(object)component != (Object)null)
				{
					target.Add(component);
				}
			}
		}

		private void AddRendererFromRootChildIndex(int childIndex, List<MeshRenderer> target)
		{
			MeshRenderer rendererFromChildIndex = GetRendererFromChildIndex(((Component)this).transform, childIndex);
			if ((Object)(object)rendererFromChildIndex != (Object)null)
			{
				target.Add(rendererFromChildIndex);
			}
		}

		private static MeshRenderer GetRendererFromChildIndex(Transform parent, int childIndex)
		{
			if ((Object)(object)parent == (Object)null || childIndex < 0 || childIndex >= parent.childCount)
			{
				return null;
			}
			return ((Component)parent.GetChild(childIndex)).GetComponent<MeshRenderer>();
		}

		private static void AddRenderersFromTransform(Transform source, List<MeshRenderer> target)
		{
			if (!((Object)(object)source == (Object)null))
			{
				MeshRenderer[] componentsInChildren = ((Component)source).GetComponentsInChildren<MeshRenderer>(true);
				if (componentsInChildren != null && componentsInChildren.Length != 0)
				{
					target.AddRange(componentsInChildren);
				}
			}
		}

		private static string NormalizePrefabName(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			string text = value.Replace("(Clone)", string.Empty);
			int num = text.IndexOf("(", StringComparison.Ordinal);
			if (num >= 0)
			{
				text = text.Substring(0, num);
			}
			return text.Trim();
		}

		private static string Localize(string value)
		{
			if (string.IsNullOrEmpty(value) || Localization.instance == null)
			{
				return value ?? string.Empty;
			}
			return Localization.instance.Localize(value);
		}

		private static string LocalizeColor(string skin)
		{
			if (string.IsNullOrEmpty(skin))
			{
				return string.Empty;
			}
			string text = Localize("$bbc_color_" + skin);
			return string.IsNullOrEmpty(text) ? skin : text;
		}
	}
	internal static class ColorizerCatalog
	{
		private sealed class PieceSet
		{
			internal readonly Dictionary<string, ColorizerPack> BySkin = new Dictionary<string, ColorizerPack>(StringComparer.Ordinal);

			internal readonly Dictionary<string, ColorizerPack> ByDye = new Dictionary<string, ColorizerPack>(StringComparer.Ordinal);
		}

		internal static readonly string[] TargetPrefabNames = new string[25]
		{
			"piece_banner01", "piece_banner02", "piece_banner03", "piece_banner04", "piece_banner05", "piece_banner06", "piece_banner07", "piece_banner08", "piece_banner09", "piece_banner10",
			"piece_banner11", "piece_cloth_hanging_door", "piece_cloth_hanging_door_blue", "piece_cloth_hanging_door_blue2", "jute_carpet", "jute_carpet_blue", "crystal_wall_1x1", "darkwood_roof_icorner", "darkwood_roof_icorner_45", "darkwood_roof_ocorner",
			"darkwood_roof_ocorner_45", "darkwood_roof_top", "darkwood_roof_top_45", "darkwood_roof_45", "darkwood_roof"
		};

		private static readonly string[] RoofPrefabNames = new string[8] { "darkwood_roof_icorner", "darkwood_roof_icorner_45", "darkwood_roof_ocorner", "darkwood_roof_ocorner_45", "darkwood_roof_top", "darkwood_roof_top_45", "darkwood_roof_45", "darkwood_roof" };

		private static readonly Dictionary<string, string> DefaultSkinByPrefab = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{ "darkwood_roof_icorner", "black" },
			{ "darkwood_roof_icorner_45", "black" },
			{ "darkwood_roof_ocorner", "black" },
			{ "darkwood_roof_ocorner_45", "black" },
			{ "darkwood_roof_top", "black" },
			{ "darkwood_roof_top_45", "black" },
			{ "darkwood_roof_45", "black" },
			{ "darkwood_roof", "black" },
			{ "crystal_wall_1x1", "white" },
			{ "piece_banner01", "black" },
			{ "piece_banner02", "blue" },
			{ "piece_banner03", "red" },
			{ "piece_banner04", "red" },
			{ "piece_banner05", "green" },
			{ "piece_banner06", "blue" },
			{ "piece_banner07", "blue" },
			{ "piece_banner08", "yellow" },
			{ "piece_banner09", "purple" },
			{ "piece_banner10", "orange" },
			{ "piece_banner11", "white" },
			{ "piece_cloth_hanging_door", "red" },
			{ "piece_cloth_hanging_door_blue", "blue" },
			{ "piece_cloth_hanging_door_blue2", "blue" },
			{ "jute_carpet", "red" },
			{ "jute_carpet_blue", "blue" }
		};

		private static readonly HashSet<string> RoofSet = new HashSet<string>(RoofPrefabNames, StringComparer.Ordinal);

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

		internal static void Build(ModAssetCatalog assets)
		{
			Sets.Clear();
			if (assets == null)
			{
				return;
			}
			AddSimple(assets, "crystal_wall_1x1", "crystalWall1");
			for (int i = 1; i <= 11; i++)
			{
				AddSimple(assets, "piece_banner" + i.ToString("00"), "banner" + i.ToString("00"));
			}
			AddSimple(assets, "piece_cloth_hanging_door", "bannerHanging");
			AddSimple(assets, "piece_cloth_hanging_door_blue", "bannerHanging2");
			AddSimple(assets, "piece_cloth_hanging_door_blue2", "bannerHanging3");
			AddComplex(assets, "jute_carpet", "juteCarpet", "juteCarpetMain", "juteCarpetDestruction");
			AddComplex(assets, "jute_carpet_blue", "juteCarpet2", "juteCarpetMain2", "juteCarpetDestruction2");
			for (int j = 0; j < RoofPrefabNames.Length; j++)
			{
				AddComplex(assets, RoofPrefabNames[j], "roofNew", "roofNew", "roofWorn");
			}
			int num = TargetPrefabNames.Length * ModAssetCatalog.ColorNames.Length;
			int num2 = 0;
			foreach (PieceSet value in Sets.Values)
			{
				num2 += value.BySkin.Count;
			}
			if (num2 != num)
			{
				ModLog.Warning("BalrondBannerColorizer" + $": color material catalog is incomplete ({num2}/{num} variants).");
			}
		}

		internal static bool IsTarget(string prefabName)
		{
			return !string.IsNullOrEmpty(prefabName) && Sets.ContainsKey(prefabName);
		}

		internal static bool IsTargetInstanceName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			if (IsTarget(name))
			{
				return true;
			}
			for (int i = 0; i < TargetPrefabNames.Length; i++)
			{
				string text = TargetPrefabNames[i];
				if (name.Length >= text.Length + 7 && string.CompareOrdinal(name, 0, text, 0, text.Length) == 0)
				{
					string a = name.Substring(text.Length).Trim();
					if (string.Equals(a, "(Clone)", StringComparison.Ordinal))
					{
						return IsTarget(text);
					}
				}
			}
			return false;
		}

		internal static bool IsRoof(string prefabName)
		{
			return !string.IsNullOrEmpty(prefabName) && RoofSet.Contains(prefabName);
		}

		internal static string GetDefaultSkin(string prefabName)
		{
			if (!string.IsNullOrEmpty(prefabName) && DefaultSkinByPrefab.TryGetValue(prefabName, out var value))
			{
				return value;
			}
			return "black";
		}

		internal static bool TryGetBySkin(string prefabName, string skin, out ColorizerPack pack)
		{
			pack = null;
			if (string.IsNullOrEmpty(prefabName) || string.IsNullOrEmpty(skin) || !Sets.TryGetValue(prefabName, out var value))
			{
				return false;
			}
			return value.BySkin.TryGetValue(skin, out pack) && pack != null;
		}

		internal static bool TryGetByDye(string prefabName, string dyePrefabName, out ColorizerPack pack)
		{
			pack = null;
			if (string.IsNullOrEmpty(prefabName) || string.IsNullOrEmpty(dyePrefabName) || !Sets.TryGetValue(prefabName, out var value))
			{
				return false;
			}
			return value.ByDye.TryGetValue(dyePrefabName, out pack) && pack != null;
		}

		internal static void Clear()
		{
			Sets.Clear();
		}

		private static void AddSimple(ModAssetCatalog assets, string pieceName, string bodySet)
		{
			AddComplex(assets, pieceName, bodySet, null, null);
		}

		private static void AddComplex(ModAssetCatalog assets, string pieceName, string bodySet, string mainSet, string destructionSet)
		{
			PieceSet pieceSet = new PieceSet();
			for (int i = 0; i < ModAssetCatalog.ColorNames.Length; i++)
			{
				string text = ModAssetCatalog.ColorNames[i];
				string text2 = ModAssetCatalog.DyePrefabNames[i];
				Material material = assets.GetMaterial(bodySet, text);
				Material val = ((mainSet != null) ? assets.GetMaterial(mainSet, text) : null);
				Material val2 = ((destructionSet != null) ? assets.GetMaterial(destructionSet, text) : null);
				if ((Object)(object)material == (Object)null || (mainSet != null && (Object)(object)val == (Object)null) || (destructionSet != null && (Object)(object)val2 == (Object)null))
				{
					ModLog.Warning("BalrondBannerColorizer: material variant missing for " + pieceName + "/" + text);
					continue;
				}
				ColorizerPack value = new ColorizerPack(pieceName, text, text2, material, val, val2);
				pieceSet.BySkin[text] = value;
				pieceSet.ByDye[text2] = value;
			}
			Sets[pieceName] = pieceSet;
		}
	}
	internal static class ColorizerInstaller
	{
		private static readonly Dictionary<string, string> ExplicitDefaultMaterialPieces = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{ "piece_banner08", "yellow" },
			{ "piece_banner05", "green" },
			{ "piece_banner07", "blue" },
			{ "piece_banner09", "purple" },
			{ "piece_banner10", "orange" },
			{ "piece_banner11", "white" }
		};

		internal static void Install(PrefabLookupCache prefabs)
		{
			if (prefabs == null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < ColorizerCatalog.TargetPrefabNames.Length; i++)
			{
				string text = ColorizerCatalog.TargetPrefabNames[i];
				GameObject val = prefabs.Get(text);
				if ((Object)(object)val == (Object)null)
				{
					ModLog.Warning("BalrondBannerColorizer: compatible vanilla prefab not found: " + text);
					continue;
				}
				num2++;
				if (InstallOnPiece(val))
				{
					num++;
				}
				if (ExplicitDefaultMaterialPieces.TryGetValue(text, out var value))
				{
					ApplyLegacyDefaultMaterial(val, text, value);
				}
			}
			ModLog.Info("BalrondBannerColorizer" + $": colorizer scene prefabs: {num2}/{ColorizerCatalog.TargetPrefabNames.Length} targets found, {num} new component(s).");
			GameObject hammer = prefabs.Get("Hammer");
			InstallOnHammer(hammer);
		}

		internal static void InstallOnObjectDb(ObjectDB db)
		{
			if ((Object)(object)db == (Object)null || db.m_items == null)
			{
				return;
			}
			for (int i = 0; i < db.m_items.Count; i++)
			{
				GameObject val = db.m_items[i];
				if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, "Hammer", StringComparison.Ordinal))
				{
					InstallOnHammer(val);
					break;
				}
			}
		}

		internal static void InstallOnRuntimePiece(Piece piece)
		{
			if (!((Object)(object)piece == (Object)null) && !((Object)(object)((Component)piece).gameObject == (Object)null) && ColorizerCatalog.IsTargetInstanceName(((Object)((Component)piece).gameObject).name))
			{
				InstallOnPiece(((Component)piece).gameObject);
			}
		}

		private static bool InstallOnPiece(GameObject prefab)
		{
			if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.GetComponent<BannerColorizer>() != (Object)null)
			{
				return false;
			}
			prefab.AddComponent<BannerColorizer>();
			return (Object)(object)prefab.GetComponent<BannerColorizer>() != (Object)null;
		}

		private static void InstallOnHammer(GameObject hammer)
		{
			ItemDrop val = (((Object)(object)hammer != (Object)null) ? hammer.GetComponent<ItemDrop>() : null);
			PieceTable val2 = (((Object)(object)val != (Object)null && val.m_itemData != null && val.m_itemData.m_shared != null) ? val.m_itemData.m_shared.m_buildPieces : null);
			if ((Object)(object)val2 == (Object)null || val2.m_pieces == null)
			{
				return;
			}
			int num = 0;
			for (int i = 0; i < val2.m_pieces.Count; i++)
			{
				GameObject val3 = val2.m_pieces[i];
				if ((Object)(object)val3 != (Object)null && ColorizerCatalog.IsTarget(((Object)val3).name) && InstallOnPiece(val3))
				{
					num++;
				}
			}
			if (num > 0)
			{
				ModLog.Info("BalrondBannerColorizer" + $": colorizer attached to {num} separate Hammer PieceTable prefab(s).");
			}
		}

		private static void ApplyLegacyDefaultMaterial(GameObject prefab, string prefabName, string skin)
		{
			if (!ColorizerCatalog.TryGetBySkin(prefabName, skin, out var pack) || (Object)(object)pack.Body == (Object)null)
			{
				return;
			}
			Transform val = prefab.transform.Find("default");
			if (!((Object)(object)val == (Object)null))
			{
				MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>();
				if ((Object)(object)component != (Object)null)
				{
					MaterialVariantBinder.Assign(component, pack.Body);
				}
			}
		}
	}
	internal sealed class ColorizerPack
	{
		internal readonly Material Body;

		internal readonly Material Main;

		internal readonly Material Destruction;

		internal readonly string PieceName;

		internal readonly string Skin;

		internal readonly string DyePrefabName;

		internal ColorizerPack(string pieceName, string skin, string dyePrefabName, Material body, Material main = null, Material destruction = null)
		{
			PieceName = pieceName;
			Skin = skin;
			DyePrefabName = dyePrefabName;
			Body = body;
			Main = main;
			Destruction = destruction;
		}
	}
	internal static class MaterialVariantBinder
	{
		internal static void Assign(MeshRenderer renderer, Material immutableVariant)
		{
			if (!((Object)(object)renderer == (Object)null) && !((Object)(object)immutableVariant == (Object)null) && ((Renderer)renderer).sharedMaterial != immutableVariant)
			{
				((Renderer)renderer).sharedMaterial = immutableVariant;
			}
		}
	}
}
namespace BalrondBannerColorizer.Config
{
	internal static class ModConfig
	{
		private const string ServerSection = "00 - Server";

		private const string DyeRecipeSection = "10 - Dye Recipes";

		private const string DyeStationSection = "20 - Dye Station";

		private static readonly Dictionary<string, string> DefaultDyeRecipes = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{ "DyeBlack", "Coal:1" },
			{ "DyeBlue", "Blueberries:2" },
			{ "DyeGreen", "Guck:1" },
			{ "DyeOrange", "Raspberry:1,Cloudberry:1" },
			{ "DyePurple", "Raspberry:1,Blueberries:1" },
			{ "DyeRed", "Bloodbag:1" },
			{ "DyeWhite", "BoneFragments:2" },
			{ "DyeYellow", "Cloudberry:2" }
		};

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

		internal const string DefaultDyeStationResourceCost = "Wood:5,Resin:5";

		internal const string NoCraftingStation = "NONE";

		internal static ConfigEntry<bool> LockConfiguration { get; private set; }

		internal static ConfigEntry<string> DyeStationResourceCost { get; private set; }

		internal static ConfigEntry<string> DyeStationCraftingStation { get; private set; }

		internal static string DefaultDyeStationCraftingStation { get; private set; } = "NONE";

		internal static event Action Changed;

		internal static void Initialize(Launch plugin, string defaultDyeStationCraftingStation)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Expected O, but got Unknown
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Expected O, but got Unknown
			if ((Object)(object)plugin == (Object)null)
			{
				throw new ArgumentNullException("plugin");
			}
			ResetEntries();
			DefaultDyeStationCraftingStation = NormalizeCraftingStationDefault(defaultDyeStationCraftingStation);
			LockConfiguration = ((BaseUnityPlugin)plugin).Config.Bind<bool>("00 - Server", "Lock Configuration", true, new ConfigDescription("When enabled, all BalrondBannerColorizer recipe and Dye Station settings are controlled by the server/host.", (AcceptableValueBase)null, Array.Empty<object>()));
			ServerSyncBridge.AddLockingConfigEntry(LockConfiguration);
			foreach (KeyValuePair<string, string> defaultDyeRecipe in DefaultDyeRecipes)
			{
				ConfigEntry<string> val = ((BaseUnityPlugin)plugin).Config.Bind<string>("10 - Dye Recipes", defaultDyeRecipe.Key, defaultDyeRecipe.Value, new ConfigDescription("Resource cost for " + defaultDyeRecipe.Key + ". Format: ItemPrefab:Amount,ItemPrefab:Amount. Use NONE for a zero-cost recipe.", (AcceptableValueBase)null, Array.Empty<object>()));
				val.SettingChanged += OnSettingChanged;
				DyeRecipeEntries[defaultDyeRecipe.Key] = val;
				ServerSyncBridge.AddConfigEntry<string>(val);
			}
			DyeStationResourceCost = ((BaseUnityPlugin)plugin).Config.Bind<string>("20 - Dye Station", "Resource Cost", "Wood:5,Resin:5", new ConfigDescription("Build resource cost for piece_dyestation. Format: ItemPrefab:Amount,ItemPrefab:Amount. Use NONE for zero build cost.", (AcceptableValueBase)null, Array.Empty<object>()));
			DyeStationResourceCost.SettingChanged += OnSettingChanged;
			ServerSyncBridge.AddConfigEntry<string>(DyeStationResourceCost);
			DyeStationCraftingStation = ((BaseUnityPlugin)plugin).Config.Bind<string>("20 - Dye Station", "Crafting Station", DefaultDyeStationCraftingStation, new ConfigDescription("Crafting-station prefab required to build piece_dyestation, for example piece_workbench or forge. Use NONE for no crafting-station requirement.", (AcceptableValueBase)null, Array.Empty<object>()));
			DyeStationCraftingStation.SettingChanged += OnSettingChanged;
			ServerSyncBridge.AddConfigEntry<string>(DyeStationCraftingStation);
		}

		internal static string GetDyeRecipe(string dyePrefabName)
		{
			if (!string.IsNullOrEmpty(dyePrefabName) && DyeRecipeEntries.TryGetValue(dyePrefabName, out var value) && value != null)
			{
				return value.Value;
			}
			return GetDefaultDyeRecipe(dyePrefabName);
		}

		internal static string GetDefaultDyeRecipe(string dyePrefabName)
		{
			if (!string.IsNullOrEmpty(dyePrefabName) && DefaultDyeRecipes.TryGetValue(dyePrefabName, out var value))
			{
				return value;
			}
			return "NONE";
		}

		internal static void Reset()
		{
			ResetEntries();
			LockConfiguration = null;
			DyeStationResourceCost = null;
			DyeStationCraftingStation = null;
			DefaultDyeStationCraftingStation = "NONE";
			ModConfig.Changed = null;
		}

		private static void ResetEntries()
		{
			foreach (ConfigEntry<string> value in DyeRecipeEntries.Values)
			{
				if (value != null)
				{
					value.SettingChanged -= OnSettingChanged;
				}
			}
			DyeRecipeEntries.Clear();
			if (DyeStationResourceCost != null)
			{
				DyeStationResourceCost.SettingChanged -= OnSettingChanged;
			}
			if (DyeStationCraftingStation != null)
			{
				DyeStationCraftingStation.SettingChanged -= OnSettingChanged;
			}
		}

		private static string NormalizeCraftingStationDefault(string value)
		{
			string text = (value ?? string.Empty).Trim();
			return (text.Length == 0) ? "NONE" : text;
		}

		private static void OnSettingChanged(object sender, EventArgs args)
		{
			ModConfig.Changed?.Invoke();
		}
	}
	internal static class RecipeCostParser
	{
		internal static bool TryBuildRequirements(string specification, Func<string, ItemDrop> itemResolver, out Requirement[] requirements, out string error)
		{
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Expected O, but got Unknown
			requirements = null;
			error = null;
			if (itemResolver == null)
			{
				error = "item resolver is unavailable";
				return false;
			}
			string text = (specification ?? string.Empty).Trim();
			if (text.Length == 0)
			{
				error = "recipe is empty";
				return false;
			}
			if (string.Equals(text, "NONE", StringComparison.OrdinalIgnoreCase))
			{
				requirements = Array.Empty<Requirement>();
				return true;
			}
			string[] array = text.Split(new char[1] { ',' });
			List<string> list = new List<string>(array.Length);
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0)
				{
					error = "empty ingredient entry";
					return false;
				}
				int num = text2.LastIndexOf(':');
				if (num <= 0 || num == text2.Length - 1)
				{
					error = "ingredient '" + text2 + "' must use PrefabName:Amount";
					return false;
				}
				string text3 = text2.Substring(0, num).Trim();
				string s = text2.Substring(num + 1).Trim();
				if (text3.Length == 0)
				{
					error = "ingredient prefab name is empty";
					return false;
				}
				if (!int.TryParse(s, out var result) || result <= 0)
				{
					error = "ingredient '" + text2 + "' has an invalid amount";
					return false;
				}
				if (dictionary.TryGetValue(text3, out var value))
				{
					long num2 = (long)value + (long)result;
					if (num2 > int.MaxValue)
					{
						error = "ingredient amount overflow for '" + text3 + "'";
						return false;
					}
					dictionary[text3] = (int)num2;
				}
				else
				{
					list.Add(text3);
					dictionary.Add(text3, result);
				}
			}
			Requirement[] array2 = (Requirement[])(object)new Requirement[list.Count];
			for (int j = 0; j < list.Count; j++)
			{
				string text4 = list[j];
				ItemDrop val = itemResolver(text4);
				if ((Object)(object)val == (Object)null)
				{
					error = "item prefab not found: " + text4;
					return false;
				}
				array2[j] = new Requirement
				{
					m_resItem = val,
					m_amount = dictionary[text4],
					m_amountPerLevel = 0,
					m_recover = true
				};
			}
			requirements = array2;
			return true;
		}
	}
	internal static class ServerSyncBridge
	{
		private static ConfigSync _sync;

		internal static void Initialize(string pluginGuid, string displayName, string version)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				throw new ArgumentException("Plugin GUID is required.", "pluginGuid");
			}
			_sync = new ConfigSync(pluginGuid)
			{
				DisplayName = displayName,
				CurrentVersion = version,
				MinimumRequiredVersion = version
			};
		}

		internal static void AddLockingConfigEntry(ConfigEntry<bool> entry)
		{
			if (entry == null)
			{
				throw new ArgumentNullException("entry");
			}
			EnsureInitialized().AddLockingConfigEntry<bool>(entry);
		}

		internal static void AddConfigEntry<T>(ConfigEntry<T> entry)
		{
			if (entry == null)
			{
				throw new ArgumentNullException("entry");
			}
			SyncedConfigEntry<T> syncedConfigEntry = EnsureInitialized().AddConfigEntry<T>(entry);
			syncedConfigEntry.SynchronizedConfig = true;
		}

		internal static void Reset()
		{
			_sync = null;
		}

		private static ConfigSync EnsureInitialized()
		{
			if (_sync == null)
			{
				throw new InvalidOperationException("ServerSyncBridge was used before initialization.");
			}
			return _sync;
		}
	}
}
namespace BalrondBannerColorizer.Assets
{
	internal static class AudioRoutingService
	{
		private sealed class ReferenceComparer : IEqualityComparer<GameObject>
		{
			internal static readonly ReferenceComparer Instance = new ReferenceComparer();

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

			public int GetHashCode(GameObject obj)
			{
				return RuntimeHelpers.GetHashCode(obj);
			}
		}

		private static readonly List<GameObject> Roots = new List<GameObject>();

		private static readonly HashSet<GameObject> RootSet = new HashSet<GameObject>(ReferenceComparer.Instance);

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

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

		private static AudioMan _liveAudioMan;

		private static bool _reportedNoSources;

		internal static void Register(IEnumerable<GameObject> roots)
		{
			if (roots == null)
			{
				return;
			}
			foreach (GameObject root in roots)
			{
				if ((Object)(object)root != (Object)null && RootSet.Add(root))
				{
					Roots.Add(root);
				}
			}
		}

		internal static void OnAudioManAwake(AudioMan audioMan)
		{
			if (!((Object)(object)audioMan == (Object)null))
			{
				_liveAudioMan = audioMan;
				Refresh();
			}
		}

		internal static void Refresh()
		{
			int num = 0;
			for (int i = 0; i < Roots.Count; i++)
			{
				GameObject val = Roots[i];
				if ((Object)(object)val != (Object)null)
				{
					num += val.GetComponentsInChildren<AudioSource>(true).Length;
				}
			}
			if (num == 0)
			{
				if (!_reportedNoSources)
				{
					_reportedNoSources = true;
					ModLog.Info("BalrondBannerColorizer: audio routing not applicable: no owned direct AudioSource components; external FX remain with Valheim.");
				}
			}
			else
			{
				if ((Object)(object)_liveAudioMan == (Object)null)
				{
					return;
				}
				AudioMixer val2 = ResolveLiveMixer(_liveAudioMan);
				AudioMixerGroup val3 = (((Object)(object)val2 != (Object)null) ? (FindExactGroup(val2, "SFX") ?? FindExactGroup(val2, "Effects")) : null);
				if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null)
				{
					WarnOnce("sfx-unavailable", "BalrondBannerColorizer: live SFX mixer group is unavailable; preserved existing audio routes (no Master fallback).");
					return;
				}
				int num2 = 0;
				int num3 = 0;
				int num4 = 0;
				for (int j = 0; j < Roots.Count; j++)
				{
					GameObject val4 = Roots[j];
					if ((Object)(object)val4 == (Object)null)
					{
						continue;
					}
					AudioSource[] componentsInChildren = val4.GetComponentsInChildren<AudioSource>(true);
					foreach (AudioSource val5 in componentsInChildren)
					{
						if ((Object)(object)val5 == (Object)null)
						{
							continue;
						}
						AudioMixerGroup outputAudioMixerGroup = val5.outputAudioMixerGroup;
						if ((Object)(object)outputAudioMixerGroup != (Object)null && ResolveMixer(outputAudioMixerGroup) == val2 && !string.Equals(((Object)outputAudioMixerGroup).name, "Master", StringComparison.OrdinalIgnoreCase))
						{
							num2++;
							continue;
						}
						try
						{
							if (outputAudioMixerGroup != val3)
							{
								val5.outputAudioMixerGroup = val3;
							}
							if (val5.outputAudioMixerGroup == val3)
							{
								num3++;
							}
							else
							{
								num4++;
							}
						}
						catch (Exception ex)
						{
							num4++;
							WarnOnce("source-route", "BalrondBannerColorizer: audio source routing failed: " + ex.Message);
						}
					}
				}
				if (num4 == 0)
				{
					ModLog.Info("BalrondBannerColorizer" + $": audio routing verified: {num2} live group(s) preserved, {num3} routed to SFX.");
				}
				else
				{
					ModLog.Warning("BalrondBannerColorizer" + $": audio routing incomplete: {num4} owned source(s) unresolved.");
				}
			}
		}

		internal static void Clear()
		{
			Roots.Clear();
			RootSet.Clear();
			WarningKeys.Clear();
			AudioManFields.Clear();
			_liveAudioMan = null;
			_reportedNoSources = false;
		}

		private static AudioMixer ResolveLiveMixer(AudioMan audioMan)
		{
			return ResolveMixer(ReadField(audioMan, "m_masterMixer")) ?? ResolveMixer(ReadField(audioMan, "m_ambientMixer")) ?? ResolveMixer(ReadField(audioMan, "m_musicMixer"));
		}

		private static object ReadField(AudioMan audioMan, string name)
		{
			try
			{
				if (!AudioManFields.TryGetValue(name, out var value))
				{
					value = typeof(AudioMan).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					AudioManFields[name] = value;
				}
				return (value != null) ? value.GetValue(audioMan) : null;
			}
			catch (Exception ex)
			{
				WarnOnce("field-" + name, "BalrondBannerColorizer: cannot read AudioMan." + name + ": " + ex.Message);
				return null;
			}
		}

		private static AudioMixer ResolveMixer(object source)
		{
			AudioMixer val = (AudioMixer)((source is AudioMixer) ? source : null);
			if (val != null)
			{
				return val;
			}
			AudioMixerGroup val2 = (AudioMixerGroup)((source is AudioMixerGroup) ? source : null);
			if (val2 != null)
			{
				try
				{
					return val2.audioMixer;
				}
				catch
				{
					return null;
				}
			}
			AudioSource val3 = (AudioSource)((source is AudioSource) ? source : null);
			if (val3 != null)
			{
				try
				{
					return ((Object)(object)val3.outputAudioMixerGroup != (Object)null) ? val3.outputAudioMixerGroup.audioMixer : null;
				}
				catch
				{
					return null;
				}
			}
			return null;
		}

		private static AudioMixerGroup FindExactGroup(AudioMixer mixer, string name)
		{
			try
			{
				AudioMixerGroup[] array = mixer.FindMatchingGroups(name);
				if (array != null)
				{
					for (int i = 0; i < array.Length; i++)
					{
						if ((Object)(object)array[i] != (Object)null && string.Equals(((Object)array[i]).name, name, StringComparison.OrdinalIgnoreCase))
						{
							return array[i];
						}
					}
				}
			}
			catch (Exception ex)
			{
				WarnOnce("group-" + name, "BalrondBannerColorizer: SFX group lookup failed: " + ex.Message);
			}
			return null;
		}

		private static void WarnOnce(string key, string message)
		{
			if (WarningKeys.Add(key))
			{
				ModLog.Warning(message);
			}
		}
	}
	internal sealed class FxReplacement
	{
		private readonly Dictionary<string, GameObject> _prefabs = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		private ZNetScene _scene;

		private int _prefabCount = -1;

		internal void Prepare(ZNetScene scene)
		{
			EnsureLookup(scene);
		}

		internal void RepairOwnedPrefabs(IEnumerable<GameObject> roots, ZNetScene scene)
		{
			if (roots == null || (Object)(object)scene == (Object)null)
			{
				return;
			}
			EnsureLookup(scene);
			foreach (GameObject root in roots)
			{
				RepairPrefab(root, scene);
			}
		}

		internal void Clear()
		{
			_scene = null;
			_prefabCount = -1;
			_prefabs.Clear();
		}

		private void RepairPrefab(GameObject root, ZNetScene scene)
		{
			if (!((Object)(object)root == (Object)null))
			{
				Piece component = root.GetComponent<Piece>();
				if ((Object)(object)component != (Object)null)
				{
					Repair(component.m_placeEffect, scene);
				}
				CraftingStation component2 = root.GetComponent<CraftingStation>();
				if ((Object)(object)component2 != (Object)null)
				{
					Repair(component2.m_craftItemEffects, scene);
					Repair(component2.m_repairItemDoneEffects, scene);
				}
				WearNTear component3 = root.GetComponent<WearNTear>();
				if ((Object)(object)component3 != (Object)null)
				{
					Repair(component3.m_hitEffect, scene);
					Repair(component3.m_destroyedEffect, scene);
				}
				Destructible component4 = root.GetComponent<Destructible>();
				if ((Object)(object)component4 != (Object)null)
				{
					Repair(component4.m_hitEffect, scene);
					Repair(component4.m_destroyedEffect, scene);
				}
				SpawnArea component5 = root.GetComponent<SpawnArea>();
				if ((Object)(object)component5 != (Object)null)
				{
					Repair(component5.m_spawnEffects, scene);
				}
				Projectile component6 = root.GetComponent<Projectile>();
				if ((Object)(object)component6 != (Object)null)
				{
					Repair(component6.m_hitEffects, scene);
					Repair(component6.m_hitWaterEffects, scene);
					Repair(component6.m_spawnOnHitEffects, scene);
				}
				Pickable component7 = root.GetComponent<Pickable>();
				if ((Object)(object)component7 != (Object)null)
				{
					Repair(component7.m_pickEffector, scene);
				}
				MineRock5 component8 = root.GetComponent<MineRock5>();
				if ((Object)(object)component8 != (Object)null)
				{
					Repair(component8.m_hitEffect, scene);
					Repair(component8.m_destroyedEffect, scene);
				}
				MineRock component9 = root.GetComponent<MineRock>();
				if ((Object)(object)component9 != (Object)null)
				{
					Repair(component9.m_hitEffect, scene);
					Repair(component9.m_destroyedEffect, scene);
				}
				ItemDrop component10 = root.GetComponent<ItemDrop>();
				if ((Object)(object)component10 != (Object)null && component10.m_itemData != null && component10.m_itemData.m_shared != null)
				{
					SharedData shared = component10.m_itemData.m_shared;
					Repair(shared.m_hitEffect, scene);
					Repair(shared.m_hitTerrainEffect, scene);
					Repair(shared.m_holdStartEffect, scene);
					Repair(shared.m_trailStartEffect, scene);
					Repair(shared.m_blockEffect, scene);
				}
			}
		}

		private void Repair(EffectList effectList, ZNetScene scene)
		{
			if (effectList == null || effectList.m_effectPrefabs == null)
			{
				return;
			}
			EnsureLookup(scene);
			EffectData[] effectPrefabs = effectList.m_effectPrefabs;
			foreach (EffectData val in effectPrefabs)
			{
				if (val != null && !((Object)(object)val.m_prefab == (Object)null))
				{
					string name = ((Object)val.m_prefab).name;
					if (_prefabs.TryGetValue(name, out var value) && (Object)(object)value != (Object)null)
					{
						val.m_prefab = value;
					}
				}
			}
		}

		private void EnsureLookup(ZNetScene scene)
		{
			if ((Object)(object)scene == (Object)null || scene.m_prefabs == null)
			{
				Clear();
				return;
			}
			int count = scene.m_prefabs.Count;
			if (_scene == scene && _prefabCount == count)
			{
				return;
			}
			_scene = scene;
			_prefabCount = count;
			_prefabs.Clear();
			for (int i = 0; i < scene.m_prefabs.Count; i++)
			{
				GameObject val = scene.m_prefabs[i];
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !_prefabs.ContainsKey(((Object)val).name))
				{
					_prefabs.Add(((Object)val).name, val);
				}
			}
		}
	}
	internal sealed class ModAssetCatalog
	{
		internal static readonly string[] ColorNames = new string[8] { "black", "blue", "green", "orange", "purple", "red", "white", "yellow" };

		internal static readonly string[] DyePrefabNames = new string[8] { "DyeBlack", "DyeBlue", "DyeGreen", "DyeOrange", "DyePurple", "DyeRed", "DyeWhite", "DyeYellow" };

		private readonly Dictionary<string, GameObject> _items = new Dictionary<string, GameObject>(StringComparer.Ordinal);

		private readonly Dictionary<string, Dictionary<string, Material>> _materialSets = new Dictionary<string, Dictionary<string, Material>>(StringComparer.Ordinal);

		private readonly List<GameObject> _ownedPrefabs = new List<GameObject>();

		internal AssetBundle AssetBundle { get; set; }

		internal GameObject DyeStation { get; set; }

		internal IReadOnlyList<GameObject> OwnedPrefabs => _ownedPrefabs;

		internal IEnumerable<GameObject> DyeItems => _items.Values;

		internal void AddItem(GameObject prefab)
		{
			if (!((Object)(object)prefab == (Object)null) && !string.IsNullOrEmpty(((Object)prefab).name))
			{
				_items[((Object)prefab).name] = prefab;
				AddOwnedPrefab(prefab);
			}
		}

		internal void AddOwnedPrefab(GameObject prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return;
			}
			for (int i = 0; i < _ownedPrefabs.Count; i++)
			{
				if (_ownedPrefabs[i] == prefab)
				{
					return;
				}
			}
			_ownedPrefabs.Add(prefab);
		}

		internal GameObject GetItem(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			_items.TryGetValue(prefabName, out var value);
			return value;
		}

		internal void SetMaterial(string setName, string colorName, Material material)
		{
			if (!string.IsNullOrEmpty(setName) && !string.IsNullOrEmpty(colorName))
			{
				if (!_materialSets.TryGetValue(setName, out var value))
				{
					value = new Dictionary<string, Material>(StringComparer.Ordinal);
					_materialSets.Add(setName, value);
				}
				value[colorName] = material;
			}
		}

		internal Material GetMaterial(string setName, string colorName)
		{
			if (string.IsNullOrEmpty(setName) || string.IsNullOrEmpty(colorName))
			{
				return null;
			}
			if (!_materialSets.TryGetValue(setName, out var value))
			{
				return null;
			}
			value.TryGetValue(colorName, out var value2);
			return value2;
		}

		internal void Clear()
		{
			_items.Clear();
			_materialSets.Clear();
			_ownedPrefabs.Clear();
			DyeStation = null;
		}
	}
	internal sealed class ModResourceLoader
	{
		private const string BundleFileName = "bannercolorizer";

		private const string AssetRoot = "Assets/Custom/BalrondBannerColorizer/";

		private static readonly string[] MaterialSetNames = new string[23]
		{
			"crystalWall1", "banner01", "banner02", "banner03", "banner04", "banner05", "banner06", "banner07", "banner08", "banner09",
			"banner10", "banner11", "bannerHanging", "bannerHanging2", "bannerHanging3", "juteCarpet", "juteCarpetMain", "juteCarpetDestruction", "juteCarpet2", "juteCarpetMain2",
			"juteCarpetDestruction2", "roofNew", "roofWorn"
		};

		internal bool Load(ModAssetCatalog catalog)
		{
			if (catalog == null)
			{
				return false;
			}
			catalog.Clear();
			AssetBundle val = LoadEmbeddedBundle("bannercolorizer");
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			catalog.AssetBundle = val;
			LoadDyeItems(val, catalog);
			LoadDyeStation(val, catalog);
			LoadMaterials(val, catalog);
			if ((Object)(object)catalog.DyeStation != (Object)null)
			{
				catalog.AddOwnedPrefab(catalog.DyeStation);
				ShaderReplacement.Replace(catalog.DyeStation);
			}
			foreach (GameObject dyeItem in catalog.DyeItems)
			{
				ShaderReplacement.Replace(dyeItem);
			}
			return (Object)(object)catalog.DyeStation != (Object)null;
		}

		internal void Unload(ModAssetCatalog catalog)
		{
			if ((Object)(object)catalog?.AssetBundle == (Object)null)
			{
				return;
			}
			try
			{
				catalog.AssetBundle.Unload(false);
			}
			catch (Exception ex)
			{
				ModLog.Warning("BalrondBannerColorizer: AssetBundle unload failed: " + ex.Message);
			}
		}

		private static void LoadDyeItems(AssetBundle bundle, ModAssetCatalog catalog)
		{
			for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++)
			{
				string text = ModAssetCatalog.DyePrefabNames[i];
				string text2 = "Assets/Custom/BalrondBannerColorizer/prefabs/" + text + ".prefab";
				GameObject val = bundle.LoadAsset<GameObject>(text2);
				if ((Object)(object)val == (Object)null)
				{
					ModLog.Warning("BalrondBannerColorizer: Missing prefab " + text2);
					continue;
				}
				ConfigureDyeItem(val, text);
				catalog.AddItem(val);
			}
		}

		private static void ConfigureDyeItem(GameObject prefab, string prefabName)
		{