Decompiled source of ServeYouRight v1.0.7

ServeYouRight.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using ModAssetOwnership;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ServeYouRight")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("ServeYouRight")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")]
[assembly: AssemblyFileVersion("1.0.7")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.7.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 ModAssetOwnership
{
	internal sealed class AssetOwner
	{
		internal readonly string Guid;

		internal readonly string Name;

		internal readonly string AssemblyName;

		internal readonly string[] Resources;

		internal AssetOwner(string guid, string name, string assemblyName, string[] resources)
		{
			Guid = guid;
			Name = (string.IsNullOrWhiteSpace(name) ? guid : name);
			AssemblyName = assemblyName;
			Resources = resources;
		}
	}
	internal static class AssetOwnerMatching
	{
		internal static AssetOwner? Resolve(string bundleName, IReadOnlyList<AssetOwner> plugins)
		{
			if (string.IsNullOrWhiteSpace(bundleName))
			{
				return null;
			}
			AssetOwner assetOwner = null;
			bool flag = false;
			foreach (AssetOwner plugin in plugins)
			{
				if (string.IsNullOrWhiteSpace(plugin.Guid))
				{
					continue;
				}
				bool flag2 = false;
				string[] resources = plugin.Resources;
				foreach (string text in resources)
				{
					if (text.Equals(bundleName, StringComparison.OrdinalIgnoreCase) || text.EndsWith("." + bundleName, StringComparison.OrdinalIgnoreCase))
					{
						flag2 = true;
						break;
					}
				}
				if (flag2)
				{
					flag = true;
					if (assetOwner != null && !assetOwner.Guid.Equals(plugin.Guid, StringComparison.OrdinalIgnoreCase))
					{
						return null;
					}
					assetOwner = plugin;
				}
			}
			if (flag)
			{
				return assetOwner;
			}
			string text2 = Normalize(Path.GetFileNameWithoutExtension(bundleName));
			if (text2.Length == 0)
			{
				return null;
			}
			foreach (AssetOwner plugin2 in plugins)
			{
				if (!string.IsNullOrWhiteSpace(plugin2.Guid) && (!(text2 != Normalize(plugin2.Name)) || !(text2 != Normalize(plugin2.Guid)) || !(text2 != Normalize(plugin2.AssemblyName))))
				{
					if (assetOwner != null && !assetOwner.Guid.Equals(plugin2.Guid, StringComparison.OrdinalIgnoreCase))
					{
						return null;
					}
					assetOwner = plugin2;
				}
			}
			return assetOwner;
		}

		internal static void Add(Dictionary<string, AssetOwner> owners, HashSet<string> ambiguous, string name, AssetOwner owner)
		{
			if (!ambiguous.Contains(name))
			{
				if (owners.TryGetValue(name, out AssetOwner value) && !value.Guid.Equals(owner.Guid, StringComparison.OrdinalIgnoreCase))
				{
					owners.Remove(name);
					ambiguous.Add(name);
				}
				else
				{
					owners[name] = owner;
				}
			}
		}

		private static string Normalize(string value)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string text = value ?? "";
			foreach (char c in text)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}
	}
}
namespace ServerSyncModTemplate
{
	[BepInPlugin("sighsorry.ServeYouRight", "ServeYouRight", "1.0.7")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class ServerSyncModTemplatePlugin : BaseUnityPlugin
	{
		public enum Toggle
		{
			On = 1,
			Off = 0
		}

		private class ConfigurationManagerAttributes
		{
			public int? Order;
		}

		internal const string ModName = "ServeYouRight";

		internal const string ModVersion = "1.0.7";

		internal const string Author = "sighsorry";

		private const string ModGUID = "sighsorry.ServeYouRight";

		private static string ConfigFileName = "sighsorry.ServeYouRight.cfg";

		private static string ConfigFileFullPath;

		private static ServerSyncModTemplatePlugin? _instance;

		private readonly Harmony _harmony = new Harmony("sighsorry.ServeYouRight");

		public static readonly ManualLogSource ServerSyncModTemplateLogger;

		private FileSystemWatcher? _watcher;

		private readonly object _reloadLock = new object();

		private static readonly object DynamicConfigLock;

		private DateTime _lastConfigReloadTime;

		private static readonly TimeSpan ConfigReloadDelay;

		private static readonly Dictionary<string, PerModCategoryConfig> PerModConfigs;

		private static bool _pendingDynamicConfigSave;

		public void Awake()
		{
			_instance = this;
			RunWithConfigAutoSaveDisabled(delegate
			{
				FoodSourceCatalog.Initialize();
				Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(OnLanguageChange));
				Assembly executingAssembly = Assembly.GetExecutingAssembly();
				_harmony.PatchAll(executingAssembly);
				SaveConfigWithoutWatcher();
				SetupWatcher();
			});
		}

		private void OnDestroy()
		{
			Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(OnLanguageChange));
			_watcher?.Dispose();
			_watcher = null;
			try
			{
				RunWithConfigAutoSaveDisabled(SaveConfigWithoutWatcher);
			}
			finally
			{
				FoodSourceCatalog.Dispose();
				ServingTrayMenu.Dispose();
				FeasterFoodInjector.ResetWorld();
				_harmony.UnpatchSelf();
				PerModConfigs.Clear();
				_instance = null;
			}
		}

		private void Update()
		{
			if (FoodSourceCatalog.RefreshPending)
			{
				FoodSourceCatalog.RefreshPending = false;
				FeasterFoodInjector.RefreshFoodPiecesAndMenu(ObjectDB.instance);
			}
		}

		private void OnLanguageChange()
		{
			FeasterFoodInjector.RefreshFoodPiecesAndMenu(ObjectDB.instance);
		}

