Decompiled source of ForgeKit v0.4.10

plugins/ForgeKit.dll

Decompiled 6 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
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 NodeCanvas.DialogueTrees;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ForgeKit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.4.10.0")]
[assembly: AssemblyInformationalVersion("0.4.10+83254ebc1e0f377c73c2296a9412cdb0c1c00f84")]
[assembly: AssemblyProduct("ForgeKit")]
[assembly: AssemblyTitle("ForgeKit")]
[assembly: AssemblyMetadata("BuildStamp", "83254ebc 2026-08-28")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace ForgeKit;

public static class BuildScenes
{
	public static string Resolve(string want, out List<string> similar)
	{
		List<string> list = new List<string>();
		int sceneCountInBuildSettings = SceneManager.sceneCountInBuildSettings;
		for (int i = 0; i < sceneCountInBuildSettings; i++)
		{
			string scenePathByBuildIndex = SceneUtility.GetScenePathByBuildIndex(i);
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(scenePathByBuildIndex);
			if (string.Equals(fileNameWithoutExtension, want, StringComparison.OrdinalIgnoreCase))
			{
				similar = new List<string>();
				return fileNameWithoutExtension;
			}
			list.Add(fileNameWithoutExtension);
		}
		similar = Suggest.Bidirectional(list, want);
		return null;
	}

	public static void Dump(ManualLogSource log)
	{
		int sceneCountInBuildSettings = SceneManager.sceneCountInBuildSettings;
		log.LogMessage((object)$"[SCENEDUMP] {sceneCountInBuildSettings} scenes in build settings.");
		for (int i = 0; i < sceneCountInBuildSettings; i++)
		{
			string scenePathByBuildIndex = SceneUtility.GetScenePathByBuildIndex(i);
			log.LogMessage((object)$"[SCENEDUMP] {i}: {scenePathByBuildIndex}");
		}
	}
}
public static class BuildStamp
{
	public const string Unknown = "unknown";

	private const string Key = "BuildStamp";

	private static string _local;

	public static string Local => _local ?? (_local = Read(typeof(BuildStamp).Assembly));

	public static string Read(Assembly asm)
	{
		if (asm == null)
		{
			return "unknown";
		}
		try
		{
			foreach (AssemblyMetadataAttribute customAttribute in asm.GetCustomAttributes<AssemblyMetadataAttribute>())
			{
				if (customAttribute.Key == "BuildStamp" && !string.IsNullOrEmpty(customAttribute.Value))
				{
					return customAttribute.Value;
				}
			}
		}
		catch
		{
		}
		return "unknown";
	}
}
public sealed class CatalogInfo
{
	public string ModGuid;

	public string ModName;

	public string ModVersion;

	public Func<ConfigFile> ConfigSource;
}
public static class CatalogDump
{
	public const int SchemaVersion = 1;

	private static readonly Dictionary<string, Action> _writers = new Dictionary<string, Action>();

	public static string Dir => Path.Combine(Paths.BepInExRootPath, "forge_catalog");

	public static void Announce(CatalogInfo info, CommandRegistry registry, string channelFile, bool firstLineOnly, float pollSeconds)
	{
		if (info == null || string.IsNullOrEmpty(info.ModGuid) || registry == null)
		{
			return;
		}
		lock (_writers)
		{
			_writers[info.ModGuid] = delegate
			{
				Write(info, registry, channelFile, firstLineOnly, pollSeconds);
			};
		}
	}

	public static void Refresh(string guid)
	{
		Action value;
		lock (_writers)
		{
			_writers.TryGetValue(guid, out value);
		}
		try
		{
			value?.Invoke();
		}
		catch
		{
		}
	}

	public static void RefreshAll()
	{
		List<Action> list;
		lock (_writers)
		{
			list = new List<Action>(_writers.Values);
		}
		foreach (Action item in list)
		{
			try
			{
				item();
			}
			catch
			{
			}
		}
	}

	private static void Write(CatalogInfo info, CommandRegistry registry, string channelFile, bool firstLineOnly, float pollSeconds)
	{
		ForgeJson forgeJson = new ForgeJson();
		forgeJson.BeginObj();
		forgeJson.Prop("schema", 1L);
		forgeJson.Name("mod").BeginObj().Prop("guid", info.ModGuid)
			.Prop("name", info.ModName)
			.Prop("version", info.ModVersion)
			.EndObj();
		forgeJson.Name("channel").BeginObj().Prop("file", channelFile)
			.Prop("firstLineOnly", firstLineOnly)
			.Name("pollSeconds")
			.Value(pollSeconds)
			.Prop("responseProtocol", (!firstLineOnly) ? 1 : 0)
			.EndObj();
		forgeJson.Prop("generatedUtc", DateTime.UtcNow.ToString("o"));
		forgeJson.Name("verbs").BeginArr();
		foreach (VerbSpec spec in registry.Specs)
		{
			forgeJson.BeginObj();
			forgeJson.Prop("verbs", spec.Verbs);
			forgeJson.Prop("help", spec.Help);
			if (!string.IsNullOrEmpty(spec.Tag))
			{
				forgeJson.Prop("tag", spec.Tag);
			}
			forgeJson.Prop("needsPlayer", spec.NeedsPlayer);
			if (spec.MasterOnly)
			{
				forgeJson.Prop("masterOnly", v: true);
			}
			ArgSpec[] array = spec.Args;
			bool flag = false;
			if (array == null)
			{
				try
				{
					array = UsageSpec.Derive(spec.Verbs, spec.Help);
				}
				catch
				{
				}
				flag = array != null;
			}
			if (array != null)
			{
				if (flag)
				{
					forgeJson.Prop("argsDerived", v: true);
				}
				forgeJson.Name("args").BeginArr();
				ArgSpec[] array2 = array;
				foreach (ArgSpec argSpec in array2)
				{
					forgeJson.BeginObj();
					forgeJson.Prop("name", argSpec.Name);
					forgeJson.Prop("type", argSpec.Type);
					if (argSpec.Optional)
					{
						forgeJson.Prop("optional", v: true);
					}
					if (argSpec.Choices != null)
					{
						forgeJson.Prop("choices", argSpec.Choices);
					}
					if (argSpec.Default != null)
					{
						forgeJson.Prop("default", argSpec.Default);
					}
					forgeJson.EndObj();
				}
				forgeJson.EndArr();
			}
			forgeJson.EndObj();
		}
		forgeJson.EndArr();
		ConfigFile val = null;
		try
		{
			val = info.ConfigSource?.Invoke();
		}
		catch
		{
		}
		forgeJson.Name("config").BeginArr();
		if (val != null)
		{
			List<ConfigDefinition> list = new List<ConfigDefinition>(val.Keys);
			foreach (ConfigDefinition item in list)
			{
				ConfigEntryBase val2;
				try
				{
					val2 = val[item];
				}
				catch
				{
					continue;
				}
				forgeJson.BeginObj();
				forgeJson.Prop("section", item.Section);
				forgeJson.Prop("key", item.Key);
				forgeJson.Prop("type", val2.SettingType.Name);
				string v = null;
				string v2 = null;
				try
				{
					v = val2.GetSerializedValue();
				}
				catch
				{
				}
				try
				{
					v2 = TomlTypeConverter.ConvertToString(val2.DefaultValue, val2.SettingType);
				}
				catch
				{
				}
				forgeJson.Prop("value", v);
				forgeJson.Prop("default", v2);
				ConfigDescription description = val2.Description;
				string text = ((description != null) ? description.Description : null);
				if (!string.IsNullOrEmpty(text))
				{
					forgeJson.Prop("description", text);
				}
				if (val2.SettingType.IsEnum)
				{
					forgeJson.Prop("choices", Enum.GetNames(val2.SettingType));
				}
				else
				{
					ConfigDescription description2 = val2.Description;
					if (((description2 != null) ? description2.AcceptableValues : null) != null)
					{
						string text2 = null;
						try
						{
							text2 = val2.Description.AcceptableValues.ToDescriptionString();
						}
						catch
						{
						}
						if (!string.IsNullOrEmpty(text2))
						{
							forgeJson.Prop("acceptable", text2.TrimStart('#', ' '));
						}
					}
				}
				forgeJson.EndObj();
			}
		}
		forgeJson.EndArr();
		forgeJson.Name("customItems").BeginArr();
		foreach (KeyValuePair<string, int> item2 in ItemNameIndex.CustomSnapshot())
		{
			forgeJson.BeginObj().Prop("name", item2.Key).Prop("id", item2.Value)
				.EndObj();
		}
		forgeJson.EndArr();
		forgeJson.EndObj();
		Directory.CreateDirectory(Dir);
		string text3 = Path.Combine(Dir, SafeFileName(info.ModGuid) + ".json");
		string text4 = text3 + ".tmp";
		File.WriteAllText(text4, forgeJson.ToString());
		if (File.Exists(text3))
		{
			File.Delete(text3);
		}
		File.Move(text4, text3);
	}

	private static string SafeFileName(string s)
	{
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		StringBuilder stringBuilder = new StringBuilder(s.Length);
		foreach (char c in s)
		{
			stringBuilder.Append((Array.IndexOf(invalidFileNameChars, c) >= 0) ? '_' : c);
		}
		return stringBuilder.ToString();
	}
}
public static class CfgSkew
{
	public static List<CfgSkewRules.Row> Drifted(ConfigFile cfg, string sectionFilter, out int total)
	{
		List<CfgSkewRules.Row> list = new List<CfgSkewRules.Row>();
		total = 0;
		if (cfg == null)
		{
			return list;
		}
		foreach (ConfigDefinition item in new List<ConfigDefinition>(cfg.Keys))
		{
			if (DevCfg.SectionMatches(sectionFilter, item.Section) || DevCfg.MatchKey(sectionFilter, item.Section, item.Key))
			{
				total++;
				ConfigEntryBase val = cfg[item];
				if (DevCfg.Skew(val, out var shippedDefault))
				{
					list.Add(new CfgSkewRules.Row(item.Section, item.Key, val.GetSerializedValue(), shippedDefault));
				}
			}
		}
		return list;
	}

	public static List<string> Sweep()
	{
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
		{
			PluginInfo value = pluginInfo.Value;
			if (value == null || pluginInfo.Key == null || !pluginInfo.Key.StartsWith("cobalt.", StringComparison.Ordinal))
			{
				continue;
			}
			BaseUnityPlugin instance = value.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				continue;
			}
			ConfigFile config;
			try
			{
				config = instance.Config;
			}
			catch
			{
				continue;
			}
			if (config != null)
			{
				int total;
				List<CfgSkewRules.Row> drifted = Drifted(config, null, out total);
				BepInPlugin metadata = value.Metadata;
				string text = CfgSkewRules.BootLine(((metadata != null) ? metadata.Name : null) ?? pluginInfo.Key, total, drifted);
				if (text != null)
				{
					list.Add(text);
				}
			}
		}
		return list;
	}
}
public static class CfgSkewRules
{
	public readonly struct Row
	{
		public readonly string Section;

		public readonly string Key;

		public readonly string Live;

		public readonly string Default;

		public Row(string section, string key, string live, string def)
		{
			Section = section;
			Key = key;
			Live = live;
			Default = def;
		}
	}

	public const string Tag = "[CFGSKEW]";

	public const int MaxKeys = 8;

	public static bool Differs(string live, string def)
	{
		if (live != null && def != null)
		{
			return !string.Equals(live, def, StringComparison.Ordinal);
		}
		return false;
	}

	public static string Describe(Row r)
	{
		return r.Section + "." + r.Key + "=" + r.Live + " (default " + r.Default + ")";
	}

	public static string BootLine(string mod, int total, IList<Row> drifted, int maxKeys = 8)
	{
		if (drifted == null || drifted.Count == 0)
		{
			return null;
		}
		if (maxKeys < 1)
		{
			maxKeys = 1;
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("[CFGSKEW]").Append(' ').Append(string.IsNullOrEmpty(mod) ? "?" : mod)
			.Append(": ")
			.Append(drifted.Count)
			.Append(" of ")
			.Append(total)
			.Append((total == 1) ? " entry differs" : " entries differ")
			.Append(" from shipped defaults — ");
		int num = Math.Min(maxKeys, drifted.Count);
		for (int i = 0; i < num; i++)
		{
			if (i > 0)
			{
				stringBuilder.Append(", ");
			}
			stringBuilder.Append(Describe(drifted[i]));
		}
		if (drifted.Count > num)
		{
			stringBuilder.Append(", …and ").Append(drifted.Count - num).Append(" more");
		}
		return stringBuilder.ToString();
	}
}
public sealed class CommandChannel
{
	private readonly string _path;

	private readonly ManualLogSource _log;

	private readonly CommandRegistry _registry;

	private readonly float _pollSeconds;

	private readonly bool _allLines;

	private readonly ScriptRunner _runner;

	private float _check;

	private long _stamp;

	private long _length = -1L;

	private readonly CatalogInfo _catalog;

	private int _catalogWrites;

	private float _bootTime = -1f;

	private ForgeOut.Capture _pendingCap;

	private int _pendingVerbs;

	private float _pendingDeadline;

	private const float PendingCaptureMaxSeconds = 120f;

	public string Path => _path;

