Decompiled source of SpawnGuard v2.1.0
Mods/SpawnGuard.dll
Decompiled 3 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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