		private void SetupWatcher()
		{
			_watcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName);
			_watcher.Changed += ReadConfigValues;
			_watcher.Created += ReadConfigValues;
			_watcher.Renamed += ReadConfigValues;
			_watcher.IncludeSubdirectories = true;
			_watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			_watcher.EnableRaisingEvents = true;
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			DateTime utcNow = DateTime.UtcNow;
			if (utcNow - _lastConfigReloadTime < ConfigReloadDelay)
			{
				return;
			}
			_lastConfigReloadTime = utcNow;
			lock (_reloadLock)
			{
				if (!File.Exists(ConfigFileFullPath))
				{
					ServerSyncModTemplateLogger.LogWarning((object)"Config file does not exist. Skipping reload.");
					return;
				}
				try
				{
					ServerSyncModTemplateLogger.LogDebug((object)"Reloading configuration...");
					ReloadConfigValues();
					ServerSyncModTemplateLogger.LogInfo((object)"Configuration reload complete.");
				}
				catch (Exception ex)
				{
					ServerSyncModTemplateLogger.LogError((object)("Error reloading configuration: " + ex.Message));
				}
				finally
				{
					_lastConfigReloadTime = DateTime.UtcNow;
				}
			}
		}

		private void ReloadConfigValues()
		{
			RunWithConfigAutoSaveDisabled(delegate
			{
				((BaseUnityPlugin)this).Config.Reload();
				FeasterFoodInjector.RefreshFoodPiecesAndMenu(ObjectDB.instance);
			});
		}

		private void RunWithConfigAutoSaveDisabled(Action action)
		{
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				action();
			}
			finally
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private void SaveConfigWithoutWatcher()
		{
			FileSystemWatcher watcher = _watcher;
			bool flag = watcher?.EnableRaisingEvents ?? false;
			if (flag)
			{
				watcher.EnableRaisingEvents = false;
			}
			try
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			finally
			{
				if (flag && watcher != null)
				{
					watcher.EnableRaisingEvents = true;
				}
			}
		}

		internal static bool UseModSpecificTab(FoodSourceMod mod, PieceCategory category)
		{
			//IL_0007: 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_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected I4, but got Unknown
			PerModCategoryConfig orCreatePerModConfig = GetOrCreatePerModConfig(mod);
			if ((int)category != 0)
			{
				return (category - 6) switch
				{
					1 => orCreatePerModConfig.Food.Value == Toggle.On, 
					2 => orCreatePerModConfig.Meads.Value == Toggle.On, 
					0 => orCreatePerModConfig.Feasts.Value == Toggle.On, 
					_ => false, 
				};
			}
			return orCreatePerModConfig.Misc.Value == Toggle.On;
		}

		private static PerModCategoryConfig GetOrCreatePerModConfig(FoodSourceMod mod)
		{
			lock (DynamicConfigLock)
			{
				if (PerModConfigs.TryGetValue(mod.Id, out PerModCategoryConfig value))
				{
					return value;
				}
				ServerSyncModTemplatePlugin instance = _instance;
				if ((Object)(object)instance == (Object)null)
				{
					throw new InvalidOperationException("Plugin instance is not initialized.");
				}
				string section = "ServingTray - " + SanitizeConfigText(mod.DisplayName) + " (" + SanitizeConfigText(mod.Id) + ")";
				PerModCategoryConfig created = null;
				instance.RunWithConfigAutoSaveDisabled(delegate
				{
					created = new PerModCategoryConfig(instance.config(section, "Misc", Toggle.On, "If on, '" + mod.DisplayName + "' Misc pieces on Feaster go to 'Misc - " + mod.DisplayName + "'. If off, they merge into vanilla Misc.", 400), instance.config(section, "Food", Toggle.On, "If on, '" + mod.DisplayName + "' Food items go to 'Food - " + mod.DisplayName + "'. If off, they merge into vanilla Food.", 300), instance.config(section, "Meads", Toggle.On, "If on, '" + mod.DisplayName + "' Mead items go to 'Meads - " + mod.DisplayName + "'. If off, they merge into vanilla Meads.", 200), instance.config(section, "Feasts", Toggle.On, "If on, '" + mod.DisplayName + "' Feast items go to 'Feasts - " + mod.DisplayName + "'. If off, they merge into vanilla Feasts.", 100));
				});
				PerModConfigs[mod.Id] = created;
				_pendingDynamicConfigSave = true;
				return created;
			}
		}

		internal static void FlushPendingDynamicConfigSave()
		{
			ServerSyncModTemplatePlugin instance = _instance;
			if (!((Object)(object)instance == (Object)null))
			{
				bool pendingDynamicConfigSave;
				lock (DynamicConfigLock)
				{
					pendingDynamicConfigSave = _pendingDynamicConfigSave;
					_pendingDynamicConfigSave = false;
				}
				if (pendingDynamicConfigSave)
				{
					instance.RunWithConfigAutoSaveDisabled(instance.SaveConfigWithoutWatcher);
				}
			}
		}

		private static string SanitizeConfigText(string text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return "Unknown";
			}
			char[] array = text.ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (array[i] == '\r' || "=\n\t\\\"'[]".IndexOf(array[i]) >= 0 || char.IsControl(array[i]))
				{
					array[i] = '_';
				}
			}
			string text2 = new string(array).Trim();
			if (!string.IsNullOrWhiteSpace(text2))
			{
				return text2;
			}
			return "Unknown";
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, string description, int order)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			return ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = order
				}
			}));
		}

		static ServerSyncModTemplatePlugin()
		{
			string configPath = Paths.ConfigPath;
			char directorySeparatorChar = Path.DirectorySeparatorChar;
			ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName;
			ServerSyncModTemplateLogger = Logger.CreateLogSource("ServeYouRight");
			DynamicConfigLock = new object();
			ConfigReloadDelay = TimeSpan.FromSeconds(1.0);
			PerModConfigs = new Dictionary<string, PerModCategoryConfig>(StringComparer.OrdinalIgnoreCase);
		}
	}
	internal sealed class PerModCategoryConfig
	{
		public ConfigEntry<ServerSyncModTemplatePlugin.Toggle> Misc { get; }

		public ConfigEntry<ServerSyncModTemplatePlugin.Toggle> Food { get; }

		public ConfigEntry<ServerSyncModTemplatePlugin.Toggle> Meads { get; }

		public ConfigEntry<ServerSyncModTemplatePlugin.Toggle> Feasts { get; }

		public PerModCategoryConfig(ConfigEntry<ServerSyncModTemplatePlugin.Toggle> misc, ConfigEntry<ServerSyncModTemplatePlugin.Toggle> food, ConfigEntry<ServerSyncModTemplatePlugin.Toggle> meads, ConfigEntry<ServerSyncModTemplatePlugin.Toggle> feasts)
		{
			Misc = misc;
			Food = food;
			Meads = meads;
			Feasts = feasts;
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	public static class ObjectDbAwakePatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(ObjectDB __instance)
		{
			FeasterFoodInjector.BeginWorld();
			FeasterFoodInjector.Inject(__instance);
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	public static class ObjectDbCopyOtherDbPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(ObjectDB __instance)
		{
			FeasterFoodInjector.BeginWorld();
			FeasterFoodInjector.Inject(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "SetPlaceMode")]
	public static class PlayerSetPlaceModePatch
	{
		private static void Postfix(Player __instance, PieceTable buildPieces)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && FeasterFoodInjector.IsFeasterTool(buildPieces))
			{
				FeasterFoodInjector.RefreshFoodPiecesAndMenu(ObjectDB.instance);
			}
		}
	}
	[HarmonyPatch(typeof(Game), "Start")]
	internal static class GameStartedPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			FeasterFoodInjector.BeginWorld();
			FeasterFoodInjector.RefreshFoodPiecesAndMenu(ObjectDB.instance);
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "OnDestroy")]
	internal static class FoodWorldShutdownPatch
	{
		private static void Prefix()
		{
			FoodSourceCatalog.ResetWorld();
			FeasterFoodInjector.ResetWorld();
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "MakePiece")]
	public static class ItemDropMakePiecePatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)((Component)__instance).GetComponent<ServeYouRightInjectedPieceMarker>() == (Object)null)
			{
				return;
			}
			ParticleSystem[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<ParticleSystem>(true);
			foreach (ParticleSystem obj in componentsInChildren)
			{
				obj.Stop(true, (ParticleSystemStopBehavior)0);
				obj.Clear(true);
				EmissionModule emission = obj.emission;
				((EmissionModule)(ref emission)).enabled = false;
				MainModule main = obj.main;
				((MainModule)(ref main)).playOnAwake = false;
				ParticleSystemRenderer component = ((Component)obj).GetComponent<ParticleSystemRenderer>();
				if ((Object)(object)component != (Object)null)
				{
					((Renderer)component).enabled = false;
				}
			}
		}
	}
	internal static class FeasterFoodInjector
	{
		private static bool _isInjecting;

		private static bool _shuttingDown;

		private static readonly Dictionary<Piece, (PieceCategory Category, FoodSourceMod? Source)> FoodGroups = new Dictionary<Piece, (PieceCategory, FoodSourceMod?)>();

		private static readonly Action<Player> UpdateKnownRecipes = AccessTools.MethodDelegate<Action<Player>>(AccessTools.Method(typeof(Player), "UpdateKnownRecipesList", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Action<Player> UpdateAvailablePieces = AccessTools.MethodDelegate<Action<Player>>(AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null), (object)null, true);

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

		internal static void ResetWorld()
		{
			_shuttingDown = true;
			FoodGroups.Clear();
			SourceModHitCache.Clear();
		}

		internal static void BeginWorld()
		{
			_shuttingDown = false;
		}

		internal static void RefreshFoodPiecesAndMenu(ObjectDB? objectDb)
		{
			if (!_shuttingDown && !((Object)(object)objectDb == (Object)null))
			{
				Inject(objectDb);
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					UpdateKnownRecipes(localPlayer);
					UpdateAvailablePieces(localPlayer);
					ServingTrayMenu.Refresh();
				}
			}
		}

		public static void Inject(ObjectDB? objectDb)
		{
			if (_isInjecting || _shuttingDown || (Object)(object)objectDb == (Object)null)
			{
				return;
			}
			_isInjecting = true;
			try
			{
				PieceTable feasterPieceTable = GetFeasterPieceTable(objectDb);
				if (!((Object)(object)feasterPieceTable == (Object)null))
				{
					FoodSourceCatalog.Refresh();
					SourceModHitCache.Clear();
					FoodGroups.Clear();
					List<CandidateFood> candidateFoods = GetCandidateFoods(objectDb);
					int num = InjectIntoTable(feasterPieceTable, candidateFoods);
					UpdateExistingMenuGroups(feasterPieceTable);
					if (num > 0)
					{
						ServerSyncModTemplatePlugin.ServerSyncModTemplateLogger.LogInfo((object)$"Added {num} mod food pieces to feaster table '{((Object)feasterPieceTable).name}'.");
					}
				}
			}
			catch (Exception arg)
			{
				ServerSyncModTemplatePlugin.ServerSyncModTemplateLogger.LogError((object)$"Failed to inject feaster foods: {arg}");
			}
			finally
			{
				_isInjecting = false;
				ServerSyncModTemplatePlugin.FlushPendingDynamicConfigSave();
			}
		}

		private static List<CandidateFood> GetCandidateFoods(ObjectDB objectDb)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Invalid comparison between Unknown and I4
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			List<CandidateFood> list = new List<CandidateFood>();
			FeastRoutingData feastRoutingData = BuildFeastRoutingData(objectDb);
			foreach (GameObject item in objectDb.m_items)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				ItemDrop component = item.GetComponent<ItemDrop>();
				SharedData val = component?.m_itemData?.m_shared;
				if (!((Object)(object)component == (Object)null) && val != null)
				{
					string prefabName = Utils.GetPrefabName(((Object)item).name);
					if (feastRoutingData.MaterialToResultPrefab.TryGetValue(prefabName, out GameObject value))
					{
						list.Add(new CandidateFood(component, value, (PieceCategory)6));
					}
					else if (!feastRoutingData.ResultPrefabNames.Contains(prefabName) && !((Object)(object)((Component)component).GetComponent<Feast>() != (Object)null) && (int)val.m_itemType == 2 && LooksLikeConsumableFood(val))
					{
						PieceCategory category = (PieceCategory)(val.m_isDrink ? 8 : 7);
						list.Add(new CandidateFood(component, ((Component)component).gameObject, category));
					}
				}
			}
			return list;
		}

		private static FeastRoutingData BuildFeastRoutingData(ObjectDB objectDb)
		{
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Invalid comparison between Unknown and I4
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Invalid comparison between Unknown and I4
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Invalid comparison between Unknown and I4
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Invalid comparison between Unknown and I4
			Dictionary<string, ItemDrop> dictionary = new Dictionary<string, ItemDrop>(StringComparer.OrdinalIgnoreCase);
			foreach (GameObject item in objectDb.m_items)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				ItemDrop component = item.GetComponent<ItemDrop>();
				if (!((Object)(object)component == (Object)null))
				{
					string prefabName = Utils.GetPrefabName(((Object)item).name);
					if (!dictionary.ContainsKey(prefabName))
					{
						dictionary[prefabName] = component;
					}
				}
			}
			Dictionary<string, GameObject> dictionary2 = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<string, ItemDrop> item2 in dictionary)
			{
				string key = item2.Key;
				ItemDrop value = item2.Value;
				SharedData val = value.m_itemData?.m_shared;
				Feast component2 = ((Component)value).GetComponent<Feast>();
				if ((Object)(object)component2 == (Object)null || val == null)
				{
					continue;
				}
				ItemDrop foodItem = component2.m_foodItem;
				string text = (((Object)(object)foodItem != (Object)null) ? Utils.GetPrefabName(((Object)((Component)foodItem).gameObject).name) : string.Empty);
				bool flag = !string.IsNullOrWhiteSpace(text) && !string.Equals(text, key, StringComparison.OrdinalIgnoreCase);
				SharedData val2 = foodItem?.m_itemData?.m_shared;
				bool flag2 = (Object)(object)foodItem != (Object)null && ((Object)(object)((Component)foodItem).GetComponent<Feast>() != (Object)null || (val2 != null && ((int)val2.m_itemType == 2 || LooksLikeConsumableFood(val2))));
				if ((int)val.m_itemType == 1)
				{
					if (flag && flag2)
					{
						dictionary2[key] = ((Component)foodItem).gameObject;
						hashSet.Add(text);
					}
				}
				else if (flag && flag2 && (int)val.m_itemType != 2 && !LooksLikeConsumableFood(val))
				{
					dictionary2[key] = ((Component)foodItem).gameObject;
					hashSet.Add(text);
				}
				else
				{
					hashSet.Add(key);
				}
			}
			foreach (KeyValuePair<string, ItemDrop> item3 in dictionary)
			{
				string key2 = item3.Key;
				SharedData val3 = item3.Value.m_itemData?.m_shared;
				if (val3 == null || (int)val3.m_itemType != 1)
				{
					continue;
				}
				ItemDrop appendToolTip = val3.m_appendToolTip;
				if (!((Object)(object)appendToolTip == (Object)null))
				{
					string prefabName2 = Utils.GetPrefabName(((Object)((Component)appendToolTip).gameObject).name);
					if (!string.Equals(prefabName2, key2, StringComparison.OrdinalIgnoreCase) && (hashSet.Contains(prefabName2) || (Object)(object)((Component)appendToolTip).GetComponent<Feast>() != (Object)null))
					{
						dictionary2[key2] = ((Component)appendToolTip).gameObject;
						hashSet.Add(prefabName2);
					}
				}
			}
			return new FeastRoutingData(dictionary2, hashSet);
		}

		private static bool LooksLikeConsumableFood(SharedData shared)
		{
			if (!(shared.m_food > 0f) && !(shared.m_foodStamina > 0f) && !(shared.m_foodEitr > 0f))
			{
				return shared.m_isDrink;
			}
			return true;
		}

		internal static PieceTable? GetFeasterPieceTable(ObjectDB? objectDb)
		{
			GameObject val = (((Object)(object)objectDb != (Object)null) ? objectDb.GetItemPrefab("Feaster") : null);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return val.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_buildPieces;
		}

		internal static bool IsFeasterTool(PieceTable? table)
		{
			if ((Object)(object)table != (Object)null)
			{
				return (Object)(object)table == (Object)(object)GetFeasterPieceTable(ObjectDB.instance);
			}
			return false;
		}

		private static void UpdateExistingMenuGroups(PieceTable table)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Invalid comparison between Unknown and I4
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Invalid comparison between Unknown and I4
			Piece val = default(Piece);
			foreach (GameObject piece in table.m_pieces)
			{
				if ((Object)(object)piece == (Object)null || !piece.TryGetComponent<Piece>(ref val) || FoodGroups.ContainsKey(val) || val.m_repairPiece || val.m_removePiece)
				{
					continue;
				}
				PieceCategory category = val.m_category;
				if ((int)category == 0 || (int)category == 7 || (int)category == 8 || (int)category == 6)
				{
					FoodSourceMod? item = null;
					if (TryResolveFoodSourceMod(piece, out var sourceMod) && ServerSyncModTemplatePlugin.UseModSpecificTab(sourceMod, category))
					{
						item = sourceMod;
					}
					FoodGroups[val] = (category, item);
				}
			}
		}

		private static int InjectIntoTable(PieceTable table, List<CandidateFood> candidates)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			EnsureBaseCategories(table);
			HashSet<int> hashSet = BuildExistingFoodHashSet(table);
			int num = 0;
			foreach (CandidateFood candidate in candidates)
			{
				ItemDrop itemDrop = candidate.ItemDrop;
				GameObject placePrefab = candidate.PlacePrefab;
				int prefabNameHash = GetPrefabNameHash(((Component)itemDrop).gameObject);
				bool num2 = hashSet.Contains(prefabNameHash);
				bool flag = (Object)(object)placePrefab.GetComponent<ServeYouRightInjectedPieceMarker>() != (Object)null;
				if (num2 && !flag)
				{
					continue;
				}
				PieceCategory category = candidate.Category;
				PieceCategory category2 = category;
				if (TryGetTemplate(table, category2, out Piece templatePiece, out WearNTear templateWearNTear) && PrepareFoodPrefab(placePrefab, itemDrop, category2, templatePiece, templateWearNTear))
				{
					FoodSourceMod? item = null;
					if (TryResolveFoodSourceMod(((Component)itemDrop).gameObject, out var sourceMod) && ServerSyncModTemplatePlugin.UseModSpecificTab(sourceMod, category))
					{
						item = sourceMod;
					}
					FoodGroups[placePrefab.GetComponent<Piece>()] = (category, item);
					if (!table.m_pieces.Contains(placePrefab))
					{
						table.m_pieces.Add(placePrefab);
						hashSet.Add(prefabNameHash);
						num++;
					}
				}
			}
			return num;
		}

		private static void EnsureBaseCategories(PieceTable table)
		{
			EnsureBaseCategoryExists(table, (PieceCategory)7, GetBaseCategoryLabel(table, (PieceCategory)7));
			EnsureBaseCategoryExists(table, (PieceCategory)8, GetBaseCategoryLabel(table, (PieceCategory)8));
			EnsureBaseCategoryExists(table, (PieceCategory)6, GetBaseCategoryLabel(table, (PieceCategory)6));
		}

		internal static void GetMenuGroup(PieceTable table, Piece piece, out string key, out string label)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected I4, but got Unknown
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			PieceCategory val = piece.m_category;
			FoodSourceMod? foodSourceMod = null;
			if (FoodGroups.TryGetValue(piece, out (PieceCategory, FoodSourceMod?) value))
			{
				(val, foodSourceMod) = value;
			}
			key = (int)val + ":" + foodSourceMod?.Id;
			label = ResolveCategoryDisplayLabel(val, GetBaseCategoryLabel(table, val));
			if (foodSourceMod.HasValue)
			{
				label = label + " - " + foodSourceMod.Value.DisplayName;
			}
		}

		private static bool TryGetTemplate(PieceTable table, PieceCategory category, out Piece templatePiece, out WearNTear templateWearNTear)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			templatePiece = null;
			templateWearNTear = null;
			Piece val = null;
			WearNTear val2 = null;
			foreach (GameObject piece in table.m_pieces)
			{
				if ((Object)(object)piece == (Object)null)
				{
					continue;
				}
				Piece component = piece.GetComponent<Piece>();
				if ((Object)(object)component == (Object)null || component.m_repairPiece || component.m_removePiece)
				{
					continue;
				}
				WearNTear component2 = piece.GetComponent<WearNTear>();
				if (!((Object)(object)component2 == (Object)null))
				{
					if (component.m_category == category)
					{
						templatePiece = component;
						templateWearNTear = component2;
						return true;
					}
					if ((Object)(object)val == (Object)null)
					{
						val = component;
						val2 = component2;
					}
				}
			}
			if ((Object)(object)val != (Object)null)
			{
				templatePiece = val;
				templateWearNTear = val2;
				return true;
			}
			ServerSyncModTemplatePlugin.ServerSyncModTemplateLogger.LogWarning((object)("No feaster template piece found for table '" + ((Object)table).name + "'."));
			return false;
		}

		private static bool PrepareFoodPrefab(GameObject foodPrefab, ItemDrop sourceItemDrop, PieceCategory category, Piece templatePiece, WearNTear templateWearNTear)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			if ((Object)(object)foodPrefab.GetComponent<ZNetView>() == (Object)null)
			{
				ServerSyncModTemplatePlugin.ServerSyncModTemplateLogger.LogWarning((object)("Skipping '" + ((Object)foodPrefab).name + "' because it has no ZNetView."));
				return false;
			}
			Piece val = foodPrefab.GetComponent<Piece>();
			if ((Object)(object)val == (Object)null)
			{
				val = foodPrefab.AddComponent<Piece>();
				CopyPieceTemplate(templatePiece, val);
			}
			WearNTear component = foodPrefab.GetComponent<WearNTear>();
			if ((Object)(object)component == (Object)null)
			{
				component = foodPrefab.AddComponent<WearNTear>();
				CopyWearNTearTemplate(templateWearNTear, component);
			}
			SharedData shared = sourceItemDrop.m_itemData.m_shared;
			val.m_name = shared.m_name;
			val.m_description = shared.m_description;
			val.m_enabled = true;
			val.m_category = category;
			val.m_repairPiece = false;
			val.m_removePiece = false;
			val.m_resources = (Requirement[])(object)new Requirement[1]
			{
				new Requirement
				{
					m_resItem = sourceItemDrop,
					m_amount = 1,
					m_amountPerLevel = 1,
					m_recover = true
				}
			};
			Sprite[] icons = shared.m_icons;
			if (icons != null && icons.Length > 0)
			{
				val.m_icon = sourceItemDrop.m_itemData.GetIcon();
			}
			if ((Object)(object)foodPrefab.GetComponent<ServeYouRightInjectedPieceMarker>() == (Object)null)
			{
				foodPrefab.AddComponent<ServeYouRightInjectedPieceMarker>();
			}
			return true;
		}

		private static void CopyPieceTemplate(Piece source, Piece destination)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			destination.m_usage = source.m_usage;
			destination.m_targetNonPlayerBuilt = source.m_targetNonPlayerBuilt;
			destination.m_icon = source.m_icon;
			destination.m_isUpgrade = source.m_isUpgrade;
			destination.m_comfort = source.m_comfort;
			destination.m_comfortGroup = source.m_comfortGroup;
			destination.m_comfortObject = source.m_comfortObject;
			destination.m_groundPiece = source.m_groundPiece;
			destination.m_allowAltGroundPlacement = source.m_allowAltGroundPlacement;
			destination.m_groundOnly = source.m_groundOnly;
			destination.m_cultivatedGroundOnly = source.m_cultivatedGroundOnly;
			destination.m_waterPiece = source.m_waterPiece;
			destination.m_clipGround = source.m_clipGround;
			destination.m_clipEverything = source.m_clipEverything;
			destination.m_noInWater = source.m_noInWater;
			destination.m_notOnWood = source.m_notOnWood;
			destination.m_notOnTiltingSurface = source.m_notOnTiltingSurface;
			destination.m_inCeilingOnly = source.m_inCeilingOnly;
			destination.m_notOnFloor = source.m_notOnFloor;
			destination.m_noClipping = source.m_noClipping;
			destination.m_onlyInTeleportArea = source.m_onlyInTeleportArea;
			destination.m_allowedInDungeons = source.m_allowedInDungeons;
			destination.m_spaceRequirement = source.m_spaceRequirement;
			destination.m_canRotate = source.m_canRotate;
			destination.m_randomInitBuildRotation = source.m_randomInitBuildRotation;
			destination.m_canBeRemoved = source.m_canBeRemoved;
			destination.m_canRockJade = source.m_canRockJade;
			destination.m_allowRotatedOverlap = source.m_allowRotatedOverlap;
			destination.m_vegetationGroundOnly = source.m_vegetationGroundOnly;
			destination.m_blockingPieces = ((source.m_blockingPieces == null) ? null : new List<Piece>(source.m_blockingPieces));
			destination.m_blockRadius = source.m_blockRadius;
			destination.m_mustConnectTo = source.m_mustConnectTo;
			destination.m_connectRadius = source.m_connectRadius;
			destination.m_mustBeAboveConnected = source.m_mustBeAboveConnected;
			destination.m_noVines = source.m_noVines;
			destination.m_extraPlacementDistance = source.m_extraPlacementDistance;
			destination.m_onlyInBiome = source.m_onlyInBiome;
			destination.m_harvest = source.m_harvest;
			destination.m_harvestRadius = source.m_harvestRadius;
			destination.m_harvestRadiusMaxLevel = source.m_harvestRadiusMaxLevel;
			destination.m_placeEffect = source.m_placeEffect;
			destination.m_dlc = source.m_dlc;
			destination.m_craftingStation = source.m_craftingStation;
			destination.m_returnResourceHeightOffset = source.m_returnResourceHeightOffset;
			destination.m_destroyedLootPrefab = source.m_destroyedLootPrefab;
		}

		private static void CopyWearNTearTemplate(WearNTear source, WearNTear destination)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			destination.m_onDestroyed = source.m_onDestroyed;
			destination.m_onDamaged = source.m_onDamaged;
			destination.m_new = source.m_new;
			destination.m_worn = source.m_worn;
			destination.m_broken = source.m_broken;
			destination.m_wet = source.m_wet;
			destination.m_noRoofWear = source.m_noRoofWear;
			destination.m_noSupportWear = source.m_noSupportWear;
			destination.m_ashDamageImmune = source.m_ashDamageImmune;
			destination.m_ashDamageResist = source.m_ashDamageResist;
			destination.m_burnable = source.m_burnable;
			destination.m_materialType = source.m_materialType;
			destination.m_supports = source.m_supports;
			destination.m_comOffset = source.m_comOffset;
			destination.m_forceCorrectCOMCalculation = source.m_forceCorrectCOMCalculation;
			destination.m_staticPosition = source.m_staticPosition;
			destination.m_nonSolidRenderers = ((source.m_nonSolidRenderers == null) ? null : new List<Renderer>(source.m_nonSolidRenderers));
			destination.m_health = source.m_health;
			destination.m_damages = source.m_damages;
			destination.m_minToolTier = source.m_minToolTier;
			destination.m_hitNoise = source.m_hitNoise;
			destination.m_destroyNoise = source.m_destroyNoise;
			destination.m_triggerPrivateArea = source.m_triggerPrivateArea;
			destination.m_destroyedEffect = source.m_destroyedEffect;
			destination.m_hitEffect = source.m_hitEffect;
			destination.m_switchEffect = source.m_switchEffect;
			destination.m_autoCreateFragments = source.m_autoCreateFragments;
			destination.m_fragmentRoots = ((source.m_fragmentRoots == null) ? null : ((GameObject[])source.m_fragmentRoots.Clone()));
		}

		private static HashSet<int> BuildExistingFoodHashSet(PieceTable table)
		{
			HashSet<int> hashSet = new HashSet<int>();
			foreach (GameObject piece in table.m_pieces)
			{
				if ((Object)(object)piece == (Object)null)
				{
					continue;
				}
				ItemDrop component = piece.GetComponent<ItemDrop>();
				if ((Object)(object)component == (Object)null)
				{
					continue;
				}
				hashSet.Add(GetPrefabNameHash(((Component)component).gameObject));
				if (!((Object)(object)piece.GetComponent<Feast>() != (Object)null) && !((Object)(object)piece.GetComponent<ServeYouRightInjectedPieceMarker>() != (Object)null))
				{
					continue;
				}
				Requirement[] array = piece.GetComponent<Piece>()?.m_resources ?? Array.Empty<Requirement>();
				foreach (Requirement val in array)
				{
					if ((Object)(object)val?.m_resItem != (Object)null)
					{
						hashSet.Add(GetPrefabNameHash(((Component)val.m_resItem).gameObject));
					}
				}
			}
			return hashSet;
		}

		private static string GetBaseCategoryLabel(PieceTable table, PieceCategory category)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			int num = table.m_categories.IndexOf(category);
			if (num >= 0 && num < table.m_categoryLabels.Count)
			{
				string text = table.m_categoryLabels[num];
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
			}
			return GetFallbackCategoryLabel(category);
		}

		private static string ResolveCategoryDisplayLabel(PieceCategory category, string baseLabel)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			if (!string.IsNullOrWhiteSpace(baseLabel))
			{
				string text = baseLabel.Trim();
				string text2 = text;
				bool flag = LooksLikeLocalizationTokenKey(text);
				if (flag && !text.StartsWith("$", StringComparison.Ordinal))
				{
					text2 = "$" + text;
				}
				if (Localization.instance != null)
				{
					string text3 = Localization.instance.Localize(text2);
					if (!string.IsNullOrWhiteSpace(text3) && !string.Equals(text3, text2, StringComparison.Ordinal))
					{
						return text3.Trim();
					}
				}
				if (flag)
				{
					return GetFallbackCategoryLabel(category);
				}
				return text;
			}
			return GetFallbackCategoryLabel(category);
		}

		private static bool LooksLikeLocalizationTokenKey(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			if (value.StartsWith("$", StringComparison.Ordinal))
			{
				return true;
			}
			if (!value.Contains("_"))
			{
				return false;
			}
			foreach (char c in value)
			{
				if (!char.IsLetterOrDigit(c) && c != '_' && c != '.' && c != '-')
				{
					return false;
				}
			}
			return true;
		}

		private unsafe static string GetFallbackCategoryLabel(PieceCategory category)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected I4, but got Unknown
			return (category - 6) switch
			{
				1 => "Food", 
				2 => "Meads", 
				0 => "Feasts", 
				_ => ((object)(*(PieceCategory*)(&category))/*cast due to .constrained prefix*/).ToString(), 
			};
		}

		private static bool TryResolveFoodSourceMod(GameObject foodPrefab, out FoodSourceMod sourceMod)
		{
			string prefabName = Utils.GetPrefabName(((Object)foodPrefab).name);
			if (SourceModHitCache.TryGetValue(prefabName, out var value))
			{
				sourceMod = value;
				return true;
			}
			if (KnownCloneSourceBridge.TryResolveSourceMod(prefabName, out var sourceMod2))
			{
				sourceMod = sourceMod2;
				SourceModHitCache[prefabName] = sourceMod;
				return true;
			}
			if (FoodSourceCatalog.TryGetOwner(prefabName, out var source))
			{
				sourceMod = source;
				SourceModHitCache[prefabName] = sourceMod;
				return true;
			}
			sourceMod = default(FoodSourceMod);
			return false;
		}

		private static void EnsureBaseCategoryExists(PieceTable table, PieceCategory category, string label)
		{
			//IL_0006: 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_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			if (table.m_categories.IndexOf(category) < 0)
			{
				table.m_categories.Add(category);
				while (table.m_categoryLabels.Count < table.m_categories.Count - 1)
				{
					table.m_categoryLabels.Add(((object)table.m_categories[table.m_categoryLabels.Count]/*cast due to .constrained prefix*/).ToString());
				}
				table.m_categoryLabels.Add(label);
			}
		}

		private static int GetPrefabNameHash(GameObject prefab)
		{
			return StringExtensionMethods.GetStableHashCode(Utils.GetPrefabName(((Object)prefab).name));
		}
	}
	internal readonly struct FoodSourceMod
	{
		public string Id { get; }

		public string DisplayName { get; }

		public FoodSourceMod(string id, string displayName)
		{
			Id = id;
			DisplayName = displayName;
		}
	}
	internal readonly struct CandidateFood
	{
		public ItemDrop ItemDrop { get; }

		public GameObject PlacePrefab { get; }

		public PieceCategory Category { get; }

		public CandidateFood(ItemDrop itemDrop, GameObject placePrefab, PieceCategory category)
		{
			//IL_000f: 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)
			ItemDrop = itemDrop;
			PlacePrefab = placePrefab;
			Category = category;
		}
	}
	internal sealed class FeastRoutingData
	{
		public Dictionary<string, GameObject> MaterialToResultPrefab { get; }

		public HashSet<string> ResultPrefabNames { get; }

		public FeastRoutingData(Dictionary<string, GameObject> materialToResultPrefab, HashSet<string> resultPrefabNames)
		{
			MaterialToResultPrefab = materialToResultPrefab;
			ResultPrefabNames = resultPrefabNames;
		}
	}
	internal static class KnownCloneSourceBridge
	{
		private const string WackyGuid = "WackyMole.WackysDatabase";

		private const string DefaultWackyName = "WackysDatabase";

		private static bool _initialized;

		private static MethodInfo? _wackyGetClonedMap;

		private static string _wackyDisplayName = "WackysDatabase";

		public static bool TryResolveSourceMod(string prefabName, out FoodSourceMod sourceMod)
		{
			sourceMod = default(FoodSourceMod);
			EnsureInitialized();
			if (_wackyGetClonedMap == null)
			{
				return false;
			}
			try
			{
				if (string.IsNullOrWhiteSpace(_wackyGetClonedMap.Invoke(null, new object[1] { prefabName }) as string))
				{
					return false;
				}
				sourceMod = new FoodSourceMod("WackyMole.WackysDatabase", _wackyDisplayName);
				return true;
			}
			catch (Exception ex)
			{
				ServerSyncModTemplatePlugin.ServerSyncModTemplateLogger.LogDebug((object)("Known clone source resolution failed for '" + prefabName + "': " + ex.Message));
				return false;
			}
		}

		private static void EnsureInitialized()
		{
			if (_initialized)
			{
				return;
			}
			_initialized = true;
			try
			{
				_wackyGetClonedMap = Type.GetType("API.WackyAPI, WackysDatabase")?.GetMethod("GetClonedMap", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(string) }, null);
				if (Chainloader.PluginInfos.TryGetValue("WackyMole.WackysDatabase", out var value))
				{
					object obj;
					if (value == null)
					{
						obj = null;
					}
					else
					{
						BepInPlugin metadata = value.Metadata;
						obj = ((metadata != null) ? metadata.Name : null);
					}
					if (obj == null)
					{
						obj = string.Empty;
					}
					string text = (string)obj;
					if (!string.IsNullOrWhiteSpace(text))
					{
						_wackyDisplayName = text;
					}
				}
			}
			catch (Exception ex)
			{
				_wackyGetClonedMap = null;
				ServerSyncModTemplatePlugin.ServerSyncModTemplateLogger.LogDebug((object)("Known clone source bridge init failed: " + ex.Message));
			}
		}
	}
	internal sealed class ServeYouRightInjectedPieceMarker : MonoBehaviour
	{
	}
	internal static class FoodSourceCatalog
	{
		private sealed class BundleContents
		{
			internal readonly AssetBundle Bundle;

			internal readonly string Name;

			internal readonly string[] Paths;

			internal BundleContents(AssetBundle bundle)
			{
				Bundle = bundle;
				Name = ((Object)bundle).name;
				Paths = bundle.GetAllAssetNames();
			}
		}

		private const string DataForgeGuid = "sighsorry.DataForge";

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

		private static readonly HashSet<string> Ambiguous = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<int, BundleContents> Bundles = new Dictionary<int, BundleContents>();

		private static readonly HashSet<string> Vanilla = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> DataForgeClones = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static bool _vanillaLoaded;

		private static MethodInfo? _getCloneSources;

		private static MethodInfo? _getState;

		private static object? _itemsDomain;

		private static EventInfo? _changed;

		private static Delegate? _handler;

		private static string _dataForgeName = "DataForge";

		internal static bool RefreshPending;

		internal static void Initialize()
		{
			if (!Chainloader.PluginInfos.TryGetValue("sighsorry.DataForge", out var value))
			{
				return;
			}
			try
			{
				Type type = ((object)value.Instance).GetType().Assembly.GetType("DataForge.DataForgeApi");
				Type type2 = type?.Assembly.GetType("DataForge.DataForgeDomain");
				if (!(type == null) && !(type2 == null) && (int?)type.GetProperty("ApiVersion")?.GetValue(null) == 1)
				{
					_itemsDomain = Enum.Parse(type2, "Items");
					_getCloneSources = type.GetMethod("GetCloneSources", new Type[1] { type2 });
					_getState = type.GetMethod("GetState", new Type[1] { type2 });
					_changed = type.GetEvent("Changed");
					if (!(_getCloneSources == null) && !(_getState == null) && !(_changed?.EventHandlerType == null))
					{
						Type type3 = _changed.EventHandlerType.GetGenericArguments()[0];
						MethodInfo method = typeof(FoodSourceCatalog).GetMethod("OnChanged", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type3);
						_handler = Delegate.CreateDelegate(_changed.EventHandlerType, method);
						_changed.AddEventHandler(null, _handler);
						_dataForgeName = (string.IsNullOrWhiteSpace(value.Metadata.Name) ? "sighsorry.DataForge" : value.Metadata.Name);
					}
				}
			}
			catch (Exception ex)
			{
				LogFailure("DataForge API binding", ex);
			}
		}

		private static void OnChanged<T>(T change)
		{
			if (change != null && change.GetType().GetProperty("Domain")?.GetValue(change)?.ToString() == "Items")
			{
				RefreshPending = true;
			}
		}

		internal static void Refresh()
		{
			List<AssetOwner> list = new List<AssetOwner>();
			foreach (PluginInfo value2 in Chainloader.PluginInfos.Values)
			{
				try
				{
					Assembly assembly = ((object)value2.Instance)?.GetType().Assembly;
					if (assembly != null)
					{
						list.Add(new AssetOwner(value2.Metadata.GUID, value2.Metadata.Name, assembly.GetName().Name ?? "", assembly.GetManifestResourceNames()));
					}
				}
				catch (Exception ex)
				{
					LogFailure("Plugin resource lookup", ex);
				}
			}
			foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles())
			{
				int instanceID = ((Object)allLoadedAssetBundle).GetInstanceID();
				if (!Bundles.TryGetValue(instanceID, out BundleContents value) || value.Bundle != allLoadedAssetBundle)
				{
					try
					{
						Bundles[instanceID] = new BundleContents(allLoadedAssetBundle);
					}
					catch (Exception ex2)
					{
						LogFailure("Bundle asset lookup", ex2);
					}
				}
			}
			Owners.Clear();
			Ambiguous.Clear();
			foreach (BundleContents value3 in Bundles.Values)
			{
				AssetOwner assetOwner = AssetOwnerMatching.Resolve(value3.Name, list);
				if (assetOwner == null)
				{
					continue;
				}
				string[] paths = value3.Paths;
				foreach (string text in paths)
				{
					if (text.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
					{
						AssetOwnerMatching.Add(Owners, Ambiguous, Path.GetFileNameWithoutExtension(text), assetOwner);
					}
				}
			}
			LoadVanillaNames();
			RefreshCloneSnapshot();
		}

		private static void LoadVanillaNames()
		{
			if (_vanillaLoaded)
			{
				return;
			}
			string path = Path.Combine(Application.dataPath, "StreamingAssets", "SoftRef", "manifest_extended");
			if (!File.Exists(path))
			{
				return;
			}
			try
			{
				foreach (string item in File.ReadLines(path))
				{
					int num = item.IndexOf("path in bundle:", StringComparison.OrdinalIgnoreCase);
					if (num >= 0)
					{
						string text = item.Substring(num + "path in bundle:".Length).Trim();
						if (text.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
						{
							Vanilla.Add(Path.GetFileNameWithoutExtension(text));
						}
					}
				}
				_vanillaLoaded = true;
			}
			catch (Exception ex)
			{
				LogFailure("Vanilla asset lookup", ex);
			}
		}

		private static void RefreshCloneSnapshot()
		{
			DataForgeClones.Clear();
			if (_getState == null || _getCloneSources == null || _itemsDomain == null)
			{
				return;
			}
			try
			{
				object[] parameters = new object[1] { _itemsDomain };
				object obj = _getState.Invoke(null, parameters);
				object obj2 = obj?.GetType().GetProperty("IsReady")?.GetValue(obj);
				if (!(obj2 is bool) || !(bool)obj2 || !(_getCloneSources.Invoke(null, parameters) is IEnumerable enumerable))
				{
					return;
				}
				foreach (object item2 in enumerable)
				{
					if (item2.GetType().GetProperty("Key")?.GetValue(item2) is string item)
					{
						DataForgeClones.Add(item);
					}
				}
			}
			catch (Exception ex)
			{
				LogFailure("DataForge clone lookup", ex);
			}
		}

		internal static bool TryGetOwner(string name, out FoodSourceMod source)
		{
			if (DataForgeClones.Contains(name))
			{
				source = new FoodSourceMod("sighsorry.DataForge", _dataForgeName);
				return true;
			}
			if (!Vanilla.Contains(name) && Owners.TryGetValue(name, out AssetOwner value))
			{
				source = new FoodSourceMod(value.Guid, value.Name);
				return true;
			}
			source = default(FoodSourceMod);
			return false;
		}

		internal static void ResetWorld()
		{
			Owners.Clear();
			Ambiguous.Clear();
			Bundles.Clear();
			DataForgeClones.Clear();
			RefreshPending = false;
		}

		internal static void Dispose()
		{
			try
			{
				if ((object)_handler != null)
				{
					_changed?.RemoveEventHandler(null, _handler);
				}
			}
			catch (Exception ex)
			{
				LogFailure("DataForge event cleanup", ex);
			}
			_handler = null;
			_changed = null;
			_getCloneSources = null;
			_getState = null;
			_itemsDomain = null;
			ResetWorld();
		}

		private static void LogFailure(string operation, Exception ex)
		{
			ServerSyncModTemplatePlugin.ServerSyncModTemplateLogger.LogDebug((object)(operation + " unavailable: " + ex.GetBaseException().Message));
		}
	}
	internal static class ServingTrayMenu
	{
		private sealed class Registration
		{
			private readonly FoodList _categories = new FoodList();

			private IPieceList? _original;

			internal PieceTable? Tool;

			internal bool WasSimple;

			internal bool Opening;

			internal bool UseCategories(List<IPieceList> lists)
			{
				if (lists.Contains((IPieceList)(object)_categories))
				{
					return true;
				}
				int num = lists.FindIndex((IPieceList list) => list is ByUsagePieceList);
				if (num < 0)
				{
					return false;
				}
				_original = lists[num];
				_categories.DisplayName = _original.DisplayName;
				lists[num] = (IPieceList)(object)_categories;
				return true;
			}

			internal void RestoreCategories(List<IPieceList> lists)
			{
				int num = lists.IndexOf((IPieceList)(object)_categories);
				if (num >= 0 && _original != null)
				{
					lists[num] = _original;
				}
				_original = null;
				_categories.Clear();
			}

			internal void Restore(List<IPieceList> lists)
			{
				RestoreCategories(lists);
				if ((Object)(object)Tool != (Object)null && !Tool.m_hideAdvancedMenu)
				{
					Tool.m_hideAdvancedMenu = WasSimple;
				}
				Tool = null;
			}
		}

		private sealed class FoodList : IPieceList
		{
			private readonly Dictionary<string, int> _ids = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

			private readonly List<(int Id, string Label)> _tags = new List<(int, string)>();

			private readonly Dictionary<Piece, int> _pieceTags = new Dictionary<Piece, int>();

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

			private readonly HashSet<Piece> _seen = new HashSet<Piece>();

			public string DisplayName { get; internal set; } = "$hud_byusage";

			public bool ShowTags => true;

			public bool CanCustomizeTags => false;

			public int TagCount => _tags.Count;

			public int TagSeparatorIndex => -1;

			public string GetTagDisplayName(int index)
			{
				return _tags[index].Label;
			}

			public int GetTagIdByIndex(int index)
			{
				return _tags[index].Id;
			}

			internal void Clear()
			{
				_tags.Clear();
				_pieceTags.Clear();
				_availableTags.Clear();
				_seen.Clear();
			}

			public void UpdateAvailableTags(PieceTable table)
			{
				//IL_0063: Unknown result type (might be due to invalid IL or missing references)
				//IL_006a: Invalid comparison between Unknown and I4
				Clear();
				if (!FeasterFoodInjector.IsFeasterTool(table))
				{
					return;
				}
				Piece val = default(Piece);
				foreach (GameObject piece in table.m_pieces)
				{
					if (!((Object)(object)piece == (Object)null) && piece.TryGetComponent<Piece>(ref val) && table.m_availablePieces.Contains(val) && !val.m_repairPiece && !val.m_removePiece && (int)val.m_category != 100)
					{
						FeasterFoodInjector.GetMenuGroup(table, val, out string key, out string label);
						if (!_ids.TryGetValue(key, out var value))
						{
							value = _ids.Count;
							_ids.Add(key, value);
						}
						_pieceTags[val] = value;
						if (_availableTags.Add(value))
						{
							_tags.Add((value, label));
						}
					}
				}
			}

			public void GetAvailablePiecesWithTag(int tagId, PieceTable table, IList<Piece> resultOut)
			{
				//IL_007e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0085: Invalid comparison between Unknown and I4
				if (!FeasterFoodInjector.IsFeasterTool(table))
				{
					return;
				}
				bool flag = _availableTags.Contains(tagId);
				_seen.Clear();
				Piece val = default(Piece);
				foreach (GameObject piece in table.m_pieces)
				{
					if (!((Object)(object)piece == (Object)null) && piece.TryGetComponent<Piece>(ref val) && table.m_availablePieces.Contains(val) && _seen.Add(val) && (!flag || tagId == -1 || val.m_repairPiece || val.m_removePiece || (int)val.m_category == 100 || (_pieceTags.TryGetValue(val, out var value) && value == tagId)))
					{
						resultOut.Add(val);
					}
				}
				_seen.Clear();
			}
		}

		private static readonly FieldRef<BuildUi, List<IPieceList>> Lists = AccessTools.FieldRefAccess<BuildUi, List<IPieceList>>("m_pieceLists");

		private static readonly FieldRef<BuildUi, PieceTable> Tool = AccessTools.FieldRefAccess<BuildUi, PieceTable>("m_currentBuildTool");

		private static readonly FieldRef<BuildUi, int> CurrentList = AccessTools.FieldRefAccess<BuildUi, int>("m_currentPieceList");

		private static readonly Dictionary<BuildUi, Registration> Registrations = new Dictionary<BuildUi, Registration>();

		internal static void BeforeOpen(BuildUi menu)
		{
			if (!Registrations.TryGetValue(menu, out Registration value))
			{
				Registrations.Add(menu, value = new Registration());
			}
			value.Restore(Lists.Invoke(menu));
			Player localPlayer = Player.m_localPlayer;
			PieceTable val = ((localPlayer != null) ? localPlayer.GetBuildTool() : null);
			if (FeasterFoodInjector.IsFeasterTool(val))
			{
				value.Opening = true;
				try
				{
					FeasterFoodInjector.RefreshFoodPiecesAndMenu(ObjectDB.instance);
				}
				finally
				{
					value.Opening = false;
				}
				if (value.UseCategories(Lists.Invoke(menu)))
				{
					value.Tool = val;
					value.WasSimple = val.m_hideAdvancedMenu;
					val.m_hideAdvancedMenu = false;
				}
			}
		}

		internal static void Close(BuildUi menu)
		{
			if (Registrations.TryGetValue(menu, out Registration value))
			{
				value.Restore(Lists.Invoke(menu));
			}
		}

		internal static void Refresh()
		{
			BuildUi val = (((Object)(object)Hud.instance != (Object)null) ? Hud.instance.m_buildUi : null);
			if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled && !((Object)(object)Player.m_localPlayer == (Object)null) && Registrations.TryGetValue(val, out Registration value) && !value.Opening)
			{
				PieceTable val2 = Tool.Invoke(val);
				if (!((Object)(object)val2 != (Object)(object)Player.m_localPlayer.GetBuildTool()) && FeasterFoodInjector.IsFeasterTool(val2))
				{
					val.SelectPieceList(CurrentList.Invoke(val), true);
				}
			}
		}

		internal static void Detach(BuildUi menu, bool destroying)
		{
			if (Registrations.TryGetValue(menu, out Registration value))
			{
				value.Restore(Lists.Invoke(menu));
				Registrations.Remove(menu);
				if (!destroying && ((Behaviour)menu).isActiveAndEnabled && (Object)(object)Player.m_localPlayer != (Object)null)
				{
					menu.SelectPieceList(CurrentList.Invoke(menu), true);
				}
			}
		}

		internal static void Dispose()
		{
			foreach (BuildUi item in new List<BuildUi>(Registrations.Keys))
			{
				if ((Object)(object)item != (Object)null)
				{
					Detach(item, destroying: false);
				}
				else
				{
					Registrations.Remove(item);
				}
			}
		}
	}
	[HarmonyPatch(typeof(BuildUi), "OpenBuildMenu")]
	internal static class TrayMenuOpenPatch
	{
		private static void Prefix(BuildUi __instance)
		{
			ServingTrayMenu.BeforeOpen(__instance);
		}

		private static Exception? Finalizer(BuildUi __instance, Exception? __exception)
		{
			if (__exception != null)
			{
				ServingTrayMenu.Close(__instance);
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(BuildUi), "Close")]
	internal static class TrayMenuClosePatch
	{
		private static void Postfix(BuildUi __instance)
		{
			ServingTrayMenu.Close(__instance);
		}
	}
	[HarmonyPatch(typeof(BuildUi), "OnDestroy")]
	internal static class TrayMenuDestroyPatch
	{
		private static void Prefix(BuildUi __instance)
		{
			ServingTrayMenu.Detach(__instance, destroying: true);
		}
	}
}