Decompiled source of Moderation Improvements v1.1.1

BepInEx/plugins/ModerationImprovements.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Epic.OnlineServices;
using Epic.OnlineServices.Connect;
using HarmonyLib;
using HouseHouse.Dream;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Reflection;
using Microsoft.CodeAnalysis;
using Mirror;
using Mirror.Authenticators;
using PlayEveryWare.EpicOnlineServices;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("AdamMady")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("ModerationImprovements")]
[assembly: AssemblyTitle("ModerationImprovements")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BigOrb
{
	internal static class Chime
	{
		private static bool _broken;

		private static AudioSource _source;

		private static AudioClip _join;

		private static AudioClip _leave;

		private static readonly Queue<bool> _pending = new Queue<bool>();

		private static float _nextPlay;

		private const int Rate = 44100;

		private const float NoteLen = 0.32f;

		private const float NoteFade = 0.03f;

		private const float NoteGap = 0.115f;

		private const float Spacing = 0.45f;

		private const int MaxQueued = 3;

		internal static bool Enabled
		{
			get
			{
				if (!_broken)
				{
					return Plugin.ChimeEnabled.Value;
				}
				return false;
			}
		}

		internal static void SetEnabled(bool on)
		{
			Plugin.ChimeEnabled.Value = on;
			if (on)
			{
				_broken = false;
			}
		}

		internal static void Play(bool join)
		{
			if (Enabled && _pending.Count < 3)
			{
				_pending.Enqueue(join);
			}
		}

		internal static void Step(GameObject holder)
		{
			if (_pending.Count == 0 || Time.unscaledTime < _nextPlay)
			{
				return;
			}
			_nextPlay = Time.unscaledTime + 0.45f;
			bool flag = _pending.Dequeue();
			try
			{
				if (Ensure(holder))
				{
					AudioClip val = (flag ? _join : _leave);
					if ((Object)(object)val != (Object)null)
					{
						_source.PlayOneShot(val, Mathf.Clamp01(Plugin.ChimeVolume.Value));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("chime: " + ex.Message));
				_pending.Clear();
				_broken = true;
			}
		}

		private static bool Ensure(GameObject holder)
		{
			if ((Object)(object)_source != (Object)null && (Object)(object)_join != (Object)null && (Object)(object)_leave != (Object)null)
			{
				return true;
			}
			if ((Object)(object)holder == (Object)null)
			{
				return false;
			}
			if ((Object)(object)_source == (Object)null)
			{
				_source = holder.GetComponent<AudioSource>();
				if ((Object)(object)_source == (Object)null)
				{
					_source = holder.AddComponent<AudioSource>();
				}
				_source.playOnAwake = false;
				_source.spatialBlend = 0f;
				_source.bypassEffects = true;
				_source.bypassListenerEffects = true;
				_source.bypassReverbZones = true;
				_source.ignoreListenerPause = true;
				_source.ignoreListenerVolume = true;
			}
			if (_join == null)
			{
				_join = Build("orbJoin", 659.25f, 880f);
			}
			if (_leave == null)
			{
				_leave = Build("orbLeave", 880f, 587.33f);
			}
			if ((Object)(object)_source != (Object)null && (Object)(object)_join != (Object)null)
			{
				return (Object)(object)_leave != (Object)null;
			}
			return false;
		}

		private static AudioClip Build(string name, float f1, float f2)
		{
			int num = 19183;
			Il2CppStructArray<float> val = new Il2CppStructArray<float>((long)num);
			for (int i = 0; i < num; i++)
			{
				float num2 = (float)i / 44100f;
				((Il2CppArrayBase<float>)(object)val)[i] = Mathf.Clamp(0.5f * (Note(num2, f1) + Note(num2 - 0.115f, f2)), -1f, 1f);
			}
			AudioClip obj = AudioClip.Create(name, num, 1, 44100, false);
			obj.SetData(val, 0);
			return obj;
		}

		private static float Note(float t, float freq)
		{
			if (t < 0f || t > 0.32f)
			{
				return 0f;
			}
			float num = Mathf.Min(1f, t / 0.006f) * Mathf.Exp((0f - t) * 14f);
			if (t > 0.29f)
			{
				num *= (0.32f - t) / 0.03f;
			}
			return num * (Mathf.Sin((float)Math.PI * 2f * freq * t) + 0.35f * Mathf.Sin((float)Math.PI * 4f * freq * t));
		}
	}
	internal static class Guard
	{
		[HarmonyPatch(typeof(HouseAuthenticator), "OnInitialAuthRequestMessage")]
		internal static class AuthPatch
		{
			private static void Postfix(NetworkConnectionToClient __0, InitialialAuthRequestMessage __1)
			{
				try
				{
					Patches.MarkFired("auth");
					if (__0 == null)
					{
						return;
					}
					string text = null;
					string text2 = null;
					string text3 = null;
					try
					{
						text = __0.address;
					}
					catch
					{
					}
					try
					{
						text2 = ((__1 != null) ? __1.platformUserId : null);
					}
					catch
					{
					}
					try
					{
						text3 = ((__1 != null) ? __1.versionNumber : null);
					}
					catch
					{
					}
					OrbState.AuthSeen(((NetworkConnection)__0).connectionId, text, text2, text3);
					string name = OrbState.RosterName(text2) ?? text2 ?? "?";
					OrbState.AddEvent("auth", text2, name, $"conn {((NetworkConnection)__0).connectionId} addr {text ?? "?"} claims {text2 ?? "?"} v{text3 ?? "?"}");
					if (OrbState.IsBannedAddress(text))
					{
						OrbState.AddEvent("autokick", text2, name, "banned address " + text + " tried to join as " + text2);
						((NetworkConnection)__0).Disconnect();
					}
					else if (OrbState.IsBanned(text2))
					{
						OrbState.AddEvent("autokick", text2, name, "banned identifier tried to join from " + (text ?? "?"));
						OrbState.BanAttachAddress(text2, text);
						((NetworkConnection)__0).Disconnect();
					}
					else if (!OrbState.LobbyAllows(text2, text))
					{
						OrbState.AddEvent("lockreject", text2, name, "lobby locked; connection was not present when locked");
						((NetworkConnection)__0).Disconnect();
					}
					else
					{
						if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2) || text == "localhost")
						{
							return;
						}
						if (IsSteam(text) && IsSteam(text2) && text != text2)
						{
							Act("idspoof", text2, name, "claims SteamID " + text2 + " but the connection is " + text, text, __0);
							return;
						}
						string text4 = Base(text2);
						lock (IdsByAddress)
						{
							if (!IdsByAddress.TryGetValue(text, out var value))
							{
								value = (IdsByAddress[text] = new HashSet<string>());
							}
							if (text4 != text)
							{
								value.Add(text4);
							}
							if (value.Count >= 2)
							{
								string detail = $"address {text} has claimed {value.Count} identifiers: {string.Join(", ", value)}";
								if (value.Count >= 3)
								{
									Act("idrotate", text2, name, detail, text, __0);
									return;
								}
								OrbState.AddAlert("idrotate", text2, name, detail);
							}
						}
						Eos.Lookup(text, text2);
					}
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("auth gate: " + ex.Message));
				}
			}
		}

		internal static class Eos
		{
			private struct QueryOptions
			{
				public int ApiVersion;

				public IntPtr LocalUserId;

				public IntPtr AccountIdTypeDeprecated;

				public IntPtr ProductUserIds;

				public uint Count;
			}

			private struct QueryInfo
			{
				public int ResultCode;

				public IntPtr ClientData;

				public IntPtr LocalUserId;
			}

			private struct CountOptions
			{
				public int ApiVersion;

				public IntPtr TargetUserId;
			}

			private struct CopyOptions
			{
				public int ApiVersion;

				public IntPtr TargetUserId;

				public uint Index;
			}

			private struct AccountInfo
			{
				public int ApiVersion;

				public IntPtr ProductUserId;

				public IntPtr DisplayName;

				public IntPtr AccountId;

				public int AccountIdType;

				public long LastLoginTime;
			}

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			private delegate void OnQuery(IntPtr info);

			internal class Mapping
			{
				public string Type;

				public string AccountId;

				public string DisplayName;

				public string When;

				public List<string> All = new List<string>();
			}

			private const string Dll = "EOSSDK-Win64-Shipping";

			private static OnQuery _cb;

			private static readonly string[] Types = new string[15]
			{
				"epic", "steam", "psn", "xbl", "discord", "gog", "nintendo", "uplay", "openid", "apple",
				"google", "oculus", "itchio", "amazon", "viveport"
			};

			internal static readonly Dictionary<string, Mapping> Map = new Dictionary<string, Mapping>(StringComparer.OrdinalIgnoreCase);

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

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

			private static readonly Dictionary<long, string> Inflight = new Dictionary<long, string>();

			private static long _seq;

			private static string File => Path.Combine(OrbState.DataDir, "eosmap.tsv");

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern IntPtr EOS_ProductUserId_FromString(string s);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern void EOS_Connect_QueryProductUserIdMappings(IntPtr h, ref QueryOptions o, IntPtr clientData, OnQuery cb);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern uint EOS_Connect_GetProductUserExternalAccountCount(IntPtr h, ref CountOptions o);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern int EOS_Connect_CopyProductUserExternalAccountByIndex(IntPtr h, ref CopyOptions o, out IntPtr info);

			[DllImport("EOSSDK-Win64-Shipping", CallingConvention = CallingConvention.Cdecl)]
			private static extern void EOS_Connect_ExternalAccountInfo_Release(IntPtr info);

			internal static void Load()
			{
				try
				{
					if (!System.IO.File.Exists(File))
					{
						return;
					}
					string[] array = System.IO.File.ReadAllLines(File);
					for (int i = 0; i < array.Length; i++)
					{
						string[] array2 = array[i].Split('\t');
						if (array2.Length >= 5)
						{
							Mapping mapping = new Mapping
							{
								Type = array2[1],
								AccountId = array2[2],
								DisplayName = array2[3],
								When = array2[4]
							};
							if (array2.Length > 5)
							{
								mapping.All.AddRange(array2[5].Split('|'));
							}
							Map[array2[0]] = mapping;
						}
					}
				}
				catch
				{
				}
			}

			internal static void Lookup(string addr, string claimed)
			{
				if (!IsEosAddr(addr))
				{
					return;
				}
				ClaimedBy[addr] = claimed;
				if (Map.TryGetValue(addr, out var value))
				{
					Compare(addr, value, claimed);
				}
				else
				{
					if (Pending.Contains(addr))
					{
						return;
					}
					try
					{
						EOSSingleton instance = EOSManager.Instance;
						ConnectInterface val = ((instance != null) ? instance.GetEOSConnectInterface() : null);
						ProductUserId val2 = ((instance != null) ? instance.GetProductUserId() : null);
						if ((Handle)(object)val == (Handle)null || (Handle)(object)val2 == (Handle)null)
						{
							return;
						}
						IntPtr intPtr = EOS_ProductUserId_FromString(addr);
						if (!(intPtr == IntPtr.Zero))
						{
							IntPtr intPtr2 = Marshal.AllocHGlobal(IntPtr.Size);
							Marshal.WriteIntPtr(intPtr2, intPtr);
							long num = ++_seq;
							lock (Inflight)
							{
								Inflight[num] = addr;
							}
							if (_cb == null)
							{
								_cb = OnQueried;
							}
							QueryOptions o = new QueryOptions
							{
								ApiVersion = 2,
								LocalUserId = ((Handle)val2).InnerHandle,
								ProductUserIds = intPtr2,
								Count = 1u
							};
							Pending.Add(addr);
							EOS_Connect_QueryProductUserIdMappings(((Handle)val).InnerHandle, ref o, (IntPtr)num, _cb);
						}
					}
					catch (Exception ex)
					{
						Plugin.Logger.LogError((object)("eos lookup: " + ex.Message));
						Pending.Remove(addr);
					}
				}
			}

			private static void OnQueried(IntPtr infoPtr)
			{
				try
				{
					QueryInfo queryInfo = Marshal.PtrToStructure<QueryInfo>(infoPtr);
					string value;
					lock (Inflight)
					{
						Inflight.TryGetValue((long)queryInfo.ClientData, out value);
						Inflight.Remove((long)queryInfo.ClientData);
					}
					if (value != null)
					{
						Pending.Remove(value);
						if (queryInfo.ResultCode != 0)
						{
							OrbState.AddEvent("eos", null, "host", $"lookup {value} → result {queryInfo.ResultCode}");
						}
						else
						{
							Read(value);
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogError((object)("eos callback: " + ex.Message));
				}
			}

			private static void Read(string addr)
			{
				EOSSingleton instance = EOSManager.Instance;
				ConnectInterface val = ((instance != null) ? instance.GetEOSConnectInterface() : null);
				if ((Handle)(object)val == (Handle)null)
				{
					return;
				}
				IntPtr targetUserId = EOS_ProductUserId_FromString(addr);
				CountOptions o = new CountOptions
				{
					ApiVersion = 1,
					TargetUserId = targetUserId
				};
				uint num = EOS_Connect_GetProductUserExternalAccountCount(((Handle)val).InnerHandle, ref o);
				Mapping mapping = new Mapping
				{
					When = OrbState.Now()
				};
				for (uint num2 = 0u; num2 < num; num2++)
				{
					CopyOptions o2 = new CopyOptions
					{
						ApiVersion = 1,
						TargetUserId = targetUserId,
						Index = num2
					};
					if (EOS_Connect_CopyProductUserExternalAccountByIndex(((Handle)val).InnerHandle, ref o2, out var info) != 0 || info == IntPtr.Zero)
					{
						continue;
					}
					try
					{
						AccountInfo accountInfo = Marshal.PtrToStructure<AccountInfo>(info);
						string text = ((accountInfo.AccountIdType >= 0 && accountInfo.AccountIdType < Types.Length) ? Types[accountInfo.AccountIdType] : ("type" + accountInfo.AccountIdType));
						string text2 = ((accountInfo.AccountId != IntPtr.Zero) ? Marshal.PtrToStringUTF8(accountInfo.AccountId) : "");
						string text3 = ((accountInfo.DisplayName != IntPtr.Zero) ? Marshal.PtrToStringUTF8(accountInfo.DisplayName) : "");
						mapping.All.Add($"{text}:{text2}:{text3}");
						if (mapping.Type == null || (mapping.Type == "epic" && text != "epic"))
						{
							mapping.Type = text;
							mapping.AccountId = text2;
							mapping.DisplayName = text3;
						}
					}
					finally
					{
						EOS_Connect_ExternalAccountInfo_Release(info);
					}
				}
				ClaimedBy.TryGetValue(addr, out var value);
				if (num == 0)
				{
					mapping.Type = "none";
				}
				Map[addr] = mapping;
				Save(addr, mapping);
				OrbState.AddEvent("eos", value, OrbState.RosterName(value) ?? value ?? "?", (num == 0) ? (addr + " has no linked account") : $"{addr} → {mapping.Type} {mapping.AccountId} \"{mapping.DisplayName}\"");
				if (num == 0)
				{
					string name = OrbState.RosterName(value) ?? value ?? "?";
					string text4 = addr + " has no external account on Epic: anonymous device-id login, not a real Steam/PSN/Xbox client (claimed " + (value ?? "?") + ")";
					OrbState.AddAlert("eosanon", value, name, text4);
					if (Plugin.GuardBanAnonymous.Value && AutoBan)
					{
						BanAddress(addr, name, "eosanon: " + text4, value);
					}
				}
				else
				{
					Compare(addr, mapping, value);
				}
			}

			private static void Compare(string addr, Mapping m, string claimed)
			{
				if (string.IsNullOrEmpty(claimed) || m == null || m.Type == "none")
				{
					return;
				}
				string text = Base(claimed);
				bool flag = false;
				bool flag2 = false;
				foreach (string item in m.All)
				{
					string[] array = item.Split(':');
					if (array.Length > 1 && array[1].Length > 0)
					{
						flag = true;
						if (array[1] == text)
						{
							flag2 = true;
						}
					}
				}
				if (!(!flag || flag2))
				{
					string name = OrbState.RosterName(claimed) ?? claimed;
					string text2 = $"Epic says {addr} is {m.Type} account {m.AccountId} (\"{m.DisplayName}\") but the client claimed {claimed}";
					OrbState.AddAlert("eosspoof", claimed, name, text2);
					if (AutoBan)
					{
						BanAddress(addr, name, "eosspoof: " + text2, claimed);
					}
				}
			}

			private static void Save(string addr, Mapping m)
			{
				try
				{
					List<string> list = new List<string>();
					if (System.IO.File.Exists(File))
					{
						list.AddRange(System.IO.File.ReadAllLines(File));
					}
					list.RemoveAll((string l) => l.StartsWith(addr + "\t", StringComparison.OrdinalIgnoreCase));
					list.Add($"{addr}\t{m.Type}\t{m.AccountId}\t{(m.DisplayName ?? "").Replace('\t', ' ')}\t{m.When}\t{string.Join("|", m.All).Replace('\t', ' ')}");
					System.IO.File.WriteAllLines(File, list);
				}
				catch
				{
				}
			}

			internal static string Json()
			{
				StringBuilder stringBuilder = new StringBuilder("{");
				foreach (KeyValuePair<string, Mapping> item in Map)
				{
					if (stringBuilder.Length > 1)
					{
						stringBuilder.Append(',');
					}
					stringBuilder.Append(OrbState.J(item.Key)).Append(":{\"type\":").Append(OrbState.J(item.Value.Type))
						.Append(",\"accountId\":")
						.Append(OrbState.J(item.Value.AccountId))
						.Append(",\"displayName\":")
						.Append(OrbState.J(item.Value.DisplayName))
						.Append(",\"when\":")
						.Append(OrbState.J(item.Value.When))
						.Append('}');
				}
				return stringBuilder.Append('}').ToString();
			}
		}

		internal static class Voice
		{
			private sealed class Bucket
			{
				public int Count;

				public int LastRate;

				public int Dropped;

				public float Window;

				public float OverSince = -1f;

				public float LastAlert = -999f;
			}

			private static readonly Dictionary<int, Bucket> B = new Dictionary<int, Bucket>();

			private const float BanAfter = 5f;

			internal static void Patch(Harmony harmony)
			{
				//IL_009e: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ac: Expected O, but got Unknown
				MethodInfo methodInfo = null;
				try
				{
					Type[] types = typeof(PlayerNetworking).Assembly.GetTypes();
					foreach (Type type in types)
					{
						if (type.Name == "MirrorIgnoranceServer")
						{
							methodInfo = type.GetMethod("OnMessageReceived", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
							break;
						}
					}
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogWarning((object)("voice relay type scan: " + ex.Message));
				}
				if (methodInfo == null)
				{
					Plugin.Logger.LogWarning((object)"voice relay hook not found, voice limiter off");
					return;
				}
				try
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Voice).GetMethod("Prefix", BindingFlags.Static | BindingFlags.Public)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				catch (Exception ex2)
				{
					Plugin.Logger.LogWarning((object)("voice relay patch: " + ex2.Message));
				}
			}

			public static bool Prefix(object __0)
			{
				int value = Plugin.GuardVoiceLimit.Value;
				if (value <= 0)
				{
					return true;
				}
				try
				{
					NetworkConnection val = (NetworkConnection)((__0 is NetworkConnection) ? __0 : null);
					if (val == null)
					{
						return true;
					}
					int connectionId = val.connectionId;
					if (connectionId == 0)
					{
						return true;
					}
					float unscaledTime = Time.unscaledTime;
					lock (B)
					{
						if (!B.TryGetValue(connectionId, out var value2))
						{
							Dictionary<int, Bucket> b = B;
							Bucket obj = new Bucket
							{
								Window = unscaledTime
							};
							value2 = obj;
							b[connectionId] = obj;
						}
						if (unscaledTime - value2.Window >= 1f)
						{
							value2.LastRate = value2.Count;
							value2.Count = 0;
							value2.Window = unscaledTime;
						}
						value2.Count++;
						if (value2.Count > value)
						{
							value2.Dropped++;
							return false;
						}
					}
				}
				catch
				{
				}
				return true;
			}

			internal static void Tick()
			{
				int value = Plugin.GuardVoiceLimit.Value;
				if (value <= 0)
				{
					return;
				}
				float unscaledTime = Time.unscaledTime;
				List<(int, Bucket)> list = null;
				lock (B)
				{
					foreach (KeyValuePair<int, Bucket> item in B)
					{
						Bucket value2 = item.Value;
						if (value2.Count > value || (unscaledTime - value2.Window < 1f && value2.LastRate > value))
						{
							if (value2.OverSince < 0f)
							{
								value2.OverSince = unscaledTime;
							}
							(list ?? (list = new List<(int, Bucket)>())).Add((item.Key, value2));
						}
						else
						{
							value2.OverSince = -1f;
						}
					}
					foreach (int item2 in B.Keys.Where((int c) => !NetworkServer.connections.ContainsKey(c)).ToList())
					{
						B.Remove(item2);
					}
				}
				if (list == null)
				{
					return;
				}
				NetworkConnectionToClient val = default(NetworkConnectionToClient);
				foreach (var (num, bucket) in list)
				{
					if (!NetworkServer.connections.TryGetValue(num, ref val) || val == null)
					{
						continue;
					}
					string text = OrbState.AuthFor(num)?.ClaimedId;
					string name = OrbState.RosterName(text) ?? text ?? $"conn {num}";
					if (unscaledTime - bucket.LastAlert > 30f)
					{
						bucket.LastAlert = unscaledTime;
						OrbState.AddAlert("voiceflood", text, name, $"{Math.Max(bucket.Count, bucket.LastRate)} voice packets/s (limit {value}), {bucket.Dropped} dropped");
					}
					if (unscaledTime - bucket.OverSince >= 5f)
					{
						string address = null;
						try
						{
							address = val.address;
						}
						catch
						{
						}
						if (AutoBan)
						{
							BanAddress(address, name, $"voiceflood: {5f:0}s sustained over {value} packets/s", text);
						}
						try
						{
							((NetworkConnection)val).Disconnect();
						}
						catch
						{
						}
						bucket.OverSince = -1f;
					}
				}
			}
		}

		[HarmonyPatch(typeof(PlayerNetworking), "InvokeUserCode_CmdSendTextChatMessage__String")]
		internal static class FakeChatPatch
		{
			private static bool Prefix(NetworkBehaviour __0, NetworkReader __1)
			{
				if (!Plugin.GuardChatFilter.Value)
				{
					return true;
				}
				try
				{
					PlayerNetworking val = ((Il2CppObjectBase)__0).TryCast<PlayerNetworking>();
					if ((Object)(object)val == (Object)null || ((NetworkBehaviour)val).isLocalPlayer)
					{
						return true;
					}
					int position = __1.Position;
					string text = null;
					try
					{
						text = NetworkReaderExtensions.ReadString(__1);
					}
					catch
					{
					}
					__1.Position = position;
					if (string.IsNullOrEmpty(text) || !FakeSystem.IsMatch(text))
					{
						return true;
					}
					Patches.MarkFired("chat/fake");
					OrbState.AddAlert("fakesys", Patches.Pid(val), Patches.Display(val), "chat styled as a system message, dropped: \"" + text + "\"");
					return false;
				}
				catch
				{
					return true;
				}
			}
		}

		private const float KickGrace = 1f;

		private static readonly Dictionary<int, float> PendingDisconnect = new Dictionary<int, float>();

		private static readonly Dictionary<string, HashSet<string>> IdsByAddress = new Dictionary<string, HashSet<string>>();

		private static readonly Dictionary<uint, string> Judged = new Dictionary<uint, string>();

		private static readonly Dictionary<string, int> Refused = new Dictionary<string, int>();

		private static readonly Regex FakeSystem = new Regex("^\\s*(?:\\[?(?:server|system|host|admin)\\]?\\s*[:\\-]|(?:the\\s+)?(?:host|server|admin|you|player|[\\w\\-]+)\\s+(?:has|have)\\s+been\\s+(?:removed|kicked|banned|disconnected)|[\\w\\-]+\\s+(?:has|have)\\s+(?:joined|left)\\s+the\\s+(?:game|lobby|server)|(?:you\\s+(?:have\\s+been|were)\\s+)?(?:kicked|banned)\\s+(?:by|from)\\s+(?:the\\s+)?(?:host|server)|(?:connection|host)\\s+(?:lost|timed\\s+out)|lost\\s+connection\\s+to\\s+(?:the\\s+)?host)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		internal static bool AutoBan => Plugin.GuardAutoBan.Value;

		internal static void Kick(PlayerNetworking pn, string reason = null)
		{
			if ((Object)(object)pn == (Object)null)
			{
				return;
			}
			NetworkConnectionToClient val = null;
			try
			{
				val = ((NetworkBehaviour)pn).connectionToClient;
			}
			catch
			{
			}
			if (val == null)
			{
				return;
			}
			lock (PendingDisconnect)
			{
				if (PendingDisconnect.ContainsKey(((NetworkConnection)val).connectionId))
				{
					return;
				}
				PendingDisconnect[((NetworkConnection)val).connectionId] = Time.unscaledTime + 1f;
			}
			try
			{
				PeckEffectTextInput.ClearTextWrittenByAuthor(pn.identifier);
			}
			catch
			{
			}
			try
			{
				pn.RPCKickUser(val);
			}
			catch
			{
			}
		}

		internal static void StepKicks()
		{
			List<int> list = null;
			lock (PendingDisconnect)
			{
				foreach (KeyValuePair<int, float> item in PendingDisconnect)
				{
					if (Time.unscaledTime >= item.Value)
					{
						(list ?? (list = new List<int>())).Add(item.Key);
					}
				}
			}
			if (list == null)
			{
				return;
			}
			NetworkConnectionToClient val = default(NetworkConnectionToClient);
			foreach (int item2 in list)
			{
				lock (PendingDisconnect)
				{
					PendingDisconnect.Remove(item2);
				}
				try
				{
					if (NetworkServer.connections.TryGetValue(item2, ref val) && val != null)
					{
						((NetworkConnection)val).Disconnect();
					}
				}
				catch
				{
				}
			}
		}

		internal static void BanAddress(string address, string name, string why, string id = null)
		{
			if (string.IsNullOrEmpty(address) || address == "localhost")
			{
				return;
			}
			if (!OrbState.IsBannedAddress(address))
			{
				OrbState.BanAdd(id, name, 0uL, address);
			}
			OrbState.AddEvent("autoban", id, name, why);
			Enumerator<int, NetworkConnectionToClient> enumerator = NetworkServer.connections.GetEnumerator();
			while (enumerator.MoveNext())
			{
				NetworkConnectionToClient value = enumerator.Current.Value;
				if (value == null || ((NetworkConnection)value).connectionId == 0)
				{
					continue;
				}
				string text = null;
				try
				{
					text = value.address;
				}
				catch
				{
				}
				if (text == address)
				{
					try
					{
						((NetworkConnection)value).Disconnect();
					}
					catch
					{
					}
				}
			}
		}

		private static void Act(string kind, string id, string name, string detail, string address, NetworkConnectionToClient conn)
		{
			OrbState.AddAlert(kind, id, name, detail);
			if (AutoBan)
			{
				BanAddress(address, name ?? id ?? "?", kind + ": " + detail, id);
				return;
			}
			try
			{
				if (conn != null)
				{
					((NetworkConnection)conn).Disconnect();
				}
			}
			catch
			{
			}
		}

		private static string Base(string id)
		{
			if (id != null)
			{
				return Regex.Replace(id, "-\\d+$", "");
			}
			return null;
		}

		private static bool IsSteam(string id)
		{
			if (id != null && id.Length == 17 && id.StartsWith("7656119"))
			{
				return id.All(char.IsDigit);
			}
			return false;
		}

		private static bool IsNumeric(string id)
		{
			if (!string.IsNullOrEmpty(id) && id.Length >= 15)
			{
				return id.All(char.IsDigit);
			}
			return false;
		}

		private static bool IsEosAddr(string a)
		{
			if (a != null && a.Length == 32)
			{
				return a.All(Uri.IsHexDigit);
			}
			return false;
		}

		internal static void Tick()
		{
			StepKicks();
			Voice.Tick();
			foreach (PlayerCharacter item in OrbBehaviour.Players())
			{
				try
				{
					PlayerNetworking playerNetworking = item.playerNetworking;
					if ((Object)(object)playerNetworking == (Object)null || ((NetworkBehaviour)playerNetworking).isLocalPlayer)
					{
						continue;
					}
					string text = null;
					string text2 = null;
					string text3 = null;
					ulong num = 0uL;
					try
					{
						text = Patches.Pid(playerNetworking);
					}
					catch
					{
					}
					try
					{
						NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)playerNetworking).connectionToClient;
						text2 = ((connectionToClient != null) ? connectionToClient.address : null);
					}
					catch
					{
					}
					try
					{
						text3 = playerNetworking.epicUserId;
					}
					catch
					{
					}
					try
					{
						num = playerNetworking.userPlatformId;
					}
					catch
					{
					}
					if (OrbState.IsUnsetIdentifier(text) || text2 == null)
					{
						continue;
					}
					string text4 = text3 + "|" + num;
					if (Judged.TryGetValue(((NetworkBehaviour)playerNetworking).netId, out var value) && value == text4)
					{
						continue;
					}
					Judged[((NetworkBehaviour)playerNetworking).netId] = text4;
					string name = Patches.Display(playerNetworking);
					if (IsEosAddr(text2) && !string.IsNullOrEmpty(text3) && !string.Equals(text3, text2, StringComparison.OrdinalIgnoreCase))
					{
						Act("epicspoof", text, name, "connection is " + text2 + " but the client reports epic id " + text3, text2, ((NetworkBehaviour)playerNetworking).connectionToClient);
					}
					else
					{
						if (num == 0L || !IsNumeric(Base(text)) || !(num.ToString() != Base(text)))
						{
							continue;
						}
						string text5 = null;
						foreach (PlayerCharacter item2 in OrbBehaviour.Players())
						{
							PlayerNetworking playerNetworking2 = item2.playerNetworking;
							if ((Object)(object)playerNetworking2 != (Object)null && ((Il2CppObjectBase)playerNetworking2).Pointer != ((Il2CppObjectBase)playerNetworking).Pointer && Base(Patches.Pid(playerNetworking2)) == num.ToString())
							{
								text5 = Patches.Display(playerNetworking2);
								break;
							}
						}
						Act("platspoof", text, name, $"claims identifier {text} but platform id is {num}" + ((text5 != null) ? (" (" + text5 + "'s, who is connected)") : ""), text2, ((NetworkBehaviour)playerNetworking).connectionToClient);
						continue;
					}
				}
				catch
				{
				}
			}
			if (Judged.Count <= 64)
			{
				return;
			}
			HashSet<uint> live = new HashSet<uint>();
			foreach (PlayerCharacter item3 in OrbBehaviour.Players())
			{
				try
				{
					live.Add(((NetworkBehaviour)item3.playerNetworking).netId);
				}
				catch
				{
				}
			}
			foreach (uint item4 in Judged.Keys.Where((uint k) => !live.Contains(k)).ToList())
			{
				Judged.Remove(item4);
			}
		}
	}
	public class Finale : IOrbScript
	{
		private ScriptHost _h;

		private int _tick;

		private readonly List<TrackedPeckState> _held = new List<TrackedPeckState>();

		private float _releaseAt;

		private readonly List<(TrackedPeckState st, float at)> _bounces = new List<(TrackedPeckState, float)>();

		private static Finale _instance;

		private readonly List<(string[] roots, float at)> _rechecks = new List<(string[], float)>();

		private static readonly (string key, string label, string[] roots, (string suffix, string label)[] probe, string note, string anchor, float radius, string[] bounce, string[] props)[] Stages = new(string, string, string[], (string, string)[], string, string, float, string[], string[])[10]
		{
			("dreamdoor", "Dream door (N-hold → vision → interior door)", new string[4] { "BlackTower/Positioner/NHoldFullSet", "BlackTower/Positioner/DreamDoorLogic", "BlackTower/BlackTowerDream", "BlackTower/Positioner/BasicDoor DreamBlocker" }, new(string, string)[4]
			{
				("/DreamDoorLogic", "door"),
				("/DreamStateSystem", "dream"),
				("/NHoldSucess", "success"),
				("DreamBlocker/BasicDoorPeckLogic", "blocker")
			}, "hold all N buttons (N = player count) at once; one presser gets the vision, door unlocks", null, 0f, null, null),
			("bell", "Bell room (4 holds → bell crushed → exit gate)", new string[2] { "EndingGate/GatePositioner/SecondGate", "EndingGate/GatePositioner/EndingGate_Door - Exit" }, new(string, string)[3]
			{
				("SpeechlessField/SpeechlessFieldLogic", "field"),
				("EndingGate_Door - Exit/AnimatingPartsEndingGate/GateOpeningLogic", "exit"),
				("NHoldPlinth0/ButtonNHoldFour/ButtonSystem", "hold0")
			}, "field 1 = bell up, 2 = crushing, 0 = crushed; cheat: nhold key=EndingGate/GatePositioner/SecondGate/NHolds", null, 0f, null, null),
			("gauntlet0", "Gauntlet 0 — peg board (9 tiles)", new string[1] { "Positioner/Level0/" }, new(string, string)[3]
			{
				("Level0/GauntletChamber00PegBoard/GauntletHallway/GauntletChamberNetworkObjects/ChallengeCompletedSystem", "complete"),
				("Level0/GourdTower_Stairs/GourdTower_Gate Challenge Complete/GourdTowerGateNetworking", "gate"),
				("Level0/GourdTower_Stairs/GourdTower_Gate All Hold/GourdTowerGateNetworking", "holdgate")
			}, "9 tiles back on the rack (by guid — wherever they were carried), gates closed, completion cleared, room doors bounced to re-scramble", null, 0f, new string[3] { "Level0/GauntletChamber00PegBoard/Room Always/BasicDoor Always/BasicDoorPeckLogic", "Room Teller1/BasicDoor Teller1/BasicDoorPeckLogic", "Room Teller2/BasicDoor Teller2/BasicDoorPeckLogic" }, new string[9] { "fc58b531-2bfa-46ca-9ecf-fb53bc5cf37f", "a2d05ea0-6cbd-433a-84af-a194a05d6536", "5eec232c-c20f-432f-95dc-1c8d6b32e159", "1690f6b6-2f55-404f-b578-dd7bf7ca3d47", "987b4ef6-7b9d-483f-9eba-4bcf1e0643f0", "556ba962-5281-47ac-9ecf-b7ba73b4ef15", "dceb4dd3-5f79-423c-b83c-39cca9e1bae2", "c02dde13-6886-411c-9b8c-b558473b1316", "8bde95f3-e638-4ddf-95c8-e30613cd9e42" }),
			Gauntlet(1, "GauntletChamber01Pointers", "Gauntlet 1 — pointers (press in order)", "tracker + lights to baseline, doors bounced to re-arm", null, 0f, new string[3] { "Level1/GauntletChamber01Pointers/DoorGroup/BasicDoor/BasicDoorPeckLogic", "DoorGroup/BasicDoor 3PlayerAndUp/BasicDoorPeckLogic", "DoorGroup/BasicDoor4Player/BasicDoorPeckLogic" }),
			Gauntlet(2, "GauntletKick02ToKick", "Gauntlet 2 — kick to kick (pomodoro → pedestal)", "receptacle/doors/turnstiles to baseline, pomodoro back to the dispenser (pose reset 40m)", "Level2/GauntletKick02ToKick/BasicReceptical", 40f, null),
			Gauntlet(3, "GauntletChamber03SimPress", "Gauntlet 3 — sim-press (4 switches)", "switches self-reset; completion + gates to baseline", null, 0f, null),
			Gauntlet(4, "GauntletChamberVolleyBall", "Gauntlet 4 — volleyball (press in order, red kettle)", "tracker + lights to baseline, kettle back by guid, doors bounced", null, 0f, new string[4] { "Level4/GauntletChamberVolleyBall/Doors/BasicDoor 1 (primary)/BasicDoorPeckLogic", "Doors/BasicDoor 2/BasicDoorPeckLogic", "Doors/BasicDoor 3/BasicDoorPeckLogic", "Doors/BasicDoor 4/BasicDoorPeckLogic" }, new string[1] { "33494b4b-6d5e-4ac4-bcbb-fb43ddeb6bf2" }),
			Gauntlet(5, "GauntletChamberInvisi", "Gauntlet 5 — invisible ink (peg board, telescope)", "9 tiles back on the rack (by guid), screen/completion/gates cleared, doors bounced to re-scramble", null, 0f, new string[4] { "Level5/GauntletChamberInvisi/Door1/BasicDoor/BasicDoorPeckLogic", "Level5/GauntletChamberInvisi/Door2/BasicDoor/BasicDoorPeckLogic", "3PlayerAndUp/Door3/", "4PlayerOnly/Door4/" }, new string[9] { "3016c28d-21d9-40fd-9875-2d3470c19efe", "abbcb967-6c81-4cae-b246-c809f364fbe0", "a3088495-97ef-4a27-bedf-98f3bedb606a", "cee0452e-f813-46b2-adbc-b2580379555c", "c7aa5a25-c784-455d-aadd-ad3b89a6b208", "e007cc19-9f5b-4d45-af25-1d722c50b74d", "7211c43f-4efd-4c8b-ae62-01fbd8a62308", "90dc6ef0-92e4-4637-821c-307a9fc97a64", "1207f056-b89d-4280-9ebf-a848b95db57e" }),
			Gauntlet(6, "GauntletChamber06Sculptures", "Gauntlet 6 — sculptures (press in order)", "tracker + lights to baseline, doors bounced to re-arm", null, 0f, new string[4] { "Level6/GauntletChamber06Sculptures/Doors/BasicDoor 1 (primary)/", "Doors/BasicDoor 2 /", "Doors/BasicDoor 3/", "Doors/BasicDoor 4/" }),
			("gauntlet7", "Gauntlet 7 — final bell (4-hold → skylight)", new string[2] { "Positioner/Level7/", "CreditsTheatre/Positioner/CreditsScreen" }, new(string, string)[3]
			{
				("Level7/SpeechlessField/SpeechlessFieldLogic", "field"),
				("Level7/GauntletEndingLogicGroup/SkylightDoorSystem", "skylight"),
				("Level7/GauntletEndingLogicGroup/GauntletIsCompletedSystem", "complete")
			}, "field 1 = bell up; reset puts the bell back, closes the skylight, clears GauntletComplete; cheat: nhold key=Level7/NHoldAnchor", null, 0f, null, null)
		};

		private static float _suppressUntil;

		private static bool _patched;

		private readonly Dictionary<IntPtr, string> _trapped = new Dictionary<IntPtr, string>();

		private float _nextTrapCheck;

		private readonly List<(TrackedPeckState st, string key)> _probeIdx = new List<(TrackedPeckState, string)>();

		private float _probeIdxAt = -999f;

		public string Name => "finale";

		public static void BounceEndingGates()
		{
			_instance?.BounceGates();
		}

		public static string DreamStatic(PlayerCharacter pc)
		{
			if (_instance == null)
			{
				return "finale script not loaded";
			}
			return _instance.GiveDream(pc);
		}

		public static void WakeStatic(PlayerCharacter pc)
		{
			DreamController item = Controller().dc;
			if ((Object)(object)item == (Object)null || (Object)(object)pc == (Object)null)
			{
				return;
			}
			try
			{
				pc.playerNetworking.ServerSetDream(item, false);
			}
			catch
			{
			}
			try
			{
				if ((Object)(object)item.dreamPlayer == (Object)(object)pc)
				{
					item.dreamPlayer = null;
				}
			}
			catch
			{
			}
		}

		private void BounceGates()
		{
			int num = 0;
			foreach (TrackedPeckState item in Object.FindObjectsOfType<TrackedPeckState>(true))
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				string text;
				try
				{
					text = Api.TransformPath(((Component)item).transform);
				}
				catch
				{
					continue;
				}
				if (text.IndexOf("EndingGate/GatePositioner/EndingGate_Door", StringComparison.OrdinalIgnoreCase) >= 0 && text.EndsWith("/GateOpeningLogic"))
				{
					try
					{
						item.SetState(1);
						_bounces.Add((item, Time.unscaledTime + 3f));
						num++;
					}
					catch
					{
					}
				}
			}
			if (num > 0)
			{
				_h?.Event("finalereset", $"ending gates: {num} bounced open→close (3s)");
			}
		}

		private static (string, string, string[], (string, string)[], string, string, float, string[], string[]) Gauntlet(int n, string chamber, string label, string note, string anchor, float radius, string[] bounce, string[] props = null)
		{
			return ("gauntlet" + n, label, new string[1] { "Positioner/Level" + n + "/" }, new(string, string)[3]
			{
				("Level" + n + "/" + chamber + "/GauntletHallway/GauntletChamberNetworkObjects/ChallengeCompletedSystem", "complete"),
				("Level" + n + "/GourdTower_Stairs/GourdTower_Gate Challenge Complete/GourdTowerGateNetworking", "gate"),
				("Level" + n + "/GourdTower_Stairs/GourdTower_Gate All Hold/GourdTowerGateNetworking", "holdgate")
			}, note, anchor, radius, bounce, props);
		}

		public void Load(ScriptHost host)
		{
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			_h = host;
			_instance = this;
			host.Command("finalereset", delegate(string id, string key, int val, string text)
			{
				//IL_007f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0084: 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_0097: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
				//IL_029f: Unknown result type (might be due to invalid IL or missing references)
				int num = 0;
				if (string.Equals(key, "all", StringComparison.OrdinalIgnoreCase))
				{
					try
					{
						HubReset.ResetStatic("BlackTower/Positioner/GourdPlinth X6");
					}
					catch (Exception ex2)
					{
						host.Warn("monument: " + ex2.Message);
					}
				}
				if (key.StartsWith("gauntlet", StringComparison.OrdinalIgnoreCase) || string.Equals(key, "all", StringComparison.OrdinalIgnoreCase))
				{
					List<string> list = new List<string>();
					foreach (PlayerCharacter item2 in Api.Players())
					{
						try
						{
							Vector3 position = ((Component)item2).transform.position;
							if (position.x > -800f && position.x < -680f && position.z > 640f && position.z < 740f && position.y > 40f)
							{
								list.Add($"{Api.Display(item2.playerNetworking)} (y {position.y:0})");
							}
						}
						catch
						{
						}
					}
					if (list.Count > 0)
					{
						host.Event("finalereset", "⚠ players inside the orb above the entrance — the tower will re-open the gates below them: " + string.Join(", ", list));
					}
				}
				(string, string, string[], (string, string)[], string, string, float, string[], string[])[] stages = Stages;
				for (int i = 0; i < stages.Length; i++)
				{
					(string, string, string[], (string, string)[], string, string, float, string[], string[]) tuple = stages[i];
					if (string.Equals(key, "all", StringComparison.OrdinalIgnoreCase) || string.Equals(key, tuple.Item1, StringComparison.OrdinalIgnoreCase))
					{
						List<string> list2 = new List<string>();
						if (tuple.Item1 == "dreamdoor")
						{
							Release();
							if (_trapped.Count > 0)
							{
								list2.Add("traps dropped: " + string.Join(", ", _trapped.Values));
								_trapped.Clear();
							}
							StopDream(list2);
						}
						if (tuple.Item1 == "bell")
						{
							BounceGates();
						}
						if (!string.IsNullOrEmpty(tuple.Item6) && tuple.Item7 > 0f)
						{
							Vector3? val2 = Anchor(tuple.Item6);
							if (!val2.HasValue)
							{
								list2.Add("anchor not found: " + tuple.Item6);
							}
							else
							{
								try
								{
									(int, int, int) tuple2 = Api.ResetPropsAround(val2.Value, tuple.Item7);
									list2.Add($"{tuple2.Item1}/{tuple2.Item2} prop(s) re-posed{((tuple2.Item3 > 0) ? $", {tuple2.Item3} held" : "")}");
								}
								catch (Exception ex3)
								{
									list2.Add("props: " + ex3.Message);
								}
							}
						}
						if (tuple.Rest.Item2 != null)
						{
							string[] item = tuple.Rest.Item2;
							foreach (string text2 in item)
							{
								try
								{
									Api.ResetProps(text2, 0);
									list2.Add("re-posed " + text2.Substring(0, Math.Min(8, text2.Length)));
								}
								catch (Exception ex4)
								{
									list2.Add("prop " + text2 + ": " + ex4.Message);
								}
							}
						}
						int value = HubReset.ResetStatesUnder(tuple.Item3, list2);
						if (tuple.Rest.Item1 != null)
						{
							foreach (TrackedPeckState item3 in Object.FindObjectsOfType<TrackedPeckState>(true))
							{
								if (!((Object)(object)item3 == (Object)null))
								{
									string text3;
									try
									{
										text3 = Api.TransformPath(((Component)item3).transform);
									}
									catch
									{
										continue;
									}
									bool flag = false;
									string[] item = tuple.Item3;
									foreach (string value2 in item)
									{
										if (text3.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0)
										{
											flag = true;
											break;
										}
									}
									if (flag && text3.EndsWith("/BasicDoorPeckLogic", StringComparison.OrdinalIgnoreCase))
									{
										item = tuple.Rest.Item1;
										foreach (string text4 in item)
										{
											if (text3.IndexOf(text4, StringComparison.OrdinalIgnoreCase) >= 0)
											{
												try
												{
													item3.SetState(1);
													_bounces.Add((item3, Time.unscaledTime + 1f));
													list2.Add("bounced " + text4.TrimEnd('/').Split('/')[^1]);
												}
												catch (Exception ex5)
												{
													list2.Add("bounce: " + ex5.Message);
												}
												break;
											}
										}
									}
								}
							}
						}
						if (tuple.Rest.Item1 != null)
						{
							_rechecks.Add((tuple.Item3, Time.unscaledTime + 2.5f));
						}
						host.Event("finalereset", $"{tuple.Item1}: {value} state(s) → baseline{((list2.Count > 0) ? (" — " + string.Join("; ", list2)) : "")}");
						num++;
					}
				}
				if (num == 0)
				{
					host.Event("finalereset", $"unknown stage {key}; stages: {string.Join(", ", Array.ConvertAll(Stages, ((string key, string label, string[] roots, (string suffix, string label)[] probe, string note, string anchor, float radius, string[] bounce, string[] props) s) => s.key))}, all");
				}
				Publish();
			});
			host.Command("dreamhold", delegate(string id, string key, int val, string text)
			{
				Release();
				int num = 4;
				try
				{
					num = Api.WorldVariant();
				}
				catch
				{
				}
				string value = "NHoldFullSet/NHoldSet" + num + "/";
				int num2 = ((val > 0) ? val : (num - 1));
				foreach (TrackedPeckState item4 in Object.FindObjectsOfType<TrackedPeckState>(true))
				{
					if (!((Object)(object)item4 == (Object)null) && _held.Count < num2)
					{
						string text2;
						try
						{
							text2 = Api.TransformPath(((Component)item4).transform);
						}
						catch
						{
							continue;
						}
						if (text2.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0 && text2.EndsWith("/ButtonSystem"))
						{
							try
							{
								item4.SetState(1);
								_held.Add(item4);
							}
							catch (Exception ex2)
							{
								host.Warn("dreamhold: " + ex2.Message);
							}
						}
					}
				}
				_releaseAt = Time.unscaledTime + 30f;
				host.Event("dreamhold", $"holding {_held.Count} of the {num}-hold buttons for 30s — press the rest for real");
			});
			host.Command("gauntletopen", delegate(string id, string key, int val, string text)
			{
				GauntletPress(key, victory: true, hold: false);
			});
			host.Command("gauntlethold", delegate(string id, string key, int val, string text)
			{
				GauntletPress(key, victory: false, hold: true);
			});
			host.Command("dreamrelease", delegate
			{
				int count = _held.Count;
				Release();
				host.Event("dreamhold", $"released {count}");
			});
			host.Command("hold", delegate(string id, string key, int val, string text)
			{
				Release();
				int num = ((val <= 0) ? 1 : val);
				foreach (TrackedPeckState item5 in Object.FindObjectsOfType<TrackedPeckState>(true))
				{
					if (!((Object)(object)item5 == (Object)null) && _held.Count < num)
					{
						string text2;
						try
						{
							text2 = Api.TransformPath(((Component)item5).transform);
						}
						catch
						{
							continue;
						}
						if (text2.IndexOf(key ?? "", StringComparison.OrdinalIgnoreCase) >= 0 && text2.EndsWith("/ButtonSystem"))
						{
							try
							{
								item5.SetState(1);
								_held.Add(item5);
							}
							catch (Exception ex2)
							{
								host.Warn("hold: " + ex2.Message);
							}
						}
					}
				}
				_releaseAt = Time.unscaledTime + 30f;
				host.Event("hold", $"holding {_held.Count} button(s) under {key} for 30s");
			});
			host.Command("dreamstop", delegate
			{
				List<string> list = new List<string>();
				StopDream(list);
				host.Event("dream", string.Join("; ", list));
			});
			try
			{
				MethodInfo method = typeof(DreamController).GetMethod("OnStartDreamPeck", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				host.Harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(Finale).GetMethod("StartDreamPrefix", BindingFlags.Static | BindingFlags.Public)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_patched = true;
			}
			catch (Exception ex)
			{
				host.Warn("patch OnStartDreamPeck: " + ex.Message);
			}
			host.Tick(delegate
			{
				if (_held.Count > 0 && Time.unscaledTime >= _releaseAt)
				{
					int count = _held.Count;
					Release();
					host.Event("dreamhold", $"auto-released {count}");
				}
				TrapTick();
				for (int num = _bounces.Count - 1; num >= 0; num--)
				{
					if (Time.unscaledTime >= _bounces[num].at)
					{
						try
						{
							_bounces[num].st.SetState(0);
						}
						catch
						{
						}
						_bounces.RemoveAt(num);
					}
				}
				for (int num2 = _rechecks.Count - 1; num2 >= 0; num2--)
				{
					if (Time.unscaledTime >= _rechecks[num2].at)
					{
						int num3 = HubReset.ResetStatesUnder(_rechecks[num2].roots, null);
						if (num3 > 0)
						{
							_h.Event("finalereset", $"settle pass: {num3} state(s) re-applied");
						}
						_rechecks.RemoveAt(num2);
					}
				}
				if (++_tick % 8 == 6 && Api.DashboardLive)
				{
					Publish();
				}
			});
			Publish();
		}

		public static bool StartDreamPrefix()
		{
			return Time.unscaledTime >= _suppressUntil;
		}

		private string GiveDream(PlayerCharacter pc)
		{
			var (val, val2) = Controller();
			if ((Object)(object)val == (Object)null)
			{
				return "no DreamController (BlackTowerDream/DreamStateSystem)";
			}
			try
			{
				_suppressUntil = Time.unscaledTime + 3f;
				int num = -1;
				try
				{
					PeckContext currentPeckContext = val2.currentPeckContext;
					if (currentPeckContext != null)
					{
						num = currentPeckContext.state;
					}
				}
				catch
				{
				}
				if (num != 1)
				{
					try
					{
						val2.SetState(1);
					}
					catch (Exception ex)
					{
						_h.Warn("dream state: " + ex.Message);
					}
				}
				val.dreamPlayer = pc;
				pc.playerNetworking.ServerSetDream(val, true);
				return null;
			}
			catch (Exception ex2)
			{
				return "failed: " + ex2.Message;
			}
		}

		private void TrapTick()
		{
			if (_trapped.Count == 0 || Time.unscaledTime < _nextTrapCheck)
			{
				return;
			}
			_nextTrapCheck = Time.unscaledTime + 1f;
			List<IntPtr> list = new List<IntPtr>();
			foreach (KeyValuePair<IntPtr, string> item in _trapped)
			{
				PlayerCharacter val = null;
				foreach (PlayerCharacter item2 in Api.Players())
				{
					if (((Il2CppObjectBase)item2).Pointer == item.Key)
					{
						val = item2;
						break;
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					list.Add(item.Key);
					continue;
				}
				bool flag = true;
				try
				{
					flag = val.dreamer.isDreaming;
				}
				catch
				{
				}
				if (!flag)
				{
					string text = GiveDream(val);
					if (text != null)
					{
						_h.Warn("dreamtrap " + item.Value + ": " + text);
					}
				}
			}
			foreach (IntPtr item3 in list)
			{
				_h.Event("dreamtrap", _trapped[item3] + " left — trap dropped");
				_trapped.Remove(item3);
			}
		}

		private void Release()
		{
			foreach (TrackedPeckState item in _held)
			{
				try
				{
					item.SetState(0);
				}
				catch
				{
				}
			}
			_held.Clear();
		}

		private static PlayerCharacter Pick(string id, string text)
		{
			if (!string.IsNullOrEmpty(id))
			{
				try
				{
					PlayerCharacter val = Api.ById(id);
					if ((Object)(object)val != (Object)null)
					{
						return val;
					}
				}
				catch
				{
				}
			}
			if (!string.IsNullOrEmpty(text))
			{
				foreach (PlayerCharacter item in Api.Players())
				{
					try
					{
						if (Api.Display(item.playerNetworking).IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
						{
							return item;
						}
					}
					catch
					{
					}
				}
			}
			return null;
		}

		private static List<PlayerCharacter> Targets(string id, string text, bool defaultHost)
		{
			List<PlayerCharacter> list = new List<PlayerCharacter>();
			bool num = string.Equals(id, "all", StringComparison.OrdinalIgnoreCase);
			bool flag = string.Equals(id, "others", StringComparison.OrdinalIgnoreCase);
			if (num || flag)
			{
				PlayerCharacter val = Api.Local();
				{
					foreach (PlayerCharacter item in Api.Players())
					{
						if ((Object)(object)item != (Object)null && (!flag || !((Object)(object)item == (Object)(object)val)))
						{
							list.Add(item);
						}
					}
					return list;
				}
			}
			PlayerCharacter val2 = Pick(id, text) ?? ((defaultHost && string.IsNullOrEmpty(id) && string.IsNullOrEmpty(text)) ? Api.Local() : null);
			if ((Object)(object)val2 != (Object)null)
			{
				list.Add(val2);
			}
			return list;
		}

		private void GauntletPress(string key, bool victory, bool hold)
		{
			int result = 0;
			if (!int.TryParse(key, out result))
			{
				_h.Event("gauntlet", "need key=<level>");
				return;
			}
			int num = 4;
			try
			{
				num = Api.WorldVariant();
			}
			catch
			{
			}
			int num2 = 0;
			foreach (TrackedPeckState item in Object.FindObjectsOfType<TrackedPeckState>(true))
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				string text;
				try
				{
					text = Api.TransformPath(((Component)item).transform);
				}
				catch
				{
					continue;
				}
				if (text.IndexOf("/Level" + result + "/", StringComparison.OrdinalIgnoreCase) < 0)
				{
					continue;
				}
				bool num3 = victory && text.EndsWith("/GauntletChamberNetworkObjects/VictoryLogic", StringComparison.OrdinalIgnoreCase);
				bool flag = hold && text.IndexOf("GourdTower_Stairs/NHoldFullSet/NHoldSet" + num + "/", StringComparison.OrdinalIgnoreCase) >= 0 && text.EndsWith("/ButtonSystem");
				if (!num3 && !flag)
				{
					continue;
				}
				try
				{
					item.SetState(1);
					num2++;
					if (flag)
					{
						_held.Add(item);
					}
				}
				catch (Exception ex)
				{
					_h.Warn("gauntlet: " + ex.Message);
				}
			}
			if (hold)
			{
				_releaseAt = Time.unscaledTime + 1.5f;
			}
			_h.Event(victory ? "gauntletopen" : "gauntlethold", $"level {result}: {num2} state(s) pressed ({(victory ? "victory" : (num + "-hold"))})");
		}

		private static Vector3? Anchor(string suffix)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			foreach (TrackedPeckState item in Object.FindObjectsOfType<TrackedPeckState>(true))
			{
				if (!((Object)(object)item == (Object)null))
				{
					string text;
					try
					{
						text = Api.TransformPath(((Component)item).transform);
					}
					catch
					{
						continue;
					}
					if (text.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
					{
						return ((Component)item).transform.position;
					}
				}
			}
			return null;
		}

		private static (DreamController dc, TrackedPeckState st) Controller()
		{
			foreach (TrackedPeckState item in Object.FindObjectsOfType<TrackedPeckState>(true))
			{
				if (!((Object)(object)item == (Object)null))
				{
					string text;
					try
					{
						text = Api.TransformPath(((Component)item).transform);
					}
					catch
					{
						continue;
					}
					if (text.EndsWith("BlackTowerDream/DreamStateSystem", StringComparison.OrdinalIgnoreCase))
					{
						return (dc: ((Component)item).GetComponent<DreamController>(), st: item);
					}
				}
			}
			return (dc: null, st: null);
		}

		private void StopDream(List<string> notes)
		{
			DreamController item = Controller().dc;
			if ((Object)(object)item == (Object)null)
			{
				notes?.Add("no DreamController");
				return;
			}
			int num = 0;
			foreach (PlayerCharacter item2 in Api.Players())
			{
				try
				{
					item2.playerNetworking.ServerSetDream(item, false);
					num++;
				}
				catch (Exception ex)
				{
					notes?.Add("stop " + Api.Display(item2.playerNetworking) + ": " + ex.Message);
				}
			}
			try
			{
				item.dreamPlayer = null;
			}
			catch
			{
			}
			notes?.Add($"dream stopped for {num} player(s)");
		}

		private void IndexProbes()
		{
			_probeIdx.Clear();
			_probeIdxAt = Time.unscaledTime;
			foreach (TrackedPeckState item2 in Object.FindObjectsOfType<TrackedPeckState>(true))
			{
				if ((Object)(object)item2 == (Object)null)
				{
					continue;
				}
				string text;
				try
				{
					text = Api.TransformPath(((Component)item2).transform);
				}
				catch
				{
					continue;
				}
				if (text.IndexOf("BlackTower", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("EndingGate", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Gauntlet", StringComparison.OrdinalIgnoreCase) < 0)
				{
					continue;
				}
				(string, string, string[], (string, string)[], string, string, float, string[], string[])[] stages = Stages;
				for (int i = 0; i < stages.Length; i++)
				{
					(string, string, string[], (string, string)[], string, string, float, string[], string[]) tuple = stages[i];
					(string, string)[] item = tuple.Item4;
					for (int j = 0; j < item.Length; j++)
					{
						var (value, text2) = item[j];
						if (text.EndsWith(value, StringComparison.OrdinalIgnoreCase))
						{
							_probeIdx.Add((item2, tuple.Item1 + "/" + text2));
						}
					}
				}
			}
		}

		private void Publish()
		{
			if (Time.unscaledTime - _probeIdxAt > 60f || _probeIdx.Count == 0)
			{
				IndexProbes();
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (var (val, key) in _probeIdx)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				int value = int.MinValue;
				try
				{
					PeckContext currentPeckContext = val.currentPeckContext;
					if (currentPeckContext != null)
					{
						value = currentPeckContext.state;
					}
				}
				catch
				{
				}
				dictionary[key] = value;
			}
			StringBuilder stringBuilder = new StringBuilder("{\"held\":").Append(_held.Count).Append(",\"trapped\":[");
			bool flag = true;
			foreach (string value7 in _trapped.Values)
			{
				if (!flag)
				{
					stringBuilder.Append(',');
				}
				flag = false;
				stringBuilder.Append(Api.Json(value7));
			}
			stringBuilder.Append("],\"stages\":[");
			for (int i = 0; i < Stages.Length; i++)
			{
				(string, string, string[], (string, string)[], string, string, float, string[], string[]) tuple2 = Stages[i];
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				bool flag2 = false;
				try
				{
					flag2 = ((!(tuple2.Item1 == "dreamdoor")) ? ((!(tuple2.Item1 == "bell")) ? ((!(tuple2.Item1 == "gauntlet7")) ? (dictionary.TryGetValue(tuple2.Item1 + "/gate", out var value2) && value2 == 1) : (dictionary.TryGetValue("gauntlet7/skylight", out var value3) && value3 == 1)) : (dictionary.TryGetValue("bell/exit", out var value4) && value4 == 1)) : (dictionary.TryGetValue("dreamdoor/door", out var value5) && value5 == 1));
				}
				catch
				{
				}
				stringBuilder.Append("{\"key\":").Append(Api.Json(tuple2.Item1)).Append(",\"label\":")
					.Append(Api.Json(tuple2.Item2))
					.Append(",\"note\":")
					.Append(Api.Json(tuple2.Item5))
					.Append(",\"done\":")
					.Append(flag2 ? "true" : "false")
					.Append(",\"probe\":{");
				bool flag3 = true;
				(string, string)[] item = tuple2.Item4;
				for (int j = 0; j < item.Length; j++)
				{
					string item2 = item[j].Item2;
					if (!flag3)
					{
						stringBuilder.Append(',');
					}
					flag3 = false;
					stringBuilder.Append(Api.Json(item2)).Append(':').Append((dictionary.TryGetValue(tuple2.Item1 + "/" + item2, out var value6) && value6 != int.MinValue) ? value6.ToString() : "null");
				}
				stringBuilder.Append("}}");
			}
			_h.Panel("finale", stringBuilder.Append("]}").ToString());
		}

		public void Unload()
		{
			Release();
			_trapped.Clear();
		}
	}
	public class HubReset : IOrbScript
	{
		private class HubIdx
		{
			public List<PropHome> Slots = new List<PropHome>();

			public Prop Key;

			public KeyBlank Blank;
		}

		private ScriptHost _h;

		private static readonly (string root, string label, string keySave, string[] roots, string note)[] Hubs = new(string, string, string, string[], string)[6]
		{
			("IntroPavilion", "Tutorial hub (intro pavilion)", "bigKeyIntro", new string[6] { "IntroPavilion", "CuttingTrails/CuttingTrailBoardwalk", "IntroCage", "Walkway-IntroExit", "MonumentMapFlag Intro", "EndingGateIndicator" }, "4 slots → blank key → 5 cutters → drawbridge. Known: cutter plate markers + key cover meshes only refresh for connected clients on rejoin (game never re-enables them over the network); host is fixed locally"),
			("Lookout_RedCone/RoofStuff", "Red tower hub (lookout roof)", "bigKeyRedZone", new string[5] { "Lookout_RedCone/RoofStuff", "CuttingTrails/CuttingTrailRedTower", "MonumentMapFlag Red", "UnlockLogics/Lookout0Red", "EndingGateIndicator (1)" }, "5 slots → red key → 5 cutters → MapRoom plinth; door/lights separate (Doors panel); verified"),
			("Lookout_GreenHourglass/RoofStuff", "Green tower hub (lookout roof)", "bigKeyGreenZone", new string[5] { "Lookout_GreenHourglass/RoofStuff", "CuttingTrails/CuttingTrailGreenTower", "MonumentMapFlag Green", "UnlockLogics/Lookout1Green", "EndingGateIndicator (2)" }, "5 slots → green key → 5 cutters → chairlift plinth (ChairliftBend_green); door/lights separate (Doors panel); indicator index verified"),
			("Lookout_BlueTube/RoofStuff", "Blue tower hub (lookout roof)", "bigKeyBlueZone", new string[5] { "Lookout_BlueTube/RoofStuff", "CuttingTrails/CuttingTrailBlueTower", "MonumentMapFlag Blue", "UnlockLogics/Lookout2Blue", "EndingGateIndicator (3)" }, "5 slots → blue key → 5 cutters; door/lights separate (Doors panel); indicator index verified"),
			("Lookout_YellowZigzag/RoofStuff", "Yellow tower hub (lookout roof)", "bigKeyYellowZone", new string[5] { "Lookout_YellowZigzag/RoofStuff", "CuttingTrails/CuttingTrailYellowTower", "MonumentMapFlag Yellow", "UnlockLogics/Lookout3Yellow", "EndingGateIndicator (4)" }, "5 slots → yellow key → 5 cutters; door/lights separate (Doors panel); indicator index verified"),
			("BlackTower/Positioner/GourdPlinth X6", "Black tower monument (6 gourds → black key)", "bigKeyBoss", new string[7] { "BlackTower/Positioner/GourdPlinth X6", "BlackTower/Positioner/BigKeyInStoneEnding", "BigKeyInBox Ending", "MonumentMapFlag Overflow", "Indicators/EndingGateIndicator - black", "OverflowMonument/Positioner/BasicDoor - to overflow", "OverflowMonument/Positioner/Lights/" }, "6 slots → black key (no cutting) → ending gate plinth; completing it un-hides the overflow monument's map flag + lights its black indicator (both re-hidden on reset); reset sends the key back to its stone")
		};

		private const float LooseRadius = 25f;

		private static readonly string[] TowerShared = new string[3] { "MonumentMapFlag Black", "BlackTower/Positioner/BasicDoor/", "BlackTower/Positioner/Lighting/" };

		private static HubReset _instance;

		private int _tick;

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

		private float _recheckAt;

		private static readonly (string plinth, string scope)[] PlinthScopes = new(string, string)[3]
		{
			("BigKeyPlinth Chairlift", "ChairLift"),
			("BigKeyPlinth Tunnels", "TunnelSystem"),
			("BigKeyPlinth Ending", "EndingGate/GatePositioner")
		};

		private readonly Dictionary<string, HubIdx> _idx = new Dictionary<string, HubIdx>();

		private float _idxBuiltAt = -999f;

		public string Name => "hubreset";

		public static IEnumerable<(string root, string label)> Table()
		{
			(string root, string label, string keySave, string[] roots, string note)[] hubs = Hubs;
			for (int i = 0; i < hubs.Length; i++)
			{
				(string, string, string, string[], string) tuple = hubs[i];
				yield return (root: tuple.Item1, label: tuple.Item2);
			}
		}

		private static bool IsTowerHub(string root)
		{
			return !root.StartsWith("BlackTower", StringComparison.OrdinalIgnoreCase);
		}

		public static void ResetStatic(string hubRoot, bool keepGourds = false)
		{
			_instance?.Reset(hubRoot, keepGourds);
		}

		public unsafe void Load(ScriptHost host)
		{
			_h = host;
			_instance = this;
			host.Command("resethub", delegate(string id, string key, int val, string text)
			{
				Reset(string.IsNullOrEmpty(key) ? Hubs[0].root : key, val == 1);
			});
			host.Command("hubfill", delegate(string id, string key, int val, string text)
			{
				//IL_008b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0092: Invalid comparison between Unknown and I4
				//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
				//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
				int num = Array.FindIndex(Hubs, ((string root, string label, string keySave, string[] roots, string note) h) => string.Equals(h.root, key, StringComparison.OrdinalIgnoreCase));
				if (num < 0)
				{
					host.Event("hubfill", "unknown hub " + key);
				}
				else
				{
					(string, string, string, string[], string) tuple = Hubs[num];
					List<PropHome> list = new List<PropHome>();
					List<PropHome> allPropHomes = PropHome.allPropHomes;
					if (allPropHomes != null)
					{
						for (int num2 = 0; num2 < allPropHomes.Count; num2++)
						{
							PropHome val2 = null;
							try
							{
								val2 = allPropHomes[num2];
							}
							catch
							{
							}
							if (!((Object)(object)val2 == (Object)null))
							{
								try
								{
									if ((int)val2.pinGroup == 19 && (Object)(object)val2.pinnedProp == (Object)null && Under(Api.TransformPath(((Component)val2).transform), tuple.Item1))
									{
										list.Add(val2);
									}
								}
								catch
								{
								}
							}
						}
					}
					int num3 = ((val > 0) ? Math.Min(val, list.Count) : list.Count);
					List<string> list2 = new List<string>();
					int num4 = 0;
					List<Prop> allProps = Prop.allProps;
					if (allProps != null)
					{
						for (int num5 = 0; num5 < allProps.Count; num5++)
						{
							if (num4 >= num3)
							{
								break;
							}
							Prop val3 = null;
							try
							{
								val3 = allProps[num5];
							}
							catch
							{
							}
							if (!((Object)(object)val3 == (Object)null))
							{
								try
								{
									if (val3.MatchesGroup((PropGroup)19) && !((Object)(object)val3.startHome == (Object)null) && !((Object)(object)val3.currentHome != (Object)(object)val3.startHome) && !Under(Api.TransformPath(((Component)val3.startHome).transform), tuple.Item1) && PuzzleReset.RootForGourd(val3) != null)
									{
										val3.ServerSetUnpinned();
										val3.ServerSetPinned(list[num4]);
										list2.Add(((object)val3.saveablePropName/*cast due to .constrained prefix*/).ToString());
										num4++;
									}
								}
								catch (Exception ex)
								{
									host.Warn("hubfill: " + ex.Message);
								}
							}
						}
					}
					host.Event("hubfill", $"{tuple.Item1}: {num4}/{list.Count} empty slot(s) filled — {string.Join(", ", list2)}");
				}
			});
			host.Command("hubkey", delegate(string id, string key, int val, string text)
			{
				//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_01f3: Unknown result type (might be due to invalid IL or missing references)
				int num = Array.FindIndex(Hubs, ((string root, string label, string keySave, string[] roots, string note) h) => string.Equals(h.root, key, StringComparison.OrdinalIgnoreCase));
				if (num < 0 || string.IsNullOrEmpty(text))
				{
					host.Event("hubkey", "need key=<hub root> text=<plinth substring>");
				}
				else
				{
					(string, string, string, string[], string) tuple = Hubs[num];
					Prop val2 = null;
					List<Prop> allProps = Prop.allProps;
					int num2 = 0;
					while (allProps != null && num2 < allProps.Count)
					{
						Prop val3 = null;
						try
						{
							val3 = allProps[num2];
						}
						catch
						{
						}
						if (!((Object)(object)val3 == (Object)null))
						{
							try
							{
								if (((object)val3.saveablePropName/*cast due to .constrained prefix*/).ToString() == tuple.Item3)
								{
									val2 = val3;
									break;
								}
							}
							catch
							{
							}
						}
						num2++;
					}
					if ((Object)(object)val2 == (Object)null)
					{
						host.Event("hubkey", "key not found: " + tuple.Item3);
					}
					else
					{
						PropHome val4 = null;
						List<PropHome> allPropHomes = PropHome.allPropHomes;
						int num3 = 0;
						while (allPropHomes != null && num3 < allPropHomes.Count)
						{
							PropHome val5 = null;
							try
							{
								val5 = allPropHomes[num3];
							}
							catch
							{
							}
							if (!((Object)(object)val5 == (Object)null))
							{
								try
								{
									if (Api.TransformPath(((Component)val5).transform).IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
									{
										val4 = val5;
										break;
									}
								}
								catch
								{
								}
							}
							num3++;
						}
						if ((Object)(object)val4 == (Object)null)
						{
							host.Event("hubkey", "no PropHome matching " + text);
						}
						else
						{
							List<string> list = new List<string>();
							try
							{
								KeyBlank val6 = KeyBlankOf(val2);
								if ((Object)(object)val6 != (Object)null)
								{
									SyncList<bool> cuts = val6.cuts;
									int num4 = cuts?.Count ?? 0;
									for (int num5 = 0; num5 < num4; num5++)
									{
										cuts[num5] = true;
									}
									val6.RefreshPropGroup();
									list.Add($"{num4} cuts set, group {(val2.MatchesGroup(val6.finishedPropGroup) ? "complete" : "NOT complete")}");
								}
								else
								{
									list.Add("no KeyBlank");
								}
								DropIfHeld(val2);
								val2.ServerSetUnpinned();
								val2.ServerSetPinned(val4);
								list.Add("pinned in " + ((Object)val4).name + (((Object)(object)val2.currentHome == (Object)(object)val4) ? "" : (" (REJECTED — currentHome is " + (((Object)(object)val2.currentHome != (Object)null) ? ((Object)val2.currentHome).name : "none") + ")")));
							}
							catch (Exception ex)
							{
								list.Add(ex.Message);
							}
							host.Event("hubkey", tuple.Item3 + ": " + string.Join("; ", list));
						}
					}
				}
			});
			host.Command("keygroups", delegate(string id, string key, int val, string text)
			{
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_00df: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
				//IL_0195: Unknown result type (might be due to invalid IL or missing references)
				//IL_019a: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
				Prop val2 = null;
				List<Prop> allProps = Prop.allProps;
				int num = 0;
				while (allProps != null && num < allProps.Count)
				{
					Prop val3 = null;
					try
					{
						val3 = allProps[num];
					}
					catch
					{
					}
					if (!((Object)(object)val3 == (Object)null))
					{
						try
						{
							if (((object)val3.saveablePropName/*cast due to .constrained prefix*/).ToString() == key)
							{
								val2 = val3;
								break;
							}
						}
						catch
						{
						}
					}
					num++;
				}
				if ((Object)(object)val2 == (Object)null)
				{
					host.Event("keygroups", "no key " + key);
				}
				else
				{
					List<string> list = new List<string>();
					try
					{
						Enumerator<PropGroup> enumerator = val2.propGroups.GetEnumerator();
						while (enumerator.MoveNext())
						{
							list.Add(((object)enumerator.Current/*cast due to .constrained prefix*/).ToString());
						}
					}
					catch
					{
					}
					if (!string.IsNullOrEmpty(text) && Enum.TryParse<PropGroup>(text, ignoreCase: true, out PropGroup result))
					{
						try
						{
							if (!val2.MatchesGroup(result))
							{
								val2.propGroups.Add(result);
								list.Add(((object)(*(PropGroup*)(&result))/*cast due to .constrained prefix*/).ToString() + " (added)");
							}
						}
						catch (Exception ex)
						{
							list.Add("add failed: " + ex.Message);
						}
					}
					List<string> list2 = new List<string>();
					List<PropHome> allPropHomes = PropHome.allPropHomes;
					int num2 = 0;
					while (allPropHomes != null && num2 < allPropHomes.Count)
					{
						PropHome val4 = null;
						try
						{
							val4 = allPropHomes[num2];
						}
						catch
						{
						}
						if (!((Object)(object)val4 == (Object)null))
						{
							try
							{
								string text2 = Api.TransformPath(((Component)val4).transform);
								if (text2.IndexOf("BigKeyPlinth", StringComparison.OrdinalIgnoreCase) >= 0)
								{
									list2.Add(text2.Split('/')[^2] + " wants " + ((object)val4.pinGroup/*cast due to .constrained prefix*/).ToString());
								}
							}
							catch
							{
							}
						}
						num2++;
					}
					host.Event("keygroups", $"{key}: [{string.Join(", ", list)}] · plinths: {string.Join("; ", list2)}");
				}
			});
			host.Command("respawnkey", delegate
			{
				//IL_0030: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Invalid comparison between Unknown and I4
				List<Prop> allProps = Prop.allProps;
				List<string> list = new List<string>();
				int value = 0;
				for (int i = 0; i < allProps.Count; i++)
				{
					Prop val2 = null;
					try
					{
						val2 = allProps[i];
					}
					catch
					{
					}
					if (!((Object)(object)val2 == (Object)null))
					{
						try
						{
							if ((int)val2.saveablePropName == 300)
							{
								value = RespawnForRemotes(((NetworkBehaviour)val2).netIdentity, list);
								break;
							}
						}
						catch
						{
						}
					}
				}
				host.Event("respawnkey", $"respawned for {value} client(s){((list.Count > 0) ? (" — " + string.Join("; ", list)) : "")}");
			});
			host.Tick(delegate
			{
				if (++_tick % 8 == 4 && Api.DashboardLive)
				{
					PublishStatus();
				}
				if (_recheck.Count > 0 && Time.unscaledTime >= _recheckAt)
				{
					foreach (string item in _recheck)
					{
						try
						{
							PuzzleReset.ResetStatic(item, 0);
						}
						catch
						{
						}
					}
					_recheck.Clear();
				}
			});
			host.Endpoint("stationdebug", delegate
			{
				//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
				StringBuilder stringBuilder = new StringBuilder("[");
				bool flag = true;
				foreach (UnlockTrailStation item2 in Object.FindObjectsOfType<UnlockTrailStation>(true))
				{
					if (!((Object)(object)item2 == (Object)null))
					{
						if (!flag)
						{
							stringBuilder.Append(',');
						}
						flag = false;
						stringBuilder.Append("{\"path\":").Append(Api.Json(Api.TransformPath(((Component)item2).transform))).Append(",\"index\":")
							.Append(item2.stationIndex);
						try
						{
							Transform arrowTransform = item2.arrowTransform;
							StringBuilder stringBuilder2 = stringBuilder.Append(",\"arrow\":");
							object value;
							if (!((Object)(object)arrowTransform != (Object)null))
							{
								value = "null";
							}
							else
							{
								string[] obj = new string[7]
								{
									((Object)arrowTransform).name,
									" localRot=",
									null,
									null,
									null,
									null,
									null
								};
								Quaternion localRotation = arrowTransform.localRotation;
								Vector3 val = ((Quaternion)(ref localRotation)).eulerAngles;
								obj[2] = ((Vector3)(ref val)).ToString("0.#");
								obj[3] = " localPos=";
								val = arrowTransform.localPosition;
								obj[4] = ((Vector3)(ref val)).ToString("0.##");
								obj[5] = " active=";
								obj[6] = ((Component)arrowTransform).gameObject.activeSelf.ToString();
								value = Api.Json(string.Concat(obj));
							}
							stringBuilder2.Append((string?)value);
						}
						catch
						{
						}
						try
						{
							PeckEffectTween peckTween = item2.peckTween;
							stringBuilder.Append(",\"tween\":").Append(((Object)(object)peckTween != (Object)null) ? Api.Json(Api.TransformPath(((Component)peckTween).transform) + " dur=" + peckTween.duration) : "null");
						}
						catch
						{
						}
						List<string> list = new List<string>();
						try
						{
							foreach (Component componentsInChild in ((Component)item2).GetComponentsInChildren<Component>(true))
							{
								try
								{
									string name = ((MemberInfo)((Object)componentsInChild).GetIl2CppType()).Name;
									if (name.StartsWith("PeckEffect") || name.Contains("Tween") || name.Contains("Toggle") || name == "TrackedPeckState")
									{
										list.Add(name + "@" + ((Object)componentsInChild.gameObject).name);
									}
								}
								catch
								{
								}
							}
						}
						catch
						{
						}
						stringBuilder.Append(",\"effects\":").Append(Api.Json(string.Join(" | ", list))).Append('}');
					}
				}
				return stringBuilder.Append(']').ToString();
			});
			host.Endpoint("keytree", delegate
			{
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0036: Invalid comparison between Unknown and I4
				List<Prop> allProps = Prop.allProps;
				Prop val = null;
				for (int i = 0; i < allProps.Count; i++)
				{
					Prop val2 = null;
					try
					{
						val2 = allProps[i];
					}
					catch
					{
					}
					if (!((Object)(object)val2 == (Object)null))
					{
						try
						{
							if ((int)val2.saveablePropName == 300)
							{
								val = val2;
								break;
							}
						}
						catch
						{
						}
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					return "{\"err\":\"no key\"}";
				}
				StringBuilder stringBuilder = new StringBuilder("[");
				bool flag = true;
				foreach (Transform componentsInChild2 in ((Component)val).GetComponentsInChildren<Transform>(true))
				{
					if (!flag)
					{
						stringBuilder.Append(',');
					}
					flag = false;
					List<string> list = new List<string>();
					foreach (Component component2 in ((Component)componentsInChild2).GetComponents<Component>())
					{
						try
						{
							string name = ((MemberInfo)((Object)component2).GetIl2CppType()).Name;
							if (name != "Transform")
							{
								list.Add(name);
							}
						}
						catch
						{
						}
					}
					Renderer component = ((Component)componentsInChild2).GetComponent<Renderer>();
					stringBuilder.Append("{\"path\":").Append(Api.Json(Api.TransformPath(componentsInChild2).Replace(Api.TransformPath(((Component)val).transform), "KEY"))).Append(",\"active\":")
						.Append(((Component)componentsInChild2).gameObject.activeSelf ? "true" : "false")
						.Append(",\"renderer\":")
						.Append(((Object)(object)component != (Object)null) ? (component.enabled ? "\"on\"" : "\"off\"") : "null")
						.Append(",\"comps\":")
						.Append(Api.Json(string.Join(",", list)))
						.Append('}');
				}
				KeyBlank val3 = KeyBlankOf(val);
				if ((Object)(object)val3 != (Object)null && val3.covers != null)
				{
					foreach (KeyBlankCover item3 in (Il2CppArrayBase<KeyBlankCover>)(object)val3.covers)
					{
						if (!((Object)(object)item3 == (Object)null))
						{
							stringBuilder.Append(",{\"cover\":").Append(Api.Json(((Object)item3).name)).Append(",\"stage\":")
								.Append(item3.currentStage)
								.Append(",\"stages\":")
								.Append(Api.Json(StageNames(item3)))
								.Append('}');
						}
					}
				}
				return stringBuilder.Append(']').ToString();
			});
			host.Endpoint("keydebug", delegate
			{
				//IL_0035: Unknown result type (might be due to invalid IL or missing references)
				//IL_003f: Invalid comparison between Unknown and I4
				//IL_021f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0224: Unknown result type (might be due to invalid IL or missing references)
				List<Prop> allProps = Prop.allProps;
				StringBuilder stringBuilder = new StringBuilder("{");
				for (int i = 0; i < allProps.Count; i++)
				{
					Prop val = null;
					try
					{
						val = allProps[i];
					}
					catch
					{
					}
					if (!((Object)(object)val == (Object)null))
					{
						try
						{
							if ((int)val.saveablePropName != 300)
							{
								continue;
							}
						}
						catch
						{
							continue;
						}
						stringBuilder.Append("\"prop\":").Append(Api.Json(((Object)val).name));
						KeyBlank val2 = KeyBlankOf(val);
						stringBuilder.Append(",\"kb\":").Append(((Object)(object)val2 != (Object)null) ? "true" : "false");
						if ((Object)(object)val2 != (Object)null)
						{
							object obj3 = null;
							string text = null;
							int num = -1;
							string text2 = "";
							try
							{
								obj3 = val2.cuts;
							}
							catch (Exception ex)
							{
								text = "cuts: " + ex.Message;
							}
							try
							{
								if (val2.cuts != null)
								{
									num = val2.cuts.Count;
									for (int j = 0; j < num; j++)
									{
										text2 += (val2.cuts[j] ? "1" : "0");
									}
								}
							}
							catch (Exception ex2)
							{
								text = text + " count: " + ex2.Message;
							}
							Il2CppArrayBase<Component> components = ((Component)val).GetComponents<Component>();
							List<string> list = new List<string>();
							foreach (Component item4 in components)
							{
								try
								{
									list.Add(((MemberInfo)((Object)item4).GetIl2CppType()).Name);
								}
								catch
								{
								}
							}
							stringBuilder.Append(",\"cutsNull\":").Append((obj3 == null) ? "true" : "false").Append(",\"count\":")
								.Append(num)
								.Append(",\"items\":")
								.Append(Api.Json(text2))
								.Append(",\"err\":")
								.Append(Api.Json(text))
								.Append(",\"covers\":")
								.Append((val2.covers != null) ? ((Il2CppArrayBase<KeyBlankCover>)(object)val2.covers).Length : (-1))
								.Append(",\"finishedGroup\":")
								.Append(Api.Json(((object)val2.finishedPropGroup/*cast due to .constrained prefix*/).ToString()))
								.Append(",\"components\":")
								.Append(Api.Json(string.Join(",", list)));
						}
						break;
					}
				}
				return stringBuilder.Append('}').ToString();
			});
		}

		private static KeyBlank KeyBlankOf(Prop p)
		{
			try
			{
				KeyBlank componentInChildren = ((Component)p).GetComponentInChildren<KeyBlank>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					return componentInChildren;
				}
			}
			catch
			{
			}
			try
			{
				KeyBlank componentInParent = ((Component)p).GetComponentInParent<KeyBlank>();
				if ((Object)(object)componentInParent != (Object)null)
				{
					return componentInParent;
				}
			}
			catch
			{
			}
			try
			{
				foreach (KeyBlank item in Object.FindObjectsOfType<KeyBlank>(true))
				{
					if ((Object)(object)item != (Object)null && (Object)(object)item.prop == (Object)(object)p)
					{
						return item;
					}
				}
			}
			catch
			{
			}
			return null;
		}

		public static int RespawnForRemotes(NetworkIdentity id, List<string> notes)
		{
			if ((Object)(object)id == (Object)null)
			{
				return 0;
			}
			int num = 0;
			try
			{
				Enumerator<int, NetworkConnectionToClient> enumerator = NetworkServer.connections.GetEnumerator();
				while (enumerator.MoveNext())
				{
					KeyValuePair<int, NetworkConnectionToClient> current = enumerator.Current;
					NetworkConnectionToClient value = current.Value;
					if (value != null && (object)value != NetworkServer.localConnection)
					{
						try
						{
							NetworkServer.HideForConnection(id, (NetworkConnection)(object)value);
							NetworkServer.ShowForConnection(id, (NetworkConnection)(object)value);
							num++;
						}
						catch (Exception ex)
						{
							notes?.Add("respawn conn " + current.Key + ": " + ex.Message);
						}
					}
				}
			}
			catch (Exception ex2)
			{
				notes?.Add("respawn: " + ex2.Message);
			}
			return num;
		}

		private static string StageNames(KeyBlankCover cv)
		{
			Il2CppReferenceArray<Transform> stages = cv.stages;
			if (stages == null)
			{
				return "";
			}
			List<string> list = new List<string>();
			for (int i = 0; i < ((Il2CppArrayBase<Transform>)(object)stages).Length; i++)
			{
				Transform val = ((Il2CppArrayBase<Transform>)(object)stages)[i];
				list.Add(((Object)(object)val == (Object)null) ? "null" : (((Object)val).name + (((Component)val).gameObject.activeSelf ? "*" : "")));
			}
			return string.Join("|", list);
		}

		private static bool Under(string path, string root)
		{
			return path.IndexOf(root, StringComparison.OrdinalIgnoreCase) >= 0;
		}

		public static int ResetStatesUnder(IEnumerable<string> roots, List<string> notes)
		{
			int num = 0;
			Il2CppArrayBase<TrackedPeckState> val = null;
			try
			{
				val = Object.FindObjectsOfType<TrackedPeckState>(true);
			}
			catch (Exception ex)
			{
				notes?.Add("states: " + ex.Message);
				return 0;
			}
			if (val == null)
			{
				return 0;
			}
			for (int i = 0; i < 2; i++)
			{
				for (int j = 0; j < val.Length; j++)
				{
					TrackedPeckState val2 = null;
					try
					{
						val2 = val[j];
					}
					catch
					{
					}
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					string text;
					try
					{
						text = Api.TransformPath(((Component)val2).transform);
					}
					catch
					{
						continue;
					}
					if (text.Contains("PlayerCharacter") || text.EndsWith("/UnhideSystem") != (i == 0))
					{
						continue;
					}
					bool flag = false;
					foreach (string root in roots)
					{
						if (Under(text, root))
						{
							flag = true;
							break;
						}
					}
					if (!flag)
					{
						continue;
					}
					string label = "";
					try
					{
						label = val2.label ?? "";
					}
					catch
					{
					}
					if (!PuzzleProbeBaseline.TryGet(PuzzleProbeBaseline.Key(text, label), out var value))
					{
						continue;
					}
					int num2 = int.MinValue;
					try
					{
						PeckContext currentPeckContext = val2.currentPeckContext;
						if (currentPeckContext != null)
						{
							num2 = currentPeckContext.state;
						}
					}
					catch
					{
					}
					if (num2 != value)
					{
						try
						{
							val2.SetState(value);
							num++;
						}
						catch (Exception ex2)
						{
							notes?.Add(text.Substring(text.LastIndexOf('/') + 1) + ": " + ex2.Message);
						}
					}
				}
			}
			return num;
		}

		private void Reset(string hubRoot, bool keepGourds)
		{
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_040d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0412: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Invalid comparison between Unknown and I4
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_0265: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_0332: Unknown result type (might be due to invalid IL or missing references)
			//IL_0337: Unknown result type (might be due to invalid IL or missing references)
			//IL_0339: Unknown result type (might be due to invalid IL or missing references)
			//IL_033e: Unknown result type (might be due to invalid IL or missing references)
			int num = Array.FindIndex(Hubs, ((string root, string label, string keySave, string[] roots, string note) h) => string.Equals(h.root, hubRoot, StringComparison.OrdinalIgnoreCase));
			if (num < 0)
			{
				_h.Event("resethub", "unknown hub " + hubRoot);
				return;
			}
			(string, string, string, string[], string) tuple = Hubs[num];
			List<string> list = new List<string>();
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			List<PropHome> allPropHomes = PropHome.allPropHomes;
			HashSet<string> hashSet = new HashSet<string>();
			HashSet<IntPtr> hashSet2 = new HashSet<IntPtr>();
			List<Vector3> list2 = new List<Vector3>();
			if (allPropHomes != null)
			{
				for (int num5 = 0; num5 < allPropHomes.Count; num5++)
				{
					PropHome val = null;
					try
					{
						val = allPropHomes[num5];
					}
					catch
					{
					}
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					try
					{
						if (!Under(Api.TransformPath(((Component)val).transform), tuple.Item1))
						{
							continue;
						}
						if ((int)val.pinGroup == 19)
						{
							list2.Add(((Component)val).transform.position);
						}
						Prop pinnedProp = val.pinnedProp;
						if ((Object)(object)pinnedProp == (Object)null || !pinnedProp.MatchesGroup((PropGroup)19))
						{
							continue;
						}
						hashSet2.Add(((Il2CppObjectBase)pinnedProp).Pointer);
						try
						{
							pinnedProp.ServerSetUnpinned();
						}
						catch
						{
						}
						if (!keepGourds && (Object)(object)pinnedProp.startHome != (Object)null)
						{
							pinnedProp.ServerSetPinned(pinnedProp.startHome);
							AddPuzzle(pinnedProp, hashSet, list);
						}
						else
						{
							if (!keepGourds)
							{
								list.Add(((Object)pinnedProp).name + ": no startHome, dropped at hub");
							}
							Vector3 position = ((Component)val).transform.position + Vector3.up * 0.5f + Random.insideUnitSphere * 0.5f;
							((Component)pinnedProp).transform.position = position;
							Rigidbody component = ((Component)pinnedProp).GetComponent<Rigidbody>();
							if ((Object)(object)component != (Object)null)
							{
								component.position = position;
								component.velocity = Vector3.zero;
							}
							try
							{
								RewardGourd val2 = PuzzleReset.RewardGourdOf(pinnedProp);
								if ((Object)(object)val2 != (Object)null)
								{
									val2.ServerSetGourdState((GourdState)1);
								}
							}
							catch
							{
							}
						}
						num2++;
					}
					catch (Exception ex)
					{
						list.Add("gourd: " + ex.Message);
					}
				}
			}
			if (!keepGourds && list2.Count > 0)
			{
				Vector3 val3 = Vector3.zero;
				foreach (Vector3 item in list2)
				{
					val3 += item;
				}
				val3 /= (float)list2.Count;
				List<Prop> allProps = Prop.allProps;
				if (allProps != null)
				{
					for (int num6 = 0; num6 < allProps.Count; num6++)
					{
						Prop val4 = null;
						try
						{
							val4 = allProps[num6];
						}
						catch
						{
						}
						if ((Object)(object)val4 == (Object)null || hashSet2.Contains(((Il2CppObjectBase)val4).Pointer))
						{
							continue;
						}
						try
						{
							bool flag;
							if (val4.MatchesGroup((PropGroup)19) && !((Object)(object)val4.startHome == (Object)null) && !((Object)(object)val4.currentHome == (Object)(object)val4.startHome))
							{
								flag = (Object)(object)HeldBy(val4) != (Object)null;
								if (flag)
								{
									goto IL_0350;
								}
								Vector3 val5 = ((Component)val4).transform.position - val3;
								if (!(((Vector3)(ref val5)).magnitude > 25f))
								{
									goto IL_0350;
								}
							}
							goto end_IL_02e4;
							IL_0350:
							DropIfHeld(val4);
							try
							{
								val4.ServerSetUnpinned();
							}
							catch
							{
							}
							val4.ServerSetPinned(val4.startHome);
							AddPuzzle(val4, hashSet, list);
							list.Add(((Object)val4).name + (flag ? " taken from a player" : " picked up loose") + " → home");
							num2++;
							end_IL_02e4:;
						}
						catch (Exception ex2)
						{
							list.Add("loose gourd: " + ex2.Message);
						}
					}
				}
			}
			Prop val6 = null;
			List<Prop> allProps2 = Prop.allProps;
			if (allProps2 != null)
			{
				for (int num7 = 0; num7 < allProps2.Count; num7++)
				{
					Prop val7 = null;
					try
					{
						val7 = allProps2[num7];
					}
					catch
					{
					}
					if ((Object)(object)val7 == (Object)null)
					{
						continue;
					}
					try
					{
						if (((object)val7.saveablePropName/*cast due to .constrained prefix*/).ToString() == tuple.Item3)
						{
							val6 = val7;
							break;
						}
					}
					catch
					{
					}
				}
			}
			List<string> list3 = new List<string>(tuple.Item4);
			if (IsTowerHub(tuple.Item1))
			{
				list3.AddRange(TowerShared);
			}
			if ((Object)(object)val6 == (Object)null)
			{
				list.Add("key not found (" + tuple.Item3 + ")");
			}
			else
			{
				try
				{
					PropHome currentHome = val6.currentHome;
					if ((Object)(object)currentHome != (Object)null && (Object)(object)currentHome != (Object)(object)val6.startHome)
					{
						string text = PlinthScopeOf(Api.TransformPath(((Component)currentHome).transform));
						if (text != null)
						{
							string[] array = text.Split('|');
							foreach (string text2 in array)
							{
								if (!list3.Contains(text2))
								{
									list3.Add(text2);
									list.Add("key was in " + ((Object)currentHome).name + " → also resetting " + text2);
								}
							}
						}
					}
				}
				catch
				{
				}
				try
				{
					DropIfHeld(val6);
					KeyBlank val8 = KeyBlankOf(val6);
					if ((Object)(object)val8 != (Object)null)
					{
						SyncList<bool> cuts = val8.cuts;
						int num9 = cuts?.Count ?? 0;
						int num10 = 0;
						for (int num11 = 0; num11 < num9; num11++)
						{
							if (cuts[num11])
							{
								cuts[num11] = false;
								num10++;
							}
						}
						try
						{
							val8.RefreshPropGroup();
						}
						catch (Exception ex3)
						{
							list.Add("refresh group: " + ex3.Message);
						}
						try
						{
							Il2CppReferenceArray<KeyBlankCover> covers = val8.covers;
							int num12 = 0;
							if (covers != null)
							{
								foreach (KeyBlankCover item2 in (Il2CppArrayBase<KeyBlankCover>)(object)covers)
								{
									if ((Object)(object)item2 == (Object)null)
									{
										continue;
									}
									item2.currentStage = 0;
									((Component)item2).gameObject.SetActive(true);
									Il2CppReferenceArray<Transform> stages = item2.stages;
									if (stages != null)
									{
										for (int num13 = 0; num13 < ((Il2CppArrayBase<Transform>)(object)stages).Length; num13++)
										{
											if ((Object)(object)((Il2CppArrayBase<Transform>)(object)stages)[num13] != (Object)null)
											{
												((Component)((Il2CppArrayBase<Transform>)(object)stages)[num13]).gameObject.SetActive(num13 == 0);
											}
										}
									}
									num12++;
								}
							}
							list.Add($"{num12} cover(s) rewound");
						}
						catch (Exception ex4)
						{
							list.Add("covers: " + ex4.Message);
						}
						try
						{
							List<PropGroup> propGroups = val6.propGroups;
							if (num9 > 0 && propGroups != null && val6.MatchesGroup(val8.finishedPropGroup))
							{
								propGroups.Remove(val8.finishedPropGroup);
								list.Add("finished group removed");
							}
							else if (num9 == 0)
							{
								list.Add("uncut key, finished group kept");
							}
						}
						catch (Exception ex5)
						{
							list.Add("group strip: " + ex5.Message);
						}
						list.Add($"key: {num10}/{num9} cuts cleared");
					}
					else
					{
						list.Add("key has no KeyBlank");
					}
					try
					{
						val6.ServerSetUnpinned();
					}
					catch
					{
					}
					if ((Object)(object)val6.startHome != (Object)null)
					{
						val6.ServerSetPinned(val6.startHome);
					}
					else
					{
						list.Add("key has no startHome");
					}
				}
				catch (Exception ex6)
				{
					list.Add("key: " + ex6.Message);
				}
			}
			num4 = ResetStatesUnder(list3, list);
			if (list3.Exists((string r) => r.StartsWith("EndingGate/GatePositioner", StringComparison.OrdinalIgnoreCase)))
			{
				try
				{
					Finale.BounceEndingGates();
				}
				catch (Exception ex7)
				{
					list.Add("gates: " + ex7.Message);
				}
			}
			int num14 = 0;
			try
			{
				foreach (UnlockTrailStation item3 in Object.FindObjectsOfType<UnlockTrailStation>(true))
				{
					if ((Object)(object)item3 == (Object)null)
					{
						continue;
					}
					string path;
					try
					{
						path = Api.TransformPath(((Component)item3).transform);
					}
					catch
					{
						continue;
					}
					bool flag2 = false;
					foreach (string item4 in list3)
					{
						if (Under(path, item4))
						{
							flag2 = true;
							break;
						}
					}
					if (!flag2)
					{
						continue;
					}
					try
					{
						PeckEffectTween peckTween = item3.peckTween;
						if ((Object)(object)peckTween != (Object)null)
						{
							peckTween.UpdateTween(0f);
							num14++;
						}
					}
					catch (Exception ex8)
					{
						list.Add("arrow: " + ex8.Message);
					}
				}
			}
			catch (Exception ex9)
			{
				list.Add("arrows: " + ex9.Message);
			}
			if (!IsTowerHub(tuple.Item1))
			{
				try
				{
					foreach (TrackedPeckState item5 in Object.FindObjectsOfType<TrackedPeckState>(true))
					{
						if ((Object)(object)item5 == (Object)null)
						{
							continue;
						}
						string text3;
						try
						{
							text3 = Api.TransformPath(((Component)item5).transform);
						}
						catch
						{
							continue;
						}
						if (!text3.EndsWith("MonumentMapFlag Black/MonumentMapFlagLogic", StringComparison.OrdinalIgnoreCase))
						{
							continue;
						}
						int num15 = int.MinValue;
						try
						{
							PeckContext currentPeckContext = item5.currentPeckContext;
							if (currentPeckContext != null)
							{
								num15 = currentPeckContext.state;
							}
						}
						catch
						{
						}
						if (num15 != 0)
						{
							item5.SetState(0);
							list.Add("black map flag → incomplete");
						}
						break;
					}
				}
				catch (Exception ex10)
				{
					list.Add("black flag: " + ex10.Message);
				}
			}
			foreach (string item6 in hashSet)
			{
				try
				{
					PuzzleReset.ResetStatic(item6, 0);
					num3++;
				}
				catch (Exception ex11)
				{
					list.Add(item6 + ": " + ex11.Message);
				}
			}
			if (hashSet.Count > 0)
			{
				_recheck.AddRange(hashSet);
				_recheckAt = Time.unscaledTime + 2f;
			}
			_h.Event("resethub", $"{tuple.Item1}: {num2} gourd(s) {(keepGourds ? "dropped at hub" : "returned")}, {num3} puzzle(s) reset, {num4} state(s) reset, {num14} arrow(s) rewound{((list.Count > 0) ? (" — " + string.Join("; ", list)) : "")}");
		}

		private static string PlinthScopeOf(string homePath)
		{
			(string, string)[] plinthScopes = PlinthScopes;
			for (int i = 0; i < plinthScopes.Length; i++)
			{
				var (value, result) = plinthScopes[i];
				if (homePath.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return result;
				}
			}
			List<string> list = new