	public CommandChannel(string fileName, ManualLogSource log, CommandRegistry registry, float pollSeconds, bool allLines, bool primeStamp)
		: this(fileName, log, registry, pollSeconds, allLines, primeStamp, null)
	{
	}

	public CommandChannel(string fileName, ManualLogSource log, CommandRegistry registry, float pollSeconds = 0.5f, bool allLines = true, bool primeStamp = false, CatalogInfo catalog = null)
	{
		_catalog = catalog;
		if (catalog != null)
		{
			CatalogDump.Announce(catalog, registry, fileName, !allLines, pollSeconds);
			ForgeOut.PruneOld();
		}
		_path = System.IO.Path.Combine(Paths.ConfigPath, fileName);
		_log = log;
		_registry = registry;
		_pollSeconds = pollSeconds;
		_allLines = allLines;
		if (primeStamp && File.Exists(_path))
		{
			_stamp = File.GetLastWriteTimeUtc(_path).Ticks;
			_length = new FileInfo(_path).Length;
		}
		_runner = new ScriptRunner(log, Run);
		_registry.Register("script", "Run several verbs with real time between them ('script moveto Bandit 2 ; wait 1 ; swing'; timing: wait <s> | waitframes <n> | waitloaded [s]); one step per frame; a new script replaces the running one.", delegate(string[] args)
		{
			_runner.Start(Tail(args));
		});
		_registry.Register("scriptcancel", "Abort the running script (if any).", delegate
		{
			_runner.Cancel();
		});
		_registry.Register("scriptstatus", "One line: which step the running script is on.", delegate
		{
			_runner.Status();
		});
	}

	private static string Tail(string[] parts)
	{
		if (parts != null && parts.Length >= 2)
		{
			return string.Join(" ", parts, 1, parts.Length - 1);
		}
		return "";
	}

	private static bool IsScriptControl(string cmd)
	{
		int num = cmd.IndexOf(' ');
		string a = ((num < 0) ? cmd : cmd.Substring(0, num));
		if (!string.Equals(a, "scriptcancel", StringComparison.OrdinalIgnoreCase))
		{
			return string.Equals(a, "scriptstatus", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	public void Tick()
	{
		_runner.Pump();
		if (_pendingCap != null)
		{
			bool flag = Time.unscaledTime >= _pendingDeadline;
			bool flag2 = _runner.IsRunning || DeferredReport.IsPending;
			if (!flag2 || flag)
			{
				if (flag && flag2)
				{
					_log.LogWarning((object)($"[CMD] script response closed after {120f:F0}s " + "while the script is STILL running (or a deferred verb report is still owed) — the rest of its output goes to the log only. (Capture released so the channel isn't wedged; 'scriptstatus' still works.)"));
				}
				_pendingCap.Finish(_pendingVerbs);
				_pendingCap = null;
				_pendingVerbs = 0;
			}
		}
		if (_catalog != null && _catalogWrites < 2)
		{
			if (_bootTime < 0f)
			{
				_bootTime = Time.unscaledTime;
			}
			if (_catalogWrites == 0)
			{
				_catalogWrites = 1;
				CatalogDump.Refresh(_catalog.ModGuid);
			}
			else if (Time.unscaledTime - _bootTime > 60f)
			{
				_catalogWrites = 2;
				CatalogDump.Refresh(_catalog.ModGuid);
			}
		}
		if (!(Time.unscaledTime - _check <= _pollSeconds))
		{
			_check = Time.unscaledTime;
			Poll();
		}
	}

	private void Poll()
	{
		try
		{
			if (!File.Exists(_path))
			{
				return;
			}
			long ticks = File.GetLastWriteTimeUtc(_path).Ticks;
			long length = new FileInfo(_path).Length;
			if (ticks == _stamp && length == _length)
			{
				return;
			}
			string[] array = File.ReadAllLines(_path);
			_stamp = ticks;
			_length = length;
			if (_allLines)
			{
				string text = ForgeOut.ParseReqId(array);
				if (_pendingCap != null)
				{
					ForgeOut.Capture capture = ((text != null) ? ForgeOut.Begin(_log, text) : null);
					int num = 0;
					string[] array2 = array;
					foreach (string text2 in array2)
					{
						string text3 = text2.Trim();
						if (text3.Length != 0 && !text3.StartsWith("#") && IsScriptControl(text3))
						{
							Run(text3);
							num++;
						}
					}
					if (num == 0)
					{
						_pendingCap.Suspend();
						try
						{
							_log.LogWarning((object)"[CMD] refused: a script batch is still running (its response file is still open). Use scriptcancel, or wait for it to finish.");
						}
						finally
						{
							_pendingCap.Resume();
						}
					}
					capture?.Finish(num);
					return;
				}
				ForgeOut.Capture capture2 = ((text != null) ? ForgeOut.Begin(_log, text) : null);
				int num2 = 0;
				try
				{
					string[] array3 = array;
					foreach (string text4 in array3)
					{
						string text5 = text4.Trim();
						if (text5.Length != 0 && !text5.StartsWith("#"))
						{
							Run(text5);
							num2++;
						}
					}
					return;
				}
				finally
				{
					if (capture2 != null && (_runner.IsRunning || DeferredReport.IsPending))
					{
						_pendingCap = capture2;
						_pendingVerbs = num2;
						_pendingDeadline = Time.unscaledTime + 120f;
					}
					else
					{
						capture2?.Finish(num2);
					}
				}
			}
			string text6 = null;
			string[] array4 = array;
			foreach (string text7 in array4)
			{
				if (!string.IsNullOrWhiteSpace(text7))
				{
					text6 = text7;
					break;
				}
			}
			if (!string.IsNullOrWhiteSpace(text6))
			{
				Run(text6.Trim());
			}
		}
		catch (Exception ex)
		{
			_log.LogWarning((object)("[CMD] poll error: " + ex.Message));
		}
	}

	public void Run(string cmd)
	{
		using (ModLog.Requested())
		{
			_log.LogMessage((object)("[CMD] run: " + cmd));
			string[] array = cmd.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			string verb = ((array.Length != 0) ? array[0] : "");
			if (_registry.TryGet(verb, out var run))
			{
				run(array);
			}
			else
			{
				_registry.PrintHelp(verb);
			}
		}
	}
}
public sealed class CommandRegistry
{
	private readonly ManualLogSource _log;

	private readonly Dictionary<string, Action<string[]>> _run = new Dictionary<string, Action<string[]>>(StringComparer.OrdinalIgnoreCase);

	private readonly List<VerbSpec> _specs = new List<VerbSpec>();

	public IReadOnlyList<VerbSpec> Specs => _specs;

	public CommandRegistry(ManualLogSource log)
	{
		_log = log;
	}

	public void Register(string verb, string help, Action<string[]> run)
	{
		Register(new string[1] { verb }, help, run);
	}

	public void Register(string[] verbs, string help, Action<string[]> run)
	{
		Register(new VerbSpec
		{
			Verbs = verbs,
			Help = help
		}, run);
	}

	public void Register(VerbSpec spec, Action<string[]> run)
	{
		string[] verbs = spec.Verbs;
		string help = spec.Help;
		string[] array = verbs;
		foreach (string v in array)
		{
			if (_run.ContainsKey(v))
			{
				_log.LogWarning((object)("[CMD] verb '" + v + "' registered twice — the later registration wins (drop the local copy, or Exclude it from the shared pack)."));
			}
			_run[v] = delegate(string[] args)
			{
				try
				{
					run(args);
				}
				catch (Exception arg)
				{
					_log.LogError((object)$"[CMD] '{v}' failed: {arg}");
				}
			};
		}
		_specs.Add(spec);
	}

	public bool TryGet(string verb, out Action<string[]> run)
	{
		return _run.TryGetValue(verb, out run);
	}

	public void PrintHelp(string verb)
	{
		if (string.IsNullOrEmpty(verb) || string.Equals(verb, "help", StringComparison.OrdinalIgnoreCase))
		{
			_log.LogMessage((object)$"[CMD] {_specs.Count} registered verbs:");
		}
		else
		{
			_log.LogWarning((object)$"[CMD] unknown verb '{verb}' — {_specs.Count} registered verbs:");
		}
		foreach (VerbSpec spec in _specs)
		{
			_log.LogMessage((object)("[CMD]   " + spec.JoinedVerbs + " — " + spec.Help));
		}
	}
}
public static class Inventories
{
	public readonly struct ConsumeResult
	{
		public readonly int Requested;

		public readonly int Before;

		public readonly int After;

		public readonly int Shortfall;

		public readonly bool Emptied;

		public readonly bool DestroyRequested;

		public readonly bool OnNonMasterClient;

		public int EffectiveAfter
		{
			get
			{
				if (!Emptied)
				{
					return After;
				}
				return 0;
			}
		}

		public bool Consumed
		{
			get
			{
				if (Shortfall == 0)
				{
					if (!Emptied)
					{
						return After == Before - Requested;
					}
					return true;
				}
				return false;
			}
		}

		public ConsumeResult(int requested, int before, int after, int shortfall, bool emptied, bool destroyRequested, bool onNonMasterClient)
		{
			Requested = requested;
			Before = before;
			After = after;
			Shortfall = shortfall;
			Emptied = emptied;
			DestroyRequested = destroyRequested;
			OnNonMasterClient = onNonMasterClient;
		}

		public string Describe()
		{
			return $"requested={Requested} before={Before} after={After} shortfall={Shortfall} " + $"emptied={Emptied} destroyRequested={DestroyRequested} nonMaster={OnNonMasterClient}";
		}
	}

	public static IEnumerable<Item> All(Character player)
	{
		foreach (var item in AllByContainer(player))
		{
			yield return item.Item;
		}
	}

	public static IEnumerable<(Item Item, string Where)> AllByContainer(Character player)
	{
		CharacterInventory val = (((Object)(object)player != (Object)null) ? player.Inventory : null);
		if ((Object)(object)val == (Object)null)
		{
			yield break;
		}
		HashSet<Item> seen = new HashSet<Item>();
		Bag bag = val.EquippedBag;
		Item[] componentsInChildren;
		if ((Object)(object)val.Pouch != (Object)null)
		{
			componentsInChildren = ((Component)val.Pouch).GetComponentsInChildren<Item>(true);
			foreach (Item val2 in componentsInChildren)
			{
				if ((Object)(object)val2 != (Object)null && (object)val2 != bag && val2.RemainingAmount > 0 && !val2.DestroyWanted && seen.Add(val2))
				{
					yield return (Item: val2, Where: "pouch");
				}
			}
		}
		if (!((Object)(object)bag != (Object)null))
		{
			yield break;
		}
		componentsInChildren = ((Component)bag).GetComponentsInChildren<Item>(true);
		foreach (Item val3 in componentsInChildren)
		{
			if ((Object)(object)val3 != (Object)null && (object)val3 != bag && val3.RemainingAmount > 0 && !val3.DestroyWanted && seen.Add(val3))
			{
				yield return (Item: val3, Where: "bag");
			}
		}
	}

	public static ConsumeResult ConsumeOne(Item item, int qty = 1)
	{
		int remainingAmount = item.RemainingAmount;
		if (qty <= 0)
		{
			return new ConsumeResult(qty, remainingAmount, remainingAmount, 0, emptied: false, destroyRequested: false, PhotonNetwork.isNonMasterClientInRoom);
		}
		int shortfall = item.RemoveQuantity(qty);
		bool isNonMasterClientInRoom = PhotonNetwork.isNonMasterClientInRoom;
		bool destroyRequested = false;
		if (isNonMasterClientInRoom && (!item.HasMultipleUses || item.RemainingAmount <= 0) && (Object)(object)ItemManager.Instance != (Object)null)
		{
			ItemManager.Instance.SendDestroyItem(item.UID);
			item.SetDestroyWanted();
			destroyRequested = true;
		}
		return new ConsumeResult(qty, remainingAmount, item.RemainingAmount, shortfall, remainingAmount <= qty, destroyRequested, isNonMasterClientInRoom);
	}
}
public static class Converge
{
	public static bool To<T>(Func<T> read, T want, Action<T> write, Func<T, T, bool> same = null)
	{
		if (read == null || write == null)
		{
			return false;
		}
		T val = read();
		if (same?.Invoke(val, want) ?? EqualityComparer<T>.Default.Equals(val, want))
		{
			return false;
		}
		write(want);
		return true;
	}
}
public sealed class Stamp<TTarget, TValue> where TTarget : class
{
	private readonly Func<TTarget, bool> _alive;

	private readonly Func<TTarget, TValue> _read;

	private readonly Action<TTarget, TValue> _write;

	private readonly TValue _neutral;

	private readonly Func<TValue, bool> _wantsNothing;

	private readonly Func<TValue, TValue, bool> _same;

	private TTarget _target;

	private TValue _value;

	private bool _warnedForeign;

	public Action<TTarget, TValue, TValue> OnForeignWriter;

	public Action<TTarget, TValue> OnStamp;

	public Action<TTarget, TValue, string> OnWithdraw;

	public string TargetLostReason = "no longer the stamped target";

	public string NothingWantedReason = "nothing is wanted right now";

	public TTarget Target => _target;

	public TValue Value => _value;

	public Stamp(Func<TTarget, bool> alive, Func<TTarget, TValue> read, Action<TTarget, TValue> write, TValue neutral, Func<TValue, bool> wantsNothing = null, Func<TValue, TValue, bool> same = null)
	{
		Stamp<TTarget, TValue> stamp = this;
		_alive = alive ?? throw new ArgumentNullException("alive");
		_read = read ?? throw new ArgumentNullException("read");
		_write = write ?? throw new ArgumentNullException("write");
		_neutral = neutral;
		_same = same ?? new Func<TValue, TValue, bool>(EqualityComparer<TValue>.Default.Equals);
		_wantsNothing = wantsNothing ?? ((Func<TValue, bool>)((TValue v) => stamp._same(v, neutral)));
	}

	public void Sync(TTarget target, TValue want)
	{
		if (_target != null && _target != target)
		{
			Clear(TargetLostReason);
		}
		if (target == null || !_alive(target))
		{
			return;
		}
		TValue val = _read(target);
		if (target == _target && !_same(val, _value) && !_warnedForeign)
		{
			_warnedForeign = true;
			OnForeignWriter?.Invoke(target, val, _value);
		}
		if (_wantsNothing(want))
		{
			if (target == _target)
			{
				Clear(NothingWantedReason);
			}
			return;
		}
		bool flag = false;
		if (!_same(val, want))
		{
			_write.Invoke(target, want);
			OnStamp?.Invoke(target, want);
			flag = true;
		}
		if (flag || target == _target)
		{
			_target = target;
			_value = want;
		}
	}

	public void Clear(string why)
	{
		if (_target != null && _alive(_target) && !_same(_read(_target), _neutral))
		{
			_write.Invoke(_target, _neutral);
			OnWithdraw?.Invoke(_target, _value, why);
		}
		_target = null;
		_value = _neutral;
	}

	public void ResetForeignWarning()
	{
		_warnedForeign = false;
	}
}
public interface ITableSource<TTable> where TTable : class
{
	TTable Table { get; }

	TTable Reload();
}
public sealed class DelegateTableSource<TTable> : ITableSource<TTable> where TTable : class
{
	private readonly Func<TTable> _load;

	private readonly Action<TTable> _announceReload;

	private TTable _table;

	public TTable Table
	{
		get
		{
			if (_table == null)
			{
				_table = _load();
			}
			return _table;
		}
	}

	public DelegateTableSource(Func<TTable> load, Action<TTable> announceReload = null)
	{
		_load = load ?? throw new ArgumentNullException("load");
		_announceReload = announceReload;
	}

	public TTable Reload()
	{
		_table = _load();
		_announceReload?.Invoke(_table);
		return _table;
	}
}
public sealed class DataAxis<TTable> where TTable : class
{
	private readonly ITableSource<TTable> _source;

	private readonly Action<Action> _bootValidateHook;

	private readonly Func<bool> _registryReady;

	private readonly Action _validate;

	public TTable Table => _source.Table;

	public DataAxis(ITableSource<TTable> source, Action<Action> bootValidateHook, Func<bool> registryReady, Action validate)
	{
		_source = source ?? throw new ArgumentNullException("source");
		if (validate != null && (bootValidateHook == null || registryReady == null))
		{
			throw new ArgumentException("a DataAxis validate requires BOTH a boot hook and a registry gate — the gate-less boot check is the exact bug class this type exists to prevent.");
		}
		_bootValidateHook = bootValidateHook;
		_registryReady = registryReady;
		_validate = validate;
	}

	public void Init()
	{
		if (_validate != null)
		{
			_bootValidateHook(Validate);
		}
	}

	public TTable Reload()
	{
		TTable result = _source.Reload();
		if (_validate != null)
		{
			Validate();
		}
		return result;
	}

	public void Validate()
	{
		if (_validate != null && _registryReady())
		{
			_validate();
		}
	}
}
public static class DeferredReport
{
	private static readonly PendingSet _pending = new PendingSet();

	private static readonly DateTime _epoch = DateTime.UtcNow;

	private static double Now => (DateTime.UtcNow - _epoch).TotalSeconds;

	public static bool IsPending => _pending.AnyAt(Now);

	public static IEnumerator Wrap(IEnumerator body, ManualLogSource log, string what)
	{
		long id = _pending.Open(Now);
		return Drive(body, log, what, id);
	}

	private static IEnumerator Drive(IEnumerator body, ManualLogSource log, string what, long id)
	{
		try
		{
			while (true)
			{
				object current;
				try
				{
					if (!body.MoveNext())
					{
						break;
					}
					current = body.Current;
				}
				catch (Exception ex)
				{
					log.LogWarning((object)("[CMD] deferred report '" + what + "' threw: " + ex.Message + " — no contract line for this call."));
					break;
				}
				yield return current;
			}
		}
		finally
		{
			_pending.Close(id);
		}
	}
}
public static class EmbeddedRes
{
	public static string Text(Assembly asm, string suffix, string logTag, ManualLogSource log = null)
	{
		string text = Array.Find(asm.GetManifestResourceNames(), (string n) => n.EndsWith(suffix, StringComparison.OrdinalIgnoreCase));
		if (text == null)
		{
			ManualLogSource obj = log ?? Plugin.Log;
			if (obj != null)
			{
				obj.LogWarning((object)(logTag + " embedded " + suffix + " not found."));
			}
			return "";
		}
		using Stream stream = asm.GetManifestResourceStream(text);
		using StreamReader streamReader = new StreamReader(stream);
		return streamReader.ReadToEnd();
	}

	public static Texture2D Texture(Assembly asm, string suffix, string logTag, ManualLogSource log = null)
	{
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Expected O, but got Unknown
		ManualLogSource val = log ?? Plugin.Log;
		try
		{
			string text = Array.Find(asm.GetManifestResourceNames(), (string n) => n.EndsWith(suffix, StringComparison.OrdinalIgnoreCase));
			if (text == null)
			{
				if (val != null)
				{
					val.LogWarning((object)(logTag + " embedded icon '" + suffix + "' missing from the DLL — keeping the donor icon."));
				}
				return null;
			}
			byte[] array;
			using (Stream stream = asm.GetManifestResourceStream(text))
			{
				using MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				array = memoryStream.ToArray();
			}
			Texture2D val2 = new Texture2D(2, 2, (TextureFormat)4, false);
			if (!ImageConversion.LoadImage(val2, array))
			{
				if (val != null)
				{
					val.LogWarning((object)(logTag + " icon '" + suffix + "' failed to decode."));
				}
				return null;
			}
			((Object)val2).hideFlags = (HideFlags)61;
			return val2;
		}
		catch (Exception ex)
		{
			if (val != null)
			{
				val.LogWarning((object)(logTag + " icon '" + suffix + "' load failed: " + ex.Message));
			}
			return null;
		}
	}
}
public sealed class ForgeJson
{
	private readonly StringBuilder _sb = new StringBuilder(4096);

	private bool _needComma;

	public override string ToString()
	{
		return _sb.ToString();
	}

	private void Sep()
	{
		if (_needComma)
		{
			_sb.Append(',');
		}
		_needComma = false;
	}

	public ForgeJson BeginObj()
	{
		Sep();
		_sb.Append('{');
		return this;
	}

	public ForgeJson EndObj()
	{
		_sb.Append('}');
		_needComma = true;
		return this;
	}

	public ForgeJson BeginArr()
	{
		Sep();
		_sb.Append('[');
		return this;
	}

	public ForgeJson EndArr()
	{
		_sb.Append(']');
		_needComma = true;
		return this;
	}

	public ForgeJson Name(string name)
	{
		Sep();
		WriteString(name);
		_sb.Append(':');
		_needComma = false;
		return this;
	}

	public ForgeJson Value(string v)
	{
		Sep();
		if (v == null)
		{
			_sb.Append("null");
		}
		else
		{
			WriteString(v);
		}
		_needComma = true;
		return this;
	}

	public ForgeJson Value(bool v)
	{
		Sep();
		_sb.Append(v ? "true" : "false");
		_needComma = true;
		return this;
	}

	public ForgeJson Value(long v)
	{
		Sep();
		_sb.Append(v.ToString(CultureInfo.InvariantCulture));
		_needComma = true;
		return this;
	}

	public ForgeJson Value(double v)
	{
		Sep();
		if (double.IsNaN(v) || double.IsInfinity(v))
		{
			_sb.Append("null");
		}
		else
		{
			_sb.Append(v.ToString("R", CultureInfo.InvariantCulture));
		}
		_needComma = true;
		return this;
	}

	public ForgeJson Prop(string name, string v)
	{
		return Name(name).Value(v);
	}

	public ForgeJson Prop(string name, bool v)
	{
		return Name(name).Value(v);
	}

	public ForgeJson Prop(string name, long v)
	{
		return Name(name).Value(v);
	}

	public ForgeJson Prop(string name, string[] items)
	{
		Name(name).BeginArr();
		if (items != null)
		{
			foreach (string v in items)
			{
				Value(v);
			}
		}
		return EndArr();
	}

	private void WriteString(string s)
	{
		_sb.Append('"');
		foreach (char c in s)
		{
			switch (c)
			{
			case '"':
				_sb.Append("\\\"");
				continue;
			case '\\':
				_sb.Append("\\\\");
				continue;
			case '\b':
				_sb.Append("\\b");
				continue;
			case '\f':
				_sb.Append("\\f");
				continue;
			case '\n':
				_sb.Append("\\n");
				continue;
			case '\r':
				_sb.Append("\\r");
				continue;
			case '\t':
				_sb.Append("\\t");
				continue;
			}
			if (c < ' ')
			{
				StringBuilder stringBuilder = _sb.Append("\\u");
				int num = c;
				stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture));
			}
			else
			{
				_sb.Append(c);
			}
		}
		_sb.Append('"');
	}
}
public static class ForgeOut
{
	public sealed class Capture : ILogListener, IDisposable
	{
		private readonly ILogSource _source;

