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