Decompiled source of SpawnGuard v2.1.0

Mods/SpawnGuard.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 BoneLib;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Data;
using Il2CppSLZ.Marrow.Pool;
using Il2CppSLZ.Marrow.Warehouse;
using Il2CppSystem.Collections.Generic;
using LabFusion.Entities;
using LabFusion.Marrow.Pool;
using LabFusion.Marrow.Serialization;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.RPC;
using LabFusion.Representation;
using LabFusion.Utilities;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using SpawnGuard;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("SpawnWall")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SpawnWall")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("0e80b46e-6f26-4e3b-9677-42beb4ac15fc")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: MelonInfo(typeof(Main), "SpawnGuard", "1.0.0", "YourName", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SpawnGuard;

public static class ConfettiPing
{
	private const string ConfettiBarcode = "BaBaCorp.MiscExplosiveDevices.Spawnable.ExplosionConfetti";

	private const float RateLimitSeconds = 0.3f;

	private const long RateLimitTicks = 3000000L;

	private static readonly Dictionary<byte, long> _lastPingTicks = new Dictionary<byte, long>();

	public static void TryPing(byte targetPlayerID)
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: 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_0090: Expected O, but got Unknown
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: 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_00d0: Expected O, but got Unknown
		//IL_00d6: Expected O, but got Unknown
		//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: 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)
		if (!SpawnGuardConfig.EnableConfettiPing.Value)
		{
			return;
		}
		long ticks = DateTime.UtcNow.Ticks;
		if (_lastPingTicks.TryGetValue(targetPlayerID, out var value) && ticks - value < 3000000)
		{
			return;
		}
		_lastPingTicks[targetPlayerID] = ticks;
		Vector3 headPosition = GetHeadPosition(targetPlayerID);
		if (headPosition == Vector3.zero)
		{
			return;
		}
		try
		{
			SpawnableCrate val = default(SpawnableCrate);
			if (!AssetWarehouse.Instance.TryGetCrate<SpawnableCrate>(new Barcode("BaBaCorp.MiscExplosiveDevices.Spawnable.ExplosionConfetti"), ref val) || (Object)(object)val == (Object)null)
			{
				MelonLogger.Warning("[SpawnGuard] Confetti crate not found — is BaBaCorp pack installed?");
				return;
			}
			NetworkAssetSpawner.Spawn(new SpawnRequestInfo
			{
				Spawnable = new Spawnable
				{
					crateRef = new SpawnableCrateReference("BaBaCorp.MiscExplosiveDevices.Spawnable.ExplosionConfetti")
				},
				Position = headPosition,
				Rotation = Quaternion.identity,
				SpawnEffect = false,
				SpawnCallback = null
			});
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Confetti ping failed: " + ex);
		}
	}

	private static Vector3 GetHeadPosition(byte playerID)
	{
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			RigRefs val = default(RigRefs);
			if (!PlayerRepUtilities.TryGetReferences(playerID, ref val) || val == null)
			{
				return Vector3.zero;
			}
			RigManager rigManager = val.RigManager;
			if ((Object)(object)rigManager == (Object)null)
			{
				return Vector3.zero;
			}
			Transform head = ((Rig)rigManager.physicsRig).m_head;
			if ((Object)(object)head != (Object)null)
			{
				return head.position;
			}
			return ((Component)rigManager).transform.position;
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Failed to get head position for confetti: " + ex);
			return Vector3.zero;
		}
	}

	public static void OnPlayerLeft(byte playerID)
	{
		_lastPingTicks.Remove(playerID);
	}

	public static void ClearAll()
	{
		_lastPingTicks.Clear();
	}
}
public static class DespawnGuard_OnDespawnRequestPatch
{
	public static bool HasFired;

	public static void ApplyPatch(Harmony harmony)
	{
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(DespawnRequestMessage), "OnHandleMessage", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				MelonLogger.Warning("[SpawnGuard] DespawnGuard: could not find OnHandleMessage on DespawnRequestMessage — despawn enforcement disabled. Check the class name in LabFusion.dll.");
				return;
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(DespawnGuard_OnDespawnRequestPatch), "Prefix", (Type[])null, (Type[])null);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			MelonLogger.Msg("[SpawnGuard] DespawnGuard patch applied successfully.");
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] DespawnGuard patch failed: " + ex);
		}
	}

	public static bool Prefix(ReceivedMessage received)
	{
		//IL_029e: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
		if (!HasFired)
		{
			HasFired = true;
			MelonLogger.Msg("[SpawnGuard] DespawnGuard prefix firing — patch confirmed active.");
		}
		if (!SpawnGuardConfig.Enabled.Value || !SpawnGuardConfig.EnableDespawnGuard.Value)
		{
			return true;
		}
		if (!NetworkInfo.IsHost)
		{
			return true;
		}
		try
		{
			byte value = ((ReceivedMessage)(ref received)).Sender.Value;
			PlayerID playerID = PlayerIDManager.GetPlayerID(value);
			bool flag = false;
			string text;
			if (PlayerID.IsNullOrInvalid(playerID))
			{
				text = "Unknown (ID " + value + ")";
			}
			else
			{
				string text2 = default(string);
				text = (MetadataHelper.TryGetDisplayName(playerID, ref text2) ? text2 : ("Player " + value));
				flag = playerID.IsMe;
			}
			if (flag)
			{
				return true;
			}
			ushort entityID = 0;
			try
			{
				DespawnRequestData val = ((ReceivedMessage)(ref received)).ReadData<DespawnRequestData>();
				if (val != null)
				{
					entityID = val.Entity.ID;
				}
			}
			catch
			{
			}
			SpawnPlayerTracker.TryGetEntityInfo(entityID, out var barcode, out var friendlyName);
			if (!SpawnPlayerTracker.TryGetOwner(entityID, out var ownerID))
			{
				if (SpawnGuardConfig.LogDespawns.Value)
				{
					string line = "[SpawnGuard] " + text + " despawned '" + friendlyName + "' (" + barcode + ") - owner unknown, allowed through.";
					SpawnLogAggregator.Enqueue(text, barcode, line);
				}
				SpawnPlayerTracker.OnEntityDespawned(entityID);
				return true;
			}
			if (SpawnPlayerTracker.IsLocalHost(ownerID) && SpawnGuardConfig.ProtectHostSpawns.Value)
			{
				string line2 = "[SpawnGuard] Blocked despawn of '" + friendlyName + "' (" + barcode + ") by " + text + " - entity was spawned by host.";
				if (SpawnGuardConfig.LogDespawns.Value || SpawnGuardConfig.LogOnly.Value)
				{
					SpawnLogAggregator.Enqueue(text, barcode, line2);
				}
				if (SpawnGuardConfig.LogOnly.Value)
				{
					return true;
				}
				SpawnNotifier.Warn("SpawnGuard", "Blocked despawn of '" + friendlyName + "' by " + text + " (host spawn protected)");
				return false;
			}
			bool flag2;
			if (PlayerID.IsNullOrInvalid(playerID))
			{
				flag2 = false;
			}
			else
			{
				PermissionLevel val2 = default(PermissionLevel);
				Color val3 = default(Color);
				FusionPermissions.FetchPermissionLevel(playerID.PlatformID, ref val2, ref val3);
				flag2 = FusionPermissions.HasSufficientPermissions(val2, SpawnGuardConfig.DespawnRequiredPermission);
			}
			if (flag2)
			{
				if (SpawnGuardConfig.LogDespawns.Value)
				{
					string line3 = "[SpawnGuard] " + text + " despawned '" + friendlyName + "' (" + barcode + ")";
					SpawnLogAggregator.Enqueue(text, barcode, line3);
				}
				SpawnPlayerTracker.OnEntityDespawned(entityID);
				return true;
			}
			string line4 = "[SpawnGuard] Blocked despawn of '" + friendlyName + "' (" + barcode + ") by " + text + " - insufficient permission.";
			if (SpawnGuardConfig.LogDespawns.Value || SpawnGuardConfig.LogOnly.Value)
			{
				SpawnLogAggregator.Enqueue(text, barcode, line4);
			}
			if (SpawnGuardConfig.LogOnly.Value)
			{
				return true;
			}
			SpawnNotifier.Warn("SpawnGuard", "Blocked despawn of '" + friendlyName + "' by " + text + " (no permission)");
			ConfettiPing.TryPing(value);
			return false;
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Error in despawn guard prefix, failing open: " + ex);
			return true;
		}
	}
}
public static class AvatarBypass
{
	public static bool ShouldBypass(string barcode)
	{
		if (!SpawnGuardConfig.AllowAvatarsThroughFirewall.Value)
		{
			return false;
		}
		if (string.IsNullOrEmpty(barcode))
		{
			return false;
		}
		return barcode.IndexOf("Avatar", StringComparison.OrdinalIgnoreCase) >= 0;
	}
}
public static class SpawnLogAggregator
{
	private struct AggregateKey : IEquatable<AggregateKey>
	{
		public readonly string PlayerName;

		public readonly string Barcode;

		public AggregateKey(string playerName, string barcode)
		{
			PlayerName = playerName;
			Barcode = barcode;
		}

		public bool Equals(AggregateKey other)
		{
			return PlayerName == other.PlayerName && Barcode == other.Barcode;
		}

		public override bool Equals(object obj)
		{
			return obj is AggregateKey && Equals((AggregateKey)obj);
		}

		public override int GetHashCode()
		{
			int num = ((PlayerName != null) ? PlayerName.GetHashCode() : 0);
			int num2 = ((Barcode != null) ? Barcode.GetHashCode() : 0);
			return num ^ (num2 << 5);
		}
	}

	private struct AggregateEntry
	{
		public string PlayerName;

		public string Barcode;

		public string FirstLine;

		public int Count;
	}

	private const int MassSpawnThreshold = 50;

	private const double FlushWindowSeconds = 0.1;

	private static readonly Color YellowColor = Color.yellow;

	private static readonly Dictionary<AggregateKey, AggregateEntry> _pending = new Dictionary<AggregateKey, AggregateEntry>();

	private static long _windowStartTicks = 0L;

	private static bool _windowOpen = false;

	private const long FlushWindowTicks = 1000000L;

	public static void Enqueue(string playerName, string barcode, string line)
	{
		AggregateKey key = new AggregateKey(playerName, barcode);
		if (_pending.TryGetValue(key, out var value))
		{
			value.Count++;
			_pending[key] = value;
		}
		else
		{
			_pending[key] = new AggregateEntry
			{
				PlayerName = playerName,
				Barcode = barcode,
				FirstLine = line,
				Count = 1
			};
		}
		_windowStartTicks = DateTime.UtcNow.Ticks;
		_windowOpen = true;
	}

	public static void Tick()
	{
		if (_windowOpen && _pending.Count != 0 && DateTime.UtcNow.Ticks - _windowStartTicks >= 1000000)
		{
			Flush();
		}
	}

	private static void Flush()
	{
		foreach (KeyValuePair<AggregateKey, AggregateEntry> item in _pending)
		{
			AggregateEntry value = item.Value;
			string text = ((value.Count != 1) ? ((value.Count <= 50) ? ("[SpawnGuard] " + value.Count + "x '" + value.Barcode + "' spawned by " + value.PlayerName + ".") : ("[SpawnGuard] MASS SPAWN ATTEMPT: " + value.Count + " spawn attempts of '" + value.Barcode + "' from " + value.PlayerName + " in 0.1s window.")) : value.FirstLine);
			MelonLogger.Msg(text);
			SpawnLogHistory.Append(text);
		}
		_pending.Clear();
		_windowOpen = false;
		SpawnLogHistory.Flush();
	}
}
public static class AmmoFilter
{
	private static readonly char[] BarcodeDelimiters = new char[6] { '.', '_', '-', ' ', '/', '\\' };

	private static readonly string[] AmmoKeywords = new string[9] { "magazine", "mag", "ammo", "bullet", "cartridge", "round", "shell", "projectile", "casing" };