		private readonly string _reqid;

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

		private bool _done;

		private bool _suspended;

		public void Suspend()
		{
			_suspended = true;
		}

		public void Resume()
		{
			_suspended = false;
		}

		internal Capture(ManualLogSource source, string reqid)
		{
			_source = (ILogSource)(object)source;
			_reqid = reqid;
			Logger.Listeners.Add((ILogListener)(object)this);
		}

		public void LogEvent(object sender, LogEventArgs e)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (_suspended || e.Source != _source)
			{
				return;
			}
			lock (_lines)
			{
				_lines.Add($"[{e.Level,-7}] {e.Data}");
			}
		}

		public void Finish(int verbsRun)
		{
			if (_done)
			{
				return;
			}
			_done = true;
			Logger.Listeners.Remove((ILogListener)(object)this);
			try
			{
				Directory.CreateDirectory(Dir);
				StringBuilder stringBuilder = new StringBuilder();
				lock (_lines)
				{
					foreach (string line in _lines)
					{
						stringBuilder.AppendLine(line);
					}
				}
				stringBuilder.AppendLine($"#forge done reqid={_reqid} verbs={verbsRun}");
				string text = Path.Combine(Dir, _reqid + ".txt");
				string text2 = text + ".tmp";
				File.WriteAllText(text2, stringBuilder.ToString());
				if (File.Exists(text))
				{
					try
					{
						File.Replace(text2, text, null);
						return;
					}
					catch (IOException)
					{
						File.Delete(text);
						File.Move(text2, text);
						return;
					}
					catch (PlatformNotSupportedException)
					{
						File.Delete(text);
						File.Move(text2, text);
						return;
					}
				}
				File.Move(text2, text);
			}
			catch
			{
			}
		}

		public void Dispose()
		{
			Finish(0);
		}
	}

	public static string Dir => Path.Combine(Paths.BepInExRootPath, "forge_out");

	public static string ParseReqId(string[] lines)
	{
		if (lines == null)
		{
			return null;
		}
		foreach (string text in lines)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				continue;
			}
			string text2 = text.Trim();
			if (!text2.StartsWith("#forge reqid=", StringComparison.OrdinalIgnoreCase))
			{
				return null;
			}
			string text3 = text2.Substring("#forge reqid=".Length).Trim();
			if (text3.Length == 0 || text3.Length > 64)
			{
				return null;
			}
			string text4 = text3;
			foreach (char c in text4)
			{
				if (!char.IsLetterOrDigit(c) && c != '-' && c != '_')
				{
					return null;
				}
			}
			return text3;
		}
		return null;
	}

	public static Capture Begin(ManualLogSource source, string reqid)
	{
		return new Capture(source, reqid);
	}

	public static void PruneOld()
	{
		try
		{
			if (!Directory.Exists(Dir))
			{
				return;
			}
			DateTime dateTime = DateTime.UtcNow.AddHours(-24.0);
			string[] files = Directory.GetFiles(Dir, "*.txt");
			foreach (string path in files)
			{
				try
				{
					if (File.GetLastWriteTimeUtc(path) < dateTime)
					{
						File.Delete(path);
					}
				}
				catch
				{
				}
			}
		}
		catch
		{
		}
	}
}
public static class IdPool
{
	private static readonly Dictionary<int, string> _claimed = new Dictionary<int, string>();

	public static readonly int[][] Ranges = new int[1][] { new int[2] { 87000, 87999 } };

	public static readonly int[][] Forbidden = new int[1][] { new int[2] { 91007000, 91009999 } };

