Decompiled source of Whitelist Remove v1.0.0

plugins/WhitelistRemove.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: AssemblyFileVersion("1.0.0")]
[assembly: Guid("E74EB49A-461D-48EA-85BC-F462D60C98C4")]
[assembly: ComVisible(false)]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyProduct("WhitelistRemove")]
[assembly: AssemblyCompany("Radamanto")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyTitle("WhitelistRemove")]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: CompilationRelaxations(8)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	[Microsoft.CodeAnalysis.Embedded]
	[CompilerGenerated]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace WhitelistRemove
{
	[BepInPlugin("radamanto.WhitelistRemove", "WhitelistRemove", "1.0.0")]
	public sealed class WhitelistRemovePlugin : BaseUnityPlugin
	{
		private readonly struct ParsedLine
		{
			public readonly string Raw;

			public readonly string Trimmed;

			public readonly bool IsCommentOrEmpty;

			public readonly string Token;

			public readonly bool IsNumericToken;

			public readonly ulong SteamId;

			public ParsedLine(string raw, string trimmed, bool isCommentOrEmpty, string token, bool isNumericToken, ulong steamId)
			{
				Raw = raw;
				Trimmed = trimmed;
				IsCommentOrEmpty = isCommentOrEmpty;
				Token = token;
				IsNumericToken = isNumericToken;
				SteamId = steamId;
			}
		}

		[CompilerGenerated]
		private sealed class <BootstrapWhenServerReady>d__17 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public WhitelistRemovePlugin <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <BootstrapWhenServerReady>d__17(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					goto IL_0044;
				case 1:
					<>1__state = -1;
					goto IL_0044;
				case 2:
					{
						<>1__state = -1;
						((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.ScanLoop());
						return false;
					}
					IL_0044:
					if ((Object)(object)ZNet.instance == (Object)null)
					{
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					if (!<>4__this.CE_Enable.Value)
					{
						return false;
					}
					if (!IsServer())
					{
						return false;
					}
					<>2__current = <>4__this.RunScanOnceCoroutine();
					<>1__state = 2;
					return true;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <RunScanOnceCoroutine>d__26 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public WhitelistRemovePlugin <>4__this;

			private string <fileName>5__1;

			private string <root>5__2;

			private string <whitelistPath>5__3;

			private long <now>5__4;

			private long <threshold>5__5;

			private HashSet<string> <online>5__6;

			private bool <onlineSeenChanged>5__7;

			private string[] <lines>5__8;

			private bool <readOk>5__9;

			private int <readAttempt>5__10;

			private List<ParsedLine> <parsed>5__11;

			private bool <lastSeenChanged>5__12;

			private int <removedCount>5__13;

			private HashSet<string> <keptIds>5__14;

			private HashSet<ulong> <keptUlong>5__15;

			private int <pruned>5__16;

			private List<string> <rebuilt>5__17;

			private bool <writeOk>5__18;

			private int <writeAttempt>5__19;

			private HashSet<string>.Enumerator <>s__20;

			private string <s>5__21;

			private ulong <osid>5__22;

			private bool <delay>5__23;

			private int <i>5__24;

			private string <raw>5__25;

			private string <t>5__26;

			private string <tok>5__27;

			private ulong <sid>5__28;

			private int <i>5__29;

			private ParsedLine <pl>5__30;

			private string <idStr>5__31;

			private ulong <sid>5__32;

			private long <lastSeen>5__33;

			private long <delta>5__34;

			private int <i>5__35;

			private ParsedLine <pl>5__36;

			private bool <delay>5__37;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <RunScanOnceCoroutine>d__26(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<fileName>5__1 = null;
				<root>5__2 = null;
				<whitelistPath>5__3 = null;
				<online>5__6 = null;
				<lines>5__8 = null;
				<parsed>5__11 = null;
				<keptIds>5__14 = null;
				<keptUlong>5__15 = null;
				<rebuilt>5__17 = null;
				<>s__20 = default(HashSet<string>.Enumerator);
				<s>5__21 = null;
				<raw>5__25 = null;
				<t>5__26 = null;
				<tok>5__27 = null;
				<pl>5__30 = default(ParsedLine);
				<idStr>5__31 = null;
				<pl>5__36 = default(ParsedLine);
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_023d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0247: Expected O, but got Unknown
				//IL_08c2: Unknown result type (might be due to invalid IL or missing references)
				//IL_08cc: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>4__this.RefreshBypassCacheIfNeeded();
					<fileName>5__1 = (<>4__this.CE_WhitelistFileName.Value ?? "permittedlist.txt").Trim();
					if (string.IsNullOrWhiteSpace(<fileName>5__1))
					{
						return false;
					}
					<root>5__2 = GetVanillaListRootPath();
					if (string.IsNullOrWhiteSpace(<root>5__2))
					{
						return false;
					}
					Directory.CreateDirectory(<root>5__2);
					<whitelistPath>5__3 = Path.Combine(<root>5__2, <fileName>5__1);
					if (!File.Exists(<whitelistPath>5__3))
					{
						return false;
					}
					<now>5__4 = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
					<threshold>5__5 = (long)Mathf.Max(1f, <>4__this.CE_InactiveThresholdSeconds.Value);
					<online>5__6 = OnlineSnapshot.GetOnlineSteamIdStrings();
					<onlineSeenChanged>5__7 = false;
					<>s__20 = <online>5__6.GetEnumerator();
					try
					{
						while (<>s__20.MoveNext())
						{
							<s>5__21 = <>s__20.Current;
							if (ulong.TryParse(<s>5__21, NumberStyles.None, CultureInfo.InvariantCulture, out <osid>5__22) && <osid>5__22 != 0)
							{
								SeenStore.SetLastSeen(<osid>5__22, <now>5__4);
								<onlineSeenChanged>5__7 = true;
							}
							<s>5__21 = null;
						}
					}
					finally
					{
						((IDisposable)<>s__20).Dispose();
					}
					<>s__20 = default(HashSet<string>.Enumerator);
					<lines>5__8 = Array.Empty<string>();
					<readOk>5__9 = false;
					<readAttempt>5__10 = 0;
					goto IL_0258;
				case 1:
					<>1__state = -1;
					goto IL_0258;
				case 2:
					{
						<>1__state = -1;
						break;
					}
					IL_0258:
					while (<readAttempt>5__10 < 3)
					{
						<delay>5__23 = false;
						try
						{
							<lines>5__8 = File.ReadAllLines(<whitelistPath>5__3, Encoding.UTF8);
							<readOk>5__9 = true;
						}
						catch (IOException)
						{
							<readAttempt>5__10++;
							if (<readAttempt>5__10 >= 3)
							{
								break;
							}
							<delay>5__23 = true;
							goto IL_022b;
						}
						catch
						{
						}
						break;
						IL_022b:
						if (<delay>5__23)
						{
							<>2__current = (object)new WaitForSecondsRealtime(0.2f);
							<>1__state = 1;
							return true;
						}
					}
					if (!<readOk>5__9)
					{
						return false;
					}
					<parsed>5__11 = new List<ParsedLine>(<lines>5__8.Length);
					<i>5__24 = 0;
					while (<i>5__24 < <lines>5__8.Length)
					{
						<raw>5__25 = <lines>5__8[<i>5__24] ?? string.Empty;
						<t>5__26 = <raw>5__25.Trim();
						if (<t>5__26.Length == 0 || <t>5__26.StartsWith("//", StringComparison.Ordinal) || <t>5__26.StartsWith("#", StringComparison.Ordinal) || <t>5__26.StartsWith(";", StringComparison.Ordinal))
						{
							<parsed>5__11.Add(new ParsedLine(<raw>5__25, <t>5__26, isCommentOrEmpty: true, string.Empty, isNumericToken: false, 0uL));
						}
						else
						{
							<tok>5__27 = FirstToken(<t>5__26);
							if (<tok>5__27.Length == 0)
							{
								<parsed>5__11.Add(new ParsedLine(<raw>5__25, <t>5__26, isCommentOrEmpty: true, string.Empty, isNumericToken: false, 0uL));
							}
							else
							{
								if (ulong.TryParse(<tok>5__27, NumberStyles.None, CultureInfo.InvariantCulture, out <sid>5__28))
								{
									<parsed>5__11.Add(new ParsedLine(<raw>5__25, <t>5__26, isCommentOrEmpty: false, <tok>5__27, isNumericToken: true, <sid>5__28));
								}
								else
								{
									<parsed>5__11.Add(new ParsedLine(<raw>5__25, <t>5__26, isCommentOrEmpty: false, <tok>5__27, isNumericToken: false, 0uL));
								}
								<raw>5__25 = null;
								<t>5__26 = null;
								<tok>5__27 = null;
							}
						}
						<i>5__24++;
					}
					if (<parsed>5__11.Count == 0)
					{
						return false;
					}
					<lastSeenChanged>5__12 = <onlineSeenChanged>5__7;
					<removedCount>5__13 = 0;
					<keptIds>5__14 = new HashSet<string>(StringComparer.Ordinal);
					<keptUlong>5__15 = new HashSet<ulong>();
					<i>5__29 = 0;
					while (<i>5__29 < <parsed>5__11.Count)
					{
						<pl>5__30 = <parsed>5__11[<i>5__29];
						if (!<pl>5__30.IsCommentOrEmpty && <pl>5__30.IsNumericToken)
						{
							<idStr>5__31 = <pl>5__30.Token;
							<sid>5__32 = <pl>5__30.SteamId;
							if (<online>5__6.Contains(<idStr>5__31))
							{
								<keptIds>5__14.Add(<idStr>5__31);
								<keptUlong>5__15.Add(<sid>5__32);
							}
							else if (<>4__this.IsBypassed(<sid>5__32))
							{
								<keptIds>5__14.Add(<idStr>5__31);
								<keptUlong>5__15.Add(<sid>5__32);
							}
							else if (!SeenStore.TryGetLastSeen(<sid>5__32, out <lastSeen>5__33))
							{
								SeenStore.SetLastSeen(<sid>5__32, <now>5__4);
								<lastSeenChanged>5__12 = true;
								<keptIds>5__14.Add(<idStr>5__31);
								<keptUlong>5__15.Add(<sid>5__32);
							}
							else
							{
								<delta>5__34 = <now>5__4 - <lastSeen>5__33;
								if (<delta>5__34 > <threshold>5__5)
								{
									<removedCount>5__13++;
									AppendRemovedLog(<>4__this._removedLogPath, <sid>5__32, <lastSeen>5__33, <delta>5__34, "inactive", ((BaseUnityPlugin)<>4__this).Logger);
									if (SeenStore.Remove(<sid>5__32))
									{
										<lastSeenChanged>5__12 = true;
									}
								}
								else
								{
									<keptIds>5__14.Add(<idStr>5__31);
									<keptUlong>5__15.Add(<sid>5__32);
									<pl>5__30 = default(ParsedLine);
									<idStr>5__31 = null;
								}
							}
						}
						<i>5__29++;
					}
					<pruned>5__16 = SeenStore.PruneTo(<keptUlong>5__15);
					if (<pruned>5__16 > 0)
					{
						<lastSeenChanged>5__12 = true;
					}
					if (<removedCount>5__13 == 0)
					{
						if (<lastSeenChanged>5__12)
						{
							SeenStore.Save(<>4__this._lastSeenPath, ((BaseUnityPlugin)<>4__this).Logger);
						}
						return false;
					}
					<rebuilt>5__17 = new List<string>(<parsed>5__11.Count);
					<i>5__35 = 0;
					while (<i>5__35 < <parsed>5__11.Count)
					{
						<pl>5__36 = <parsed>5__11[<i>5__35];
						if (<pl>5__36.IsCommentOrEmpty)
						{
							<rebuilt>5__17.Add(<pl>5__36.Raw);
						}
						else if (!<pl>5__36.IsNumericToken)
						{
							<rebuilt>5__17.Add(<pl>5__36.Raw);
						}
						else
						{
							if (<keptIds>5__14.Contains(<pl>5__36.Token))
							{
								<rebuilt>5__17.Add(<pl>5__36.Raw);
							}
							<pl>5__36 = default(ParsedLine);
						}
						<i>5__35++;
					}
					<writeOk>5__18 = false;
					<writeAttempt>5__19 = 0;
					break;
				}
				while (<writeAttempt>5__19 < 3)
				{
					<delay>5__37 = false;
					try
					{
						FileIoRetry.AtomicWriteText(<whitelistPath>5__3, string.Join(Environment.NewLine, <rebuilt>5__17) + Environment.NewLine);
						<writeOk>5__18 = true;
					}
					catch (IOException)
					{
						<writeAttempt>5__19++;
						if (<writeAttempt>5__19 >= 3)
						{
							break;
						}
						<delay>5__37 = true;
						goto IL_08b0;
					}
					catch
					{
					}
					break;
					IL_08b0:
					if (<delay>5__37)
					{
						<>2__current = (object)new WaitForSecondsRealtime(0.2f);
						<>1__state = 2;
						return true;
					}
				}
				if (!<writeOk>5__18)
				{
					return false;
				}
				SeenStore.Save(<>4__this._lastSeenPath, ((BaseUnityPlugin)<>4__this).Logger);
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <ScanLoop>d__19 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public WhitelistRemovePlugin <>4__this;

			private float <interval>5__1;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ScanLoop>d__19(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_005b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					if (!<>4__this.CE_Enable.Value || !IsServer())
					{
						break;
					}
					<>2__current = <>4__this.RunScanOnceCoroutine();
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					break;
				}
				<interval>5__1 = Mathf.Max(5f, <>4__this.CE_ScanIntervalSeconds.Value);
				<>2__current = (object)new WaitForSecondsRealtime(<interval>5__1);
				<>1__state = 1;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		internal const string ModName = "WhitelistRemove";

		internal const string ModVersion = "1.0.0";

		internal const string Author = "radamanto";

		internal const string ModGUID = "radamanto.WhitelistRemove";

		private readonly Harmony _harmony = new Harmony("radamanto.WhitelistRemove");

		private ConfigEntry<bool> CE_Enable = null;

		private ConfigEntry<string> CE_WhitelistFileName = null;

		private ConfigEntry<float> CE_ScanIntervalSeconds = null;

		private ConfigEntry<float> CE_InactiveThresholdSeconds = null;

		private ConfigEntry<string> CE_BypassSteamIds = null;

		private ConfigEntry<bool> CE_BypassAdmins = null;

		private string _dataDir = string.Empty;

		private string _lastSeenPath = string.Empty;

		private string _removedLogPath = string.Empty;

		private string _bypassRawLast = string.Empty;

		private readonly HashSet<ulong> _bypassIds = new HashSet<ulong>();

		private void Awake()
		{
			CE_Enable = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enable", true, "Enable the whitelist inactivity cleaner.");
			CE_WhitelistFileName = ((BaseUnityPlugin)this).Config.Bind<string>("02 - Whitelist", "WhitelistFileName", "permittedlist.txt", "Whitelist file name.");
			CE_ScanIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("03 - Scan", "ScanIntervalSeconds", 900f, "How often the mod scans the whitelist and removes inactive users.");
			CE_InactiveThresholdSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("03 - Scan", "InactiveThresholdSeconds", 259200f, "If a whitelisted SteamID did not log in for more than this amount of seconds, it will be removed.");
			CE_BypassSteamIds = ((BaseUnityPlugin)this).Config.Bind<string>("04 - Bypass", "BypassSteamIds", "", "SteamIDs that are NEVER removed (comma separated).");
			CE_BypassAdmins = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - Bypass", "BypassAdmins", true, "If true, SteamIDs that are admins on the server are NEVER removed.");
			_dataDir = Path.Combine(Paths.ConfigPath, "WhitelistRemove");
			_lastSeenPath = Path.Combine(_dataDir, "lastseen.json");
			_removedLogPath = Path.Combine(_dataDir, "removed.log");
			Directory.CreateDirectory(_dataDir);
			SeenStore.Load(_lastSeenPath, ((BaseUnityPlugin)this).Logger);
			_harmony.PatchAll(typeof(WhitelistRemovePlugin).Assembly);
			((MonoBehaviour)this).StartCoroutine(BootstrapWhenServerReady());
		}

		[IteratorStateMachine(typeof(<BootstrapWhenServerReady>d__17))]
		private IEnumerator BootstrapWhenServerReady()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <BootstrapWhenServerReady>d__17(0)
			{
				<>4__this = this
			};
		}

		private static bool IsServer()
		{
			try
			{
				return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer();
			}
			catch
			{
				return false;
			}
		}

		[IteratorStateMachine(typeof(<ScanLoop>d__19))]
		private IEnumerator ScanLoop()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ScanLoop>d__19(0)
			{
				<>4__this = this
			};
		}

		private void RefreshBypassCacheIfNeeded()
		{
			string text = CE_BypassSteamIds.Value ?? string.Empty;
			if (string.Equals(text, _bypassRawLast, StringComparison.Ordinal))
			{
				return;
			}
			_bypassRawLast = text;
			_bypassIds.Clear();
			string[] array = text.Split(new char[5] { ',', ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0 && !text2.StartsWith("//", StringComparison.Ordinal) && !text2.StartsWith("#", StringComparison.Ordinal) && !text2.StartsWith(";", StringComparison.Ordinal) && ulong.TryParse(text2, NumberStyles.None, CultureInfo.InvariantCulture, out var result))
				{
					_bypassIds.Add(result);
				}
			}
		}

		private bool IsBypassed(ulong steamId)
		{
			if (_bypassIds.Contains(steamId))
			{
				return true;
			}
			if (CE_BypassAdmins.Value)
			{
				try
				{
					if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsAdmin(steamId.ToString(CultureInfo.InvariantCulture)))
					{
						return true;
					}
				}
				catch
				{
				}
			}
			return false;
		}

		private static string FirstToken(string trimmed)
		{
			int i;
			for (i = 0; i < trimmed.Length && !char.IsWhiteSpace(trimmed[i]); i++)
			{
			}
			return (i <= 0) ? string.Empty : trimmed.Substring(0, i);
		}

		private static string? TryGetSavedirFromCommandLine()
		{
			try
			{
				string[] commandLineArgs = Environment.GetCommandLineArgs();
				for (int i = 0; i < commandLineArgs.Length; i++)
				{
					string text = commandLineArgs[i] ?? string.Empty;
					if (string.Equals(text, "-savedir", StringComparison.OrdinalIgnoreCase))
					{
						if (i + 1 < commandLineArgs.Length)
						{
							string text2 = (commandLineArgs[i + 1] ?? string.Empty).Trim().Trim(new char[1] { '"' });
							if (!string.IsNullOrWhiteSpace(text2))
							{
								return text2;
							}
						}
						return null;
					}
					if (!text.StartsWith("-savedir", StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					int num = text.IndexOf('=');
					int num2 = text.IndexOf(':');
					int num3 = -1;
					if (num >= 0)
					{
						num3 = num;
					}
					else if (num2 >= 0)
					{
						num3 = num2;
					}
					if (num3 >= 0 && num3 + 1 < text.Length)
					{
						string text3 = text.Substring(num3 + 1).Trim().Trim(new char[1] { '"' });
						if (!string.IsNullOrWhiteSpace(text3))
						{
							return text3;
						}
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private static string GetVanillaListRootPath()
		{
			string text = TryGetSavedirFromCommandLine();
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return Application.persistentDataPath;
		}

		[IteratorStateMachine(typeof(<RunScanOnceCoroutine>d__26))]
		private IEnumerator RunScanOnceCoroutine()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <RunScanOnceCoroutine>d__26(0)
			{
				<>4__this = this
			};
		}

		private static void AppendRemovedLog(string removedLogPath, ulong steamId, long lastSeen, long inactiveSeconds, string reason, ManualLogSource logger)
		{
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(removedLogPath) ?? ".");
				string text = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture);
				string text2 = DateTimeOffset.FromUnixTimeSeconds(lastSeen).ToString("yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture);
				string text3 = "[" + text + "] removed steamid=" + steamId.ToString(CultureInfo.InvariantCulture) + " reason=" + reason + " lastSeen=" + text2 + " inactiveSeconds=" + inactiveSeconds.ToString(CultureInfo.InvariantCulture);
				File.AppendAllText(removedLogPath, text3 + Environment.NewLine, Encoding.UTF8);
			}
			catch
			{
			}
		}
	}
	internal static class SeenStore
	{
		private static readonly object LockObj = new object();

		private static readonly Dictionary<ulong, long> LastSeenUnixBySteamId = new Dictionary<ulong, long>();

		private static bool _loaded;

		internal static void Load(string path, ManualLogSource logger)
		{
			lock (LockObj)
			{
				if (_loaded)
				{
					return;
				}
				_loaded = true;
				if (!File.Exists(path))
				{
					return;
				}
				try
				{
					string[] array = File.ReadAllLines(path, Encoding.UTF8);
					foreach (string text in array)
					{
						string text2 = (text ?? string.Empty).Trim();
						if (text2.Length == 0 || text2[0] != '"')
						{
							continue;
						}
						int num = text2.IndexOf('"', 1);
						if (num <= 1)
						{
							continue;
						}
						string s = text2.Substring(1, num - 1);
						int num2 = text2.IndexOf(':', num + 1);
						if (num2 >= 0)
						{
							string s2 = text2.Substring(num2 + 1).Trim().TrimEnd(new char[1] { ',' });
							if (ulong.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) && long.TryParse(s2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
							{
								LastSeenUnixBySteamId[result] = result2;
							}
						}
					}
				}
				catch
				{
				}
			}
		}

		internal static bool TryGetLastSeen(ulong steamId, out long lastSeen)
		{
			lock (LockObj)
			{
				return LastSeenUnixBySteamId.TryGetValue(steamId, out lastSeen);
			}
		}

		internal static void SetLastSeen(ulong steamId, long unixSeconds)
		{
			lock (LockObj)
			{
				LastSeenUnixBySteamId[steamId] = unixSeconds;
			}
		}

		internal static void MarkSeenNow(ulong steamId)
		{
			long unixSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			SetLastSeen(steamId, unixSeconds);
		}

		internal static bool Remove(ulong steamId)
		{
			lock (LockObj)
			{
				return LastSeenUnixBySteamId.Remove(steamId);
			}
		}

		internal static int PruneTo(HashSet<ulong> keepSteamIds)
		{
			lock (LockObj)
			{
				if (LastSeenUnixBySteamId.Count == 0)
				{
					return 0;
				}
				int num = 0;
				ulong[] array = LastSeenUnixBySteamId.Keys.ToArray();
				foreach (ulong num2 in array)
				{
					if (!keepSteamIds.Contains(num2) && LastSeenUnixBySteamId.Remove(num2))
					{
						num++;
					}
				}
				return num;
			}
		}

		internal static void Save(string path, ManualLogSource logger)
		{
			lock (LockObj)
			{
				try
				{
					StringBuilder stringBuilder = new StringBuilder(4096);
					stringBuilder.AppendLine("{");
					foreach (KeyValuePair<ulong, long> item in LastSeenUnixBySteamId.OrderBy((KeyValuePair<ulong, long> k) => k.Key))
					{
						stringBuilder.Append("  \"");
						stringBuilder.Append(item.Key.ToString(CultureInfo.InvariantCulture));
						stringBuilder.Append("\": ");
						stringBuilder.Append(item.Value.ToString(CultureInfo.InvariantCulture));
						stringBuilder.AppendLine(",");
					}
					string text = stringBuilder.ToString();
					if (LastSeenUnixBySteamId.Count > 0)
					{
						int num = text.LastIndexOf(",\n", StringComparison.Ordinal);
						if (num >= 0)
						{
							text = text.Remove(num, 1);
						}
					}
					text += "}\n";
					FileIoRetry.AtomicWriteText(path, text);
				}
				catch
				{
				}
			}
		}
	}
	internal static class OnlineSnapshot
	{
		private static bool TryGetSteamIdString(ZNetPeer peer, out string steamIdStr)
		{
			steamIdStr = string.Empty;
			try
			{
				if (peer == null)
				{
					return false;
				}
				ISocket socket = peer.m_socket;
				if (socket == null)
				{
					return false;
				}
				string text = (socket.GetHostName() ?? string.Empty).Trim();
				if (text.Length == 0)
				{
					return false;
				}
				int num = text.IndexOf(':');
				if (num >= 0)
				{
					text = text.Substring(0, num).Trim();
				}
				if (text.Length == 0)
				{
					return false;
				}
				if (!ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out var _))
				{
					return false;
				}
				steamIdStr = text;
				return true;
			}
			catch
			{
				return false;
			}
		}

		internal static HashSet<string> GetOnlineSteamIdStrings()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			try
			{
				if ((Object)(object)ZNet.instance == (Object)null)
				{
					return hashSet;
				}
				List<ZNetPeer> peers = ZNet.instance.GetPeers();
				for (int i = 0; i < peers.Count; i++)
				{
					ZNetPeer val = peers[i];
					if (val != null && TryGetSteamIdString(val, out string steamIdStr))
					{
						hashSet.Add(steamIdStr);
					}
				}
			}
			catch
			{
			}
			return hashSet;
		}
	}
	internal static class FileIoRetry
	{
		internal const int MaxRetries = 3;

		internal const float RetryDelaySeconds = 0.2f;

		internal static void AtomicWriteText(string path, string content)
		{
			string path2 = Path.GetDirectoryName(path) ?? ".";
			Directory.CreateDirectory(path2);
			string text = path + ".tmp";
			File.WriteAllText(text, content, Encoding.UTF8);
			if (File.Exists(path))
			{
				try
				{
					string text2 = path + ".bak";
					File.Replace(text, path, text2, ignoreMetadataErrors: true);
					TryDelete(text2);
					return;
				}
				catch
				{
				}
			}
			if (File.Exists(path))
			{
				File.Delete(path);
			}
			File.Move(text, path);
		}

		private static void TryDelete(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class PeerSeenPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private static void ZNet_RPC_PeerInfo_Postfix(ZNet __instance, ZRpc rpc)
		{
			try
			{
				if ((Object)(object)__instance == (Object)null || !__instance.IsServer())
				{
					return;
				}
				List<ZNetPeer> peers = __instance.GetPeers();
				for (int i = 0; i < peers.Count; i++)
				{
					ZNetPeer val = peers[i];
					if (val == null || val.m_rpc != rpc)
					{
						continue;
					}
					ISocket socket = val.m_socket;
					if (socket == null)
					{
						break;
					}
					string text = (socket.GetHostName() ?? string.Empty).Trim();
					if (text.Length != 0)
					{
						int num = text.IndexOf(':');
						if (num >= 0)
						{
							text = text.Substring(0, num).Trim();
						}
						if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out var result) && result != 0)
						{
							SeenStore.MarkSeenNow(result);
						}
					}
					break;
				}
			}
			catch
			{
			}
		}
	}
}