	public static bool IsAmmoOrMagazine(string barcode, string crateTitle = null)
	{
		if (MatchesKeywords(barcode))
		{
			return true;
		}
		if (!string.IsNullOrEmpty(crateTitle) && MatchesKeywords(crateTitle))
		{
			return true;
		}
		return false;
	}

	public static bool HasMagazineComponent(Poolee poolee)
	{
		if ((Object)(object)poolee == (Object)null)
		{
			return false;
		}
		try
		{
			return (Object)(object)((Component)poolee).gameObject.GetComponentInChildren<Magazine>(true) != (Object)null;
		}
		catch
		{
			return false;
		}
	}

	private static bool MatchesKeywords(string input)
	{
		if (string.IsNullOrEmpty(input))
		{
			return false;
		}
		string[] array = input.Split(BarcodeDelimiters, StringSplitOptions.RemoveEmptyEntries);
		string[] array2 = array;
		foreach (string text in array2)
		{
			string[] ammoKeywords = AmmoKeywords;
			foreach (string text2 in ammoKeywords)
			{
				if (string.Equals(text, text2, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				if (text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
		}
		return false;
	}
}
public static class SpawnGraceWindow
{
	private struct GraceState
	{
		public long WindowEndTicks;

		public int SpawnsUsed;
	}

	private const int MaxGraceSpawns = 5;

	private const long TicksPerSecond = 10000000L;

	private static readonly Dictionary<byte, GraceState> _states = new Dictionary<byte, GraceState>();

	public static void OnPlayerJoined(byte playerID)
	{
		float value = SpawnGuardConfig.SpawnCooldown.Value;
		float num = 5f * value + 0.5f;
		long windowEndTicks = DateTime.UtcNow.Ticks + (long)(num * 10000000f);
		_states[playerID] = new GraceState
		{
			WindowEndTicks = windowEndTicks,
			SpawnsUsed = 0
		};
		MelonLogger.Msg("[SpawnGuard] Grace window opened for player " + playerID.ToString() + " — " + num.ToString("F1") + "s, up to " + 5 + " spawns.");
	}

	public static bool IsInGrace(byte playerID)
	{
		if (!_states.TryGetValue(playerID, out var value))
		{
			return false;
		}
		return DateTime.UtcNow.Ticks < value.WindowEndTicks;
	}

	public static bool TryConsumeGraceSpawn(byte playerID)
	{
		if (!_states.TryGetValue(playerID, out var value))
		{
			return false;
		}
		if (value.SpawnsUsed >= 5)
		{
			return false;
		}
		value.SpawnsUsed++;
		_states[playerID] = value;
		return true;
	}

	public static void OnPlayerLeft(byte playerID)
	{
		_states.Remove(playerID);
	}

	public static void ClearAll()
	{
		_states.Clear();
	}
}
public static class SpawnLogStore
{
	public struct SpawnEntry
	{
		public string FriendlyName;

		public int Count;

		public bool WasBlocked;
	}

	private struct GlobalKey
	{
		public string ShortName;

		public string FriendlyName;

		public bool WasBlocked;

		public override bool Equals(object obj)
		{
			if (!(obj is GlobalKey globalKey))
			{
				return false;
			}
			return ShortName == globalKey.ShortName && FriendlyName == globalKey.FriendlyName && WasBlocked == globalKey.WasBlocked;
		}

		public override int GetHashCode()
		{
			int num = ((ShortName != null) ? ShortName.GetHashCode() : 0);
			int num2 = ((FriendlyName != null) ? FriendlyName.GetHashCode() : 0);
			return num ^ (num2 << 3) ^ (WasBlocked ? 1 : 0);
		}
	}

	private const int MaxEntriesPerPlayer = 200;

	private const int MaxGlobalEntries = 500;

	private const int PlayerNameMaxLen = 10;

	private static readonly Dictionary<byte, List<SpawnEntry>> _playerLogs = new Dictionary<byte, List<SpawnEntry>>();

	private static readonly Dictionary<byte, string> _playerNames = new Dictionary<byte, string>();

	private static readonly List<GlobalKey> _globalOrder = new List<GlobalKey>();

	private static readonly Dictionary<GlobalKey, int> _globalCounts = new Dictionary<GlobalKey, int>();

	public static void Record(byte playerID, string playerName, string friendlyName, bool wasBlocked)
	{
		playerName = StripRichText(playerName);
		friendlyName = StripRichText(friendlyName);
		if (!_playerNames.ContainsKey(playerID))
		{
			_playerNames[playerID] = playerName;
		}
		if (!_playerLogs.TryGetValue(playerID, out var value))
		{
			value = new List<SpawnEntry>(16);
			_playerLogs[playerID] = value;
		}
		bool flag = false;
		for (int num = value.Count - 1; num >= 0; num--)
		{
			SpawnEntry value2 = value[num];
			if (value2.FriendlyName == friendlyName && value2.WasBlocked == wasBlocked)
			{
				value2.Count++;
				value[num] = value2;
				flag = true;
				break;
			}
		}
		if (!flag)
		{
			if (value.Count >= 200)
			{
				value.RemoveAt(0);
			}
			value.Add(new SpawnEntry
			{
				FriendlyName = friendlyName,
				Count = 1,
				WasBlocked = wasBlocked
			});
		}
		string shortName = Truncate(playerName, 10);
		GlobalKey globalKey = new GlobalKey
		{
			ShortName = shortName,
			FriendlyName = friendlyName,
			WasBlocked = wasBlocked
		};
		if (_globalCounts.ContainsKey(globalKey))
		{
			_globalCounts[globalKey]++;
			return;
		}
		if (_globalOrder.Count >= 500)
		{
			GlobalKey key = _globalOrder[0];
			_globalOrder.RemoveAt(0);
			_globalCounts.Remove(key);
		}
		_globalOrder.Add(globalKey);
		_globalCounts[globalKey] = 1;
	}

	public static List<byte> GetPlayerIDs()
	{
		return new List<byte>(_playerLogs.Keys);
	}

	public static string GetPlayerName(byte playerID)
	{
		string value;
		return _playerNames.TryGetValue(playerID, out value) ? value : ("Player " + playerID);
	}

	public static List<SpawnEntry> GetPlayerLog(byte playerID)
	{
		if (_playerLogs.TryGetValue(playerID, out var value))
		{
			return new List<SpawnEntry>(value);
		}
		return new List<SpawnEntry>();
	}

	public static List<string> GetGlobalLog()
	{
		List<string> list = new List<string>(_globalOrder.Count);
		for (int num = _globalOrder.Count - 1; num >= 0; num--)
		{
			GlobalKey key = _globalOrder[num];
			int num2 = _globalCounts[key];
			string text = (key.WasBlocked ? "[BLOCKED] " : "");
			list.Add(text + key.ShortName + ": " + ((num2 > 1) ? (num2 + "x ") : "") + key.FriendlyName);
		}
		return list;
	}

	public static void OnPlayerLeft(byte playerID)
	{
	}

	public static void ClearAll()
	{
		_playerLogs.Clear();
		_playerNames.Clear();
		_globalOrder.Clear();
		_globalCounts.Clear();
	}

	private static string Truncate(string s, int maxLen)
	{
		if (string.IsNullOrEmpty(s))
		{
			return s;
		}
		return (s.Length <= maxLen) ? s : s.Substring(0, maxLen);
	}

	private static string StripRichText(string input)
	{
		if (string.IsNullOrEmpty(input))
		{
			return input;
		}
		StringBuilder stringBuilder = new StringBuilder(input.Length);
		bool flag = false;
		foreach (char c in input)
		{
			switch (c)
			{
			case '<':
				flag = true;
				continue;
			case '>':
				flag = false;
				continue;
			}
			if (!flag)
			{
				stringBuilder.Append(c);
			}
		}
		string text = stringBuilder.ToString();
		return (text.Trim().Length == 0) ? input : text;
	}
}
public static class SpawnPlayerTracker
{
	private struct EntityInfo
	{
		public string Barcode;

		public string FriendlyName;

		public Vector3 LastKnownPosition;
	}

	private const string SpawngunBarcode = "c1534c5a-5747-42a2-bd08-ab3b47616467";

	private static readonly Dictionary<byte, List<ushort>> _playerEntities = new Dictionary<byte, List<ushort>>();

	private static readonly Dictionary<ushort, byte> _entityOwner = new Dictionary<ushort, byte>();

	private static readonly Dictionary<ushort, EntityInfo> _entityInfo = new Dictionary<ushort, EntityInfo>();

	private const float SortInterval = 5f;

	private const float BurstBuffer = 0.25f;

	private static float _lastSortTime = 0f;

	private static float _pendingSortTime = -1f;

	public static void Tick()
	{
		float time = Time.time;
		if (_pendingSortTime >= 0f && time >= _pendingSortTime)
		{
			_pendingSortTime = -1f;
			SortAllByDistance();
		}
		if (time - _lastSortTime >= 5f)
		{
			_lastSortTime = time;
			SortAllByDistance();
		}
	}

	private static void SortAllByDistance()
	{
		foreach (KeyValuePair<byte, List<ushort>> playerEntity in _playerEntities)
		{
			SortByDistance(playerEntity.Key, playerEntity.Value);
		}
	}

	private static void SortByDistance(byte ownerID, List<ushort> list)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		if (list.Count <= 1)
		{
			return;
		}
		Vector3 playerPos = GetPlayerPosition(ownerID);
		List<ushort> list2 = new List<ushort>(list.Count);
		List<ushort> list3 = new List<ushort>(2);
		foreach (ushort item in list)
		{
			_entityInfo.TryGetValue(item, out var value);
			if (value.Barcode == "c1534c5a-5747-42a2-bd08-ab3b47616467")
			{
				list3.Add(item);
			}
			else
			{
				list2.Add(item);
			}
		}
		list2.Sort(delegate(ushort a, ushort b)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			float entityDistance = GetEntityDistance(a, playerPos);
			return GetEntityDistance(b, playerPos).CompareTo(entityDistance);
		});
		list.Clear();
		list.AddRange(list2);
		list.AddRange(list3);
	}

	private static Vector3 GetPlayerPosition(byte ownerID)
	{
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			PlayerID playerID = PlayerIDManager.GetPlayerID(ownerID);
			if (PlayerID.IsNullOrInvalid(playerID))
			{
				return Vector3.zero;
			}
			RigRefs val = default(RigRefs);
			if (PlayerRepUtilities.TryGetReferences(ownerID, ref val) && val != null && (Object)(object)val.RigManager != (Object)null)
			{
				return ((Object)(object)((Rig)val.RigManager.physicsRig).m_head != (Object)null) ? ((Rig)val.RigManager.physicsRig).m_head.position : ((Component)val.RigManager).transform.position;
			}
		}
		catch
		{
		}
		return Vector3.zero;
	}

	private static float GetEntityDistance(ushort entityID, Vector3 playerPos)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		if (!_entityInfo.TryGetValue(entityID, out var value))
		{
			return float.MaxValue;
		}
		return Vector3.Distance(value.LastKnownPosition, playerPos);
	}

	public static void OnPlayerLeft(byte playerID)
	{
		if (!_playerEntities.TryGetValue(playerID, out var value))
		{
			return;
		}
		foreach (ushort item in value)
		{
			_entityOwner.Remove(item);
			_entityInfo.Remove(item);
		}
		_playerEntities.Remove(playerID);
	}

	public static void ClearAll()
	{
		_playerEntities.Clear();
		_entityOwner.Clear();
		_entityInfo.Clear();
	}

	public static bool TryGetOwner(ushort entityID, out byte ownerID)
	{
		return _entityOwner.TryGetValue(entityID, out ownerID);
	}