	public static bool InPool(int id)
	{
		int[][] ranges = Ranges;
		foreach (int[] array in ranges)
		{
			if (id >= array[0] && id <= array[1])
			{
				return true;
			}
		}
		return false;
	}

	public static bool IsForbidden(int id)
	{
		int[][] forbidden = Forbidden;
		foreach (int[] array in forbidden)
		{
			if (id >= array[0] && id <= array[1])
			{
				return true;
			}
		}
		return false;
	}

	public static bool Claim(int id, string what, Action<string> refuse)
	{
		if (IsForbidden(id))
		{
			refuse?.Invoke($"[IDS] {what}: id {id} is from the RETIRED block — those numbers were " + "re-minted and are dead. Not registering it.");
			return false;
		}
		if (!InPool(id))
		{
			refuse?.Invoke($"[IDS] {what}: id {id} is outside the range allocated to these mods " + "(" + RangeText() + "). Registering it would collide with whatever mod DOES own that number, in a save that has already written it down. Not registering it.");
			return false;
		}
		if (_claimed.TryGetValue(id, out var value) && value != what)
		{
			refuse?.Invoke($"[IDS] {what}: id {id} was already registered this session by '{value}'. " + "Two things cannot share one id. Not registering the second.");
			return false;
		}
		_claimed[id] = what;
		return true;
	}

	public static bool ClaimShared(int id, string what, Action<string> warn)
	{
		if (IsForbidden(id))
		{
			warn?.Invoke($"[IDS] {what}: id {id} is from a retired block — not registering it.");
			return false;
		}
		if (_claimed.TryGetValue(id, out var value) && value != what)
		{
			warn?.Invoke($"[IDS] {what}: id {id} was already registered this session by '{value}'. " + "Two things cannot share one id. Not registering the second.");
			return false;
		}
		if (!InPool(id))
		{
			warn?.Invoke($"[IDS] {what}: id {id} is outside this workspace's allocation ({RangeText()}) " + "— assuming it comes from your own. Registering it.");
		}
		_claimed[id] = what;
		return true;
	}

	public static IEnumerable<string> Census()
	{
		yield return $"[IDS] pool {RangeText()} — {_claimed.Count} id(s) claimed this session";
		foreach (KeyValuePair<int, string> item in _claimed)
		{
			yield return $"[IDS]   {item.Key}  {item.Value}";
		}
	}

	public static void Reset()
	{
		_claimed.Clear();
	}

	private static string RangeText()
	{
		List<string> list = new List<string>();
		int[][] ranges = Ranges;
		foreach (int[] array in ranges)
		{
			list.Add(array[0] + "-" + array[1]);
		}
		return string.Join(", ", list.ToArray());
	}
}
public static class ItemNameIndex
{
	private static Dictionary<string, int> _index;

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

	public static int Count => Ensure().Count + _custom.Count;

	public static void RegisterCustom(string displayName, int itemId)
	{
		if (!string.IsNullOrEmpty(displayName))
		{
			_custom[displayName.Trim()] = itemId;
		}
	}

	public static List<KeyValuePair<string, int>> CustomSnapshot()
	{
		return new List<KeyValuePair<string, int>>(_custom);
	}

	public static bool TryResolve(string displayName, out int itemId)
	{
		if (string.IsNullOrEmpty(displayName))
		{
			itemId = 0;
			return false;
		}
		string key = displayName.Trim();
		if (_custom.TryGetValue(key, out itemId))
		{
			return true;
		}
		return Ensure().TryGetValue(key, out itemId);
	}

	public static bool TryResolveCatalog(string candidate, out int itemId, out string via)
	{
		Dictionary<string, Item> iTEM_PREFABS = ResourcesPrefabManager.ITEM_PREFABS;
		if (iTEM_PREFABS == null)
		{
			itemId = 0;
			via = null;
			return false;
		}
		foreach (KeyValuePair<string, Item> item in iTEM_PREFABS)
		{
			Item value = item.Value;
			if (!((Object)(object)value == (Object)null))
			{
				string text = (((Object)(object)((Component)value).gameObject != (Object)null) ? ((Object)((Component)value).gameObject).name : null);
				if (text != null && (string.Equals(text, candidate, StringComparison.OrdinalIgnoreCase) || text.EndsWith("_" + candidate, StringComparison.OrdinalIgnoreCase)))
				{
					itemId = value.ItemID;
					via = "prefab name '" + candidate + "'";
					return true;
				}
			}
		}
		if (TryResolve(candidate, out var itemId2))
		{
			itemId = itemId2;
			via = "display name '" + candidate + "'";
			return true;
		}
		itemId = 0;
		via = null;
		return false;
	}

	public static bool TryResolveArg(string key, Func<Item, bool> filter, string noun, out int itemId, out string via, out string problem)
	{
		itemId = 0;
		via = null;
		problem = null;
		string text = key?.Trim();
		if (string.IsNullOrEmpty(text))
		{
			problem = "no name or ItemID given.";
			return false;
		}
		if (DevNum.TryInt(text, out itemId))
		{
			via = "numeric id";
			return true;
		}
		if (filter == null)
		{
			if (TryResolveCatalog(text, out itemId, out via))
			{
				return true;
			}
		}
		else if (TryResolveFiltered(text, filter, out itemId, out via))
		{
			return true;
		}
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, int> item in Suggest(text, 8, filter))
		{
			list.Add($"'{item.Key}' ({item.Value})");
		}
		string text2 = (string.IsNullOrEmpty(noun) ? "matched nothing" : ("matched no " + noun));
		problem = "'" + text + "' " + text2 + " — " + ((list.Count > 0) ? ("did you mean: " + string.Join(", ", list.ToArray())) : "no similar display names") + ".";
		itemId = 0;
		return false;
	}

	private static bool TryResolveFiltered(string candidate, Func<Item, bool> filter, out int itemId, out string via)
	{
		itemId = 0;
		via = null;
		Dictionary<string, Item> iTEM_PREFABS = ResourcesPrefabManager.ITEM_PREFABS;
		if (iTEM_PREFABS == null)
		{
			return false;
		}
		int num = 0;
		int num2 = 0;
		foreach (Item value in iTEM_PREFABS.Values)
		{
			if (!((Object)(object)value == (Object)null) && filter(value))
			{
				string text = (((Object)(object)((Component)value).gameObject != (Object)null) ? ((Object)((Component)value).gameObject).name : null);
				if (text != null && (string.Equals(text, candidate, StringComparison.OrdinalIgnoreCase) || text.EndsWith("_" + candidate, StringComparison.OrdinalIgnoreCase)) && (num == 0 || value.ItemID < num))
				{
					num = value.ItemID;
				}
				if (!string.IsNullOrEmpty(value.Name) && string.Equals(value.Name.Trim(), candidate, StringComparison.OrdinalIgnoreCase) && (num2 == 0 || value.ItemID < num2))
				{
					num2 = value.ItemID;
				}
			}
		}
		if (num != 0)
		{
			itemId = num;
			via = "prefab name '" + candidate + "'";
			return true;
		}
		if (num2 != 0)
		{
			itemId = num2;
			via = "display name '" + candidate + "'";
			return true;
		}
		return false;
	}

	public static List<KeyValuePair<string, int>> Suggest(string fragment, int max = 8, Func<Item, bool> filter = null)
	{
		List<KeyValuePair<string, int>> result = new List<KeyValuePair<string, int>>();
		if (string.IsNullOrEmpty(fragment))
		{
			return result;
		}
		string needle = fragment.Trim();
		if (needle.Length == 0)
		{
			return result;
		}
		result = ForgeKit.Suggest.TwoPass(new IEnumerable<KeyValuePair<string, int>>[2]
		{
			Ensure(),
			_custom
		}, (KeyValuePair<string, int> kv, Suggest.Pass pass) => ForgeKit.Suggest.Matches(kv.Key, needle, pass) && Passes(kv.Value, filter));
		result.Sort(delegate(KeyValuePair<string, int> a, KeyValuePair<string, int> b)
		{
			int num = string.Compare(a.Key, b.Key, StringComparison.OrdinalIgnoreCase);
			return (num == 0) ? a.Value.CompareTo(b.Value) : num;
		});
		if (result.Count > max)
		{
			result.RemoveRange(max, result.Count - max);
		}
		return result;
	}

	private static bool Passes(int itemId, Func<Item, bool> filter)
	{
		if (filter == null)
		{
			return true;
		}
		ResourcesPrefabManager instance = ResourcesPrefabManager.Instance;
		Item val = ((instance != null) ? instance.GetItemPrefab(itemId) : null);
		if ((Object)(object)val != (Object)null)
		{
			return filter(val);
		}
		return false;
	}

	private static Dictionary<string, int> Ensure()
	{
		if (_index != null)
		{
			return _index;
		}
		Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
		Dictionary<string, Item> iTEM_PREFABS = ResourcesPrefabManager.ITEM_PREFABS;
		if (iTEM_PREFABS == null)
		{
			return dictionary;
		}
		foreach (Item value2 in iTEM_PREFABS.Values)
		{
			if (!((Object)(object)value2 == (Object)null) && !string.IsNullOrEmpty(value2.Name))
			{
				string key = value2.Name.Trim();
				if (!dictionary.TryGetValue(key, out var value) || value2.ItemID < value)
				{
					dictionary[key] = value2.ItemID;
				}
			}
		}
		if (dictionary.Count > 0)
		{
			_index = dictionary;
		}
		return dictionary;
	}
}
internal static class KeybindRegistry
{
	internal struct Bound
	{
		public string Mod;

		public string Action;

		public string ConfigHint;

		public string MainKey;

		public string[] Modifiers;
	}

	internal const string NoneKey = "None";

	private static readonly Dictionary<string, Bound> _claims = new Dictionary<string, Bound>();

	internal static int Count => _claims.Count;

	internal static void Reset()
	{
		_claims.Clear();
	}

	internal static bool SameCombo(string aMain, string[] aMods, string bMain, string[] bMods)
	{
		if (aMain != bMain)
		{
			return false;
		}
		List<string> list = new List<string>(aMods ?? new string[0]);
		List<string> list2 = new List<string>(bMods ?? new string[0]);
		if (list.Count != list2.Count)
		{
			return false;
		}
		list.Sort(StringComparer.Ordinal);
		list2.Sort(StringComparer.Ordinal);
		for (int i = 0; i < list.Count; i++)
		{
			if (list[i] != list2[i])
			{
				return false;
			}
		}
		return true;
	}

	internal static string ComboText(string mainKey, string[] modifiers)
	{
		StringBuilder stringBuilder = new StringBuilder();
		if (modifiers != null)
		{
			foreach (string value in modifiers)
			{
				stringBuilder.Append(value).Append('+');
			}
		}
		return stringBuilder.Append(mainKey).ToString();
	}

	internal static string Claim(string mod, string action, string mainKey, string[] modifiers, string configHint)
	{
		string text = mod + "|" + action;
		if (mainKey == "None")
		{
			_claims.Remove(text);
			return null;
		}
		_claims[text] = new Bound
		{
			Mod = mod,
			Action = action,
			ConfigHint = configHint,
			MainKey = mainKey,
			Modifiers = (modifiers ?? new string[0])
		};
		List<Bound> list = Others(mainKey, modifiers, text);
		if (list.Count == 0)
		{
			return null;
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("[KEYBIND] CONFLICT on " + ComboText(mainKey, modifiers) + ": ");
		stringBuilder.Append(Describe(_claims[text]));
		foreach (Bound item in list)
		{
			stringBuilder.Append(" AND ").Append(Describe(item));
		}
		stringBuilder.Append(". One keypress fires ALL of them — that is a real gameplay bug, not just noise ");
		stringBuilder.Append("(Bug 26: opening the spawn menu silently cast Hunt as One). Rebind one of them: ");
		stringBuilder.Append(RebindAdvice(mainKey, modifiers, text, list));
		return stringBuilder.ToString();
	}

	internal static string Report()
	{
		if (_claims.Count == 0)
		{
			return "[KEYBIND] no keys claimed.";
		}
		SortedDictionary<string, List<Bound>> sortedDictionary = new SortedDictionary<string, List<Bound>>(StringComparer.Ordinal);
		foreach (Bound value2 in _claims.Values)
		{
			string key = ComboText(value2.MainKey, value2.Modifiers);
			if (!sortedDictionary.TryGetValue(key, out var value))
			{
				value = (sortedDictionary[key] = new List<Bound>());
			}
			value.Add(value2);
		}
		StringBuilder stringBuilder = new StringBuilder("[KEYBIND] claimed keys:");
		int num = 0;
		foreach (KeyValuePair<string, List<Bound>> item in sortedDictionary)
		{
			bool flag = item.Value.Count > 1;
			if (flag)
			{
				num++;
			}
			stringBuilder.Append(string.Format("\n  {0,-12}{1}", item.Key, flag ? "*** CONFLICT *** " : ""));
			for (int i = 0; i < item.Value.Count; i++)
			{
				stringBuilder.Append((i == 0) ? "" : " AND ").Append(Describe(item.Value[i]));
			}
		}
		stringBuilder.Append($"\n  {num} conflict(s) across {sortedDictionary.Count} key(s).");
		return stringBuilder.ToString();
	}

	internal static bool IsFree(string mainKey, string[] modifiers)
	{
		if (mainKey == "None")
		{
			return true;
		}
		foreach (KeyValuePair<string, Bound> claim in _claims)
		{
			if (SameCombo(claim.Value.MainKey, claim.Value.Modifiers, mainKey, modifiers))
			{
				return false;
			}
		}
		return true;
	}

	internal static bool HasConflicts()
	{
		foreach (KeyValuePair<string, Bound> claim in _claims)
		{
			if (Others(claim.Value.MainKey, claim.Value.Modifiers, claim.Key).Count > 0)
			{
				return true;
			}
		}
		return false;
	}

	private static List<Bound> Others(string mainKey, string[] modifiers, string selfId)
	{
		List<Bound> list = new List<Bound>();
		foreach (KeyValuePair<string, Bound> claim in _claims)
		{
			if (claim.Key != selfId && SameCombo(claim.Value.MainKey, claim.Value.Modifiers, mainKey, modifiers))
			{
				list.Add(claim.Value);
			}
		}
		return list;
	}

	private static string Describe(Bound c)
	{
		return c.Mod + " '" + c.Action + "'" + ((c.ConfigHint == null) ? " (HARDCODED — cannot be rebound)" : (" (" + c.ConfigHint + ")"));
	}

	private static string RebindAdvice(string mainKey, string[] modifiers, string selfId, List<Bound> others)
	{
		List<Bound> list = new List<Bound>();
		Bound item = _claims[selfId];
		if (item.ConfigHint != null)
		{
			list.Add(item);
		}
		foreach (Bound other in others)
		{
			if (other.ConfigHint != null)
			{
				list.Add(other);
			}
		}
		if (list.Count == 0)
		{
			return "BOTH are hardcoded — this needs a code fix, there is nothing the player can do about " + ComboText(mainKey, modifiers) + ".";
		}
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < list.Count; i++)
		{
			stringBuilder.Append((i == 0) ? "" : " or ").Append(list[i].Mod + "'s " + list[i].ConfigHint);
		}
		return stringBuilder.ToString() + ".";
	}
}
public static class Keybinds
{
	private static readonly HashSet<ConfigEntry<KeyboardShortcut>> _subscribed = new HashSet<ConfigEntry<KeyboardShortcut>>();

