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