Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Whitelist Remove v1.0.0
plugins/WhitelistRemove.dll
Decompiled 3 months agousing 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 { } } } }