	public static bool TryGetEntityInfo(ushort entityID, out string barcode, out string friendlyName)
	{
		if (_entityInfo.TryGetValue(entityID, out var value))
		{
			barcode = value.Barcode;
			friendlyName = value.FriendlyName;
			return true;
		}
		barcode = (friendlyName = "(unknown)");
		return false;
	}

	public static void OnSpawnRegistered(byte ownerID, ushort entityID, string barcode, string friendlyName, Vector3 spawnPosition = default(Vector3))
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		_entityOwner[entityID] = ownerID;
		_entityInfo[entityID] = new EntityInfo
		{
			Barcode = barcode,
			FriendlyName = friendlyName,
			LastKnownPosition = spawnPosition
		};
		List<ushort> orCreate = GetOrCreate(ownerID);
		PruneStale(orCreate);
		orCreate.Add(entityID);
		if (SpawnGuardConfig.EnableObjectLimit.Value && NetworkInfo.IsHost && (!IsLocalHost(ownerID) || SpawnGuardConfig.ObjectLimitAppliesToHost.Value))
		{
			int value = SpawnGuardConfig.ObjectLimit.Value;
			if (orCreate.Count > value && _pendingSortTime < 0f)
			{
				_pendingSortTime = Time.time + 0.25f;
			}
			EnforceLimit(ownerID, orCreate, value);
		}
	}

	private static void EnforceLimit(byte ownerID, List<ushort> list, int limit)
	{
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		while (list.Count > limit)
		{
			ushort num = list[0];
			list.RemoveAt(0);
			_entityOwner.Remove(num);
			_entityInfo.Remove(num);
			if (NetworkEntityManager.IDManager.RegisteredEntities.GetEntity(num) != null)
			{
				NetworkAssetSpawner.Despawn(new DespawnRequestInfo
				{
					EntityID = num,
					DespawnEffect = false
				});
				MelonLogger.Msg("[SpawnGuard] Object limit: despawned entity (" + num + ") for player " + ownerID + " — limit " + limit + ".");
			}
		}
	}

	public static void OnEntityDespawned(ushort entityID)
	{
		if (_entityOwner.TryGetValue(entityID, out var value))
		{
			_entityOwner.Remove(entityID);
			_entityInfo.Remove(entityID);
			if (_playerEntities.TryGetValue(value, out var value2))
			{
				value2.Remove(entityID);
			}
		}
	}

	private static void PruneStale(List<ushort> list)
	{
		for (int num = list.Count - 1; num >= 0; num--)
		{
			ushort num2 = list[num];
			if (NetworkEntityManager.IDManager.RegisteredEntities.GetEntity(num2) == null)
			{
				_entityOwner.Remove(num2);
				_entityInfo.Remove(num2);
				list.RemoveAt(num);
			}
		}
	}

	private static List<ushort> GetOrCreate(byte playerID)
	{
		if (!_playerEntities.TryGetValue(playerID, out var value))
		{
			value = new List<ushort>(SpawnGuardConfig.ObjectLimit.Value + 4);
			_playerEntities[playerID] = value;
		}
		return value;
	}

	public static bool IsLocalHost(byte playerID)
	{
		PlayerID playerID2 = PlayerIDManager.GetPlayerID(playerID);
		return playerID2 != null && playerID2.IsMe;
	}
}
public class Main : MelonMod
{
	private Harmony _harmony;

	public override void OnInitializeMelon()
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Expected O, but got Unknown
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Expected O, but got Unknown
		SpawnGuardConfig.Initialize();
		BarcodeBrowser.InitializePrefs();
		_harmony = new Harmony("com.yourname.spawnguard");
		_harmony.PatchAll(typeof(Main).Assembly);
		DespawnGuard_OnDespawnRequestPatch.ApplyPatch(_harmony);
		MultiplayerHooking.OnPlayerLeft += new PlayerUpdate(OnPlayerLeft);
		MultiplayerHooking.OnPlayerJoined += new PlayerUpdate(OnPlayerJoined);
		MelonLogger.Msg("[SpawnGuard] Initialized.");
	}

	public override void OnLateInitializeMelon()
	{
		BoneMenuHandler.Initialize();
	}

	public override void OnUpdate()
	{
		SpawnLogAggregator.Tick();
		if (NetworkInfo.IsHost)
		{
			SpawnPlayerTracker.Tick();
		}
		if (Input.GetKeyDown((KeyCode)99))
		{
			BarcodeBrowser.ToggleFromKeyboard();
		}
	}

	public override void OnGUI()
	{
		BarcodeBrowser.OnGUI();
	}

	public override void OnDeinitializeMelon()
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Expected O, but got Unknown
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Expected O, but got Unknown
		MultiplayerHooking.OnPlayerLeft -= new PlayerUpdate(OnPlayerLeft);
		MultiplayerHooking.OnPlayerJoined -= new PlayerUpdate(OnPlayerJoined);
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}

	public override void OnApplicationQuit()
	{
		SpawnLogHistory.Flush();
	}

	private static void OnPlayerJoined(PlayerID playerID)
	{
		if (playerID != null && NetworkInfo.IsHost)
		{
			byte smallID = playerID.SmallID;
			if (SpawnGuardConfig.EnableRateLimit.Value && SpawnGuardConfig.Enabled.Value)
			{
				SpawnGraceWindow.OnPlayerJoined(smallID);
			}
			AttributeExistingEntities(smallID, playerID);
		}
	}

	private static void OnPlayerLeft(PlayerID playerID)
	{
		if (playerID != null)
		{
			byte smallID = playerID.SmallID;
			SpawnPlayerTracker.OnPlayerLeft(smallID);
			ConfettiPing.OnPlayerLeft(smallID);
			VehicleTracker.OnPlayerLeft(smallID);
			ExplosiveCooldown.OnPlayerLeft(smallID);
			SpawnGraceWindow.OnPlayerLeft(smallID);
			PlayerLogMenuHandler.OnPlayerLeft(smallID);
		}
	}

	private static void AttributeExistingEntities(byte smallID, PlayerID playerID)
	{
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		if (!NetworkInfo.IsHost)
		{
			return;
		}
		try
		{
			Dictionary<ushort, NetworkEntity> iDEntityLookup = NetworkEntityManager.IDManager.RegisteredEntities.IDEntityLookup;
			foreach (KeyValuePair<ushort, NetworkEntity> item in iDEntityLookup)
			{
				ushort key = item.Key;
				if (SpawnPlayerTracker.TryGetOwner(key, out var _))
				{
					continue;
				}
				NetworkEntity value = item.Value;
				if (value != null)
				{
					if (PlayerID.op_Implicit(value.OwnerID) == smallID)
					{
						SpawnPlayerTracker.OnSpawnRegistered(smallID, key, "(pre-session)", "(pre-session)");
						continue;
					}
					byte localSmallID = PlayerIDManager.LocalSmallID;
					SpawnPlayerTracker.OnSpawnRegistered(localSmallID, key, "(pre-session)", "(pre-session)");
				}
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] AttributeExistingEntities failed: " + ex);
		}
	}
}
[HarmonyPatch(typeof(SpawnResponseMessage), "OnSpawnFinished")]
public static class SpawnGuard_OnSpawnFinishedPatch
{
	private static readonly HashSet<ushort> _seenEntityIDs = new HashSet<ushort>();

	private static readonly Dictionary<ushort, float> _seenTimes = new Dictionary<ushort, float>();

	private static readonly List<ushort> _staleBuffer = new List<ushort>(64);

	private const float PruneAfter = 2f;

	private const int PruneThreshold = 2000;