	private static ManualLogSource L => Plugin.Log;

	private static string Main(KeyboardShortcut c)
	{
		//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)
		return ((object)((KeyboardShortcut)(ref c)).MainKey/*cast due to .constrained prefix*/).ToString();
	}

	private static string[] Mods(KeyboardShortcut c)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		List<string> list = new List<string>();
		foreach (KeyCode modifier in ((KeyboardShortcut)(ref c)).Modifiers)
		{
			list.Add(((object)modifier/*cast due to .constrained prefix*/).ToString());
		}
		return list.ToArray();
	}

	public static void Claim(string mod, string action, ConfigEntry<KeyboardShortcut> entry)
	{
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		if (entry == null)
		{
			ManualLogSource l = L;
			if (l != null)
			{
				l.LogWarning((object)("[KEYBINDS] " + mod + "/" + action + ": no config entry — key NOT registered, so a collision on it cannot be detected."));
			}
			return;
		}
		string hint = "[" + ((ConfigEntryBase)entry).Definition.Section + "] " + ((ConfigEntryBase)entry).Definition.Key;
		Claim(mod, action, entry.Value, hint);
		if (_subscribed.Add(entry))
		{
			entry.SettingChanged += delegate
			{
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				Claim(mod, action, entry.Value, hint);
			};
		}
	}

	public static void Claim(string mod, string action, KeyCode key, string configHint)
	{
		//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)
		Keybinds.Claim(mod, action, new KeyboardShortcut(key, Array.Empty<KeyCode>()), configHint);
	}

	public static void Claim(string mod, string action, KeyboardShortcut combo, string configHint)
	{
		//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)
		string text = KeybindRegistry.Claim(mod, action, Main(combo), Mods(combo), configHint);
		if (text != null)
		{
			ManualLogSource l = L;
			if (l != null)
			{
				l.LogWarning((object)text);
			}
		}
	}

	public static string Report()
	{
		return KeybindRegistry.Report();
	}

	public static bool IsFree(KeyboardShortcut combo)
	{
		//IL_0000: 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)
		return KeybindRegistry.IsFree(Main(combo), Mods(combo));
	}

	public static bool IsFree(KeyCode key)
	{
		//IL_0000: 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)
		return Keybinds.IsFree(new KeyboardShortcut(key, Array.Empty<KeyCode>()));
	}

	public static bool HasConflicts()
	{
		return KeybindRegistry.HasConflicts();
	}
}
public static class KitContract
{
	public readonly struct Declaration
	{
		public readonly string Consumer;

		public readonly string KitGuid;

		public readonly string BuiltAgainst;

		public Declaration(string consumer, string kitGuid, string builtAgainst)
		{
			Consumer = consumer;
			KitGuid = kitGuid;
			BuiltAgainst = builtAgainst;
		}
	}

	public const string CompatSinceField = "COMPAT_SINCE";

	public const string GuidPrefix = "cobalt.";

	private static readonly List<Declaration> s_declared = new List<Declaration>();

	public static IReadOnlyList<Declaration> Declared => s_declared;

	public static void Declare(string consumer, string kitGuid, string builtAgainstVersion)
	{
		if (string.IsNullOrEmpty(consumer) || string.IsNullOrEmpty(kitGuid))
		{
			return;
		}
		for (int i = 0; i < s_declared.Count; i++)
		{
			if (s_declared[i].Consumer == consumer && s_declared[i].KitGuid == kitGuid)
			{
				return;
			}
		}
		s_declared.Add(new Declaration(consumer, kitGuid, builtAgainstVersion ?? ""));
	}

	public static List<KeyValuePair<bool, string>> Report(out bool anyError)
	{
		anyError = false;
		List<KeyValuePair<bool, string>> list = new List<KeyValuePair<bool, string>>();
		if (s_declared.Count == 0)
		{
			list.Add(new KeyValuePair<bool, string>(key: false, "[CONTRACT] no consumer declared a kit version. Either only kits are installed, or every consumer predates KitContract — the handshake cannot check anything; see [STAMP] below."));
			return list;
		}
		foreach (Declaration item in s_declared)
		{
			string kitName = item.KitGuid;
			string text = null;
			string compatSince = null;
			if (Chainloader.PluginInfos.TryGetValue(item.KitGuid, out var value) && value != null)
			{
				BepInPlugin metadata = value.Metadata;
				kitName = ((metadata != null) ? metadata.Name : null) ?? item.KitGuid;
				BepInPlugin metadata2 = value.Metadata;
				text = ((metadata2 == null) ? null : metadata2.Version?.ToString());
				BaseUnityPlugin instance = value.Instance;
				compatSince = ReadConst(((Object)(object)instance != (Object)null) ? ((object)instance).GetType() : null, "COMPAT_SINCE");
			}
			KitContractRules.Verdict v = ((text == null) ? KitContractRules.Verdict.Unknown : KitContractRules.Judge(item.BuiltAgainst, text, compatSince));
			bool flag = KitContractRules.IsError(v);
			anyError |= flag;
			list.Add(new KeyValuePair<bool, string>(flag, (text == null) ? ("[CONTRACT] " + item.Consumer + " built against " + item.KitGuid + " " + item.BuiltAgainst + " — kit not loaded (BepInEx refused or skipped it; see the chainloader lines above).") : KitContractRules.Describe(item.Consumer, kitName, item.BuiltAgainst, text, compatSince, v)));
		}
		return list;
	}

	public static string StampCensus(out bool skew)
	{
		SortedDictionary<string, List<string>> sortedDictionary = new SortedDictionary<string, List<string>>(StringComparer.Ordinal);
		foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
		{
			if (pluginInfo.Key == null || !pluginInfo.Key.StartsWith("cobalt.", StringComparison.Ordinal))
			{
				continue;
			}
			Assembly asm = null;
			try
			{
				PluginInfo value = pluginInfo.Value;
				BaseUnityPlugin val = ((value != null) ? value.Instance : null);
				if ((Object)(object)val != (Object)null)
				{
					asm = ((object)val).GetType().Assembly;
				}
			}
			catch
			{
			}
			string key = BuildStamp.Read(asm);
			if (!sortedDictionary.TryGetValue(key, out var value2))
			{
				value2 = (sortedDictionary[key] = new List<string>());
			}
			List<string> list2 = value2;
			PluginInfo value3 = pluginInfo.Value;
			object obj2;
			if (value3 == null)
			{
				obj2 = null;
			}
			else
			{
				BepInPlugin metadata = value3.Metadata;
				obj2 = ((metadata != null) ? metadata.Name : null);
			}
			if (obj2 == null)
			{
				obj2 = pluginInfo.Key;
			}
			PluginInfo value4 = pluginInfo.Value;
			object arg;
			if (value4 == null)
			{
				arg = null;
			}
			else
			{
				BepInPlugin metadata2 = value4.Metadata;
				arg = ((metadata2 != null) ? metadata2.Version : null);
			}
			list2.Add($"{obj2} {arg}");
		}
		int num = 0;
		foreach (string key2 in sortedDictionary.Keys)
		{
			if (key2 != "unknown")
			{
				num++;
			}
		}
		skew = num > 1;
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append(skew ? ($"[STAMP] BUILD SKEW: {num} different builds are installed side by side. Kits are shared DLLs — " + "a mod built against one build of a kit and running on another fails at first call, not at load. Install everything from ONE bundle/release:") : $"[STAMP] {sortedDictionary.Count} build(s) installed:");
		foreach (KeyValuePair<string, List<string>> item in sortedDictionary)
		{
			item.Value.Sort(StringComparer.Ordinal);
			stringBuilder.Append(string.Format("\n[STAMP]   {0,-20} {1}", item.Key, string.Join(", ", item.Value)));
		}
		return stringBuilder.ToString();
	}

	public static bool IsAlphaBundleInstall()
	{
		try
		{
			string path = Path.Combine(Paths.BepInExRootPath, "cobalt-bundles");
			return Directory.Exists(path) && Directory.GetFiles(path, "*.json").Length != 0;
		}
		catch
		{
			return false;
		}
	}

	public static string ToastText(bool contractError, bool stampSkew)
	{
		if (contractError)
		{
			return "Mod version mismatch: a mod was built against a different kit build. Reinstall all mods from ONE bundle. Details: BepInEx/LogOutput.log [CONTRACT]";
		}
		if (!stampSkew)
		{
			return null;
		}
		return "Mods from two different alpha bundles are installed. Reinstall from ONE bundle. See [STAMP] in BepInEx/LogOutput.log";
	}

	private static string ReadConst(Type t, string name)
	{
		if (t == null)
		{
			return null;
		}
		try
		{
			FieldInfo field = t.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
			if (field == null)
			{
				return null;
			}
			object obj = (field.IsLiteral ? field.GetRawConstantValue() : field.GetValue(null));
			return obj as string;
		}
		catch
		{
			return null;
		}
	}
}
public static class KitContractRules
{
	public enum Verdict
	{
		Match,
		KitNewerCompatible,
		ConsumerBelowFloor,
		ConsumerAhead,
		Unknown
	}

	public static bool IsError(Verdict v)
	{
		if (v != Verdict.ConsumerBelowFloor)
		{
			return v == Verdict.ConsumerAhead;
		}
		return true;
	}

	public static Version Parse(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return null;
		}
		s = s.Trim();
		if (s.StartsWith("v", StringComparison.OrdinalIgnoreCase))
		{
			s = s.Substring(1);
		}
		if (!Version.TryParse(s, out Version result) || s.IndexOf('-') >= 0 || s.IndexOf('+') >= 0)
		{
			return null;
		}
		return result;
	}

	public static Verdict Judge(string builtAgainst, string running, string compatSince)
	{
		Version version = Parse(builtAgainst);
		Version version2 = Parse(running);
		if (version == null || version2 == null)
		{
			return Verdict.Unknown;
		}
		int num = Compare(version, version2);
		if (num == 0)
		{
			return Verdict.Match;
		}
		if (num > 0)
		{
			return Verdict.ConsumerAhead;
		}
		Version version3 = Parse(compatSince);
		if (version3 != null && Compare(version, version3) < 0)
		{
			return Verdict.ConsumerBelowFloor;
		}
		return Verdict.KitNewerCompatible;
	}

	public static int Compare(Version a, Version b)
	{
		return Norm(a).CompareTo(Norm(b));
	}

	private static Version Norm(Version v)
	{
		return new Version(Math.Max(v.Major, 0), Math.Max(v.Minor, 0), Math.Max(v.Build, 0), 0);
	}

	public static string Describe(string consumer, string kitName, string builtAgainst, string running, string compatSince, Verdict v)
	{
		string text = "[CONTRACT] " + consumer + " built against " + kitName + " " + builtAgainst + ", running " + running;
		return v switch
		{
			Verdict.Match => text + " — match.", 
			Verdict.KitNewerCompatible => text + " — newer kit, binary-compatible (floor " + (compatSince ?? "none") + ").", 
			Verdict.ConsumerBelowFloor => text + " — VERSION SKEW: " + kitName + " only binds consumers built against >= " + compatSince + " (COMPAT_SINCE). Public members " + consumer + " was compiled to call no longer exist in this shape; expect MissingMethod/MissingFieldException at first use. Remedy: install " + consumer + " and " + kitName + " from the SAME bundle/release.", 
			Verdict.ConsumerAhead => text + " — VERSION SKEW (consumer AHEAD): " + consumer + " was built against a NEWER " + kitName + " than is installed. " + kitName + " is the stale half — update it. Expect MissingMethodException the first time a newer seam is touched.", 
			_ => text + " — could not compare (unparseable version).", 
		};
	}
}
public static class Lifecycle
{
	private static readonly Dictionary<object, int> _generations = new Dictionary<object, int>();

