Decompiled source of SpawnGuard v1.0.0

Mods/SpawnGuard.dll

Decompiled 2 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using HarmonyLib;
using Il2CppSLZ.Marrow.Pool;
using LabFusion.Marrow.Serialization;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.Representation;
using MelonLoader;
using MelonLoader.Preferences;
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: MelonInfo(typeof(Main), "SpawnGuard", "1.0.0", "YourName", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[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: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SpawnGuard;

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<int> RequiredPermissionRaw;

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

	public static void Initialize()
	{
		_category = MelonPreferences.CreateCategory("SpawnGuard");
		Enabled = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Master toggle. When off, SpawnGuard does nothing.", false, false, (ValueValidator)null, (string)null);
		LogOnly = _category.CreateEntry<bool>("LogOnly", false, "Log Only (Debug)", "Logs what WOULD be blocked instead of actually blocking.", false, false, (ValueValidator)null, (string)null);
		ShowNotifications = _category.CreateEntry<bool>("ShowNotifications", true, "Show Notifications", "Show an in-headset warning whenever a spawn is blocked.", false, false, (ValueValidator)null, (string)null);
		LogAllSpawns = _category.CreateEntry<bool>("LogAllSpawns", false, "Log All Spawns", "Log every spawn request regardless of outcome.", false, false, (ValueValidator)null, (string)null);
		PreventOnHost = _category.CreateEntry<bool>("PreventOnHost", true, "Prevent At Source (Host Only)", "Blocks a disallowed spawn before SpawnResponseData is broadcast. Only active when you are the host.", false, false, (ValueValidator)null, (string)null);
		AllowAvatarsThroughFirewall = _category.CreateEntry<bool>("AllowAvatarsThroughFirewall", true, "Allow Avatars (Temporary Bypass)", "TEMPORARY: 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 how many spawns a player can make within the cooldown window.", false, false, (ValueValidator)null, (string)null);
		SpawnCooldown = _category.CreateEntry<float>("SpawnCooldown", 0.5f, "Spawn Cooldown (s)", "Length of the rate-limit window in seconds. Range 0.2-3.0.", false, false, (ValueValidator)null, (string)null);
		SpawnBurstCount = _category.CreateEntry<int>("SpawnBurstCount", 1, "Spawns Before Rate Limit", "How many spawns are allowed within one cooldown window before throttling kicks in. 1 = one spawn per window, 2 = two spawns before the third is blocked, etc.", false, false, (ValueValidator)null, (string)null);
		RateLimitAppliesToHost = _category.CreateEntry<bool>("RateLimitAppliesToHost", false, "Rate Limit: Include Host", "When off (default), the host is exempt from the spawn cooldown. Enable if host PlayerID spoofing is a concern.", false, false, (ValueValidator)null, (string)null);
		RequiredPermissionRaw = _category.CreateEntry<int>("RequiredPermission", 0, "Required Permission Level", "Minimum permission to spawn without being blocked. -1=GUEST, 0=DEFAULT, 1=OPERATOR, 2=OWNER.", false, false, (ValueValidator)null, (string)null);
		SpawnLogHistory.Initialize(_category);
	}

	public static void Save()
	{
		_category.SaveToFile(false);
	}
}
public static class BoneMenuHandler
{
	private static Page mainPage;

	private static readonly Color MenuColor = Color.white;

	public static void Initialize()
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		mainPage = Page.Root.CreatePage("SpawnGuard", MenuColor, 0, true);
		CreateElements();
	}

	private static void CreateElements()
	{
		//IL_000b: 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_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Unknown result type (might be due to invalid IL or missing references)
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_021e: 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_02ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_031f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0324: Unknown result type (might be due to invalid IL or missing references)
		mainPage.CreateBool("Enabled", MenuColor, SpawnGuardConfig.Enabled.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.Enabled.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateBool("Log Only (Debug)", Color.yellow, SpawnGuardConfig.LogOnly.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.LogOnly.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateBool("Show Notifications", MenuColor, SpawnGuardConfig.ShowNotifications.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.ShowNotifications.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateBool("Log All Spawns", Color.green, SpawnGuardConfig.LogAllSpawns.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.LogAllSpawns.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateBool("Prevent At Source (Host Only)", new Color(1f, 0.5f, 0f, 1f), SpawnGuardConfig.PreventOnHost.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.PreventOnHost.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateFloat("Notification Cooldown (s)", Color.white, SpawnGuardConfig.NotificationCooldown.Value, 0.5f, 0f, 10f, (Action<float>)delegate(float value)
		{
			SpawnGuardConfig.NotificationCooldown.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateBool("Allow Avatars (Temporary Bypass)", Color.red, SpawnGuardConfig.AllowAvatarsThroughFirewall.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.AllowAvatarsThroughFirewall.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateBool("Enable Spawn Rate Limit", Color.cyan, SpawnGuardConfig.EnableRateLimit.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.EnableRateLimit.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateFloat("Spawn Cooldown (s)", Color.cyan, SpawnGuardConfig.SpawnCooldown.Value, 0.1f, 0.2f, 3f, (Action<float>)delegate(float value)
		{
			SpawnGuardConfig.SpawnCooldown.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateInt("Spawns Before Rate Limit", Color.cyan, SpawnGuardConfig.SpawnBurstCount.Value, 1, 1, 10, (Action<int>)delegate(int value)
		{
			SpawnGuardConfig.SpawnBurstCount.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateBool("Rate Limit: Include Host", Color.magenta, SpawnGuardConfig.RateLimitAppliesToHost.Value, (Action<bool>)delegate(bool value)
		{
			SpawnGuardConfig.RateLimitAppliesToHost.Value = value;
			SpawnGuardConfig.Save();
		});
		mainPage.CreateFunction("Print Last Session Log", Color.magenta, (Action)delegate
		{
			MelonLogger.Msg("[SpawnGuard] --- Last Session Log ---\n" + SpawnLogHistory.GetSavedLog());
		});
		mainPage.CreateEnum("Required Permission", Color.cyan, (Enum)(object)SpawnGuardConfig.RequiredPermission, (Action<Enum>)delegate(Enum value)
		{
			SpawnGuardConfig.RequiredPermissionRaw.Value = Convert.ToInt32(value);
			SpawnGuardConfig.Save();
		});
	}
}
[HarmonyPatch(typeof(SpawnResponseMessage), "OnSpawnFinished")]
public static class SpawnGuard_OnSpawnFinishedPatch
{
	private static readonly Dictionary<ushort, float> _recentlyHandled = new Dictionary<ushort, float>();

	private const float DedupeWindowSeconds = 1f;

	private const int DedupeMapPruneThreshold = 2000;

	[HarmonyPostfix]
	public static void Postfix(SpawnResponseData data, Poolee poolee)
	{
		if (!SpawnGuardConfig.Enabled.Value || !SpawnGuardConfig.LogAllSpawns.Value || NetworkInfo.IsHost)
		{
			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 (_recentlyHandled.TryGetValue(data.EntityID, out var value) && time - value < 1f)
			{
				return;
			}
			_recentlyHandled[data.EntityID] = time;
			if (_recentlyHandled.Count > 2000)
			{
				List<ushort> list = new List<ushort>();
				foreach (KeyValuePair<ushort, float> item in _recentlyHandled)
				{
					if (time - item.Value > 1f)
					{
						list.Add(item.Key);
					}
				}
				for (int i = 0; i < list.Count; i++)
				{
					_recentlyHandled.Remove(list[i]);
				}
			}
			byte ownerID = data.OwnerID;
			PlayerID playerID = PlayerIDManager.GetPlayerID(ownerID);
			string text3 = default(string);
			string text2 = ((!PlayerID.IsNullOrInvalid(playerID)) ? (MetadataHelper.TryGetDisplayName(playerID, ref text3) ? text3 : ("Player " + ownerID)) : ("Unknown (ID " + ownerID + ")"));
			string line = "[SpawnGuard] " + text2 + " spawned " + text;
			SpawnLogAggregator.Enqueue(text2, text, line);
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Error in spawn observer postfix: " + ex);
		}
	}
}
public class Main : MelonMod
{
	private Harmony _harmony;

	public override void OnInitializeMelon()
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		SpawnGuardConfig.Initialize();
		_harmony = new Harmony("com.yourname.spawnguard");
		_harmony.PatchAll(typeof(Main).Assembly);
		MelonLogger.Msg("[SpawnGuard] Initialized.");
	}

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

	public override void OnUpdate()
	{
		SpawnLogAggregator.Tick();
	}

	public override void OnDeinitializeMelon()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}

	public override void OnApplicationQuit()
	{
		SpawnLogHistory.Flush();
	}
}
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
	{
		public string PlayerName;

		public string Barcode;

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

		public override bool Equals(object obj)
		{
			if (!(obj is AggregateKey aggregateKey))
			{
				return false;
			}
			return PlayerName == aggregateKey.PlayerName && Barcode == aggregateKey.Barcode;
		}

		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 FirstLine;

		public int Count;
	}

	private const int MassSpawnThreshold = 50;

	private const double FlushWindowSeconds = 0.1;

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

	private static double _windowStartUtc = 0.0;

	private static bool _windowOpen = false;

	public static void Enqueue(string playerName, string barcode, string line)
	{
		double windowStartUtc = UtcSeconds();
		AggregateKey key = new AggregateKey(playerName, barcode);
		if (_pending.TryGetValue(key, out var value))
		{
			value.Count++;
			_pending[key] = value;
		}
		else
		{
			_pending[key] = new AggregateEntry
			{
				FirstLine = line,
				Count = 1
			};
		}
		_windowStartUtc = windowStartUtc;
		_windowOpen = true;
	}

	public static void Tick()
	{
		if (_windowOpen && _pending.Count != 0)
		{
			double num = UtcSeconds() - _windowStartUtc;
			if (!(num < 0.1))
			{
				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 '" + item.Key.Barcode + "' spawned by " + item.Key.PlayerName + ".") : ("[SpawnGuard] MASS SPAWN ATTEMPT: " + value.Count + " spawn attempts of '" + item.Key.Barcode + "' from " + item.Key.PlayerName + " in 0.1s window.")) : value.FirstLine);
			MelonLogger.Msg(ConsoleColor.Yellow, text);
			SpawnLogHistory.Append(text);
		}
		_pending.Clear();
		_windowOpen = false;
	}

	private static double UtcSeconds()
	{
		return (double)DateTime.UtcNow.Ticks / 10000000.0;
	}
}
public static class SpawnRateLimiter
{
	private struct PlayerState
	{
		public long WindowStartTicks;

		public int SpawnsInWindow;
	}

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

	private const long TicksPerSecond = 10000000L;

	private const long IdleEvictTicks = 3000000000L;

	private const int PruneThreshold = 512;

	public static bool TryAllowSpawn(byte playerID, bool isLocalHost)
	{
		if (!SpawnGuardConfig.EnableRateLimit.Value)
		{
			return true;
		}
		if (isLocalHost && !SpawnGuardConfig.RateLimitAppliesToHost.Value)
		{
			return true;
		}
		long ticks = DateTime.UtcNow.Ticks;
		long num = (long)(SpawnGuardConfig.SpawnCooldown.Value * 10000000f);
		int value = SpawnGuardConfig.SpawnBurstCount.Value;
		if (!_states.TryGetValue(playerID, out var value2))
		{
			_states[playerID] = new PlayerState
			{
				WindowStartTicks = ticks,
				SpawnsInWindow = 1
			};
			MaybePrune(ticks);
			return true;
		}
		long num2 = ticks - value2.WindowStartTicks;
		if (num2 >= num)
		{
			_states[playerID] = new PlayerState
			{
				WindowStartTicks = ticks,
				SpawnsInWindow = 1
			};
			return true;
		}
		if (value2.SpawnsInWindow < value)
		{
			value2.SpawnsInWindow++;
			_states[playerID] = value2;
			return true;
		}
		_states[playerID] = new PlayerState
		{
			WindowStartTicks = ticks,
			SpawnsInWindow = 0
		};
		return false;
	}

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

	public static void ClearAll()
	{
		_states.Clear();
	}

	private static void MaybePrune(long nowTicks)
	{
		if (_states.Count <= 512)
		{
			return;
		}
		List<byte> list = new List<byte>();
		foreach (KeyValuePair<byte, PlayerState> state in _states)
		{
			if (nowTicks - state.Value.WindowStartTicks > 3000000000u)
			{
				list.Add(state.Key);
			}
		}
		for (int i = 0; i < list.Count; i++)
		{
			_states.Remove(list[i]);
		}
	}
}
public static class SpawnNotifier
{
	private static float _lastShownTime = -999f;

	private static int _suppressedCount = 0;

	public static void Warn(string title, string message)
	{
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: 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_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Expected O, but got Unknown
		if (!SpawnGuardConfig.ShowNotifications.Value)
		{
			return;
		}
		try
		{
			float time = Time.time;
			float value = SpawnGuardConfig.NotificationCooldown.Value;
			if (time - _lastShownTime < value)
			{
				_suppressedCount++;
				return;
			}
			string text = message;
			if (_suppressedCount > 0)
			{
				text = text + " (+" + _suppressedCount + " more)";
			}
			Notifier.Send(new Notification
			{
				ShowTitleOnPopup = true,
				Title = NotificationText.op_Implicit(title),
				Message = NotificationText.op_Implicit(text),
				PopupLength = 4f,
				Type = (NotificationType)1
			});
			_lastShownTime = time;
			_suppressedCount = 0;
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Notification failed: " + ex);
		}
	}
}
public static class SpawnLogHistory
{
	private const int MaxEntries = 5000;

	private static MelonPreferences_Category _category;

	private static MelonPreferences_Entry<string> _savedLog;

	private static readonly List<string> _entries = new List<string>();

	public static void Initialize(MelonPreferences_Category category)
	{
		_category = category;
		_savedLog = _category.CreateEntry<string>("LastSessionLog", "", "Last Session Log", "The full SpawnGuard event log from the most recent game session, kept across scene changes and updated continuously so it survives even a crash. Overwritten starting with the first event of a new session.", false, false, (ValueValidator)null, (string)null);
		_entries.Clear();
	}

	public static void Append(string line)
	{
		try
		{
			float time = Time.time;
			int num = (int)(time / 60f);
			int num2 = (int)time % 60;
			string item = "[" + num.ToString("D2") + ":" + num2.ToString("D2") + "] " + line;
			_entries.Add(item);
			if (_entries.Count > 5000)
			{
				_entries.RemoveAt(0);
			}
			Flush();
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Failed to append to session log: " + ex);
		}
	}

	public static void Flush()
	{
		if (_savedLog == null || _category == null)
		{
			return;
		}
		try
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < _entries.Count; i++)
			{
				stringBuilder.Append(_entries[i]);
				stringBuilder.Append('\n');
			}
			_savedLog.Value = stringBuilder.ToString();
			_category.SaveToFile(false);
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Failed to flush session log: " + ex);
		}
	}

	public static string GetSavedLog()
	{
		return (_savedLog != null) ? _savedLog.Value : "";
	}
}
[HarmonyPatch(typeof(SpawnRequestMessage), "OnHandleMessage")]
public static class SpawnGuard_OnSpawnRequestPatch
{
	[HarmonyPrefix]
	public static bool Prefix(ReceivedMessage received)
	{
		//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bf: 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)";
			try
			{
				SerializedSpawnData val = ((ReceivedMessage)(ref received)).ReadData<SerializedSpawnData>();
				if (val != null)
				{
					text3 = val.Barcode;
				}
			}
			catch
			{
			}
			if (AvatarBypass.ShouldBypass(text3))
			{
				return true;
			}
			if (!SpawnRateLimiter.TryAllowSpawn(value, isLocalHost))
			{
				string text4 = "[SpawnGuard] Rate-limited spawn of '" + text3 + "' from " + text + " - cooldown not elapsed, timer reset.";
				if (SpawnGuardConfig.LogOnly.Value)
				{
					string line = "(log-only) " + text4;
					SpawnLogAggregator.Enqueue(text, text3, line);
					return true;
				}
				SpawnLogAggregator.Enqueue(text, text3, text4);
				SpawnNotifier.Warn("SpawnGuard", "Rate-limited '" + text3 + "' from " + text);
				return false;
			}
			bool flag;
			if (PlayerID.IsNullOrInvalid(playerID))
			{
				flag = false;
			}
			else
			{
				PermissionLevel val2 = default(PermissionLevel);
				Color val3 = default(Color);
				FusionPermissions.FetchPermissionLevel(playerID.PlatformID, ref val2, ref val3);
				flag = FusionPermissions.HasSufficientPermissions(val2, SpawnGuardConfig.RequiredPermission);
			}
			if (SpawnGuardConfig.LogAllSpawns.Value && flag)
			{
				string line2 = "[SpawnGuard] " + text + " spawned " + text3;
				SpawnLogAggregator.Enqueue(text, text3, line2);
				return true;
			}
			if (flag)
			{
				return true;
			}
			if (SpawnGuardConfig.LogOnly.Value)
			{
				string line3 = "[SpawnGuard] (log-only) Would block spawn of '" + text3 + "' from " + text + " - no permission.";
				SpawnLogAggregator.Enqueue(text, text3, line3);
				return true;
			}
			string line4 = "[SpawnGuard] Blocked spawn of '" + text3 + "' from " + text + " - no permission. Never broadcast.";
			SpawnLogAggregator.Enqueue(text, text3, line4);
			SpawnNotifier.Warn("SpawnGuard", "Blocked '" + text3 + "' from " + text + " (no permission)");
			return false;
		}
		catch (Exception ex)
		{
			MelonLogger.Error("[SpawnGuard] Error in host-prevention prefix, failing open: " + ex);
			return true;
		}
	}
}