	[HarmonyPostfix]
	public static void Postfix(SpawnResponseData data, Poolee poolee)
	{
		//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: 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)
		//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01af: Expected O, but got Unknown
		//IL_0254: Unknown result type (might be due to invalid IL or missing references)
		//IL_0260: Expected O, but got Unknown
		if (!SpawnGuardConfig.Enabled.Value)
		{
			return;
		}
		try
		{
			if (data == null)
			{
				return;
			}
			string text = "(unknown)";
			if (data.SpawnData != null && !string.IsNullOrEmpty(data.SpawnData.Barcode))
			{
				text = data.SpawnData.Barcode;
			}
			if (AvatarBypass.ShouldBypass(text))
			{
				return;
			}
			float time = Time.time;
			if (_seenEntityIDs.Contains(data.EntityID))
			{
				return;
			}
			_seenEntityIDs.Add(data.EntityID);
			_seenTimes[data.EntityID] = time;
			if (_seenTimes.Count > 2000)
			{
				_staleBuffer.Clear();
				foreach (KeyValuePair<ushort, float> seenTime in _seenTimes)
				{
					if (time - seenTime.Value > 2f)
					{
						_staleBuffer.Add(seenTime.Key);
					}
				}
				for (int i = 0; i < _staleBuffer.Count; i++)
				{
					_seenEntityIDs.Remove(_staleBuffer[i]);
					_seenTimes.Remove(_staleBuffer[i]);
				}
			}
			byte ownerID = data.OwnerID;
			if (NetworkInfo.IsHost)
			{
				string friendlyName = text;
				try
				{
					SpawnableCrate val = default(SpawnableCrate);
					if (AssetWarehouse.Instance.TryGetCrate<SpawnableCrate>(new Barcode(text), ref val) && (Object)(object)val != (Object)null)
					{
						friendlyName = ((Scannable)val).Title ?? text;
					}
				}
				catch
				{
				}
				Vector3 spawnPosition = Vector3.zero;
				try
				{
					if ((Object)(object)poolee != (Object)null)
					{
						spawnPosition = ((Component)poolee).transform.position;
					}
				}
				catch
				{
				}
				SpawnPlayerTracker.OnSpawnRegistered(ownerID, data.EntityID, text, friendlyName, spawnPosition);
				if (TagFilter.IsVehicle(text))
				{
					VehicleTracker.OnVehicleSpawned(ownerID, data.EntityID);
				}
			}
			else
			{
				if (!SpawnGuardConfig.LogAllSpawns.Value)
				{
					return;
				}
				string text2 = text;
				try
				{
					SpawnableCrate val2 = default(SpawnableCrate);
					if (AssetWarehouse.Instance.TryGetCrate<SpawnableCrate>(new Barcode(text), ref val2) && (Object)(object)val2 != (Object)null)
					{
						text2 = ((Scannable)val2).Title ?? text;
					}
				}
				catch
				{
				}
				PlayerID playerID = PlayerIDManager.GetPlayerID(ownerID);
				string text4 = default(string);
				string text3 = ((!PlayerID.IsNullOrInvalid(playerID)) ? (MetadataHelper.TryGetDisplayName(playerID, ref text4) ? text4 : ("Player " + ownerID)) : ("Unknown (ID " + ownerID + ")"));
				string line = "[SpawnGuard] " + text3 + " spawned '" + text2 + "' (" + text + ")";
				SpawnLogAggregator.Enqueue(text3, text, line);
				SpawnLogStore.Record(ownerID, text3, text2, wasBlocked: false);
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Error in spawn observer postfix: " + ex);
		}
	}
}
[HarmonyPatch(typeof(SpawnRequestMessage), "OnHandleMessage")]
public static class SpawnGuard_OnSpawnRequestPatch
{
	[HarmonyPrefix]
	public unsafe static bool Prefix(ReceivedMessage received)
	{
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ed: Expected O, but got Unknown
		//IL_0728: Unknown result type (might be due to invalid IL or missing references)
		//IL_072a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0796: Unknown result type (might be due to invalid IL or missing references)
		//IL_079b: Unknown result type (might be due to invalid IL or missing references)
		if (!SpawnGuardConfig.Enabled.Value || !SpawnGuardConfig.PreventOnHost.Value)
		{
			return true;
		}
		if (!NetworkInfo.IsHost)
		{
			return true;
		}
		try
		{
			byte value = ((ReceivedMessage)(ref received)).Sender.Value;
			PlayerID playerID = PlayerIDManager.GetPlayerID(value);
			bool isLocalHost = false;
			string text;
			if (PlayerID.IsNullOrInvalid(playerID))
			{
				text = "Unknown (ID " + value + ")";
			}
			else
			{
				string text2 = default(string);
				text = (MetadataHelper.TryGetDisplayName(playerID, ref text2) ? text2 : ("Player " + value));
				isLocalHost = playerID.IsMe;
			}
			string text3 = "(unknown)";
			string text4 = "(unknown)";
			try
			{
				SerializedSpawnData val = ((ReceivedMessage)(ref received)).ReadData<SerializedSpawnData>();
				if (val != null)
				{
					text3 = val.Barcode;
					SpawnableCrate val2 = default(SpawnableCrate);
					text4 = ((!AssetWarehouse.Instance.TryGetCrate<SpawnableCrate>(new Barcode(text3), ref val2) || !((Object)(object)val2 != (Object)null)) ? text3 : (((Scannable)val2).Title ?? text3));
				}
			}
			catch
			{
			}
			if (AvatarBypass.ShouldBypass(text3))
			{
				return true;
			}
			if (BarcodeBrowser.IsBarcodeBlocked(text3))
			{
				string text5 = "[SpawnGuard] BROWSER BLOCKED: '" + text4 + "' (" + text3 + ") from " + text + ".";
				MelonLogger.Msg(text5);
				if (!SpawnGuardConfig.LogOnly.Value)
				{
					SpawnLogAggregator.Enqueue(text, text3, text5);
					SpawnLogStore.Record(value, text, text4, wasBlocked: true);
					SpawnNotifier.Warn("SpawnGuard", "Blocked (browser list): '" + text4 + "' from " + text);
					ConfettiPing.TryPing(value);
					return false;
				}
				SpawnLogAggregator.Enqueue(text, text3, "(log-only) " + text5);
				return true;
			}
			if (TagFilter.IsBlacklisted(text3, isLocalHost, out var matchedTag))
			{
				string text6 = "[SpawnGuard] Blocked [tag:" + matchedTag + "] '" + text4 + "' (" + text3 + ") from " + text + ".";
				MelonLogger.Msg(text6);
				if (!SpawnGuardConfig.LogOnly.Value)
				{
					SpawnLogAggregator.Enqueue(text, text3, text6);
					SpawnLogStore.Record(value, text, text4, wasBlocked: true);
					SpawnNotifier.Warn("SpawnGuard", "Blocked [" + matchedTag + "] '" + text4 + "' from " + text);
					ConfettiPing.TryPing(value);
					return false;
				}
				SpawnLogAggregator.Enqueue(text, text3, "(log-only) " + text6);
				return true;
			}
			if (SpawnGuardConfig.EnableExplosiveCooldown.Value && TagFilter.IsExplosive(text3) && !ExplosiveCooldown.TryAllowExplosive(value, isLocalHost))
			{
				string text7 = "[SpawnGuard] Explosive cooldown: blocked '" + text4 + "' (" + text3 + ") from " + text + ".";
				MelonLogger.Msg(text7);
				if (!SpawnGuardConfig.LogOnly.Value)
				{
					SpawnLogAggregator.Enqueue(text, text3, text7);
					SpawnLogStore.Record(value, text, text4, wasBlocked: true);
					SpawnNotifier.Warn("SpawnGuard", "Explosive cooldown — blocked '" + text4 + "' from " + text);
					ConfettiPing.TryPing(value);
					return false;
				}
				SpawnLogAggregator.Enqueue(text, text3, "(log-only) " + text7);
				return true;
			}
			if (AmmoFilter.IsAmmoOrMagazine(text3, text4) && SpawnGuardConfig.AllowMagazines.Value)
			{
				if (!((!SpawnGraceWindow.IsInGrace(value)) ? (!SpawnGuardConfig.EnableRateLimit.Value || SpawnRateLimiter.TryAllowSpawn(value, isLocalHost)) : SpawnGraceWindow.TryConsumeGraceSpawn(value)))
				{
					string text8 = "[SpawnGuard] Rate-limited ammo '" + text4 + "' from " + text + ".";
					MelonLogger.Msg(text8);
					if (!SpawnGuardConfig.LogOnly.Value)
					{
						SpawnLogAggregator.Enqueue(text, text3, text8);
						SpawnLogStore.Record(value, text, text4, wasBlocked: true);
						SpawnNotifier.Warn("SpawnGuard", "Rate-limited ammo '" + text4 + "' from " + text);
						return false;
					}
					SpawnLogAggregator.Enqueue(text, text3, "(log-only) " + text8);
					return true;
				}
				if (SpawnGuardConfig.LogAllSpawns.Value)
				{
					SpawnLogStore.Record(value, text, text4, wasBlocked: false);
					SpawnLogAggregator.Enqueue(text, text3, "[SpawnGuard] " + text + " spawned ammo '" + text4 + "' (" + text3 + ")");
				}
				return true;
			}
			if (SpawnGraceWindow.IsInGrace(value))
			{
				if (!SpawnGraceWindow.TryConsumeGraceSpawn(value))
				{
					string text9 = "[SpawnGuard] Grace cap reached — blocked '" + text4 + "' from " + text + " (5 grace spawns used).";
					MelonLogger.Msg(text9);
					if (!SpawnGuardConfig.LogOnly.Value)
					{
						SpawnLogAggregator.Enqueue(text, text3, text9);
						SpawnLogStore.Record(value, text, text4, wasBlocked: true);
						SpawnNotifier.Warn("SpawnGuard", "Grace cap — blocked '" + text4 + "' from " + text);
						return false;
					}
					SpawnLogAggregator.Enqueue(text, text3, "(log-only) " + text9);
					return true;
				}
			}
			else if (SpawnGuardConfig.EnableRateLimit.Value && !SpawnRateLimiter.TryAllowSpawn(value, isLocalHost))
			{
				string text10 = "[SpawnGuard] Rate-limited '" + text4 + "' from " + text + " — cooldown not elapsed, timer reset.";
				MelonLogger.Msg(text10);
				if (!SpawnGuardConfig.LogOnly.Value)
				{
					SpawnLogAggregator.Enqueue(text, text3, text10);
					SpawnLogStore.Record(value, text, text4, wasBlocked: true);
					SpawnNotifier.Warn("SpawnGuard", "Rate-limited '" + text4 + "' from " + text);
					return false;
				}
				SpawnLogAggregator.Enqueue(text, text3, "(log-only) " + text10);
				return true;
			}
			if (PlayerID.IsNullOrInvalid(playerID))
			{
				bool flag = false;
			}
			else
			{
				PermissionLevel val3 = default(PermissionLevel);
				Color val4 = default(Color);
				FusionPermissions.FetchPermissionLevel(playerID.PlatformID, ref val3, ref val4);
				if (!FusionPermissions.HasSufficientPermissions(val3, SpawnGuardConfig.RequiredPermission))
				{
					string text11 = "[SpawnGuard] Blocked '" + text4 + "' (" + text3 + ") from " + text + " — permission " + ((object)(*(PermissionLevel*)(&val3))/*cast due to .constrained prefix*/).ToString() + " < required " + ((object)SpawnGuardConfig.RequiredPermission/*cast due to .constrained prefix*/).ToString() + ".";
					MelonLogger.Msg(text11);
					if (!SpawnGuardConfig.LogOnly.Value)
					{
						SpawnLogAggregator.Enqueue(text, text3, text11);
						SpawnLogStore.Record(value, text, text4, wasBlocked: true);
						SpawnNotifier.Warn("SpawnGuard", "Blocked '" + text4 + "' from " + text + " (no permission)");
						ConfettiPing.TryPing(value);
						return false;
					}
					SpawnLogAggregator.Enqueue(text, text3, "(log-only) " + text11);
					return true;
				}
			}
			if (SpawnGuardConfig.LogAllSpawns.Value)
			{
				string text12 = "[SpawnGuard] " + text + " spawned '" + text4 + "' (" + text3 + ")";
				MelonLogger.Msg(text12);
				SpawnLogStore.Record(value, text, text4, wasBlocked: false);
				SpawnLogAggregator.Enqueue(text, text3, text12);
			}
			return true;
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Error in host-prevention prefix, failing open: " + ex);
			return true;
		}
	}
}
public static class SpawnGuardConfig
{
	private static MelonPreferences_Category _category;

	public static MelonPreferences_Entry<bool> Enabled;

	public static MelonPreferences_Entry<bool> LogOnly;

	public static MelonPreferences_Entry<bool> ShowNotifications;

	public static MelonPreferences_Entry<bool> LogAllSpawns;

	public static MelonPreferences_Entry<bool> PreventOnHost;

	public static MelonPreferences_Entry<bool> AllowAvatarsThroughFirewall;

	public static MelonPreferences_Entry<float> NotificationCooldown;

	public static MelonPreferences_Entry<bool> EnableRateLimit;

	public static MelonPreferences_Entry<float> SpawnCooldown;

	public static MelonPreferences_Entry<int> SpawnBurstCount;

	public static MelonPreferences_Entry<bool> RateLimitAppliesToHost;

	public static MelonPreferences_Entry<bool> EnableObjectLimit;

	public static MelonPreferences_Entry<int> ObjectLimit;

	public static MelonPreferences_Entry<bool> ObjectLimitAppliesToHost;

	public static MelonPreferences_Entry<bool> AllowMagazines;

	public static MelonPreferences_Entry<bool> EnableVehicleLimit;

	public static MelonPreferences_Entry<int> VehicleLimitCount;

	public static MelonPreferences_Entry<bool> EnableExplosiveCooldown;

	public static MelonPreferences_Entry<float> ExplosiveCooldownSeconds;

	public static MelonPreferences_Entry<bool> EnableTagBlacklist;

	public static MelonPreferences_Entry<bool> BlockTag_Gun;

	public static MelonPreferences_Entry<bool> BlockTag_Melee;

	public static MelonPreferences_Entry<bool> BlockTag_Blade;

	public static MelonPreferences_Entry<bool> BlockTag_Blunt;

	public static MelonPreferences_Entry<bool> BlockTag_Vehicle;

	public static MelonPreferences_Entry<bool> BlockTag_NPC;

	public static MelonPreferences_Entry<bool> BlockTag_Gadget;

	public static MelonPreferences_Entry<bool> BlockTag_Prop;

	public static MelonPreferences_Entry<bool> BlockTag_Explosive;

	public static MelonPreferences_Entry<bool> TagBlacklistEnforceOnHost;

	public static MelonPreferences_Entry<bool> EnableDespawnGuard;

	public static MelonPreferences_Entry<bool> LogDespawns;

	public static MelonPreferences_Entry<bool> ProtectHostSpawns;

	public static MelonPreferences_Entry<int> DespawnRequiredPermissionRaw;

	public static MelonPreferences_Entry<bool> EnableConfettiPing;

	public static MelonPreferences_Entry<int> RequiredPermissionRaw;

	public static PermissionLevel RequiredPermission => (PermissionLevel)(sbyte)RequiredPermissionRaw.Value;

	public static PermissionLevel DespawnRequiredPermission => (PermissionLevel)(sbyte)DespawnRequiredPermissionRaw.Value;