	private static bool _pollThrowNoted;

	public static bool IsSanePosition(Vector3 p)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		if (p.y > -3000f)
		{
			return ((Vector3)(ref p)).sqrMagnitude < 100000000f;
		}
		return false;
	}

	public static bool TryGetFirstLocalCharacter(out Character player)
	{
		player = null;
		CharacterManager instance = CharacterManager.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			return false;
		}
		try
		{
			player = instance.GetFirstLocalCharacter();
		}
		catch (NullReferenceException)
		{
			return false;
		}
		return (Object)(object)player != (Object)null;
	}

	public static Character FirstLocalCharacterOrNull()
	{
		if (!TryGetFirstLocalCharacter(out var player))
		{
			return null;
		}
		return player;
	}

	public static int InvalidateWaits(object waitKey)
	{
		if (waitKey == null)
		{
			return 0;
		}
		_generations.TryGetValue(waitKey, out var value);
		value++;
		_generations[waitKey] = value;
		return value;
	}

	private static bool IsStale(object waitKey, int myGen)
	{
		if (waitKey != null && _generations.TryGetValue(waitKey, out var value))
		{
			return value != myGen;
		}
		return false;
	}

	public static IEnumerator WhenPlayerReady(Func<Character> getPlayer, Action<Character> onReady, Action<string> onTimeout = null, float timeoutSeconds = 30f, object waitKey = null)
	{
		int myGen = ((waitKey != null) ? InvalidateWaits(waitKey) : 0);
		float t0 = Time.unscaledTime;
		Character player = null;
		while (Time.unscaledTime - t0 < timeoutSeconds)
		{
			if (IsStale(waitKey, myGen))
			{
				LogSuperseded(waitKey);
				yield break;
			}
			try
			{
				player = getPlayer();
			}
			catch (Exception ex)
			{
				if (!_pollThrowNoted)
				{
					_pollThrowNoted = true;
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogMessage((object)("[LIFECYCLE] a WhenPlayerReady resolver threw (" + ex.GetType().Name + ") — treated as 'not ready yet' (MP9). Said once per session."));
					}
				}
				player = null;
			}
			if ((Object)(object)player != (Object)null && IsSanePosition(((Component)player).transform.position))
			{
				break;
			}
			player = null;
			yield return (object)new WaitForSecondsRealtime(0.5f);
		}
		if (IsStale(waitKey, myGen))
		{
			LogSuperseded(waitKey);
		}
		else if ((Object)(object)player == (Object)null)
		{
			onTimeout?.Invoke($"player not ready within {timeoutSeconds:F0}s");
		}
		else
		{
			onReady(player);
		}
	}

	private static void LogSuperseded(object waitKey)
	{
		ManualLogSource log = Plugin.Log;
		if (log != null)
		{
			log.LogMessage((object)$"[LIFECYCLE] superseded a stale WhenPlayerReady wait (key={waitKey}).");
		}
	}
}
public static class LoadGate
{
	public const float DefaultTimeoutSeconds = 180f;

	internal const float VanillaFixedDelta = 0.022f;

	private const float SettleSeconds = 2f;

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

	private static int _nextToken;

	private static bool _gotoLatched;

	private static float _gotoLatchedAt;

	public static bool Armed => _armed.Count > 0;

	public static void LatchGoto()
	{
		_gotoLatched = true;
		_gotoLatchedAt = Time.unscaledTime;
	}

	public static bool IsGotoLatched()
	{
		if (!_gotoLatched)
		{
			return false;
		}
		float num = Time.unscaledTime - _gotoLatchedAt;
		if (num > 180f)
		{
			_gotoLatched = false;
			return false;
		}
		if (num > 2f)
		{
			NetworkLevelLoader instance = NetworkLevelLoader.Instance;
			bool flag = false;
			try
			{
				flag = (Object)(object)instance != (Object)null && instance.IsOverallLoadingDone;
			}
			catch
			{
			}
			if (flag)
			{
				_gotoLatched = false;
				return false;
			}
		}
		return true;
	}

	public static string FormatGateState(NetworkLevelLoader nll)
	{
		if ((Object)(object)nll == (Object)null)
		{
			return "n/a (NetworkLevelLoader.Instance null — main menu?)";
		}
		Func<Func<object>, string> func = delegate(Func<object> f)
		{
			try
			{
				object obj = f();
				return (obj == null) ? "null" : obj.ToString();
			}
			catch (Exception ex)
			{
				return "threw:" + ex.GetType().Name;
			}
		};
		return "continueAfter=" + func(() => nll.ContinueAfterLoading) + " gameplayLoading=" + func(() => nll.IsGameplayLoading) + " sceneLoading=" + func(() => nll.IsSceneLoading) + " preping=" + func(() => nll.m_prepingLoadLevel) + " allDone=" + func(() => nll.AllPlayerDoneLoading) + " allReady=" + func(() => nll.AllPlayerReadyToContinue) + " overallDone=" + func(() => nll.IsOverallLoadingDone) + " masterLoadingUI=" + func(() => (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsMasterLoadingDisplayed) + " joiningWorld=" + func(() => nll.IsJoiningWorld) + " doneIds=[" + func(() => string.Join(",", ToStrings(nll.m_doneLoadingPlayers))) + "] readyIds=[" + func(() => string.Join(",", ToStrings(nll.m_readyToContinuePlayers))) + "] waitingOthers=" + func(() => nll.m_waitingForOtherPlayers) + " prologuePanel=" + func(() => (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsProloguePanelDisplayed) + " scene='" + func(delegate
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			return ((Scene)(ref activeScene)).name;
		}) + "'";
	}

	private static string[] ToStrings(List<int> ids)
	{
		if (ids == null)
		{
			return new string[0];
		}
		string[] array = new string[ids.Count];
		for (int i = 0; i < ids.Count; i++)
		{
			array[i] = ids[i].ToString();
		}
		return array;
	}

	public static LoadSnapshot Snapshot(NetworkLevelLoader nll)
	{
		LoadSnapshot result = default(LoadSnapshot);
		if ((Object)(object)nll == (Object)null)
		{
			return result;
		}
		try
		{
			result.OverallDone = nll.IsOverallLoadingDone;
		}
		catch
		{
		}
		try
		{
			result.SaveInProgress = nll.IsSaveInProgress;
		}
		catch
		{
		}
		try
		{
			result.Preping = nll.m_prepingLoadLevel;
		}
		catch
		{
		}
		try
		{
			result.GameplayLoading = nll.IsGameplayLoading;
		}
		catch
		{
		}
		try
		{
			result.SceneLoading = nll.IsSceneLoading;
		}
		catch
		{
		}
		try
		{
			result.AllDone = nll.AllPlayerDoneLoading;
		}
		catch
		{
		}
		try
		{
			result.ContinueAfter = nll.ContinueAfterLoading;
		}
		catch
		{
		}
		try
		{
			result.MasterLoadingUi = (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsMasterLoadingDisplayed;
		}
		catch
		{
		}
		try
		{
			result.ProloguePanelUp = (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsProloguePanelDisplayed;
		}
		catch
		{
		}
		return result;
	}

	public static bool LoadInFlight(NetworkLevelLoader nll, out string why)
	{
		why = null;
		if ((Object)(object)nll == (Object)null)
		{
			return false;
		}
		try
		{
			if (nll.IsSceneLoading)
			{
				why = "IsSceneLoading";
				return true;
			}
		}
		catch
		{
		}
		try
		{
			if (nll.IsGameplayLoading)
			{
				why = "IsGameplayLoading";
				return true;
			}
		}
		catch
		{
		}
		try
		{
			if (nll.m_prepingLoadLevel)
			{
				why = "m_prepingLoadLevel";
				return true;
			}
		}
		catch
		{
		}
		try
		{
			if (!nll.IsOverallLoadingDone)
			{
				bool flag = false;
				try
				{
					flag = nll.m_waitingForOtherPlayers;
				}
				catch
				{
				}
				why = (flag ? "!IsOverallLoadingDone (m_waitingForOtherPlayers — a peer never checked in; 'unstick force forceready' if you are sure)" : "!IsOverallLoadingDone");
				return true;
			}
		}
		catch
		{
		}
		if (IsGotoLatched())
		{
			why = "a previous dev goto is still latched";
			return true;
		}
		return false;
	}

	public static int ArmForVerb(ManualLogSource log, string reason)
	{
		return Arm(log, reason, "[LOADGATE]", null, null, null, null, 180f, releasesGotoLatch: true);
	}

	public static int Arm(ManualLogSource log, string reason, string tag = "[LOADGATE]", Func<bool> alive = null, Func<bool> gatePhaseReached = null, Action onPassed = null, Func<string> extraFields = null, float timeoutSeconds = 180f, bool releasesGotoLatch = false)
	{
		int num = ++_nextToken;
		_armed.Add(num);
		Runner.Instance.StartCoroutine(Watch(num, log, reason ?? "?", tag ?? "[LOADGATE]", alive, gatePhaseReached, onPassed, extraFields, timeoutSeconds, releasesGotoLatch));
		return num;
	}

	public static void Disarm(int token)
	{
		_armed.Remove(token);
	}

	private static IEnumerator Watch(int token, ManualLogSource log, string reason, string tag, Func<bool> alive, Func<bool> gatePhaseReached, Action onPassed, Func<string> extraFields, float timeoutSeconds, bool releasesGotoLatch)
	{
		float armedAt = Time.unscaledTime;
		float lastReport = armedAt;
		bool sawLoading = false;
		bool passed = false;
		bool warnedMsgQueue = false;
		bool rescuedTimeScale = false;
		float msgQueueDownSince = -1f;
		string phase = "unknown";
		List<string> phases = new List<string>();
		while (_armed.Contains(token))
		{
			bool flag = false;
			float num = Time.unscaledTime - armedAt;
			try
			{
				if (alive != null && !alive())
				{
					flag = true;
				}
			}
			catch (Exception ex)
			{
				log.LogWarning((object)(tag + " liveness check threw (" + ex.GetType().Name + ") — disarming '" + reason + "'."));
				flag = true;
			}
			if (!flag)
			{
				try
				{
					NetworkLevelLoader instance = NetworkLevelLoader.Instance;
					LoadSnapshot s = Snapshot(instance);
					string text = LoadPhase.Classify(s);
					if (text != phase)
					{
						phase = text;
						phases.Add(text);
					}
					if (s.GameplayLoading)
					{
						sawLoading = true;
					}
					bool flag2 = gatePhaseReached?.Invoke() ?? (sawLoading && !s.GameplayLoading && num > 1f);
					if (!passed && flag2 && LoadPhase.GateGuardHolds(s) && (Object)(object)instance != (Object)null)
					{
						passed = true;
						if (onPassed != null)
						{
							onPassed();
						}
						else
						{
							instance.SetContinueAfterLoading();
						}
						log.LogMessage((object)$"{tag} passed the continue gate for '{reason}' after {num:F1}s — no keypress needed.");
					}
					float timeScale = Time.timeScale;
					if (!rescuedTimeScale && timeScale < 0.01f && !s.OverallDone)
					{
						rescuedTimeScale = true;
						int gamePausedByPlayer = Global.GamePausedByPlayer;
						if (gamePausedByPlayer != -1)
						{
							PauseMenu.Pause(false);
							log.LogWarning((object)($"{tag} timeScale was {timeScale:F3} with the pause menu claimed by player {gamePausedByPlayer} " + "during '" + reason + "' — closed it (PauseMenu.Pause(false)); a scaled-time load never advances."));
						}
						else
						{
							Time.timeScale = 1f;
							Time.fixedDeltaTime = 0.022f;
							log.LogWarning((object)($"{tag} timeScale was {timeScale:F3} (unclaimed) during '{reason}' — restored to 1; " + "a scaled-time load never advances (BlackFade/Invoke are scaled)."));
						}
					}
					bool flag3 = true;
					try
					{
						flag3 = PhotonNetwork.isMessageQueueRunning;
					}
					catch
					{
					}
					if (!flag3)
					{
						if (msgQueueDownSince < 0f)
						{
							msgQueueDownSince = Time.unscaledTime;
						}
						if (!warnedMsgQueue && Time.unscaledTime - msgQueueDownSince > 10f)
						{
							warnedMsgQueue = true;
							log.LogWarning((object)(tag + " PhotonNetwork.isMessageQueueRunning has been FALSE for " + $"{Time.unscaledTime - msgQueueDownSince:F0}s during '{reason}' — the loader disables it for the " + "load and re-enables it only at NetworkLevelLoader.cs:1307, so this machine is network-dead until the load gets past that point. A peer will see this box as frozen, not as slow."));
						}
					}
					else
					{
						msgQueueDownSince = -1f;
					}
					if (Time.unscaledTime - lastReport >= 5f)
					{
						lastReport = Time.unscaledTime;
						string text2;
						try
						{
							text2 = extraFields?.Invoke();
						}
						catch
						{
							text2 = "extraFields:threw";
						}
						float num2 = -1f;
						try
						{
							if ((Object)(object)instance != (Object)null && instance.m_async != null)
							{
								num2 = instance.m_async.progress;
							}
						}
						catch
						{
						}
						log.LogMessage((object)($"{tag} phase={phase} t={num:F0}s " + (string.IsNullOrEmpty(text2) ? "" : (text2 + " ")) + FormatGateState(instance) + $" msgQueue={flag3} timeScale={timeScale:F2} asyncProgress={num2:F2} sawLoading={sawLoading}"));
					}
					if (s.OverallDone && sawLoading && num > 2f)
					{
						log.LogMessage((object)($"{tag} load '{reason}' reached overallDone after {num:F1}s " + "(phases: " + string.Join(" -> ", phases.ToArray()) + ")"));
						if (releasesGotoLatch)
						{
							_gotoLatched = false;
						}
						flag = true;
					}
					else if (num > timeoutSeconds)
					{
						log.LogError((object)($"{tag} ERROR: load '{reason}' still not done after {timeoutSeconds:F0}s — " + "disarming; last phase=" + phase + ". Run 'unstick' for the full state."));
						flag = true;
					}
				}
				catch (Exception ex2)
				{
					log.LogWarning((object)(tag + " watch poll threw (watch continues): " + ex2.GetType().Name + ": " + ex2.Message));
				}
			}
			if (flag)
			{
				break;
			}
			yield return (object)new WaitForSecondsRealtime(0.25f);
		}
		_armed.Remove(token);
	}
}
public struct LoadSnapshot
{
	public bool OverallDone;

	public bool SaveInProgress;

	public bool Preping;

	public bool GameplayLoading;

	public bool SceneLoading;

	public bool AllDone;

	public bool ContinueAfter;

	public bool MasterLoadingUi;

	public bool ProloguePanelUp;
}
public static class LoadPhase
{
	public const string Done = "done";

	public const string Saving = "saving";

	public const string Preping = "preping";

	public const string Prologue = "prologue";

	public const string SceneLoading = "scene-loading";

	public const string Fading = "fading";

	public const string Gate = "gate";

	public const string WaitingPlayers = "waiting-players";

	public const string PostGate = "post-gate";

	public const string Unknown = "unknown";

	public static string Classify(LoadSnapshot s)
	{
		if (s.SaveInProgress)
		{
			return "saving";
		}
		if (s.Preping)
		{
			return "preping";
		}
		if (s.ProloguePanelUp)
		{
			return "prologue";
		}
		if (s.OverallDone)
		{
			return "done";
		}
		if (s.SceneLoading)
		{
			return "scene-loading";
		}
		if (s.GameplayLoading)
		{
			return "fading";
		}
		if (s.AllDone && !s.ContinueAfter && s.MasterLoadingUi)
		{
			return "gate";
		}
		if (!s.AllDone)
		{
			return "waiting-players";
		}
		if (s.ContinueAfter)
		{
			return "post-gate";
		}
		return "unknown";
	}

	public static bool GateGuardHolds(LoadSnapshot s)
	{
		if (!s.ContinueAfter && !s.GameplayLoading && s.AllDone && s.MasterLoadingUi)
		{
			return !s.ProloguePanelUp;
		}
		return false;
	}
}
public enum LogTier
{
	Quiet,
	Normal,
	Verbose,
	Trace
}
public static class LogTiers
{
	public const int RankAlways = 0;

	public const int RankMessage = 1;

	public const int RankInfo = 2;

	public const int RankDebug = 3;

	public static bool Emits(LogTier tier, int rank)
	{
		return rank <= (int)tier;
	}

	public static bool Emits(LogTier tier, int rank, bool requested)
	{
		if (!requested)
		{
			return Emits(tier, rank);
		}
		return true;
	}

	public static bool TryParse(string text, ref LogTier tier)
	{
		if (string.IsNullOrEmpty(text))
		{
			return false;
		}
		switch (text.Trim().ToLowerInvariant())
		{
		case "quiet":
		case "q":
			tier = LogTier.Quiet;
			return true;
		case "n":
		case "default":
		case "normal":
			tier = LogTier.Normal;
			return true;
		case "v":
		case "verbose":
			tier = LogTier.Verbose;
			return true;
		case "debug":
		case "trace":
		case "t":
			tier = LogTier.Trace;
			return true;
		default:
			return false;
		}
	}

	public static string Names()
	{
		return "Quiet|Normal|Verbose|Trace";
	}
}
public sealed class ModLog
{
	private sealed class RequestedScope : IDisposable
	{
		private bool _done;

		internal RequestedScope()
		{
			s_requestedDepth++;
		}

		public void Dispose()
		{
			if (!_done)
			{
				_done = true;
				if (s_requestedDepth > 0)
				{
					s_requestedDepth--;
				}
			}
		}
	}

	private readonly ManualLogSource _sink;

	private static int s_requestedDepth;

	internal const string Section = "Diag";

	internal const string Key = "LogLevel";

	public const LogTier ShippedDefault = LogTier.Verbose;

	private const string Help = "How much this mod writes to the log. Quiet = warnings/errors only. Normal adds boot lines, on-screen notices and self-test results. Verbose (default) adds the per-feature diagnostics — the default is deliberately chatty while these mods are still being debugged, so a bug report arrives with context. Trace adds per-tick detail. NOTE: Trace also needs BepInEx.cfg's [Logging.Disk] LogLevels to include Debug — stock BepInEx drops it. Verbose does not: Info is passed by default.";

	private static bool s_sinkWarned;

	public ManualLogSource Sink => _sink;

	public LogTier Tier { get; set; }

	public static bool RequestedOutput => s_requestedDepth > 0;

	public bool TraceOn => LogTiers.Emits(Tier, 3, RequestedOutput);

	public bool VerboseOn => LogTiers.Emits(Tier, 2, RequestedOutput);

	public bool MessageOn => LogTiers.Emits(Tier, 1, RequestedOutput);

	public ModLog(ManualLogSource sink, LogTier tier = LogTier.Verbose)
	{
		if (sink == null)
		{
			throw new ArgumentNullException("sink");
		}
		_sink = sink;
		Tier = tier;
	}

	public static IDisposable Requested()
	{
		return new RequestedScope();
	}

	public void LogFatal(object data)
	{
		_sink.LogFatal(data);
	}

	public void LogError(object data)
	{
		_sink.LogError(data);
	}

	public void LogWarning(object data)
	{
		_sink.LogWarning(data);
	}

	public void LogMessage(object data)
	{
		if (LogTiers.Emits(Tier, 1, RequestedOutput))
		{
			_sink.LogMessage(data);
		}
	}

	public void LogInfo(object data)
	{
		if (LogTiers.Emits(Tier, 2, RequestedOutput))
		{
			_sink.LogInfo(data);
		}
	}

	public void LogDebug(object data)
	{
		if (LogTiers.Emits(Tier, 3, RequestedOutput))
		{
			_sink.LogDebug(data);
		}
	}

	public void Log(LogLevel level, object data)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		if (LogTiers.Emits(Tier, RankOf(level), RequestedOutput))
		{
			_sink.Log(level, data);
		}
	}

	public static int RankOf(LogLevel level)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: 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)
		//IL_000b: 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_0012: Unknown result type (might be due to invalid IL or missing references)
		if ((level & 0x20) != 0)
		{
			return 3;
		}
		if ((level & 0x10) != 0)
		{
			return 2;
		}
		if ((level & 8) != 0)
		{
			return 1;
		}
		return 0;
	}

