Decompiled source of SpawnLogger v2.3.1

Mods/SpawnLogger.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using BoneLib;
using BoneLib.BoneMenu;
using HarmonyLib;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Pool;
using Il2CppSLZ.Marrow.Warehouse;
using LabFusion.Entities;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.Utilities;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using SpawnLogger;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(/*Could not decode attribute arguments.*/)]
[assembly: MelonInfo(typeof(SpawnLoggerMod), "Spawn Logger", "2.3.1", "Local Utility", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : System.Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(/*Could not decode attribute arguments.*/)]
	internal sealed class RefSafetyRulesAttribute : System.Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SpawnLogger
{
	public sealed class SpawnLoggerMod : MelonMod
	{
		private sealed class Identity
		{
			public byte SmallId;

			public ulong PlatformId;

			public string Name;
		}

		private sealed class SpawnSnapshot
		{
			public Identity ClaimedOwner;

			public Identity TransportSender;

			public float CapturedAt;

			public string AlertReason;
		}

		private sealed class PlayerHistory
		{
			public Page Page;

			public readonly List<string> Entries = new List<string>();
		}

		private sealed class FeedItem
		{
			public ulong PlatformId;

			public string Text;
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static PlayerUpdate <0>__OnPlayerJoined;

			public static PlayerUpdate <1>__OnPlayerLeft;

			public static UpdateEvent <2>__OnLevelLoaded;

			public static Action <3>__ClearLogs;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Action<bool> <>9__26_0;

			public static Action<bool> <>9__26_1;

			public static Action <>9__49_0;

			public static Action <>9__49_1;

			public static Action <>9__50_0;

			public static Action <>9__50_1;

			internal void <CreateMenu>b__26_0(bool value)
			{
				SavePreference(_clearOnLeave, value);
			}

			internal void <CreateMenu>b__26_1(bool value)
			{
				SavePreference(_logSlzContent, value);
			}

			internal void <RenderFeed>b__49_0()
			{
			}

			internal void <RenderFeed>b__49_1()
			{
			}

			internal void <RenderEntries>b__50_0()
			{
			}

			internal void <RenderEntries>b__50_1()
			{
			}
		}

		private const int MaxEntries = 30;

		private const float LevelSettleSeconds = 5f;

		private const float SnapshotLifetimeSeconds = 120f;

		private const float CleanupIntervalSeconds = 5f;

		private static readonly Dictionary<ushort, SpawnSnapshot> PendingSpawns = new Dictionary<ushort, SpawnSnapshot>();

		private static readonly HashSet<ushort> SeenEntityIds = new HashSet<ushort>();

		private static readonly Dictionary<ulong, PlayerHistory> PlayerHistories = new Dictionary<ulong, PlayerHistory>();

		private static readonly List<FeedItem> SpawnFeed = new List<FeedItem>();

		private static readonly List<FeedItem> AlertFeed = new List<FeedItem>();

		private static MelonPreferences_Entry<bool> _clearOnLeave;

		private static MelonPreferences_Entry<bool> _logSlzContent;

		private static Page _spawnPage;

		private static Page _playersPage;

		private static Page _alertPage;

		private static bool _menuCreated;

		private static float _loggingReadyAt = 1f / 0f;

		private static float _nextCleanupAt;

		public override void OnInitializeMelon()
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Expected O, but got Unknown
			MelonPreferences_Category val = MelonPreferences.CreateCategory("SpawnLogger");
			_clearOnLeave = val.CreateEntry<bool>("AutoClearOnPlayerLeave", true, "Auto-Clear On Player Leave", "Remove a disconnected player's entries from the in-game log.", false, false, (ValueValidator)null, (string)null);
			_logSlzContent = val.CreateEntry<bool>("LogSlzContent", false, "Log SLZ Content", "Include SLZ-authored and internal-pallet spawns. Magazines are always excluded.", false, false, (ValueValidator)null, (string)null);
			Hooking.OnUIRigCreated += new Action(CreateMenu);
			object obj = <>O.<0>__OnPlayerJoined;
			if (obj == null)
			{
				PlayerUpdate val2 = OnPlayerJoined;
				<>O.<0>__OnPlayerJoined = val2;
				obj = (object)val2;
			}
			MultiplayerHooking.OnPlayerJoined += (PlayerUpdate)obj;
			object obj2 = <>O.<1>__OnPlayerLeft;
			if (obj2 == null)
			{
				PlayerUpdate val3 = OnPlayerLeft;
				<>O.<1>__OnPlayerLeft = val3;
				obj2 = (object)val3;
			}
			MultiplayerHooking.OnPlayerLeft += (PlayerUpdate)obj2;
			object obj3 = <>O.<2>__OnLevelLoaded;
			if (obj3 == null)
			{
				UpdateEvent val4 = OnLevelLoaded;
				<>O.<2>__OnLevelLoaded = val4;
				obj3 = (object)val4;
			}
			MultiplayerHooking.OnTargetLevelLoaded += (UpdateEvent)obj3;
		}

		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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//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_004f: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			Hooking.OnUIRigCreated -= new Action(CreateMenu);
			object obj = <>O.<0>__OnPlayerJoined;
			if (obj == null)
			{
				PlayerUpdate val = OnPlayerJoined;
				<>O.<0>__OnPlayerJoined = val;
				obj = (object)val;
			}
			MultiplayerHooking.OnPlayerJoined -= (PlayerUpdate)obj;
			object obj2 = <>O.<1>__OnPlayerLeft;
			if (obj2 == null)
			{
				PlayerUpdate val2 = OnPlayerLeft;
				<>O.<1>__OnPlayerLeft = val2;
				obj2 = (object)val2;
			}
			MultiplayerHooking.OnPlayerLeft -= (PlayerUpdate)obj2;
			object obj3 = <>O.<2>__OnLevelLoaded;
			if (obj3 == null)
			{
				UpdateEvent val3 = OnLevelLoaded;
				<>O.<2>__OnLevelLoaded = val3;
				obj3 = (object)val3;
			}
			MultiplayerHooking.OnTargetLevelLoaded -= (UpdateEvent)obj3;
			ResetLevelState();
		}

		public override void OnUpdate()
		{
			//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_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextCleanupAt || PendingSpawns.Count == 0)
			{
				return;
			}
			_nextCleanupAt = realtimeSinceStartup + 5f;
			List<ushort> val = new List<ushort>();
			Enumerator<ushort, SpawnSnapshot> enumerator = PendingSpawns.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					KeyValuePair<ushort, SpawnSnapshot> current = enumerator.Current;
					if (realtimeSinceStartup - current.Value.CapturedAt > 120f)
					{
						val.Add(current.Key);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			Enumerator<ushort> enumerator2 = val.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					ushort current2 = enumerator2.Current;
					PendingSpawns.Remove(current2);
				}
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void OnLevelLoaded()
		{
			ResetLevelState();
			_loggingReadyAt = Time.realtimeSinceStartup + 5f;
		}

		private static void ResetLevelState()
		{
			PendingSpawns.Clear();
			SeenEntityIds.Clear();
			_nextCleanupAt = 0f;
		}

		private void CreateMenu()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			if (_menuCreated)
			{
				return;
			}
			Page val = Page.Root.CreatePage("Spawn Logger", Color.yellow, 0, true);
			Color red = Color.red;
			object obj = <>O.<3>__ClearLogs;
			if (obj == null)
			{
				Action val2 = ClearLogs;
				<>O.<3>__ClearLogs = val2;
				obj = (object)val2;
			}
			val.CreateFunction("Clear All Logs", red, (Action)obj);
			val.CreateBool("Auto-Clear On Leave", Color.yellow, _clearOnLeave.Value, (Action<bool>)delegate(bool value)
			{
				SavePreference(_clearOnLeave, value);
			});
			val.CreateBool("Log SLZ Content", Color.yellow, _logSlzContent.Value, (Action<bool>)delegate(bool value)
			{
				SavePreference(_logSlzContent, value);
			});
			_spawnPage = val.CreatePage("All Spawns", Color.yellow, 0, true);
			_playersPage = val.CreatePage("Players", Color.yellow, 0, true);
			_alertPage = val.CreatePage("Suspicious", Color.red, 0, true);
			_menuCreated = true;
			Enumerator<PlayerID> enumerator = PlayerIDManager.PlayerIDs.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					PlayerID current = enumerator.Current;
					RefreshHistory(GetOrCreateHistory(CreateIdentity(current, 0)));
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			RefreshMainPages();
		}

		private static void SavePreference(MelonPreferences_Entry<bool> preference, bool value)
		{
			preference.Value = value;
			MelonPreferences.Save();
		}

		private static void OnPlayerJoined(PlayerID player)
		{
			if (player != null)
			{
				RefreshHistory(GetOrCreateHistory(CreateIdentity(player, 0)));
			}
		}

		private static void OnPlayerLeft(PlayerID player)
		{
			if (player == null)
			{
				return;
			}
			PreserveDepartingIdentity(player);
			if (_clearOnLeave != null && _clearOnLeave.Value)
			{
				RemovePlayerHistory(player.PlatformID);
				SpawnFeed.RemoveAll((Predicate<FeedItem>)((FeedItem item) => item.PlatformId == player.PlatformID));
				AlertFeed.RemoveAll((Predicate<FeedItem>)((FeedItem item) => item.PlatformId == player.PlatformID));
				RefreshMainPages();
			}
		}

		private static void PreserveDepartingIdentity(PlayerID player)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			Identity claimedOwner = CreateIdentity(player, 0);
			Enumerator<ushort, SpawnSnapshot> enumerator = PendingSpawns.Values.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					SpawnSnapshot current = enumerator.Current;
					if (current.ClaimedOwner.SmallId == player.SmallID && current.ClaimedOwner.PlatformId == 0)
					{
						current.ClaimedOwner = claimedOwner;
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void RemovePlayerHistory(ulong platformId)
		{
			PlayerHistory playerHistory = default(PlayerHistory);
			if (!PlayerHistories.TryGetValue(platformId, ref playerHistory))
			{
				return;
			}
			PlayerHistories.Remove(platformId);
			if (playerHistory.Page != null)
			{
				Page playersPage = _playersPage;
				if (playersPage != null)
				{
					playersPage.RemovePage(playerHistory.Page);
				}
			}
		}

		private static Identity CreateIdentity(PlayerID player, byte fallbackSmallId = 0)
		{
			if (player == null)
			{
				return new Identity
				{
					SmallId = fallbackSmallId,
					Name = $"Unknown (ID {fallbackSmallId})"
				};
			}
			string text = default(string);
			return new Identity
			{
				SmallId = player.SmallID,
				PlatformId = player.PlatformID,
				Name = ((MetadataHelper.TryGetDisplayName(player, ref text) && !string.IsNullOrWhiteSpace(text)) ? text : $"Player {player.SmallID}")
			};
		}

		internal static void CaptureResponse(SpawnResponseData data, ReceivedMessage message)
		{
			if (data?.SpawnData != null)
			{
				ulong valueOrDefault = ((ReceivedMessage)(ref message)).PlatformID.GetValueOrDefault();
				Identity identity = ((valueOrDefault == 0L) ? null : CreateIdentity(PlayerIDManager.GetPlayerID(valueOrDefault), 0));
				if (identity != null && identity.PlatformId == 0)
				{
					identity.PlatformId = valueOrDefault;
				}
				PendingSpawns[data.EntityID] = new SpawnSnapshot
				{
					ClaimedOwner = CreateIdentity(PlayerIDManager.GetPlayerID(data.OwnerID), data.OwnerID),
					TransportSender = identity,
					CapturedAt = Time.realtimeSinceStartup,
					AlertReason = BuildAlertReason(data, identity)
				};
			}
		}

		private static string BuildAlertReason(SpawnResponseData data, Identity transportSender)
		{
			PlayerID hostID = PlayerIDManager.GetHostID();
			bool flag = hostID != null && transportSender != null && transportSender.PlatformId != hostID.PlatformID;
			bool flag2 = NetworkEntityManager.IDManager.RegisteredEntities.GetEntity(data.EntityID) != null;
			if (!flag && !flag2)
			{
				return null;
			}
			string text = (flag ? "spawn response came directly from a non-host client" : string.Empty);
			if (flag && transportSender.SmallId != data.OwnerID)
			{
				text += $"; payload claimed owner ID {data.OwnerID}";
			}
			if (flag2)
			{
				text = text + ((text.Length == 0) ? string.Empty : "; ") + "entity ID was already registered";
			}
			return text;
		}

		internal static void CompleteSpawn(SpawnResponseData data, Poolee poolee)
		{
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Invalid comparison between Unknown and I4
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			if (data?.SpawnData == null)
			{
				return;
			}
			SpawnSnapshot spawnSnapshot = default(SpawnSnapshot);
			PendingSpawns.TryGetValue(data.EntityID, ref spawnSnapshot);
			PendingSpawns.Remove(data.EntityID);
			if (Time.realtimeSinceStartup < _loggingReadyAt || !SeenEntityIds.Add(data.EntityID))
			{
				return;
			}
			string barcode = data.SpawnData.Barcode;
			if (string.IsNullOrWhiteSpace(barcode))
			{
				return;
			}
			TryResolveCrate(barcode, out var crate);
			if (!IsMagazine(poolee, crate, barcode) && !ShouldExcludeSlz(crate, barcode))
			{
				spawnSnapshot = spawnSnapshot ?? new SpawnSnapshot
				{
					ClaimedOwner = CreateIdentity(PlayerIDManager.GetPlayerID(data.OwnerID), data.OwnerID)
				};
				string itemName = (string.IsNullOrWhiteSpace((crate != null) ? ((Scannable)crate).Title : null) ? barcode : ((Scannable)crate).Title);
				if (!string.IsNullOrEmpty(spawnSnapshot.AlertReason))
				{
					AddAlert(spawnSnapshot, data.SpawnData.SpawnSource, itemName);
				}
				else if ((int)data.SpawnData.SpawnSource != 1)
				{
					AddNormalSpawn(spawnSnapshot.ClaimedOwner, itemName);
				}
			}
		}

		private static bool TryResolveCrate(string barcode, out Crate crate)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			crate = null;
			AssetWarehouse instance = AssetWarehouse.Instance;
			return instance != null && instance.TryGetCrate(new Barcode(barcode), ref crate) && (Object)(object)crate != (Object)null;
		}

		private static bool ShouldExcludeSlz(Crate crate, string barcode)
		{
			return (_logSlzContent == null || !_logSlzContent.Value) && IsSlzContent(crate, barcode);
		}

		private static bool IsSlzContent(Crate crate, string barcode)
		{
			if (barcode.StartsWith("SLZ.", (StringComparison)5))
			{
				return true;
			}
			Pallet val = ((crate != null) ? crate.Pallet : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			if (val.Internal)
			{
				return true;
			}
			string text = val.Author?.Trim();
			return string.Equals(text, "SLZ", (StringComparison)5) || string.Equals(text, "Stress Level Zero", (StringComparison)5) || string.Equals(text, "StressLevelZero", (StringComparison)5);
		}

		private static bool IsMagazine(Poolee poolee, Crate crate, string barcode)
		{
			if ((Object)(object)poolee != (Object)null && (Object)(object)((Component)poolee).gameObject.GetComponentInChildren<Magazine>(true) != (Object)null)
			{
				return true;
			}
			string text = ((crate != null) ? ((Scannable)crate).Title : null) ?? string.Empty;
			return text.IndexOf("magazine", (StringComparison)5) >= 0 || text.IndexOf(" ammo mag", (StringComparison)5) >= 0 || barcode.IndexOf(".Magazine.", (StringComparison)5) >= 0 || barcode.EndsWith(".Magazine", (StringComparison)5);
		}

		private static void AddNormalSpawn(Identity owner, string itemName)
		{
			string text = $"[{System.DateTime.Now:HH:mm:ss}] {owner.Name}: {itemName}";
			AddToFeed(SpawnFeed, owner.PlatformId, text);
			PlayerHistory orCreateHistory = GetOrCreateHistory(owner);
			InsertNewest(orCreateHistory.Entries, text);
			RefreshHistory(orCreateHistory);
			RefreshSpawnPage();
			MelonLogger.Msg(text);
		}

		private static void AddAlert(SpawnSnapshot snapshot, EntitySource source, string itemName)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			Identity transportSender = snapshot.TransportSender;
			string text = transportSender?.Name ?? "Unresolved sender";
			string text2 = $"[{System.DateTime.Now:HH:mm:ss}] ALERT: {text} -> {itemName} (claimed {snapshot.ClaimedOwner.Name}; source {source}; {snapshot.AlertReason})";
			AddToFeed(AlertFeed, transportSender?.PlatformId ?? 0, text2);
			RefreshAlertPage();
			MelonLogger.Warning(text2);
		}

		private static void AddToFeed(List<FeedItem> feed, ulong platformId, string text)
		{
			feed.Insert(0, new FeedItem
			{
				PlatformId = platformId,
				Text = text
			});
			Trim<FeedItem>(feed);
		}

		private static PlayerHistory GetOrCreateHistory(Identity identity)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			ulong num = ((identity.PlatformId != 0L) ? identity.PlatformId : (18446744073709551615uL - (ulong)identity.SmallId));
			PlayerHistory playerHistory = default(PlayerHistory);
			if (!PlayerHistories.TryGetValue(num, ref playerHistory))
			{
				playerHistory = new PlayerHistory();
				PlayerHistories.Add(num, playerHistory);
			}
			if (playerHistory.Page == null && _playersPage != null)
			{
				playerHistory.Page = _playersPage.CreatePage(identity.Name, Color.white, 0, true);
			}
			if (playerHistory.Page != null)
			{
				playerHistory.Page.Name = identity.Name;
			}
			return playerHistory;
		}

		private static void RefreshHistory(PlayerHistory history)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			RenderEntries(history.Page, history.Entries, "No spawns logged yet", Color.white);
		}

		private static void ClearLogs()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			SpawnFeed.Clear();
			AlertFeed.Clear();
			Enumerator<ulong, PlayerHistory> enumerator = PlayerHistories.Values.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					PlayerHistory current = enumerator.Current;
					current.Entries.Clear();
					RefreshHistory(current);
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			RefreshMainPages();
		}

		private static void RefreshMainPages()
		{
			RefreshSpawnPage();
			RefreshAlertPage();
		}

		private static void RefreshSpawnPage()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			RenderFeed(_spawnPage, SpawnFeed, "No spawns logged yet", Color.white);
		}

		private static void RefreshAlertPage()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			RenderFeed(_alertPage, AlertFeed, "No suspicious spawns detected", Color.red);
		}

		private static void RenderFeed(Page page, List<FeedItem> feed, string emptyText, Color color)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			if (page == null)
			{
				return;
			}
			page.RemoveAll();
			if (feed.Count == 0)
			{
				Color gray = Color.gray;
				object obj = <>c.<>9__49_0;
				if (obj == null)
				{
					Action val = delegate
					{
					};
					<>c.<>9__49_0 = val;
					obj = (object)val;
				}
				page.CreateFunction(emptyText, gray, (Action)obj);
				return;
			}
			Enumerator<FeedItem> enumerator = feed.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					FeedItem current = enumerator.Current;
					string text = current.Text;
					object obj2 = <>c.<>9__49_1;
					if (obj2 == null)
					{
						Action val2 = delegate
						{
						};
						<>c.<>9__49_1 = val2;
						obj2 = (object)val2;
					}
					page.CreateFunction(text, color, (Action)obj2);
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void RenderEntries(Page page, List<string> entries, string emptyText, Color color)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			if (page == null)
			{
				return;
			}
			page.RemoveAll();
			if (entries.Count == 0)
			{
				Color gray = Color.gray;
				object obj = <>c.<>9__50_0;
				if (obj == null)
				{
					Action val = delegate
					{
					};
					<>c.<>9__50_0 = val;
					obj = (object)val;
				}
				page.CreateFunction(emptyText, gray, (Action)obj);
				return;
			}
			Enumerator<string> enumerator = entries.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					string current = enumerator.Current;
					object obj2 = <>c.<>9__50_1;
					if (obj2 == null)
					{
						Action val2 = delegate
						{
						};
						<>c.<>9__50_1 = val2;
						obj2 = (object)val2;
					}
					page.CreateFunction(current, color, (Action)obj2);
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void InsertNewest<T>(List<T> entries, T item)
		{
			entries.Insert(0, item);
			Trim<T>(entries);
		}

		private static void Trim<T>(List<T> entries)
		{
			if (entries.Count > 30)
			{
				entries.RemoveRange(30, entries.Count - 30);
			}
		}
	}
	[HarmonyPatch(typeof(SpawnResponseMessage), "OnHandleMessage")]
	internal static class SpawnResponseReceivedPatch
	{
		private static void Prefix(ReceivedMessage received)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				SpawnLoggerMod.CaptureResponse(((ReceivedMessage)(ref received)).ReadData<SpawnResponseData>(), received);
			}
			catch (System.Exception ex)
			{
				MelonLogger.Warning("Spawn Logger could not inspect a response: " + ex.Message);
			}
		}
	}
	[HarmonyPatch(typeof(SpawnResponseMessage), "OnSpawnFinished")]
	internal static class SpawnFinishedPatch
	{
		private static void Postfix(SpawnResponseData data, Poolee poolee)
		{
			try
			{
				SpawnLoggerMod.CompleteSpawn(data, poolee);
			}
			catch (System.Exception ex)
			{
				MelonLogger.Error($"Spawn Logger failed to inspect a spawn: {ex}");
			}
		}
	}
}