	public static void Initialize()
	{
		_category = MelonPreferences.CreateCategory("SpawnGuard");
		Enabled = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Master toggle.", false, false, (ValueValidator)null, (string)null);
		LogOnly = _category.CreateEntry<bool>("LogOnly", false, "Debug Mode", "Disables enforcement, logs what would have been blocked.", false, false, (ValueValidator)null, (string)null);
		ShowNotifications = _category.CreateEntry<bool>("ShowNotifications", true, "Show Notifications", "In-headset warning on block.", false, false, (ValueValidator)null, (string)null);
		LogAllSpawns = _category.CreateEntry<bool>("LogAllSpawns", false, "Log All Spawns", "Log every spawn regardless of outcome.", false, false, (ValueValidator)null, (string)null);
		PreventOnHost = _category.CreateEntry<bool>("PreventOnHost", true, "Intercept Unauthorized Spawns", "Blocks disallowed spawns at source. Host only.", false, false, (ValueValidator)null, (string)null);
		AllowAvatarsThroughFirewall = _category.CreateEntry<bool>("AllowAvatarsThroughFirewall", true, "Avatar Bypass", "Barcodes containing 'Avatar' skip SpawnGuard entirely.", false, false, (ValueValidator)null, (string)null);
		NotificationCooldown = _category.CreateEntry<float>("NotificationCooldown", 2.5f, "Notification Cooldown (s)", "Minimum seconds between in-headset popups.", false, false, (ValueValidator)null, (string)null);
		EnableRateLimit = _category.CreateEntry<bool>("EnableRateLimit", false, "Enable Spawn Rate Limit", "Limits spawns per cooldown window.", false, false, (ValueValidator)null, (string)null);
		SpawnCooldown = _category.CreateEntry<float>("SpawnCooldown", 0.5f, "Spawn Cooldown (s)", "Cooldown window length. Range 0.2-3.0.", false, false, (ValueValidator)null, (string)null);
		SpawnBurstCount = _category.CreateEntry<int>("SpawnBurstCount", 1, "Spawns Before Rate Limit", "Spawns allowed per window.", false, false, (ValueValidator)null, (string)null);
		RateLimitAppliesToHost = _category.CreateEntry<bool>("RateLimitAppliesToHost", false, "Rate Limit: Include Host", "When off, host exempt from rate limit.", false, false, (ValueValidator)null, (string)null);
		EnableObjectLimit = _category.CreateEntry<bool>("EnableObjectLimit", false, "Enable Object Limit", "Caps spawned objects per player.", false, false, (ValueValidator)null, (string)null);
		ObjectLimit = _category.CreateEntry<int>("ObjectLimit", 10, "Object Limit Per Player", "Max spawned objects. Range 1-20.", false, false, (ValueValidator)null, (string)null);
		ObjectLimitAppliesToHost = _category.CreateEntry<bool>("ObjectLimitAppliesToHost", false, "Object Limit: Include Host", "When off, host exempt from object limit.", false, false, (ValueValidator)null, (string)null);
		AllowMagazines = _category.CreateEntry<bool>("AllowMagazines", true, "Allow Ammo & Magazines", "Ammo/magazines bypass permission check. Still rate/object limited.", false, false, (ValueValidator)null, (string)null);
		EnableVehicleLimit = _category.CreateEntry<bool>("EnableVehicleLimit", false, "Enable Vehicle Limit", "Spawning a new vehicle despawns the player's previous one.", false, false, (ValueValidator)null, (string)null);
		VehicleLimitCount = _category.CreateEntry<int>("VehicleLimitCount", 1, "Max Vehicles Per Player", "Range 1-5.", false, false, (ValueValidator)null, (string)null);
		EnableExplosiveCooldown = _category.CreateEntry<bool>("EnableExplosiveCooldown", false, "Enable Explosive Cooldown", "Per-player cooldown between explosive spawns.", false, false, (ValueValidator)null, (string)null);
		ExplosiveCooldownSeconds = _category.CreateEntry<float>("ExplosiveCooldownSeconds", 30f, "Explosive Cooldown (s)", "Range 10-180 seconds.", false, false, (ValueValidator)null, (string)null);
		EnableTagBlacklist = _category.CreateEntry<bool>("EnableTagBlacklist", false, "Enable Tag Blacklist", "Blocked tag categories cannot be spawned regardless of permission level.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Gun = _category.CreateEntry<bool>("BlockTag_Gun", false, "Block: Guns", "Blocks spawns tagged Gun, Pistol, Rifle, Shotgun, SMG.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Melee = _category.CreateEntry<bool>("BlockTag_Melee", false, "Block: Melee", "Blocks spawns tagged Melee.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Blade = _category.CreateEntry<bool>("BlockTag_Blade", false, "Block: Bladed", "Blocks spawns tagged Blade.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Blunt = _category.CreateEntry<bool>("BlockTag_Blunt", false, "Block: Blunt", "Blocks spawns tagged Blunt.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Vehicle = _category.CreateEntry<bool>("BlockTag_Vehicle", false, "Block: Vehicles", "Blocks spawns tagged Vehicle.", false, false, (ValueValidator)null, (string)null);
		BlockTag_NPC = _category.CreateEntry<bool>("BlockTag_NPC", false, "Block: NPCs", "Blocks spawns tagged NPC.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Gadget = _category.CreateEntry<bool>("BlockTag_Gadget", false, "Block: Gadgets", "Blocks spawns tagged Gadget.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Prop = _category.CreateEntry<bool>("BlockTag_Prop", false, "Block: Props", "Blocks spawns tagged Prop.", false, false, (ValueValidator)null, (string)null);
		BlockTag_Explosive = _category.CreateEntry<bool>("BlockTag_Explosive", false, "Block: Explosives", "Blocks spawns tagged Explosive.", false, false, (ValueValidator)null, (string)null);
		TagBlacklistEnforceOnHost = _category.CreateEntry<bool>("TagBlacklistEnforceOnHost", false, "Blacklist: Enforce On Host", "When off, host exempt from tag blacklist.", false, false, (ValueValidator)null, (string)null);
		EnableDespawnGuard = _category.CreateEntry<bool>("EnableDespawnGuard", false, "Enable Despawn Guard", "Enforces despawn permissions on host.", false, false, (ValueValidator)null, (string)null);
		LogDespawns = _category.CreateEntry<bool>("LogDespawns", false, "Log Despawns", "Log despawn events through the aggregator.", false, false, (ValueValidator)null, (string)null);
		ProtectHostSpawns = _category.CreateEntry<bool>("ProtectHostSpawns", true, "Protect Host Spawns", "Non-host cannot despawn host-spawned objects.", false, false, (ValueValidator)null, (string)null);
		DespawnRequiredPermissionRaw = _category.CreateEntry<int>("DespawnRequiredPermission", 0, "Despawn Required Permission", "-1=GUEST, 0=DEFAULT, 1=OPERATOR, 2=OWNER.", false, false, (ValueValidator)null, (string)null);
		EnableConfettiPing = _category.CreateEntry<bool>("EnableConfettiPing", false, "Enable Confetti Ping", "Confetti on denied players. 0.3s rate limit per player.", false, false, (ValueValidator)null, (string)null);
		RequiredPermissionRaw = _category.CreateEntry<int>("RequiredPermission", 0, "Required Spawn Permission", "-1=GUEST, 0=DEFAULT, 1=OPERATOR, 2=OWNER.", false, false, (ValueValidator)null, (string)null);
		SpawnLogHistory.Initialize();
	}

	public static void Save()
	{
		_category.SaveToFile(false);
	}
}
public static class TagFilter
{
	public const string TagGun = "Gun";

	public const string TagPistol = "Pistol";

	public const string TagRifle = "Rifle";

	public const string TagShotgun = "Shotgun";

	public const string TagSMG = "SMG";

	public const string TagMelee = "Melee";

	public const string TagBlade = "Blade";

	public const string TagBlunt = "Blunt";

	public const string TagVehicle = "Vehicle";

	public const string TagNPC = "NPC";

	public const string TagGadget = "Gadget";

	public const string TagProp = "Prop";

	public const string TagExplosive = "Explosive";

	public static bool IsBlacklisted(string barcode, bool isLocalHost, out string matchedTag)
	{
		matchedTag = null;
		if (!SpawnGuardConfig.EnableTagBlacklist.Value)
		{
			return false;
		}
		if (isLocalHost && !SpawnGuardConfig.TagBlacklistEnforceOnHost.Value)
		{
			return false;
		}
		SpawnableCrate val = ResolveCrate(barcode);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		List<string> tags = ((Crate)val).Tags;
		if (tags == null || tags.Count == 0)
		{
			return false;
		}
		for (int i = 0; i < tags.Count; i++)
		{
			string text = tags[i];
			if (!string.IsNullOrEmpty(text))
			{
				if (SpawnGuardConfig.BlockTag_Gun.Value && (Eq(text, "Gun") || Eq(text, "Pistol") || Eq(text, "Rifle") || Eq(text, "Shotgun") || Eq(text, "SMG")))
				{
					matchedTag = "Gun (" + text + ")";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_Melee.Value && Eq(text, "Melee"))
				{
					matchedTag = "Melee";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_Blade.Value && Eq(text, "Blade"))
				{
					matchedTag = "Bladed";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_Blunt.Value && Eq(text, "Blunt"))
				{
					matchedTag = "Blunt";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_Vehicle.Value && Eq(text, "Vehicle"))
				{
					matchedTag = "Vehicle";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_NPC.Value && Eq(text, "NPC"))
				{
					matchedTag = "NPC";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_Gadget.Value && Eq(text, "Gadget"))
				{
					matchedTag = "Gadget";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_Prop.Value && Eq(text, "Prop"))
				{
					matchedTag = "Prop";
					return true;
				}
				if (SpawnGuardConfig.BlockTag_Explosive.Value && Eq(text, "Explosive"))
				{
					matchedTag = "Explosive";
					return true;
				}
			}
		}
		return false;
	}

	public static bool IsVehicle(string barcode)
	{
		SpawnableCrate val = ResolveCrate(barcode);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		return HasTag(val, "Vehicle");
	}

	public static bool IsExplosive(string barcode)
	{
		SpawnableCrate val = ResolveCrate(barcode);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		return HasTag(val, "Explosive");
	}

	public static bool HasTag(SpawnableCrate crate, string tag)
	{
		try
		{
			List<string> tags = ((Crate)crate).Tags;
			if (tags == null)
			{
				return false;
			}
			for (int i = 0; i < tags.Count; i++)
			{
				if (Eq(tags[i], tag))
				{
					return true;
				}
			}
			return false;
		}
		catch
		{
			return false;
		}
	}

	private static bool Eq(string a, string b)
	{
		return string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
	}

	private static SpawnableCrate ResolveCrate(string barcode)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Expected O, but got Unknown
		try
		{
			SpawnableCrate result = default(SpawnableCrate);
			AssetWarehouse.Instance.TryGetCrate<SpawnableCrate>(new Barcode(barcode), ref result);
			return result;
		}
		catch
		{
			return null;
		}
	}

	public static void DumpAllTags()
	{
		try
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			int num = 0;
			Enumerator<SpawnableCrate> enumerator = AssetWarehouse.Instance.GetCrates<SpawnableCrate>((ICrateFilter<SpawnableCrate>)null).GetEnumerator();
			while (enumerator.MoveNext())
			{
				SpawnableCrate current = enumerator.Current;
				num++;
				List<string> tags = ((Crate)current).Tags;
				if (tags == null)
				{
					continue;
				}
				for (int i = 0; i < tags.Count; i++)
				{
					string text = tags[i];
					if (!string.IsNullOrEmpty(text))
					{
						hashSet.Add(text);
					}
				}
			}
			MelonLogger.Msg("[SpawnGuard] Tag dump — scanned " + num + " SpawnableCrates, found " + hashSet.Count + " unique tags:");
			foreach (string item in hashSet)
			{
				MelonLogger.Msg("[SpawnGuard]   " + item);
			}
			if (hashSet.Count == 0)
			{
				MelonLogger.Msg("[SpawnGuard] No tags found — warehouse may not be loaded yet.");
			}
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Tag dump failed: " + ex);
		}
	}
}
public static class BoneMenuHandler
{
	private static readonly Color ColorCyan = Color.cyan;

	private static readonly Color ColorRed = Color.red;

	private static readonly Color ColorYellow = Color.yellow;

	private static readonly Color ColorOrange = new Color(1f, 0.5f, 0f, 1f);

	public static void Initialize()
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		Page val = Page.Root.CreatePage("SpawnGuard", ColorCyan, 0, true);
		val.CreateBool("Enabled", ColorCyan, SpawnGuardConfig.Enabled.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.Enabled.Value = value;
			SpawnGuardConfig.Save();
		});
		BuildEnforcementPage(val);
		BuildNotificationsPage(val);
		BuildLoggingPage(val);
		BuildMiscPage(val);
	}