	public static LogLevel FloorOf(LogTier tier)
	{
		return (LogLevel)(tier switch
		{
			LogTier.Trace => 32, 
			LogTier.Verbose => 16, 
			LogTier.Quiet => 4, 
			_ => 8, 
		});
	}

	public static ModLog Ungated(ManualLogSource sink)
	{
		if (sink != null)
		{
			return new ModLog(sink, LogTier.Trace);
		}
		return null;
	}

	public static implicit operator ManualLogSource(ModLog log)
	{
		return log?._sink;
	}

	public static ModLog Bind(BaseUnityPlugin plugin, ManualLogSource sink)
	{
		if ((Object)(object)plugin == (Object)null)
		{
			throw new ArgumentNullException("plugin");
		}
		ConfigEntry<LogTier> entry = plugin.Config.Bind<LogTier>("Diag", "LogLevel", LogTier.Verbose, "How much this mod writes to the log. Quiet = warnings/errors only. Normal adds boot lines, on-screen notices and self-test results. Verbose (default) adds the per-feature diagnostics — the default is deliberately chatty while these mods are still being debugged, so a bug report arrives with context. Trace adds per-tick detail. NOTE: Trace also needs BepInEx.cfg's [Logging.Disk] LogLevels to include Debug — stock BepInEx drops it. Verbose does not: Info is passed by default.");
		ModLog log = new ModLog(sink, entry.Value);
		entry.SettingChanged += delegate
		{
			log.Tier = entry.Value;
		};
		WarnIfSinkDrops(sink, entry.Value);
		return log;
	}

	private unsafe static void WarnIfSinkDrops(ManualLogSource sink, LogTier tier)
	{
		//IL_000d: 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_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		if (s_sinkWarned || sink == null)
		{
			return;
		}
		try
		{
			LogLevel val = FloorOf(tier);
			foreach (ILogListener listener in Logger.Listeners)
			{
				if (listener == null || ((object)listener).GetType().Name.IndexOf("Disk", StringComparison.OrdinalIgnoreCase) < 0)
				{
					continue;
				}
				PropertyInfo property = ((object)listener).GetType().GetProperty("DisplayedLogLevel", BindingFlags.Instance | BindingFlags.Public);
				if (!(property == null))
				{
					LogLevel val2 = (LogLevel)property.GetValue(listener, null);
					if ((val2 & val) == 0)
					{
						s_sinkWarned = true;
						sink.LogWarning((object)("[LOGLEVEL] Diag.LogLevel = " + tier.ToString() + " needs " + ((object)(*(LogLevel*)(&val))/*cast due to .constrained prefix*/).ToString() + ", but BepInEx's disk log is set to '" + ((object)(*(LogLevel*)(&val2))/*cast due to .constrained prefix*/).ToString() + "'. Those lines are being dropped before they reach the file — add " + ((object)(*(LogLevel*)(&val))/*cast due to .constrained prefix*/).ToString() + " to [Logging.Disk] LogLevels in BepInEx/config/BepInEx.cfg."));
					}
				}
				break;
			}
		}
		catch
		{
		}
	}
}
public sealed class NameCandidates
{
	public struct Validation
	{
		public string Raw { get; }

		public IReadOnlyList<string> Known { get; }

		public IReadOnlyList<string> Unknown { get; }

		public bool NoneKnown => Known.Count == 0;

		internal Validation(string raw, IReadOnlyList<string> known, IReadOnlyList<string> unknown)
		{
			Raw = raw;
			Known = known;
			Unknown = unknown;
		}
	}

	private readonly Func<string> _readRaw;

	private readonly Func<string, bool> _exists;

	private readonly Action<string> _onResolved;

	private readonly Action<string> _onUnresolved;

	private readonly HashSet<string> _warned = new HashSet<string>();

	private string _resolvedKey;

	private string _resolvedId;

	private string _validatedKey;

	private string _missParkedKey;

	private float _missRetryAt;

	public const float MissRetrySeconds = 30f;

	internal static Func<float> Clock;

	public string Label { get; }

	public string Raw => _readRaw() ?? "";

	public string Cached => _resolvedId;

	static NameCandidates()
	{
		Clock = () => 0f;
		Clock = () => Time.unscaledTime;
	}

	public static bool StatusPrefabExists(string name)
	{
		ResourcesPrefabManager instance = ResourcesPrefabManager.Instance;
		return (Object)(object)((instance != null) ? instance.GetStatusEffectPrefab(name) : null) != (Object)null;
	}

	public static bool ItemPrefabExists(string name)
	{
		ResourcesPrefabManager instance = ResourcesPrefabManager.Instance;
		return (Object)(object)((instance != null) ? instance.GetItemPrefab(name) : null) != (Object)null;
	}

	public NameCandidates(string label, Func<string> readRaw, Func<string, bool> exists, Action<string> onResolved = null, Action<string> onUnresolved = null)
	{
		Label = label;
		_readRaw = readRaw ?? throw new ArgumentNullException("readRaw");
		_exists = exists ?? throw new ArgumentNullException("exists");
		_onResolved = onResolved;
		_onUnresolved = onUnresolved;
	}

	public IReadOnlyList<string> Parse()
	{
		return Split(Raw);
	}

	public static IReadOnlyList<string> Split(string raw)
	{
		List<string> list = new List<string>();
		if (string.IsNullOrEmpty(raw))
		{
			return list;
		}
		string[] array = raw.Split(new char[1] { ',' });
		foreach (string text in array)
		{
			string text2 = text.Trim();
			if (text2.Length > 0 && !list.Contains(text2))
			{
				list.Add(text2);
			}
		}
		return list;
	}

