Decompiled source of SpawnLogger v2.5.0

Mods/SpawnLogger.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
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.5.0", "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 string PlatformId;

			public string Name;

			public bool Departed;
		}

		private sealed class SpawnSnapshot
		{
			public Identity ClaimedOwner;

			public Identity TransportSender;

			public float CapturedAt;

			public string AlertReason;

			public string Barcode;

			public EntitySource Source;

			public bool CrateMissing;
		}

		private sealed class PlayerHistory
		{
			public Page Page;

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

		private sealed class FeedItem
		{
			public string PlatformId;

			public string Owner;

			public string Item;

			public string Time;

			public int Count = 1;

			public float LastAt;

			public string Text => (Count > 1) ? $"[{Time}] {Owner}: {Item} x{Count}" : $"[{Time}] {Owner}: {Item}";
		}

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

			public static Action <4>__RefreshCountersPage;

			public static Action <5>__LogCounters;
		}

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

			public static Action<bool> <>9__36_0;

			public static Action<bool> <>9__36_1;

			public static Action <>9__37_0;

			public static Action <>9__37_1;

			public static Action <>9__61_0;

			public static Action <>9__61_1;

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

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

			internal void <RefreshCountersPage>b__37_0()
			{
			}

			internal void <RefreshCountersPage>b__37_1()
			{
			}

			internal void <RenderItems>b__61_0()
			{
			}

			internal void <RenderItems>b__61_1()
			{
			}
		}

		private const int MaxEntries = 30;

		private const float LevelSettleSeconds = 5f;

		private const float SnapshotLifetimeSeconds = 300f;

		private const float CleanupIntervalSeconds = 5f;

		private const float BurstWindowSeconds = 4f;

		private const float RenderIntervalSeconds = 0.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<string, PlayerHistory> PlayerHistories = new Dictionary<string, PlayerHistory>();

		private static readonly Dictionary<byte, Identity> KnownIdentities = new Dictionary<byte, Identity>();

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

		private static readonly HashSet<PlayerHistory> DirtyHistories = new HashSet<PlayerHistory>();

		private static readonly SortedDictionary<string, int> Counters = new SortedDictionary<string, int>((IComparer<string>)(object)StringComparer.Ordinal);

		private static MelonPreferences_Entry<bool> _clearOnLeave;

		private static MelonPreferences_Entry<bool> _logSlzContent;

		private static Page _spawnPage;

		private static Page _playersPage;

		private static Page _countersPage;

		private static bool _menuCreated;

		private static bool _spawnPageDirty;

		private static float _loggingReadyAt = 1f / 0f;

		private static float _nextCleanupAt;

		private static float _nextRenderAt;

		public override void OnInitializeMelon()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_0078: 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)
			//IL_0083: Expected O, but got Unknown
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: 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);
			EosPeerCapture.TryApply(((MelonBase)this).HarmonyInstance);
			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;
			LogCounters();
			ResetLevelState();
			KnownIdentities.Clear();
			DirtyHistories.Clear();
		}

		public override void OnUpdate()
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			FlushPages(realtimeSinceStartup);
			if (realtimeSinceStartup < _nextCleanupAt || PendingSpawns.Count == 0)
			{
				return;
			}
			_nextCleanupAt = realtimeSinceStartup + 5f;
			List<ushort> val = null;
			Enumerator<ushort, SpawnSnapshot> enumerator = PendingSpawns.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					KeyValuePair<ushort, SpawnSnapshot> current = enumerator.Current;
					if (realtimeSinceStartup - current.Value.CapturedAt > 300f)
					{
						(val ?? (val = new List<ushort>())).Add(current.Key);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			if (val == null)
			{
				return;
			}
			Enumerator<ushort> enumerator2 = val.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					ushort current2 = enumerator2.Current;
					SpawnSnapshot snapshot = PendingSpawns[current2];
					PendingSpawns.Remove(current2);
					try
					{
						ReportIncomplete(current2, snapshot);
					}
					catch (System.Exception ex)
					{
						MelonLogger.Warning("Spawn Logger could not report an unfinished spawn: " + ex.Message);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
		}

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

		private static void Count(string key)
		{
			int num = default(int);
			Counters.TryGetValue(key, ref num);
			Counters[key] = num + 1;
		}

		private static void LogCounters()
		{
			//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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (Counters.Count == 0)
			{
				return;
			}
			List<string> val = new List<string>();
			Enumerator<string, int> enumerator = Counters.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					KeyValuePair<string, int> current = enumerator.Current;
					val.Add($"{current.Key}={current.Value}");
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			MelonLogger.Msg("Counters: " + string.Join("; ", (System.Collections.Generic.IEnumerable<string>)val));
		}

		private static void FlushPages(float now)
		{
			//IL_003b: 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)
			if (now < _nextRenderAt || (!_spawnPageDirty && DirtyHistories.Count == 0))
			{
				return;
			}
			_nextRenderAt = now + 0.5f;
			Enumerator<PlayerHistory> enumerator = DirtyHistories.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					PlayerHistory current = enumerator.Current;
					RefreshHistory(current);
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			DirtyHistories.Clear();
			if (_spawnPageDirty)
			{
				_spawnPageDirty = false;
				RefreshSpawnPage();
			}
		}

		private static void MarkDirty(PlayerHistory history)
		{
			DirtyHistories.Add(history);
			_spawnPageDirty = true;
		}

		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);
			_countersPage = val.CreatePage("Debug Counters", Color.gray, 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();
			}
			RefreshSpawnPage();
			RefreshCountersPage();
		}

		private static void RefreshCountersPage()
		{
			//IL_0027: 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_003c: 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_0047: 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_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: 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_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: 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_0142: Expected O, but got Unknown
			if (_countersPage == null)
			{
				return;
			}
			_countersPage.RemoveAll();
			Page countersPage = _countersPage;
			Color yellow = Color.yellow;
			object obj = <>O.<4>__RefreshCountersPage;
			if (obj == null)
			{
				Action val = RefreshCountersPage;
				<>O.<4>__RefreshCountersPage = val;
				obj = (object)val;
			}
			countersPage.CreateFunction("Refresh", yellow, (Action)obj);
			Page countersPage2 = _countersPage;
			Color yellow2 = Color.yellow;
			object obj2 = <>O.<5>__LogCounters;
			if (obj2 == null)
			{
				Action val2 = LogCounters;
				<>O.<5>__LogCounters = val2;
				obj2 = (object)val2;
			}
			countersPage2.CreateFunction("Write To Log", yellow2, (Action)obj2);
			if (Counters.Count == 0)
			{
				Page countersPage3 = _countersPage;
				Color gray = Color.gray;
				object obj3 = <>c.<>9__37_0;
				if (obj3 == null)
				{
					Action val3 = delegate
					{
					};
					<>c.<>9__37_0 = val3;
					obj3 = (object)val3;
				}
				countersPage3.CreateFunction("Nothing counted yet", gray, (Action)obj3);
				return;
			}
			Enumerator<string, int> enumerator = Counters.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					KeyValuePair<string, int> current = enumerator.Current;
					Page countersPage4 = _countersPage;
					string obj4 = $"{current.Key}: {current.Value}";
					Color white = Color.white;
					object obj5 = <>c.<>9__37_1;
					if (obj5 == null)
					{
						Action val4 = delegate
						{
						};
						<>c.<>9__37_1 = val4;
						obj5 = (object)val4;
					}
					countersPage4.CreateFunction(obj4, white, (Action)obj5);
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		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)
			{
				string platformId = FusionIds.Of(player);
				RemovePlayerHistory(platformId);
				SpawnFeed.RemoveAll((Predicate<FeedItem>)((FeedItem item) => string.Equals(item.PlatformId, platformId, (StringComparison)4)));
				RefreshSpawnPage();
			}
		}

		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 && string.IsNullOrEmpty(current.ClaimedOwner.PlatformId))
					{
						current.ClaimedOwner = claimedOwner;
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void RemovePlayerHistory(string platformId)
		{
			PlayerHistory playerHistory = default(PlayerHistory);
			if (!PlayerHistories.TryGetValue(platformId, ref playerHistory))
			{
				return;
			}
			PlayerHistories.Remove(platformId);
			DirtyHistories.Remove(playerHistory);
			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)
			{
				Identity identity = default(Identity);
				if (KnownIdentities.TryGetValue(fallbackSmallId, ref identity))
				{
					return new Identity
					{
						SmallId = identity.SmallId,
						PlatformId = identity.PlatformId,
						Name = identity.Name,
						Departed = true
					};
				}
				return new Identity
				{
					SmallId = fallbackSmallId,
					Name = $"Unknown (ID {fallbackSmallId})"
				};
			}
			string text = default(string);
			Identity identity2 = new Identity
			{
				SmallId = player.SmallID,
				PlatformId = FusionIds.Of(player),
				Name = (CleanName(MetadataHelper.TryGetDisplayName(player, ref text) ? text : null) ?? $"Player {player.SmallID}")
			};
			KnownIdentities[player.SmallID] = identity2;
			return identity2;
		}

		private static string CleanName(string raw)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if (string.IsNullOrEmpty(raw))
			{
				return null;
			}
			StringBuilder val = new StringBuilder(raw.Length);
			for (int i = 0; i < raw.Length; i++)
			{
				if (raw[i] == '<')
				{
					int num = raw.IndexOf('>', i);
					if (num > i)
					{
						i = num;
						continue;
					}
				}
				val.Append(raw[i]);
			}
			string text = ((object)val).ToString().Trim();
			return (text.Length == 0) ? null : text;
		}

		internal static void CaptureResponse(SpawnResponseData data, ReceivedMessage message)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: 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)
			if (data?.SpawnData != null)
			{
				Count("responses received");
				string text = FusionIds.Of(message);
				PlayerID val = (string.IsNullOrEmpty(text) ? null : FusionIds.Find(text));
				Identity transportSender = (string.IsNullOrEmpty(text) ? null : ((val != null) ? CreateIdentity(val, 0) : new Identity
				{
					PlatformId = text,
					Name = "Unknown (" + text + ")"
				}));
				string barcode = data.SpawnData.Barcode;
				PendingSpawns[data.EntityID] = new SpawnSnapshot
				{
					ClaimedOwner = CreateIdentity(PlayerIDManager.GetPlayerID(data.OwnerID), data.OwnerID),
					TransportSender = transportSender,
					CapturedAt = Time.realtimeSinceStartup,
					AlertReason = BuildAlertReason(data, transportSender),
					Barcode = barcode,
					Source = data.SpawnData.SpawnSource,
					CrateMissing = (!string.IsNullOrWhiteSpace(barcode) && !TryResolveCrate(barcode, out var _))
				};
			}
		}

		private static string BuildAlertReason(SpawnResponseData data, Identity transportSender)
		{
			PlayerID hostID = PlayerIDManager.GetHostID();
			bool flag = hostID != null && transportSender != null && !string.Equals(transportSender.PlatformId, FusionIds.Of(hostID), (StringComparison)4);
			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_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Invalid comparison between Unknown and I4
			//IL_016d: 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);
			Count("spawns completed");
			if (Time.realtimeSinceStartup < _loggingReadyAt)
			{
				Count("dropped: level settling");
				return;
			}
			if (!SeenEntityIds.Add(data.EntityID))
			{
				Count("dropped: duplicate entity ID");
				return;
			}
			string barcode = data.SpawnData.Barcode;
			if (string.IsNullOrWhiteSpace(barcode))
			{
				Count("dropped: no barcode");
				return;
			}
			TryResolveCrate(barcode, out var crate);
			if (IsMagazine(poolee, crate, barcode))
			{
				Count("dropped: magazine");
				return;
			}
			if (ShouldExcludeSlz(crate, barcode))
			{
				Count("dropped: SLZ content");
				return;
			}
			spawnSnapshot = spawnSnapshot ?? new SpawnSnapshot
			{
				ClaimedOwner = CreateIdentity(PlayerIDManager.GetPlayerID(data.OwnerID), data.OwnerID)
			};
			ResolveLateOwner(spawnSnapshot, data);
			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);
			}
			else
			{
				Count("dropped: scene source");
			}
		}

		private static void ResolveLateOwner(SpawnSnapshot snapshot, SpawnResponseData data)
		{
			if (string.IsNullOrEmpty(snapshot.ClaimedOwner.PlatformId))
			{
				Identity identity = CreateIdentity(PlayerIDManager.GetPlayerID(data.OwnerID), data.OwnerID);
				if (!string.IsNullOrEmpty(identity.PlatformId))
				{
					snapshot.ClaimedOwner = identity;
				}
			}
		}

		private static void ReportIncomplete(ushort entityId, SpawnSnapshot snapshot)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Invalid comparison between Unknown and I4
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			if (!snapshot.CrateMissing)
			{
				Count("never completed: crate was installed");
				return;
			}
			Count("never completed: crate not installed");
			if (snapshot.CapturedAt < _loggingReadyAt || !SeenEntityIds.Add(entityId))
			{
				return;
			}
			string barcode = snapshot.Barcode;
			if ((int)snapshot.Source != 1 && !IsMagazineBarcode(barcode) && ((_logSlzContent != null && _logSlzContent.Value) || !barcode.StartsWith("SLZ.", (StringComparison)5)))
			{
				if (!string.IsNullOrEmpty(snapshot.AlertReason))
				{
					AddAlert(snapshot, snapshot.Source, barcode);
				}
				else
				{
					AddNormalSpawn(snapshot.ClaimedOwner, barcode + " (not installed)");
				}
			}
		}

		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 || IsMagazineBarcode(barcode);
		}

		private static bool IsMagazineBarcode(string barcode)
		{
			return barcode.IndexOf(".Magazine.", (StringComparison)5) >= 0 || barcode.EndsWith(".Magazine", (StringComparison)5);
		}

		private static void AddNormalSpawn(Identity owner, string itemName)
		{
			if (string.IsNullOrEmpty(owner.PlatformId))
			{
				Count("owner unresolved");
			}
			Count("logged");
			string text = (owner.Departed ? (owner.Name + " (left)") : owner.Name);
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			PlayerHistory orCreateHistory = GetOrCreateHistory(owner);
			FeedItem feedItem = ((orCreateHistory.Entries.Count > 0) ? orCreateHistory.Entries[0] : null);
			if (feedItem != null && feedItem.Item == itemName && realtimeSinceStartup - feedItem.LastAt <= 4f)
			{
				feedItem.Count++;
				feedItem.LastAt = realtimeSinceStartup;
				SpawnFeed.Remove(feedItem);
				SpawnFeed.Insert(0, feedItem);
			}
			else
			{
				feedItem = new FeedItem
				{
					PlatformId = owner.PlatformId,
					Owner = text,
					Item = itemName,
					Time = System.DateTime.Now.ToString("HH:mm:ss"),
					LastAt = realtimeSinceStartup
				};
				SpawnFeed.Insert(0, feedItem);
				Trim<FeedItem>(SpawnFeed);
				InsertNewest(orCreateHistory.Entries, feedItem);
			}
			MarkDirty(orCreateHistory);
			MelonLogger.Msg($"[{System.DateTime.Now:HH:mm:ss}] {text}: {itemName}");
		}

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

		private static PlayerHistory GetOrCreateHistory(Identity identity)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			string text = ((!string.IsNullOrEmpty(identity.PlatformId)) ? identity.PlatformId : ("small:" + identity.SmallId));
			PlayerHistory playerHistory = default(PlayerHistory);
			if (!PlayerHistories.TryGetValue(text, ref playerHistory))
			{
				playerHistory = new PlayerHistory();
				PlayerHistories.Add(text, 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)
		{
			RenderItems(history.Page, history.Entries);
		}

		private static void ClearLogs()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			SpawnFeed.Clear();
			DirtyHistories.Clear();
			_spawnPageDirty = false;
			Enumerator<string, 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();
			}
			RefreshSpawnPage();
		}

		private static void RefreshSpawnPage()
		{
			RenderItems(_spawnPage, SpawnFeed);
		}

		private static void RenderItems(Page page, List<FeedItem> items)
		{
			//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)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_006e: 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_0092: Expected O, but got Unknown
			if (page == null)
			{
				return;
			}
			page.RemoveAll();
			if (items.Count == 0)
			{
				Color gray = Color.gray;
				object obj = <>c.<>9__61_0;
				if (obj == null)
				{
					Action val = delegate
					{
					};
					<>c.<>9__61_0 = val;
					obj = (object)val;
				}
				page.CreateFunction("No spawns logged yet", gray, (Action)obj);
				return;
			}
			Enumerator<FeedItem> enumerator = items.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					FeedItem current = enumerator.Current;
					string text = current.Text;
					Color white = Color.white;
					object obj2 = <>c.<>9__61_1;
					if (obj2 == null)
					{
						Action val2 = delegate
						{
						};
						<>c.<>9__61_1 = val2;
						obj2 = (object)val2;
					}
					page.CreateFunction(text, white, (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);
			}
		}
	}
	internal static class FusionIds
	{
		private static readonly PropertyInfo PlayerPlatformId = AccessTools.Property(typeof(PlayerID), "PlatformID");

		private static readonly PropertyInfo MessagePlatformId = AccessTools.Property(typeof(ReceivedMessage), "PlatformID");

		[ThreadStatic]
		internal static string CurrentEosPeer;

		public static string Of(PlayerID player)
		{
			return (player == null || PlayerPlatformId == (PropertyInfo)null) ? null : Normalize(PlayerPlatformId.GetValue((object)player));
		}

		public static string Of(ReceivedMessage message)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			string text = ((MessagePlatformId == (PropertyInfo)null) ? null : Normalize(MessagePlatformId.GetValue((object)message)));
			return text ?? CurrentEosPeer;
		}

		public static PlayerID Find(string platformId)
		{
			//IL_0015: 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)
			if (string.IsNullOrEmpty(platformId))
			{
				return null;
			}
			Enumerator<PlayerID> enumerator = PlayerIDManager.PlayerIDs.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					PlayerID current = enumerator.Current;
					if (string.Equals(Of(current), platformId, (StringComparison)4))
					{
						return current;
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			return null;
		}

		private static string Normalize(object value)
		{
			if (value != null)
			{
				if (!(value is string text))
				{
					if (value is ulong num)
					{
						return (num == 0L) ? null : num.ToString();
					}
					return value.ToString();
				}
				return (text.Length == 0) ? null : text;
			}
			return null;
		}
	}
	internal static class EosPeerCapture
	{
		public static void TryApply(Harmony harmony)
		{
			//IL_0045: 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)
			//IL_0067: Expected O, but got Unknown
			//IL_0067: Expected O, but got Unknown
			System.Type type = AccessTools.TypeByName("LabFusion.Network.EOSP2PReceiver");
			MethodInfo val = ((type == (System.Type)null) ? null : AccessTools.Method(type, "ProcessPacket", (System.Type[])null, (System.Type[])null));
			if (!(val == (MethodInfo)null))
			{
				harmony.Patch((MethodBase)(object)val, new HarmonyMethod(typeof(EosPeerCapture), "Prefix", (System.Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(EosPeerCapture), "Finalizer", (System.Type[])null), (HarmonyMethod)null);
				MelonLogger.Msg("EOS network layer detected; attributing spawns by ProductUserId.");
			}
		}

		private static void Prefix(object[] __args)
		{
			FusionIds.CurrentEosPeer = ((__args.Length <= 2) ? null : __args[2]?.ToString());
		}

		private static System.Exception Finalizer(System.Exception __exception)
		{
			FusionIds.CurrentEosPeer = null;
			return __exception;
		}
	}
	[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}");
			}
		}
	}
}