	private static void BuildEnforcementPage(Page parent)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: 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_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b9: 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)
		//IL_022d: Unknown result type (might be due to invalid IL or missing references)
		//IL_026b: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02df: Unknown result type (might be due to invalid IL or missing references)
		//IL_031c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0356: Unknown result type (might be due to invalid IL or missing references)
		Page val = parent.CreatePage("Enforcement (Host Only)", ColorRed, 0, true);
		val.CreateBool("Intercept Unauthorized Spawns", ColorRed, SpawnGuardConfig.PreventOnHost.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.PreventOnHost.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateFunction("Block Barcodes & Pallets", Color.white, (Action)delegate
		{
			BarcodeBrowser.OpenFromBoneMenu();
		});
		val.CreateEnum("Required Spawn Permission", ColorRed, (Enum)(object)SpawnGuardConfig.RequiredPermission, (Action<Enum>)delegate(Enum value)
		{
			SpawnGuardConfig.RequiredPermissionRaw.Value = Convert.ToInt32(value);
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Allow Ammo & Magazines", ColorRed, SpawnGuardConfig.AllowMagazines.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.AllowMagazines.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Enable Spawn Rate Limit", ColorRed, SpawnGuardConfig.EnableRateLimit.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableRateLimit.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateFloat("Spawn Cooldown (s)", ColorRed, SpawnGuardConfig.SpawnCooldown.Value, 0.1f, 0.2f, 3f, (Action<float>)delegate(float value)
		{
			SpawnGuardConfig.SpawnCooldown.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateInt("Spawns Before Rate Limit", ColorRed, SpawnGuardConfig.SpawnBurstCount.Value, 1, 1, 10, (Action<int>)delegate(int value)
		{
			SpawnGuardConfig.SpawnBurstCount.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Rate Limit: Include Host", ColorRed, SpawnGuardConfig.RateLimitAppliesToHost.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.RateLimitAppliesToHost.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Enable Object Limit", ColorRed, SpawnGuardConfig.EnableObjectLimit.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableObjectLimit.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateInt("Object Limit Per Player", ColorRed, SpawnGuardConfig.ObjectLimit.Value, 1, 1, 20, (Action<int>)delegate(int value)
		{
			SpawnGuardConfig.ObjectLimit.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Object Limit: Include Host", ColorRed, SpawnGuardConfig.ObjectLimitAppliesToHost.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.ObjectLimitAppliesToHost.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Enable Vehicle Limit", ColorRed, SpawnGuardConfig.EnableVehicleLimit.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableVehicleLimit.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateInt("Max Vehicles Per Player", ColorRed, SpawnGuardConfig.VehicleLimitCount.Value, 1, 1, 5, (Action<int>)delegate(int value)
		{
			SpawnGuardConfig.VehicleLimitCount.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Enable Explosive Cooldown", ColorRed, SpawnGuardConfig.EnableExplosiveCooldown.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableExplosiveCooldown.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateFloat("Explosive Cooldown (s)", ColorRed, SpawnGuardConfig.ExplosiveCooldownSeconds.Value, 10f, 10f, 180f, (Action<float>)delegate(float value)
		{
			SpawnGuardConfig.ExplosiveCooldownSeconds.Value = value;
			SpawnGuardConfig.Save();
		});
		BuildTagBlacklistPage(val);
		BuildDespawnGuardPage(val);
	}

	private static void BuildTagBlacklistPage(Page parent)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0176: 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_01ea: 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)
		//IL_025e: Unknown result type (might be due to invalid IL or missing references)
		Page val = parent.CreatePage("Tag Blacklist", ColorYellow, 0, true);
		val.CreateBool("Enable Tag Blacklist", ColorYellow, SpawnGuardConfig.EnableTagBlacklist.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableTagBlacklist.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Guns", ColorYellow, SpawnGuardConfig.BlockTag_Gun.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Gun.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Melee", ColorYellow, SpawnGuardConfig.BlockTag_Melee.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Melee.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Bladed", ColorYellow, SpawnGuardConfig.BlockTag_Blade.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Blade.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Blunt", ColorYellow, SpawnGuardConfig.BlockTag_Blunt.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Blunt.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Vehicles", ColorYellow, SpawnGuardConfig.BlockTag_Vehicle.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Vehicle.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: NPCs", ColorYellow, SpawnGuardConfig.BlockTag_NPC.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_NPC.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Gadgets", ColorYellow, SpawnGuardConfig.BlockTag_Gadget.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Gadget.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Props", ColorYellow, SpawnGuardConfig.BlockTag_Prop.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Prop.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Block: Explosives", ColorYellow, SpawnGuardConfig.BlockTag_Explosive.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.BlockTag_Explosive.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Enforce On Host", ColorYellow, SpawnGuardConfig.TagBlacklistEnforceOnHost.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.TagBlacklistEnforceOnHost.Value = value;
			SpawnGuardConfig.Save();
		});
	}

	private static void BuildDespawnGuardPage(Page parent)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		Page val = parent.CreatePage("Despawn Guard", ColorRed, 0, true);
		val.CreateBool("Enable Despawn Guard", ColorRed, SpawnGuardConfig.EnableDespawnGuard.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableDespawnGuard.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Log Despawns", ColorRed, SpawnGuardConfig.LogDespawns.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.LogDespawns.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Protect Host Spawns", ColorRed, SpawnGuardConfig.ProtectHostSpawns.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.ProtectHostSpawns.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateEnum("Required Despawn Permission", ColorRed, (Enum)(object)SpawnGuardConfig.DespawnRequiredPermission, (Action<Enum>)delegate(Enum value)
		{
			SpawnGuardConfig.DespawnRequiredPermissionRaw.Value = Convert.ToInt32(value);
			SpawnGuardConfig.Save();
		});
	}

	private static void BuildNotificationsPage(Page parent)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		Page val = parent.CreatePage("Notifications", ColorYellow, 0, true);
		val.CreateBool("Show Notifications", ColorYellow, SpawnGuardConfig.ShowNotifications.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.ShowNotifications.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateFloat("Notification Cooldown (s)", ColorYellow, SpawnGuardConfig.NotificationCooldown.Value, 0.5f, 0f, 10f, (Action<float>)delegate(float value)
		{
			SpawnGuardConfig.NotificationCooldown.Value = value;
			SpawnGuardConfig.Save();
		});
	}

	private static void BuildLoggingPage(Page parent)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		Page val = parent.CreatePage("Logging", ColorCyan, 0, true);
		val.CreateBool("Log All Spawns", ColorCyan, SpawnGuardConfig.LogAllSpawns.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.LogAllSpawns.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateFunction("Print Last Session Log", ColorCyan, (Action)delegate
		{
			MelonLogger.Msg("[SpawnGuard] --- Last Session Log ---\n" + SpawnLogHistory.GetSavedLog());
		});
	}

	private static void BuildMiscPage(Page parent)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		Page val = parent.CreatePage("Misc", ColorOrange, 0, true);
		val.CreateBool("Debug Mode", ColorOrange, SpawnGuardConfig.LogOnly.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.LogOnly.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Avatar Bypass (KEEP ON)", ColorOrange, SpawnGuardConfig.AllowAvatarsThroughFirewall.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.AllowAvatarsThroughFirewall.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateBool("Player Ping (Confetti)", ColorOrange, SpawnGuardConfig.EnableConfettiPing.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableConfettiPing.Value = value;
			SpawnGuardConfig.Save();
		});
		val.CreateFunction("Dump All Tags To Console", ColorOrange, (Action)delegate
		{
			TagFilter.DumpAllTags();
		});
	}
}
public static class ExplosiveCooldown
{
	private static readonly Dictionary<byte, long> _lastAllowedTicks = new Dictionary<byte, long>();

	private const long TicksPerSecond = 10000000L;

	public static bool TryAllowExplosive(byte playerID, bool isLocalHost)
	{
		if (!SpawnGuardConfig.EnableExplosiveCooldown.Value)
		{
			return true;
		}
		if (isLocalHost && !SpawnGuardConfig.TagBlacklistEnforceOnHost.Value)
		{
			return true;
		}
		long ticks = DateTime.UtcNow.Ticks;
		long num = (long)(SpawnGuardConfig.ExplosiveCooldownSeconds.Value * 10000000f);
		if (_lastAllowedTicks.TryGetValue(playerID, out var value) && ticks - value < num)
		{
			return false;
		}
		_lastAllowedTicks[playerID] = ticks;
		return true;
	}

	public static void OnPlayerLeft(byte playerID)
	{
		_lastAllowedTicks.Remove(playerID);
	}

	public static void ClearAll()
	{
		_lastAllowedTicks.Clear();
	}
}
public static class BarcodeBrowser
{
	private class BarcodeEntry
	{
		public string Title;

		public string Barcode;

		public string Pallet;

		public string PalletBarcode;

		public string Tags;

		public bool FromPallet;
	}

	private static readonly List<BarcodeEntry> AllEntries = new List<BarcodeEntry>();

	private static List<BarcodeEntry> Results = new List<BarcodeEntry>();

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

	private static bool _loaded;

	private static bool _open;

	private static bool _openedByBoneMenu;

	private static string _search = "";

	private static string _previousSearch = "";

	private static float _listScroll;

	private static float _targetScroll;

	private static float _scrollVelocity;

	private static float _listContentHeight;

	private static bool _stylesReady;

	private static GUIStyle _windowStyle;

	private static GUIStyle _titleStyle;

	private static GUIStyle _labelStyle;

	private static GUIStyle _smallStyle;

	private static GUIStyle _redButtonStyle;

	private static GUIStyle _searchStyle;

	private static Texture2D _windowTex;

	private static Texture2D _btnTex;

	private static Texture2D _btnHoverTex;

	private static Texture2D _btnActiveTex;

	private const float CardW = 650f;

	private const float CardBaseH = 70f;

	private const float BtnW = 80f;

	private const float BlockBtnW = 80f;

	private const float BtnH = 28f;

	private const float BtnGap = 6f;

	private const float BtnY = 36f;

	private const float RightPad = 12f;

	private const float CopyX = 558f;

	private const float BlockX = 472f;

	private const float SpawnX = 386f;

	private static MelonPreferences_Category _blockCategory;

	private static MelonPreferences_Entry<string> _blockedBarcodesEntry;

	private static MelonPreferences_Entry<string> _blockedPalletsEntry;

	public static readonly HashSet<string> BlockedBarcodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	public static readonly HashSet<string> BlockedPalletPrefixes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	public static bool IsOpen => _open;

	public static void OpenFromBoneMenu()
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Expected O, but got Unknown
		_open = true;
		if (!_loaded)
		{
			LoadDatabase();
		}
		Notifier.Send(new Notification
		{
			Title = NotificationText.op_Implicit("Barcode Blocker"),
			Message = NotificationText.op_Implicit("The menu has appeared on your desktop. Check your BONELAB window!"),
			Type = (NotificationType)0,
			ShowTitleOnPopup = true,
			PopupLength = 4f
		});
	}

	public static void ToggleFromKeyboard()
	{
		_open = !_open;
		if (_open && !_loaded)
		{
			LoadDatabase();
		}
	}

	public static void OnGUI()
	{
		if (_open)
		{
			BuildStyles();
			if (_search != _previousSearch)
			{
				Filter();
			}
			DrawBrowser();
		}
	}

	public static void InitializePrefs()
	{
		_blockCategory = MelonPreferences.CreateCategory("SpawnGuard_BarcodeBlocker");
		_blockedBarcodesEntry = _blockCategory.CreateEntry<string>("BlockedBarcodes", "", "Blocked Barcodes", "Comma-separated list of barcodes blocked by the in-game browser.", false, false, (ValueValidator)null, (string)null);
		_blockedPalletsEntry = _blockCategory.CreateEntry<string>("BlockedPallets", "", "Blocked Pallets", "Comma-separated list of pallet barcode prefixes blocked by the browser.", false, false, (ValueValidator)null, (string)null);
		LoadFromPrefs();
	}

	private static void LoadFromPrefs()
	{
		BlockedBarcodes.Clear();
		BlockedPalletPrefixes.Clear();
		string text = _blockedBarcodesEntry.Value ?? "";
		string[] array = text.Split(new char[1] { ',' });
		foreach (string text2 in array)
		{
			string text3 = text2.Trim();
			if (!string.IsNullOrEmpty(text3))
			{
				BlockedBarcodes.Add(text3);
			}
		}
		string text4 = _blockedPalletsEntry.Value ?? "";
		string[] array2 = text4.Split(new char[1] { ',' });
		foreach (string text5 in array2)
		{
			string text6 = text5.Trim();
			if (!string.IsNullOrEmpty(text6))
			{
				BlockedPalletPrefixes.Add(text6);
			}
		}
	}

	private static void SaveToPrefs()
	{
		_blockedBarcodesEntry.Value = string.Join(",", BlockedBarcodes);
		_blockedPalletsEntry.Value = string.Join(",", BlockedPalletPrefixes);
		_blockCategory.SaveToFile(false);
	}

	public static bool IsBarcodeBlocked(string barcode)
	{
		if (string.IsNullOrEmpty(barcode))
		{
			return false;
		}
		if (BlockedBarcodes.Contains(barcode))
		{
			return true;
		}
		foreach (string blockedPalletPrefix in BlockedPalletPrefixes)
		{
			if (barcode.StartsWith(blockedPalletPrefix, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}

	private static bool IsEntryBlocked(BarcodeEntry entry)
	{
		if (BlockedBarcodes.Contains(entry.Barcode))
		{
			return true;
		}
		if (!string.IsNullOrEmpty(entry.PalletBarcode))
		{
			foreach (string blockedPalletPrefix in BlockedPalletPrefixes)
			{
				if (entry.PalletBarcode.StartsWith(blockedPalletPrefix, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
		}
		return false;
	}

	private static void ToggleBlock(BarcodeEntry entry)
	{
		if (IsEntryBlocked(entry))
		{
			BlockedBarcodes.Remove(entry.Barcode);
			if (!string.IsNullOrEmpty(entry.PalletBarcode))
			{
				BlockedPalletPrefixes.Remove(entry.PalletBarcode);
			}
		}
		else
		{
			BlockedBarcodes.Add(entry.Barcode);
		}
		SaveToPrefs();
	}

	private static void ToggleBlockPallet(BarcodeEntry entry)
	{
		if (!string.IsNullOrEmpty(entry.PalletBarcode))
		{
			if (BlockedPalletPrefixes.Contains(entry.PalletBarcode))
			{
				BlockedPalletPrefixes.Remove(entry.PalletBarcode);
			}
			else
			{
				BlockedPalletPrefixes.Add(entry.PalletBarcode);
			}
			SaveToPrefs();
		}
	}

	private static void BuildStyles()
	{
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Expected O, but got Unknown
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Expected O, but got Unknown
		//IL_011b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_0135: Unknown result type (might be due to invalid IL or missing references)
		//IL_0143: Expected O, but got Unknown
		//IL_014d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Expected O, but got Unknown
		//IL_0193: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bb: Expected O, but got Unknown
		//IL_0204: Unknown result type (might be due to invalid IL or missing references)
		//IL_0219: Unknown result type (might be due to invalid IL or missing references)
		//IL_022e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Unknown result type (might be due to invalid IL or missing references)
		//IL_0248: Unknown result type (might be due to invalid IL or missing references)
		//IL_0256: Expected O, but got Unknown
		if (!_stylesReady)
		{
			_stylesReady = true;
			Color c = default(Color);
			((Color)(ref c))..ctor(0.1f, 0.02f, 0.02f, 0.98f);
			Color c2 = default(Color);
			((Color)(ref c2))..ctor(0.35f, 0.04f, 0.04f, 1f);
			Color c3 = default(Color);
			((Color)(ref c3))..ctor(0.52f, 0.07f, 0.07f, 1f);
			Color c4 = default(Color);
			((Color)(ref c4))..ctor(0.65f, 0.1f, 0.1f, 1f);
			_windowTex = MakeTex(c);
			_btnTex = MakeTex(c2);
			_btnHoverTex = MakeTex(c3);
			_btnActiveTex = MakeTex(c4);
			_windowStyle = new GUIStyle(GUI.skin.box);
			_windowStyle.normal.background = _windowTex;
			_titleStyle = new GUIStyle(GUI.skin.label)
			{
				fontSize = 20,
				fontStyle = (FontStyle)1
			};
			_titleStyle.normal.textColor = new Color(1f, 0.35f, 0.35f, 1f);
			_labelStyle = new GUIStyle(GUI.skin.label)
			{
				fontSize = 13
			};
			_labelStyle.normal.textColor = Color.white;
			_smallStyle = new GUIStyle(GUI.skin.label)
			{
				fontSize = 11
			};
			_smallStyle.normal.textColor = new Color(1f, 0.65f, 0.65f, 1f);
			_redButtonStyle = new GUIStyle(GUI.skin.button)
			{
				fontSize = 11
			};
			_redButtonStyle.normal.background = _btnTex;
			_redButtonStyle.hover.background = _btnHoverTex;
			_redButtonStyle.active.background = _btnActiveTex;
			_redButtonStyle.normal.textColor = Color.white;
			_redButtonStyle.hover.textColor = Color.white;
			_redButtonStyle.active.textColor = Color.white;
			_searchStyle = new GUIStyle(GUI.skin.textField)
			{
				fontSize = 14
			};
		}
	}

	private static Texture2D MakeTex(Color c)
	{
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Expected O, but got Unknown
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		Texture2D val = new Texture2D(2, 2);
		val.SetPixels(Il2CppStructArray<Color>.op_Implicit((Color[])(object)new Color[4] { c, c, c, c }));
		val.Apply();
		return val;
	}

	private static void DrawBrowser()
	{
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Invalid comparison between Unknown and I4
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: 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_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_015a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: 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_01b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_022e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0262: Unknown result type (might be due to invalid IL or missing references)
		//IL_028b: Unknown result type (might be due to invalid IL or missing references)
		//IL_03df: Unknown result type (might be due to invalid IL or missing references)
		//IL_04fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0526: Unknown result type (might be due to invalid IL or missing references)
		//IL_0493: Unknown result type (might be due to invalid IL or missing references)
		//IL_04d8: Unknown result type (might be due to invalid IL or missing references)
		float num = 720f;
		float num2 = 640f;
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2);
		Rect val2 = default(Rect);
		((Rect)(ref val2))..ctor(((Rect)(ref val)).x + 18f, ((Rect)(ref val)).y + 160f, 650f, 420f);
		if (((Rect)(ref val2)).Contains(Event.current.mousePosition) && (int)Event.current.type == 6)
		{
			_targetScroll += Event.current.delta.y * 32f;
			Event.current.Use();
		}
		GUI.Box(val, "", _windowStyle);
		GUI.BeginGroup(val);
		GUI.Label(new Rect(18f, 10f, 600f, 28f), "╔═ SPAWNGUARD BARCODE BLOCKER ══════════════════╗", _titleStyle);
		GUI.Label(new Rect(18f, 38f, 660f, 18f), "║  Search spawnables. BLOCK blocks a single item. Right-click BLOCK to block entire pallet.", _smallStyle);
		GUI.Label(new Rect(18f, 56f, 600f, 18f), "║  Blocked items cannot be spawned by anyone (host enforcement).", _smallStyle);
		if (GUI.Button(new Rect(670f, 12f, 30f, 25f), "X", _redButtonStyle))
		{
			_open = false;
		}
		GUI.Label(new Rect(18f, 84f, 60f, 25f), "SEARCH", _labelStyle);
		_search = GUI.TextField(new Rect(78f, 81f, 480f, 30f), _search ?? "", _searchStyle);
		if (GUI.Button(new Rect(568f, 81f, 60f, 30f), "CLEAR", _redButtonStyle))
		{
			_search = "";
			GUIUtility.keyboardControl = 0;
		}
		if (GUI.Button(new Rect(635f, 81f, 65f, 30f), "RELOAD", _redButtonStyle))
		{
			LoadDatabase();
		}
		GUI.Label(new Rect(18f, 118f, 660f, 18f), "────────────────────────────────────────────────────────", _smallStyle);
		GUI.Label(new Rect(18f, 132f, 600f, 18f), Results.Count + " matching  /  " + AllEntries.Count + " indexed  |  " + BlockedBarcodes.Count + " barcodes blocked, " + BlockedPalletPrefixes.Count + " pallets blocked", _smallStyle);
		Rect val3 = default(Rect);
		((Rect)(ref val3))..ctor(18f, 155f, 650f, 430f);
		float num3 = (float)Results.Count * 70f;
		_listContentHeight = Mathf.Max(((Rect)(ref val3)).height, num3);
		float num4 = Mathf.Max(0f, _listContentHeight - ((Rect)(ref val3)).height);
		_targetScroll = Mathf.Clamp(_targetScroll, 0f, num4);
		_listScroll = Mathf.SmoothDamp(_listScroll, _targetScroll, ref _scrollVelocity, 0.15f);
		if (Mathf.Abs(_listScroll - _targetScroll) < 0.05f)
		{
			_listScroll = _targetScroll;
			_scrollVelocity = 0f;
		}
		GUI.BeginGroup(val3);
		float num5 = 0f - _listScroll;
		foreach (BarcodeEntry result in Results)
		{
			if (num5 + 70f >= 0f && num5 <= ((Rect)(ref val3)).height)
			{
				DrawEntry(result, num5);
			}
			num5 += 70f;
		}
		GUI.EndGroup();
		if (num4 > 0f)
		{
			float num6 = 675f;
			float num7 = 155f;
			float num8 = 430f;
			GUI.Box(new Rect(num6, num7, 8f, num8), "");
			float num9 = Mathf.Max(35f, num8 * (num8 / _listContentHeight));
			float num10 = num7 + _listScroll / num4 * (num8 - num9);
			GUI.Box(new Rect(num6, num10, 8f, num9), "");
		}
		GUI.Label(new Rect(18f, 592f, 680f, 18f), "╚════════════════════════════════════════════════╝", _smallStyle);
		GUI.Label(new Rect(25f, 610f, 600f, 16f), "MOUSE WHEEL // SCROLL        C // TOGGLE", _smallStyle);
		GUI.EndGroup();
	}

	private static void DrawEntry(BarcodeEntry entry, float y)
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_0135: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_019b: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0220: Unknown result type (might be due to invalid IL or missing references)
		bool flag = IsEntryBlocked(entry);
		GUI.Box(new Rect(0f, y, 650f, 70f), "");
		string text = (entry.FromPallet ? "[Pallet]" : "[Crate]");
		GUI.Label(new Rect(10f, y + 4f, 430f, 18f), text + "  " + (entry.Title ?? "Unnamed"), _labelStyle);
		string text2 = entry.Pallet ?? "";
		if (!string.IsNullOrEmpty(entry.Tags))
		{
			text2 = text2 + ((text2.Length > 0) ? "  |  " : "") + entry.Tags;
		}
		GUI.Label(new Rect(10f, y + 22f, 370f, 16f), text2, _smallStyle);
		GUI.Label(new Rect(10f, y + 42f, 372f, 16f), entry.Barcode ?? "", _smallStyle);
		if (GUI.Button(new Rect(558f, y + 36f, 80f, 28f), "Copy", _redButtonStyle))
		{
			GUIUtility.systemCopyBuffer = entry.Barcode;
		}
		string text3 = (flag ? "UNBLOCK" : "BLOCK");
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(472f, y + 36f, 80f, 28f);
		if ((int)Event.current.type == 0 && ((Rect)(ref val)).Contains(Event.current.mousePosition) && Event.current.button == 1)
		{
			ToggleBlockPallet(entry);
			Event.current.Use();
		}
		if (GUI.Button(val, text3, _redButtonStyle))
		{
			ToggleBlock(entry);
		}
		bool flag2 = IsSpawnAlive(entry.Barcode);
		string text4 = (flag2 ? "Despawn" : "Spawn Local");
		if (GUI.Button(new Rect(386f, y + 36f, 80f, 28f), text4, _redButtonStyle))
		{
			if (flag2)
			{
				DespawnBarcode(entry.Barcode);
			}
			else
			{
				SpawnLocalBarcode(entry.Barcode);
			}
		}
	}

	private static bool IsSpawnAlive(string barcode)
	{
		if (string.IsNullOrEmpty(barcode))
		{
			return false;
		}
		if (!SpawnedObjects.TryGetValue(barcode, out var value))
		{
			return false;
		}
		if ((Object)(object)value == (Object)null)
		{
			SpawnedObjects.Remove(barcode);
			return false;
		}
		return true;
	}

	[MethodImpl(MethodImplOptions.NoInlining)]
	private static void SpawnLocalBarcode(string barcode)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Expected O, but got Unknown
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Expected O, but got Unknown
		//IL_0096: 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)
		if (string.IsNullOrEmpty(barcode) || IsSpawnAlive(barcode))
		{
			return;
		}
		Transform head = Player.Head;
		if ((Object)(object)head == (Object)null)
		{
			return;
		}
		Vector3 val = head.position + head.forward * 1.5f;
		Quaternion val2 = Quaternion.LookRotation(head.forward, Vector3.up);
		try
		{
			Spawnable val3 = new Spawnable
			{
				crateRef = new SpawnableCrateReference(barcode),
				policyData = null
			};
			string cap = barcode;
			LocalAssetSpawner.Register(val3);
			LocalAssetSpawner.Spawn(val3, val, val2, (Action<Poolee>)delegate(Poolee p)
			{
				if ((Object)(object)p != (Object)null)
				{
					SpawnedObjects[cap] = p;
				}
			});
		}
		catch
		{
		}
	}

	private static void DespawnBarcode(string barcode)
	{
		if (!SpawnedObjects.TryGetValue(barcode, out var value))
		{
			return;
		}
		SpawnedObjects.Remove(barcode);
		if (!((Object)(object)value != (Object)null))
		{
			return;
		}
		try
		{
			AssetSpawner.Despawn(value);
		}
		catch
		{
		}
	}

	private static void LoadDatabase()
	{
		_loaded = false;
		AllEntries.Clear();
		Results.Clear();
		_listScroll = (_targetScroll = (_scrollVelocity = 0f));
		LoadRuntimeCrates();
		LoadPalletFiles();
		Results.AddRange(AllEntries);
		SortResults();
		_loaded = true;
		_previousSearch = _search;
	}

	private static void LoadRuntimeCrates()
	{
		try
		{
			AssetWarehouse instance = AssetWarehouse.Instance;
			if (instance == null)
			{
				return;
			}
			List<Crate> crates = instance.GetCrates();
			if (crates == null)
			{
				return;
			}
			for (int i = 0; i < crates.Count; i++)
			{
				try
				{
					Crate val = crates[i];
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					SpawnableCrate val2 = ((Il2CppObjectBase)val).TryCast<SpawnableCrate>();
					if ((Object)(object)val2 == (Object)null || ((Scannable)val2).Barcode == (Barcode)null)
					{
						continue;
					}
					string iD = ((Scannable)val2).Barcode.ID;
					if (string.IsNullOrEmpty(iD))
					{
						continue;
					}
					string title = ((Scannable)val2).Title ?? "Unnamed";
					string pallet = "";
					string palletBarcode = "";
					if ((Object)(object)((Crate)val2).Pallet != (Object)null)
					{
						pallet = ((Scannable)((Crate)val2).Pallet).Title ?? ((Crate)val2).Pallet.Author ?? "";
						int num = iD.LastIndexOf('.');
						if (num > 0)
						{
							int num2 = iD.LastIndexOf('.', num - 1);
							palletBarcode = ((num2 > 0) ? iD.Substring(0, num2) : iD.Substring(0, num));
						}
					}
					AddOrMerge(title, iD, pallet, palletBarcode, "", fromPallet: false);
				}
				catch
				{
				}
			}
		}
		catch
		{
		}
	}

	private static void LoadPalletFiles()
	{
		List<string> list = new List<string>();
		string path = Path.Combine(Application.persistentDataPath, "Mods");
		if (Directory.Exists(path))
		{
			try
			{
				list.AddRange(Directory.EnumerateFiles(path, "*.pallet.json", SearchOption.AllDirectories));
			}
			catch
			{
			}
		}
		foreach (string item in list)
		{
			try
			{
				ParsePalletFile(item);
			}
			catch
			{
			}
		}
	}

	private static void ParsePalletFile(string path)
	{
		string text = File.ReadAllText(path);
		string palletName = Path.GetFileNameWithoutExtension(path).Replace(".pallet", "");
		int num = text.IndexOf("\"objects\"", StringComparison.OrdinalIgnoreCase);
		if (num < 0)
		{
			return;
		}
		int num2 = text.IndexOf('{', num);
		if (num2 >= 0)
		{
			int num3 = FindBrace(text, num2);
			if (num3 >= 0)
			{
				ParseObjects(text.Substring(num2 + 1, num3 - num2 - 1), palletName);
			}
		}
	}

	private static void ParseObjects(string objects, string palletName)
	{
		int num = 0;
		while (num < objects.Length)
		{
			int num2 = NextQuote(objects, num);
			if (num2 < 0)
			{
				break;
			}
			int num3 = QuoteEnd(objects, num2);
			if (num3 < 0)
			{
				break;
			}
			int num4 = objects.IndexOf(':', num3 + 1);
			if (num4 < 0)
			{
				break;
			}
			int num5 = SkipWS(objects, num4 + 1);
			if (num5 >= objects.Length)
			{
				break;
			}
			if (objects[num5] != '{')
			{
				num = num5 + 1;
				continue;
			}
			int num6 = FindBrace(objects, num5);
			if (num6 < 0)
			{
				break;
			}
			string json = objects.Substring(num5, num6 - num5 + 1);
			string str = GetStr(json, "barcode");
			if (!string.IsNullOrEmpty(str))
			{
				string text = GetStr(json, "title");
				if (string.IsNullOrEmpty(text))
				{
					text = "Unnamed";
				}
				string arr = GetArr(json, "tags");
				string palletBarcode = "";
				int num7 = str.IndexOf(".Spawnable.", StringComparison.OrdinalIgnoreCase);
				if (num7 > 0)
				{
					palletBarcode = str.Substring(0, num7);
				}
				AddOrMerge(text, str, palletName, palletBarcode, arr, fromPallet: true);
			}
			num = num6 + 1;
		}
	}

	private static void AddOrMerge(string title, string barcode, string pallet, string palletBarcode, string tags, bool fromPallet)
	{
		if (string.IsNullOrEmpty(barcode))
		{
			return;
		}
		BarcodeEntry barcodeEntry = AllEntries.FirstOrDefault((BarcodeEntry x) => string.Equals(x.Barcode, barcode, StringComparison.OrdinalIgnoreCase));
		if (barcodeEntry == null)
		{
			AllEntries.Add(new BarcodeEntry
			{
				Title = title,
				Barcode = barcode,
				Pallet = pallet,
				PalletBarcode = palletBarcode,
				Tags = tags,
				FromPallet = fromPallet
			});
			return;
		}
		if (!fromPallet)
		{
			barcodeEntry.Title = title;
			barcodeEntry.Pallet = pallet;
			barcodeEntry.FromPallet = false;
		}
		if (string.IsNullOrEmpty(barcodeEntry.Tags))
		{
			barcodeEntry.Tags = tags;
		}
		if (string.IsNullOrEmpty(barcodeEntry.PalletBarcode))
		{
			barcodeEntry.PalletBarcode = palletBarcode;
		}
	}

	private static void Filter()
	{
		string text = (_search ?? "").Trim().ToLowerInvariant();
		Results.Clear();
		foreach (BarcodeEntry allEntry in AllEntries)
		{
			string text2 = (allEntry.Title + " " + allEntry.Barcode + " " + allEntry.Pallet + " " + allEntry.Tags).ToLowerInvariant();
			if (text.Length == 0 || text2.Contains(text))
			{
				Results.Add(allEntry);
			}
		}
		SortResults();
		_listScroll = (_targetScroll = (_scrollVelocity = 0f));
		_previousSearch = _search;
	}

	private static void SortResults()
	{
		Results = Results.OrderBy<BarcodeEntry, string>((BarcodeEntry x) => x.Title ?? "", StringComparer.OrdinalIgnoreCase).ThenBy<BarcodeEntry, string>((BarcodeEntry x) => x.Barcode ?? "", StringComparer.OrdinalIgnoreCase).ToList();
	}

	private static int SkipWS(string s, int i)
	{
		while (i < s.Length && char.IsWhiteSpace(s[i]))
		{
			i++;
		}
		return i;
	}

	private static int NextQuote(string s, int i)
	{
		bool flag = false;
		while (i < s.Length)
		{
			if (s[i] == '\\' && !flag)
			{
				flag = true;
			}
			else
			{
				if (s[i] == '"' && !flag)
				{
					return i;
				}
				flag = false;
			}
			i++;
		}
		return -1;
	}

	private static int QuoteEnd(string s, int i)
	{
		bool flag = false;
		for (int j = i + 1; j < s.Length; j++)
		{
			if (s[j] == '"' && !flag)
			{
				return j;
			}
			flag = s[j] == '\\' && !flag;
		}
		return -1;
	}

	private static int FindBrace(string s, int start)
	{
		int num = 0;
		bool flag = false;
		bool flag2 = false;
		for (int i = start; i < s.Length; i++)
		{
			char c = s[i];
			if (flag)
			{
				if (c == '"' && !flag2)
				{
					flag = false;
				}
				flag2 = c == '\\' && !flag2;
				continue;
			}
			switch (c)
			{
			case '"':
				flag = true;
				continue;
			case '{':
				num++;
				break;
			}
			if (c != '}' || --num != 0)
			{
				continue;
			}
			return i;
		}
		return -1;
	}

	private static string GetStr(string json, string key)
	{
		string text = "\"" + key + "\"";
		int num = json.IndexOf(text, StringComparison.OrdinalIgnoreCase);
		if (num < 0)
		{
			return "";
		}
		int num2 = json.IndexOf(':', num + text.Length);
		if (num2 < 0)
		{
			return "";
		}
		int num3 = NextQuote(json, num2 + 1);
		if (num3 < 0)
		{
			return "";
		}
		int num4 = QuoteEnd(json, num3);
		if (num4 < 0)
		{
			return "";
		}
		return json.Substring(num3 + 1, num4 - num3 - 1).Replace("\\\"", "\"").Replace("\\\\", "\\")
			.Replace("\\n", "\n");
	}

	private static string GetArr(string json, string key)
	{
		string text = "\"" + key + "\"";
		int num = json.IndexOf(text, StringComparison.OrdinalIgnoreCase);
		if (num < 0)
		{
			return "";
		}
		int num2 = json.IndexOf(':', num + text.Length);
		if (num2 < 0)
		{
			return "";
		}
		int num3 = json.IndexOf('[', num2