	public string Resolve()
	{
		string raw = Raw;
		if (_resolvedId != null && raw == _resolvedKey)
		{
			return _resolvedId;
		}
		if (_missParkedKey != null)
		{
			if (raw == _missParkedKey && Clock() < _missRetryAt)
			{
				return null;
			}
			_missParkedKey = null;
		}
		foreach (string item in Split(raw))
		{
			if (_exists(item))
			{
				_resolvedKey = raw;
				_resolvedId = item;
				_onResolved?.Invoke(item);
				return item;
			}
		}
		_resolvedKey = null;
		_resolvedId = null;
		_missParkedKey = raw;
		_missRetryAt = Clock() + 30f;
		if (_warned.Add(raw))
		{
			_onUnresolved?.Invoke(raw);
		}
		return null;
	}

	public bool TryValidate(out Validation result)
	{
		string raw = Raw;
		if (raw == _validatedKey)
		{
			result = default(Validation);
			return false;
		}
		_validatedKey = raw;
		List<string> list = new List<string>();
		List<string> list2 = new List<string>();
		foreach (string item in Split(raw))
		{
			(_exists(item) ? list : list2).Add(item);
		}
		result = new Validation(raw, list, list2);
		return true;
	}

	public T FindFirst<T>(Func<string, T> lookup) where T : class
	{
		if (lookup == null)
		{
			return null;
		}
		foreach (string item in Split(Raw))
		{
			T val = lookup(item);
			if (val != null)
			{
				return val;
			}
		}
		return null;
	}

	public void Invalidate()
	{
		_resolvedKey = null;
		_resolvedId = null;
		_validatedKey = null;
		_missParkedKey = null;
		_warned.Clear();
	}
}
public static class Notify
{
	public static ManualLogSource Log;

	private static ManualLogSource L => Log ?? Plugin.Log;

	public static void Player(Character character, string message)
	{
		L.LogMessage((object)("[NOTIFY] " + message));
		if ((Object)(object)character != (Object)null && (Object)(object)character.CharacterUI != (Object)null)
		{
			character.CharacterUI.ShowInfoNotification(message);
		}
		else
		{
			L.LogWarning((object)"[NOTIFY] no CharacterUI to show the toast on (logged only).");
		}
	}
}
public sealed class PendingSet
{
	public const double AbandonedAfterSeconds = 30.0;

	private readonly Dictionary<long, double> _open = new Dictionary<long, double>();

	private readonly List<long> _scratch = new List<long>();

	private long _nextId;

	public int RawCount => _open.Count;

	public long Open(double now)
	{
		long num = ++_nextId;
		_open[num] = now;
		return num;
	}

	public void Close(long id)
	{
		_open.Remove(id);
	}

	public int CountAt(double now)
	{
		Reap(now);
		return _open.Count;
	}

	public bool AnyAt(double now)
	{
		return CountAt(now) > 0;
	}

	private void Reap(double now)
	{
		if (_open.Count == 0)
		{
			return;
		}
		_scratch.Clear();
		foreach (KeyValuePair<long, double> item in _open)
		{
			if (now - item.Value >= 30.0)
			{
				_scratch.Add(item.Key);
			}
		}
		for (int i = 0; i < _scratch.Count; i++)
		{
			_open.Remove(_scratch[i]);
		}
	}
}
[BepInPlugin("cobalt.forgekit", "ForgeKit", "0.4.10")]
public class Plugin : BaseUnityPlugin
{
	public const string GUID = "cobalt.forgekit";

	public const string NAME = "ForgeKit";

	public const string VERSION = "0.4.10";

	public const string COMPAT_SINCE = "0.4.4";

	internal static ManualLogSource Log;

	private bool _censusLogged;

	internal void Awake()
	{
		Log = ((BaseUnityPlugin)this).Logger;
		Log.LogMessage((object)"ForgeKit 0.4.10 loaded.");
		Log.LogMessage((object)("[FORGEKIT] build " + BuildStamp.Local));
	}

	internal void Update()
	{
		if (_censusLogged)
		{
			return;
		}
		_censusLogged = true;
		((Behaviour)this).enabled = false;
		try
		{
			Log.LogMessage((object)Keybinds.Report());
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("[KEYBINDS] census failed: " + ex.GetType().Name + ": " + ex.Message + " — the [CONTRACT]/[STAMP] lines below are unaffected."));
		}
		bool anyError = false;
		bool skew = false;
		try
		{
			foreach (KeyValuePair<bool, string> item in KitContract.Report(out anyError))
			{
				if (item.Key)
				{
					Log.LogError((object)item.Value);
				}
				else
				{
					Log.LogMessage((object)item.Value);
				}
			}
			string text = KitContract.StampCensus(out skew);
			if (skew)
			{
				Log.LogWarning((object)text);
			}
			else
			{
				Log.LogMessage((object)text);
			}
		}
		catch (Exception ex2)
		{
			Log.LogWarning((object)("[CONTRACT] handshake report failed: " + ex2.GetType().Name + ": " + ex2.Message));
		}
		try
		{
			foreach (string item2 in CfgSkew.Sweep())
			{
				Log.LogWarning((object)item2);
			}
		}
		catch (Exception ex3)
		{
			Log.LogWarning((object)("[CFGSKEW] config-drift census failed: " + ex3.GetType().Name + ": " + ex3.Message + " — the [CONTRACT]/[STAMP] lines above are unaffected."));
		}
		string toast = KitContract.ToastText(anyError, skew && KitContract.IsAlphaBundleInstall());
		if (toast != null)
		{
			((MonoBehaviour)this).StartCoroutine(Lifecycle.WhenPlayerReady(Lifecycle.FirstLocalCharacterOrNull, delegate(Character c)
			{
				Notify.Player(c, toast);
			}, null, 1800f));
		}
	}
}
public static class ParticleProbe
{
	public static void Dump(bool listAll, ManualLogSource log)
	{
		//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)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Invalid comparison between Unknown and I4
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Invalid comparison between Unknown and I4
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Invalid comparison between Unknown and I4
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0154: Unknown result type (might be due to invalid IL or missing references)
		ParticleSystem[] array = Resources.FindObjectsOfTypeAll<ParticleSystem>();
		List<string> list = new List<string>();
		List<string> list2 = new List<string>();
		List<string> list3 = new List<string>();
		int num = 0;
		ParticleSystem[] array2 = array;
		foreach (ParticleSystem val in array2)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			ShapeModule shape;
			try
			{
				shape = val.shape;
			}
			catch
			{
				continue;
			}
			if (!((ShapeModule)(ref shape)).enabled)
			{
				continue;
			}
			ParticleSystemShapeType shapeType = ((ShapeModule)(ref shape)).shapeType;
			if ((int)shapeType == 6 || (int)shapeType == 13 || (int)shapeType == 14)
			{
				string source;
				bool baked;
				Mesh val2 = MeshOf(shape, shapeType, out source, out baked);
				float num2 = MeshArea(val2);
				string arg = (((Object)(object)val2 == (Object)null) ? "NULL" : ((Object)val2).name);
				int num3 = ((!((Object)(object)val2 == (Object)null)) ? val2.vertexCount : 0);
				bool isPlaying = val.isPlaying;
				if (isPlaying)
				{
					num++;
				}
				bool flag = (Object)(object)val2 == (Object)null || num3 == 0 || (num2 >= 0f && num2 <= 1E-07f);
				bool flag2 = !flag && isPlaying && num2 < 0f;
				if (baked)
				{
					Object.Destroy((Object)(object)val2);
				}
				string item = "  " + Path(((Component)val).transform) + "\n" + $"      shape={shapeType} mesh={arg} ({source}) " + $"verts={num3} " + "area=" + ((num2 < 0f) ? "UNREADABLE" : num2.ToString("F6")) + " " + $"playing={isPlaying} activeInHierarchy={((Component)val).gameObject.activeInHierarchy} " + "emissionEnabled=" + EmissionOn(val) + " emissionRate=" + Rate(val);
				if (flag)
				{
					list.Add(item);
				}
				else if (flag2)
				{
					list2.Add(item);
				}
				if (listAll)
				{
					list3.Add(item);
				}
			}
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append($"[PSDUMP] {array.Length} ParticleSystem(s) in memory; {num} of the mesh-shaped ones are PLAYING.\n");
		if (list.Count == 0)
		{
			stringBuilder.Append("[PSDUMP] no PROVABLY zero-area mesh emitter found.\n");
			if (list2.Count == 0)
			{
				stringBuilder.Append("[PSDUMP] (If output_log.txt IS still spamming, the emitter's mesh is being invalidated at RUNTIME —\n         re-run psdump WHILE the spam is flowing, and check the DDOL scene / a scene mid-unload.)\n");
			}
		}
		else
		{
			stringBuilder.Append($"[PSDUMP] {list.Count} ZERO-AREA MESH EMITTER(S) — these are the spam. A PLAYING one logs every frame:\n");
			foreach (string item2 in list)
			{
				stringBuilder.Append(item2).Append('\n');
			}
		}
		if (list2.Count > 0)
		{
			stringBuilder.Append($"[PSDUMP] {list2.Count} SUSPECT(S) — playing, mesh-shaped, but the mesh is not readable so its area\n" + "         cannot be verified from script. Do NOT read this as clean:\n");
			foreach (string item3 in list2)
			{
				stringBuilder.Append(item3).Append('\n');
			}
		}
		if (listAll && list3.Count > 0)
		{
			stringBuilder.Append($"[PSDUMP] all {list3.Count} mesh-shaped system(s):\n");
			foreach (string item4 in list3)
			{
				stringBuilder.Append(item4).Append('\n');
			}
		}
		log.LogMessage((object)stringBuilder.ToString());
	}

	private unsafe static Mesh MeshOf(ShapeModule shape, ParticleSystemShapeType t, out string source, out bool baked)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Invalid comparison between Unknown and I4
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Invalid comparison between Unknown and I4
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Invalid comparison between Unknown and I4
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Expected O, but got Unknown
		source = ((object)(*(ParticleSystemShapeType*)(&t))/*cast due to .constrained prefix*/).ToString();
		baked = false;
		if ((int)t != 6)
		{
			if ((int)t != 13)
			{
				if ((int)t == 14)
				{
					SkinnedMeshRenderer skinnedMeshRenderer = ((ShapeModule)(ref shape)).skinnedMeshRenderer;
					if ((Object)(object)skinnedMeshRenderer == (Object)null)
					{
						source = "SkinnedMeshRenderer=NULL (shape was never bound to a renderer)";
						return null;
					}
					int num = ((skinnedMeshRenderer.bones != null) ? skinnedMeshRenderer.bones.Length : 0);
					int num2 = 0;
					if (skinnedMeshRenderer.bones != null)
					{
						Transform[] bones = skinnedMeshRenderer.bones;
						foreach (Transform val in bones)
						{
							if ((Object)(object)val == (Object)null)
							{
								num2++;
							}
						}
					}
					source = $"SkinnedMeshRenderer '{((Object)skinnedMeshRenderer).name}' bones={num} dead={num2}";
					try
					{
						Mesh val2 = new Mesh();
						skinnedMeshRenderer.BakeMesh(val2);
						source += " [baked]";
						baked = true;
						return val2;
					}
					catch
					{
						return skinnedMeshRenderer.sharedMesh;
					}
				}
				return null;
			}
			MeshRenderer meshRenderer = ((ShapeModule)(ref shape)).meshRenderer;
			if ((Object)(object)meshRenderer == (Object)null)
			{
				source = "MeshRenderer=NULL";
				return null;
			}
			source = "MeshRenderer '" + ((Object)meshRenderer).name + "'";
			MeshFilter component = ((Component)meshRenderer).GetComponent<MeshFilter>();
			if (!((Object)(object)component == (Object)null))
			{
				return component.sharedMesh;
			}
			return null;
		}
		return ((ShapeModule)(ref shape)).mesh;
	}

	private static float MeshArea(Mesh mesh)
	{
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//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)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//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)
		if ((Object)(object)mesh == (Object)null || mesh.vertexCount == 0)
		{
			return 0f;
		}
		Vector3[] vertices;
		int[] triangles;
		try
		{
			if (!mesh.isReadable)
			{
				return -1f;
			}
			vertices = mesh.vertices;
			triangles = mesh.triangles;
		}
		catch
		{
			return -1f;
		}
		float num = 0f;
		for (int i = 0; i + 2 < triangles.Length; i += 3)
		{
			float num2 = num;
			Vector3 val = Vector3.Cross(vertices[triangles[i + 1]] - vertices[triangles[i]], vertices[triangles[i + 2]] - vertices[triangles[i]]);
			num = num2 + ((Vector3)(ref val)).magnitude * 0.5f;
		}
		return num;
	}

	private static string Rate(ParticleSystem ps)
	{
		//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_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			EmissionModule emission = ps.emission;
			MinMaxCurve rateOverTime = ((EmissionModule)(ref emission)).rateOverTime;
			return ((MinMaxCurve)(ref rateOverTime)).constant.ToString("F1");
		}
		catch
		{
			return "?";
		}
	}

	private static string EmissionOn(ParticleSystem ps)
	{
		//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)
		try
		{
			EmissionModule emission = ps.emission;
			return ((EmissionModule)(ref emission)).enabled.ToString();
		}
		catch
		{
			return "?";
		}
	}

	private static string Path(Transform t)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: 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