using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ZoneSavior")]
[assembly: AssemblyDescription("Dedicated-server zone archiving, zone bundle restore, zone UI, and per-zone WearNTear limits for Valheim.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("ZoneSavior")]
[assembly: AssemblyCopyright("Copyright 2026 sighsorry")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("B276E8F6-7F9F-4A0F-8E7D-757383FF7CB1")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
{
object IEnumerator.Current => _item;
T IEnumerator<T>.Current => _item;
public Enumerator(T item)
{
_item = item;
}
bool IEnumerator.MoveNext()
{
if (!_moveNextCalled)
{
return _moveNextCalled = true;
}
return false;
}
void IEnumerator.Reset()
{
_moveNextCalled = false;
}
void IDisposable.Dispose()
{
}
}
int ICollection.Count => 1;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
object IList.this[int index]
{
get
{
if (index != 0)
{
throw new IndexOutOfRangeException();
}
return _item;
}
set
{
throw new NotSupportedException();
}
}
bool IList.IsFixedSize => true;
bool IList.IsReadOnly => true;
int IReadOnlyCollection<T>.Count => 1;
T IReadOnlyList<T>.this[int index]
{
get
{
if (index != 0)
{
throw new IndexOutOfRangeException();
}
return _item;
}
}
int ICollection<T>.Count => 1;
bool ICollection<T>.IsReadOnly => true;
T IList<T>.this[int index]
{
get
{
if (index != 0)
{
throw new IndexOutOfRangeException();
}
return _item;
}
set
{
throw new NotSupportedException();
}
}
public <>z__ReadOnlySingleElementList(T item)
{
_item = item;
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(_item);
}
void ICollection.CopyTo(Array array, int index)
{
array.SetValue(_item, index);
}
int IList.Add(object value)
{
throw new NotSupportedException();
}
void IList.Clear()
{
throw new NotSupportedException();
}
bool IList.Contains(object value)
{
return EqualityComparer<T>.Default.Equals(_item, (T)value);
}
int IList.IndexOf(object value)
{
if (!EqualityComparer<T>.Default.Equals(_item, (T)value))
{
return -1;
}
return 0;
}
void IList.Insert(int index, object value)
{
throw new NotSupportedException();
}
void IList.Remove(object value)
{
throw new NotSupportedException();
}
void IList.RemoveAt(int index)
{
throw new NotSupportedException();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return new Enumerator(_item);
}
void ICollection<T>.Add(T item)
{
throw new NotSupportedException();
}
void ICollection<T>.Clear()
{
throw new NotSupportedException();
}
bool ICollection<T>.Contains(T item)
{
return EqualityComparer<T>.Default.Equals(_item, item);
}
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
{
array[arrayIndex] = _item;
}
bool ICollection<T>.Remove(T item)
{
throw new NotSupportedException();
}
int IList<T>.IndexOf(T item)
{
if (!EqualityComparer<T>.Default.Equals(_item, item))
{
return -1;
}
return 0;
}
void IList<T>.Insert(int index, T item)
{
throw new NotSupportedException();
}
void IList<T>.RemoveAt(int index)
{
throw new NotSupportedException();
}
}
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
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;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class ExtensionMarkerAttribute : Attribute
{
private readonly string <Name>k__BackingField;
public string Name => <Name>k__BackingField;
public ExtensionMarkerAttribute(string name)
{
<Name>k__BackingField = name;
}
}
}
namespace ZoneSavior
{
internal static class AutoArchiveCommands
{
[CompilerGenerated]
private static class <>O
{
public static ConsoleEvent <0>__HandleCommand;
public static Action<long, ZPackage> <1>__RPC_HandleRequest;
public static Action<long, ZPackage> <2>__RPC_HandleResult;
public static Func<char, bool> <3>__IsDigit;
}
[CompilerGenerated]
private sealed class <>c__DisplayClass24_0
{
public ZoneBundleCommandResult result;
internal void <ExecuteRestoreAsync>b__0(ZoneBundleCommandResult value)
{
result = value;
}
}
[CompilerGenerated]
private sealed class <ExecuteRestoreAsync>d__24 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public string tag;
public Action<AutoArchiveCommandResult> onComplete;
private <>c__DisplayClass24_0 <>8__1;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ExecuteRestoreAsync>d__24(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>8__1 = null;
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>8__1 = new <>c__DisplayClass24_0();
<>8__1.result = ZoneBundleCommandResult.Fail("Archive restore failed before it started.");
<>2__current = ZoneBundleCommands.RestoreTagToOriginalZonesAsync(tag, delegate(ZoneBundleCommandResult value)
{
<>8__1.result = value;
});
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
onComplete(<>8__1.result.Success ? AutoArchiveCommandResult.Ok(new <>z__ReadOnlySingleElementList<string>(<>8__1.result.Message)) : AutoArchiveCommandResult.Fail(<>8__1.result.Message));
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();
}
}
private const string ScanCommand = "zs_archive_scan";
private const string StatusCommand = "zs_archive_status";
private const string PlayerCommand = "zs_archive_player";
private const string ListCommand = "zs_archive_list";
private const string MarkSeenCommand = "zs_archive_mark_seen";
private const string IgnorePlayerCommand = "zs_archive_ignore_player";
private const string RestoreCommand = "zs_archive_restore";
private const string ScheduleCommand = "zs_archive_schedule";
private const string DebugZoneCommand = "zs_archive_debug_zone";
private const string RequestRpcName = "sighsorry.ZoneSavior_AutoArchiveCommandRequest";
private const string ResultRpcName = "sighsorry.ZoneSavior_AutoArchiveCommandResult";
private static readonly Regex ZoneSpecPattern = new Regex("^\\s*\\(\\s*(-?\\d+)\\s*,\\s*(-?\\d+)\\s*\\)\\s*$", RegexOptions.Compiled);
private static ManualLogSource _logger = null;
private static bool _initialized;
private static readonly ZoneRpcRegistrar RpcRegistrar = new ZoneRpcRegistrar();
public static void Initialize(ManualLogSource logger)
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Expected O, but got Unknown
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_006d: Expected O, but got Unknown
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Expected O, but got Unknown
//IL_00de: Unknown result type (might be due to invalid IL or missing references)
//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
//IL_00d5: Expected O, but got Unknown
//IL_0112: Unknown result type (might be due to invalid IL or missing references)
//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
//IL_0103: Unknown result type (might be due to invalid IL or missing references)
//IL_0109: Expected O, but got Unknown
//IL_0146: Unknown result type (might be due to invalid IL or missing references)
//IL_0132: 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_013d: Expected O, but got Unknown
//IL_017a: Unknown result type (might be due to invalid IL or missing references)
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_016b: Unknown result type (might be due to invalid IL or missing references)
//IL_0171: Expected O, but got Unknown
//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
//IL_019a: Unknown result type (might be due to invalid IL or missing references)
//IL_019f: Unknown result type (might be due to invalid IL or missing references)
//IL_01a5: Expected O, but got Unknown
//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
//IL_01d9: Expected O, but got Unknown
if (!_initialized)
{
_initialized = true;
_logger = logger;
object obj = <>O.<0>__HandleCommand;
if (obj == null)
{
ConsoleEvent val = HandleCommand;
<>O.<0>__HandleCommand = val;
obj = (object)val;
}
new ConsoleCommand("zs_archive_scan", "[dry|save|reset] - Runs an auto archive scan.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj2 = <>O.<0>__HandleCommand;
if (obj2 == null)
{
ConsoleEvent val2 = HandleCommand;
<>O.<0>__HandleCommand = val2;
obj2 = (object)val2;
}
new ConsoleCommand("zs_archive_status", "- Prints auto archive activity status.", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj3 = <>O.<0>__HandleCommand;
if (obj3 == null)
{
ConsoleEvent val3 = HandleCommand;
<>O.<0>__HandleCommand = val3;
obj3 = (object)val3;
}
new ConsoleCommand("zs_archive_player", "steamID [dry|save|reset] - Runs an auto archive scan filtered to one Steam owner.", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj4 = <>O.<0>__HandleCommand;
if (obj4 == null)
{
ConsoleEvent val4 = HandleCommand;
<>O.<0>__HandleCommand = val4;
obj4 = (object)val4;
}
new ConsoleCommand("zs_archive_list", "- Lists recent auto archive runs.", (ConsoleEvent)obj4, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj5 = <>O.<0>__HandleCommand;
if (obj5 == null)
{
ConsoleEvent val5 = HandleCommand;
<>O.<0>__HandleCommand = val5;
obj5 = (object)val5;
}
new ConsoleCommand("zs_archive_mark_seen", "playerID - Marks a playerID as seen now.", (ConsoleEvent)obj5, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj6 = <>O.<0>__HandleCommand;
if (obj6 == null)
{
ConsoleEvent val6 = HandleCommand;
<>O.<0>__HandleCommand = val6;
obj6 = (object)val6;
}
new ConsoleCommand("zs_archive_ignore_player", "playerID [on|off] - Protects or unprotects a playerID from auto archive.", (ConsoleEvent)obj6, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj7 = <>O.<0>__HandleCommand;
if (obj7 == null)
{
ConsoleEvent val7 = HandleCommand;
<>O.<0>__HandleCommand = val7;
obj7 = (object)val7;
}
new ConsoleCommand("zs_archive_restore", "tag - Restores an archived tag to its original zones.", (ConsoleEvent)obj7, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj8 = <>O.<0>__HandleCommand;
if (obj8 == null)
{
ConsoleEvent val8 = HandleCommand;
<>O.<0>__HandleCommand = val8;
obj8 = (object)val8;
}
new ConsoleCommand("zs_archive_schedule", "[status|now|clear|last yyyy-MM-dd HH:mm|next yyyy-MM-dd HH:mm] - Shows or adjusts the automatic archive scan schedule anchor. Local server time is used unless the date includes Z or an offset.", (ConsoleEvent)obj8, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
object obj9 = <>O.<0>__HandleCommand;
if (obj9 == null)
{
ConsoleEvent val9 = HandleCommand;
<>O.<0>__HandleCommand = val9;
obj9 = (object)val9;
}
new ConsoleCommand("zs_archive_debug_zone", "(x,z) - Writes a YAML report explaining auto archive eligibility for one zone.", (ConsoleEvent)obj9, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
RegisterRpcs();
}
}
internal static void RegisterRpcs()
{
RpcRegistrar.EnsureRegistered(delegate(ZRoutedRpc routedRpc)
{
routedRpc.Register<ZPackage>("sighsorry.ZoneSavior_AutoArchiveCommandRequest", (Action<long, ZPackage>)RPC_HandleRequest);
routedRpc.Register<ZPackage>("sighsorry.ZoneSavior_AutoArchiveCommandResult", (Action<long, ZPackage>)RPC_HandleResult);
});
}
private static void HandleCommand(ConsoleEventArgs args)
{
EnsureCommandReady();
DispatchRequest(new AutoArchiveCommandRequest
{
Command = ((args.Args.Length != 0) ? args.Args[0] : ""),
Args = args.Args.ToList()
}, args.Context);
}
private static void DispatchRequest(AutoArchiveCommandRequest request, Terminal? context)
{
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0037: Expected O, but got Unknown
Terminal context2 = context;
if (ZNet.instance.IsServer())
{
StartRequest(request, delegate(AutoArchiveCommandResult result)
{
ShowResult(result, context2);
});
return;
}
RegisterRpcs();
ZPackage val = new ZPackage();
val.Write(ZoneBundleSerialization.Serialize(request));
ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "sighsorry.ZoneSavior_AutoArchiveCommandRequest", new object[1] { val });
Terminal obj = context2;
if (obj != null)
{
obj.AddString(request.Command + " request sent to server.");
}
}
private static void RPC_HandleRequest(long sender, ZPackage package)
{
if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
{
return;
}
try
{
if (!IsAuthorizedSender(sender))
{
SendResult(sender, AutoArchiveCommandResult.Fail("Admin only."));
return;
}
StartRequest(ZoneBundleSerialization.Deserialize<AutoArchiveCommandRequest>(package.ReadString()), delegate(AutoArchiveCommandResult result)
{
SendResult(sender, result);
});
}
catch (Exception ex)
{
_logger.LogError((object)$"Auto archive command RPC failed: {ex}");
SendResult(sender, AutoArchiveCommandResult.Fail(ex.Message));
}
}
private static void SendResult(long target, AutoArchiveCommandResult result)
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Expected O, but got Unknown
if (ZRoutedRpc.instance != null)
{
ZPackage val = new ZPackage();
val.Write(ZoneBundleSerialization.Serialize(result));
ZRoutedRpc.instance.InvokeRoutedRPC(target, "sighsorry.ZoneSavior_AutoArchiveCommandResult", new object[1] { val });
}
}
private static void RPC_HandleResult(long sender, ZPackage package)
{
if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())
{
ShowResult(ZoneBundleSerialization.Deserialize<AutoArchiveCommandResult>(package.ReadString()), (Terminal?)(object)Console.instance);
}
}
private static void StartRequest(AutoArchiveCommandRequest request, Action<AutoArchiveCommandResult> onComplete)
{
if ((Object)(object)ZoneSaviorPlugin.Instance != (Object)null && TryGetRestoreTag(request, out string tag, out AutoArchiveCommandResult validationResult))
{
if (validationResult != null)
{
onComplete(validationResult);
}
else
{
((MonoBehaviour)ZoneSaviorPlugin.Instance).StartCoroutine(ExecuteRestoreAsync(tag, onComplete));
}
}
else
{
onComplete(ExecuteRequest(request));
}
}
private static bool TryGetRestoreTag(AutoArchiveCommandRequest request, out string tag, out AutoArchiveCommandResult? validationResult)
{
tag = "";
validationResult = null;
string text = request.Command;
string[] array = request.Args?.ToArray() ?? Array.Empty<string>();
if (string.IsNullOrWhiteSpace(text) && array.Length != 0)
{
text = array[0];
}
if (!string.Equals(text, "zs_archive_restore", StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (array.Length < 2)
{
validationResult = AutoArchiveCommandResult.Fail("Syntax: zs_archive_restore tag");
return true;
}
tag = array[1];
return true;
}
[IteratorStateMachine(typeof(<ExecuteRestoreAsync>d__24))]
private static IEnumerator ExecuteRestoreAsync(string tag, Action<AutoArchiveCommandResult> onComplete)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ExecuteRestoreAsync>d__24(0)
{
tag = tag,
onComplete = onComplete
};
}
private static AutoArchiveCommandResult ExecuteRequest(AutoArchiveCommandRequest request)
{
List<string> messages = new List<string>();
try
{
string text = request.Command;
string[] array = request.Args?.ToArray() ?? Array.Empty<string>();
if (string.IsNullOrWhiteSpace(text) && array.Length != 0)
{
text = array[0];
}
switch (text)
{
case "zs_archive_scan":
ExecuteScan(array, messages);
break;
case "zs_archive_status":
ExecuteStatus(messages);
break;
case "zs_archive_player":
ExecutePlayer(array, messages);
break;
case "zs_archive_list":
ExecuteList(messages);
break;
case "zs_archive_mark_seen":
ExecuteMarkSeen(array, messages);
break;
case "zs_archive_ignore_player":
ExecuteIgnorePlayer(array, messages);
break;
case "zs_archive_restore":
ExecuteRestore(array, messages);
break;
case "zs_archive_schedule":
ExecuteSchedule(array, messages);
break;
case "zs_archive_debug_zone":
ExecuteDebugZone(array, messages);
break;
default:
throw new InvalidOperationException("Unsupported auto archive command '" + text + "'.");
}
return AutoArchiveCommandResult.Ok(messages);
}
catch (Exception ex)
{
_logger.LogError((object)$"Auto archive command '{request.Command}' failed: {ex}");
return AutoArchiveCommandResult.Fail(ex.Message);
}
}
private static void ExecuteScan(string[] args, List<string> messages)
{
ParseMode(args.Skip(1), out var dryRun, out var reset);
if (!AutoArchiveService.QueueManualScan(dryRun, reset))
{
messages.Add("Auto archive scan could not be started. World may not be ready or another scan is running.");
}
else
{
messages.Add("Auto archive scan started.");
}
}
private static void ExecuteStatus(List<string> messages)
{
AutoArchiveState state = AutoArchiveStore.State;
int num = state.Players.Sum((PlayerActivityRecord player) => player.PlayerIds.Count);
string arg = (AutoArchiveConfig.Enabled ? $"enabled, interval: {AutoArchiveConfig.ScanIntervalMinutes} minute(s), next auto scan: {FormatDate(GetNextAutoScanUtc(state.LastAutoScanUtc))}" : "disabled (Scan Interval Minutes = 0)");
messages.Add($"Auto archive automatic scan: {arg}, dry run: {AutoArchiveConfig.DryRun}, reset after save: {AutoArchiveConfig.ResetAfterSave}.");
messages.Add($"Players: {state.Players.Count} platform record(s), {num} playerID(s), ignored: {state.IgnoredPlayerIds.Count}.");
messages.Add($"Runs: {state.Runs.Count}, last scan: {FormatDate(state.LastScanUtc)}, last auto scan: {FormatDate(state.LastAutoScanUtc)}.");
messages.Add("Activity file: " + AutoArchiveStore.FilePath);
}
private static void ExecutePlayer(string[] args, List<string> messages)
{
if (args.Length < 2)
{
throw new InvalidOperationException("Syntax: zs_archive_player steamID [dry|save|reset]");
}
string targetLabel;
List<long> targetPlayerIds = ResolveTargetPlayerIds(args[1], out targetLabel);
ParseMode(args.Skip(2), out var dryRun, out var reset);
if (!AutoArchiveService.QueueManualScan(dryRun, reset, targetPlayerIds))
{
messages.Add("Auto archive player scan could not be started. World may not be ready or another scan is running.");
}
else
{
messages.Add("Auto archive scan started for " + targetLabel + ".");
}
}
private static void ExecuteList(List<string> messages)
{
List<ArchiveRunRecord> list = AutoArchiveStore.State.Runs.OrderByDescending((ArchiveRunRecord run) => run.CreatedUtc).Take(10).ToList();
if (list.Count == 0)
{
messages.Add("No auto archive runs recorded.");
return;
}
foreach (ArchiveRunRecord item in list)
{
messages.Add($"{item.RunId}: dry={item.DryRun}, reset={item.ResetAfterSave}, candidates={item.CandidateZones}, processed={item.ProcessedZones}, clusters={item.Clusters.Count}");
foreach (ArchiveClusterRecord item2 in item.Clusters.Take(5))
{
string text = (string.IsNullOrWhiteSpace(item2.Tag) ? "-" : item2.Tag);
messages.Add($" {item2.Status} {text}: zones={item2.Zones.Count}, pieces={item2.PieceCount}, reason={item2.Reason}");
}
}
}
private static void ExecuteMarkSeen(string[] args, List<string> messages)
{
if (args.Length < 2 || !long.TryParse(args[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
throw new InvalidOperationException("Syntax: zs_archive_mark_seen playerID");
}
AutoArchiveStore.RecordManualPlayerSeen(result, DateTime.UtcNow);
AutoArchiveStore.Flush(force: true);
messages.Add($"Marked playerID {result} as seen now.");
}
private static void ExecuteIgnorePlayer(string[] args, List<string> messages)
{
if (args.Length < 2 || !long.TryParse(args[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
throw new InvalidOperationException("Syntax: zs_archive_ignore_player playerID [on|off]");
}
bool flag = args.Length < 3 || !string.Equals(args[2], "off", StringComparison.OrdinalIgnoreCase);
AutoArchiveStore.SetIgnored(result, flag);
AutoArchiveStore.Flush(force: true);
messages.Add(flag ? $"playerID {result} is now protected from auto archive." : $"playerID {result} is no longer ignored.");
}
private static void ExecuteRestore(string[] args, List<string> messages)
{
if (args.Length < 2)
{
throw new InvalidOperationException("Syntax: zs_archive_restore tag");
}
ZoneBundleCommandResult zoneBundleCommandResult = ZoneBundleCommands.RestoreTagToOriginalZones(args[1]);
messages.Add(zoneBundleCommandResult.Message);
}
private static void ExecuteSchedule(string[] args, List<string> messages)
{
if (args.Length < 2 || string.Equals(args[1], "status", StringComparison.OrdinalIgnoreCase))
{
messages.Add(BuildScheduleMessage());
return;
}
string a = args[1];
if (string.Equals(a, "now", StringComparison.OrdinalIgnoreCase))
{
AutoArchiveStore.SetLastAutoScanUtc(DateTime.UtcNow);
AutoArchiveStore.Flush(force: true);
messages.Add(BuildScheduleMessage());
return;
}
if (string.Equals(a, "clear", StringComparison.OrdinalIgnoreCase))
{
AutoArchiveStore.SetLastAutoScanUtc(DateTime.MinValue);
AutoArchiveStore.Flush(force: true);
messages.Add(BuildScheduleMessage());
return;
}
if (args.Length < 3)
{
throw new InvalidOperationException("Syntax: zs_archive_schedule [status|now|clear|last yyyy-MM-dd HH:mm|next yyyy-MM-dd HH:mm]");
}
DateTime dateTime = ParseScheduleDate(args.Skip(2));
if (string.Equals(a, "last", StringComparison.OrdinalIgnoreCase))
{
AutoArchiveStore.SetLastAutoScanUtc(dateTime);
AutoArchiveStore.Flush(force: true);
messages.Add(BuildScheduleMessage());
return;
}
if (string.Equals(a, "next", StringComparison.OrdinalIgnoreCase))
{
if (!AutoArchiveConfig.Enabled)
{
throw new InvalidOperationException("Automatic auto archive scans are disabled because Scan Interval Minutes is 0.");
}
AutoArchiveStore.SetLastAutoScanUtc(dateTime - TimeSpan.FromMinutes(AutoArchiveConfig.ScanIntervalMinutes));
AutoArchiveStore.Flush(force: true);
messages.Add(BuildScheduleMessage());
return;
}
throw new InvalidOperationException("Syntax: zs_archive_schedule [status|now|clear|last yyyy-MM-dd HH:mm|next yyyy-MM-dd HH:mm]");
}
private static void ExecuteDebugZone(string[] args, List<string> messages)
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: 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_0037: Unknown result type (might be due to invalid IL or missing references)
Vector2i val = ParseZoneSpec(args.Skip(1));
AutoArchiveZoneDebugReport autoArchiveZoneDebugReport = BuildZoneDebugReport(val);
string text = WriteZoneDebugReport(autoArchiveZoneDebugReport);
messages.Add($"Archive debug zone ({val.x},{val.y}): zdo={autoArchiveZoneDebugReport.Summary.TotalZdos}, candidatePieces={autoArchiveZoneDebugReport.Summary.AutoArchiveCandidatePieces}, creators={autoArchiveZoneDebugReport.Summary.CandidateCreators}, wouldCandidate={autoArchiveZoneDebugReport.Summary.WouldBeCandidateZone}.");
messages.Add("Reason: " + autoArchiveZoneDebugReport.Summary.Reason);
messages.Add("Wrote YAML: " + text);
}
private static string BuildScheduleMessage()
{
DateTime lastAutoScanUtc = AutoArchiveStore.State.LastAutoScanUtc;
if (!AutoArchiveConfig.Enabled)
{
return "Auto archive schedule: disabled (Scan Interval Minutes = 0), last auto scan=" + FormatDate(lastAutoScanUtc) + ".";
}
return $"Auto archive schedule: interval={AutoArchiveConfig.ScanIntervalMinutes} minute(s), last auto scan={FormatDate(lastAutoScanUtc)}, next auto scan={FormatDate(GetNextAutoScanUtc(lastAutoScanUtc))}.";
}
private static AutoArchiveZoneDebugReport BuildZoneDebugReport(Vector2i zone)
{
//IL_003d: 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_0098: Unknown result type (might be due to invalid IL or missing references)
//IL_0139: Unknown result type (might be due to invalid IL or missing references)
//IL_0145: Unknown result type (might be due to invalid IL or missing references)
if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null)
{
throw new InvalidOperationException("World ZDO systems are not ready.");
}
List<ZDO> list = new List<ZDO>();
ZDOMan.instance.FindObjects(zone, list);
DateTime utcNow = DateTime.UtcNow;
AutoArchiveZoneDebugReport autoArchiveZoneDebugReport = new AutoArchiveZoneDebugReport();
ZNet instance = ZNet.instance;
autoArchiveZoneDebugReport.World = ((instance != null) ? instance.GetWorldName() : null) ?? "unknown";
autoArchiveZoneDebugReport.CreatedAt = ZoneSaviorTimestamp.Format(utcNow);
autoArchiveZoneDebugReport.Zone = new ZoneBundleZone
{
X = zone.x,
Z = zone.y
};
autoArchiveZoneDebugReport.Settings = new AutoArchiveZoneDebugSettings
{
DryRun = AutoArchiveConfig.DryRun,
ResetAfterSave = AutoArchiveConfig.ResetAfterSave,
InactiveDays = AutoArchiveConfig.InactiveDays,
UnknownOwnerGraceDays = AutoArchiveConfig.UnknownOwnerGraceDays,
MinimumPiecesPerCluster = AutoArchiveConfig.MinimumPiecesPerCluster,
SmallClusterAction = AutoArchiveConfig.SmallClusterAction.ToString(),
MaxZonesPerRun = AutoArchiveConfig.MaxZonesPerRun
};
AutoArchiveZoneDebugReport autoArchiveZoneDebugReport2 = autoArchiveZoneDebugReport;
HashSet<ZDOID> hashSet = new HashSet<ZDOID>();
foreach (ZDO item in list)
{
if (item != null && item.IsValid() && hashSet.Add(item.m_uid))
{
AutoArchiveZoneDebugObject autoArchiveZoneDebugObject = BuildZoneDebugObject(zone, item);
autoArchiveZoneDebugReport2.Objects.Add(autoArchiveZoneDebugObject);
AddExclusionCounts(autoArchiveZoneDebugReport2.ExclusionCounts, autoArchiveZoneDebugObject.ExclusionReasons);
}
}
List<long> list2 = (from playerId in (from entry in autoArchiveZoneDebugReport2.Objects
where entry.AutoArchiveCandidatePiece
select entry.CreatorPlayerId into playerId
where playerId != 0
select playerId).Distinct()
orderby playerId
select playerId).ToList();
autoArchiveZoneDebugReport2.Creators = list2.Select((long playerId) => BuildCreatorDebug(playerId, utcNow)).ToList();
bool flag = autoArchiveZoneDebugReport2.Creators.Count > 0 && autoArchiveZoneDebugReport2.Creators.All((AutoArchiveZoneDebugCreator creator) => creator.Eligible);
autoArchiveZoneDebugReport2.Summary = new AutoArchiveZoneDebugSummary
{
TotalZdos = autoArchiveZoneDebugReport2.Objects.Count,
InRequestedZone = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.InRequestedZone),
WearNTear = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.HasWearNTear),
PlayerBuildRecipe = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.HasBuildRecipe),
AutoArchiveCandidatePieces = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.AutoArchiveCandidatePiece),
CandidateCreators = list2.Count,
ObjectDbReady = ((Object)(object)ObjectDB.instance != (Object)null),
WouldBeCandidateZone = (autoArchiveZoneDebugReport2.Objects.Any((AutoArchiveZoneDebugObject entry) => entry.AutoArchiveCandidatePiece) && flag)
};
autoArchiveZoneDebugReport2.Summary.Reason = BuildDebugSummaryReason(autoArchiveZoneDebugReport2, flag);
return autoArchiveZoneDebugReport2;
}
private static AutoArchiveZoneDebugObject BuildZoneDebugObject(Vector2i requestedZone, ZDO zdo)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_0186: Unknown result type (might be due to invalid IL or missing references)
//IL_0194: Unknown result type (might be due to invalid IL or missing references)
//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
Vector3 position = zdo.GetPosition();
Vector2i zone = ZoneSystem.GetZone(position);
GameObject prefab = ZNetScene.instance.GetPrefab(zdo.GetPrefab());
bool flag = (Object)(object)prefab != (Object)null && Object.op_Implicit((Object)(object)prefab);
bool flag2 = flag && (Object)(object)prefab.GetComponent<ZNetView>() != (Object)null;
bool flag3 = flag && (Object)(object)prefab.GetComponent<WearNTear>() != (Object)null;
bool hasPiece = flag && (Object)(object)prefab.GetComponent<Piece>() != (Object)null;
bool flag4 = flag && ZoneBlueprintCommands.HasBuildRecipe(prefab);
bool flag5 = zone.x == requestedZone.x && zone.y == requestedZone.y;
long @long = zdo.GetLong(ZDOVars.s_creator, 0L);
List<string> list = new List<string>();
if (!flag5)
{
list.Add("outside_requested_zone");
}
if (!flag)
{
list.Add("prefab_missing");
}
if (flag && !flag2)
{
list.Add("no_znetview");
}
if (!flag3)
{
list.Add("not_wearntear");
}
if (@long == 0L)
{
list.Add("creatorless_or_missing_creator");
}
if (flag3 && !flag4)
{
list.Add("no_player_build_recipe_or_resource_cost");
}
bool flag6 = flag5 && @long != 0 && flag3 && flag4;
if (flag6)
{
list.Clear();
}
AutoArchiveZoneDebugObject autoArchiveZoneDebugObject = new AutoArchiveZoneDebugObject();
autoArchiveZoneDebugObject.ZdoId = ((object)(ZDOID)(ref zdo.m_uid)).ToString();
autoArchiveZoneDebugObject.PrefabHash = zdo.GetPrefab();
autoArchiveZoneDebugObject.Prefab = (flag ? Utils.GetPrefabName(prefab) : "");
autoArchiveZoneDebugObject.Position = new float[3]
{
Round(position.x),
Round(position.y),
Round(position.z)
};
autoArchiveZoneDebugObject.ObjectZone = new ZoneBundleZone
{
X = zone.x,
Z = zone.y
};
autoArchiveZoneDebugObject.CreatorPlayerId = @long;
autoArchiveZoneDebugObject.CreatorName = zdo.GetString(ZDOVars.s_creatorName, "");
autoArchiveZoneDebugObject.HasPrefab = flag;
autoArchiveZoneDebugObject.HasZNetView = flag2;
autoArchiveZoneDebugObject.HasWearNTear = flag3;
autoArchiveZoneDebugObject.HasPiece = hasPiece;
autoArchiveZoneDebugObject.HasBuildRecipe = flag4;
autoArchiveZoneDebugObject.InRequestedZone = flag5;
autoArchiveZoneDebugObject.AutoArchiveCandidatePiece = flag6;
autoArchiveZoneDebugObject.ExclusionReasons = list;
return autoArchiveZoneDebugObject;
}
private static AutoArchiveZoneDebugCreator BuildCreatorDebug(long playerId, DateTime utcNow)
{
AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = AutoArchiveStore.EvaluateCreatorArchiveEligibility(playerId, utcNow, AutoArchiveConfig.InactiveDays, AutoArchiveConfig.UnknownOwnerGraceDays, recordUnknownPlayer: false);
return new AutoArchiveZoneDebugCreator
{
PlayerId = autoArchiveCreatorEligibility.PlayerId,
PlatformId = autoArchiveCreatorEligibility.PlatformId,
Names = autoArchiveCreatorEligibility.Names,
RecordedInActivity = autoArchiveCreatorEligibility.RecordedInActivity,
UnknownActivityRecord = autoArchiveCreatorEligibility.UnknownActivityRecord,
Ignored = autoArchiveCreatorEligibility.Ignored,
Protected = autoArchiveCreatorEligibility.Protected,
Eligible = autoArchiveCreatorEligibility.Eligible,
Reason = autoArchiveCreatorEligibility.Reason
};
}
private static string BuildDebugSummaryReason(AutoArchiveZoneDebugReport report, bool allCreatorsEligible)
{
if (report.Summary.AutoArchiveCandidatePieces == 0)
{
return "No WearNTear with a non-zero creator and a registered player build recipe was found in this zone.";
}
if (!allCreatorsEligible)
{
string text = string.Join("; ", from creator in report.Creators
where !creator.Eligible
select creator.Reason);
return "At least one candidate creator is not archive-eligible: " + text;
}
if (report.Summary.AutoArchiveCandidatePieces < AutoArchiveConfig.MinimumPiecesPerCluster)
{
return $"Zone has candidate pieces, but this single-zone piece count is below Minimum Pieces Per Cluster ({report.Summary.AutoArchiveCandidatePieces}/{AutoArchiveConfig.MinimumPiecesPerCluster}). Cluster adjacency may still change the final action.";
}
return "This zone would be an auto archive candidate before cluster adjacency and max-zones-per-run checks.";
}
private static string WriteZoneDebugReport(AutoArchiveZoneDebugReport report)
{
string text = Path.Combine(ZoneSaviorPlugin.DataStorageFullPath, "Diagnostics");
Directory.CreateDirectory(text);
string path = string.Format("archive_debug_zone_{0}_{1}_{2}.yml", report.Zone.X, report.Zone.Z, DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture));
string text2 = Path.Combine(text, path);
File.WriteAllText(text2, ZoneBundleSerialization.Serialize(report));
return text2;
}
private static Vector2i ParseZoneSpec(IEnumerable<string> values)
{
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
string input = string.Join(" ", values).Trim();
Match match = ZoneSpecPattern.Match(input);
if (!match.Success || !int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
{
throw new InvalidOperationException("Syntax: zs_archive_debug_zone (x,z)");
}
return new Vector2i(result, result2);
}
private static void AddExclusionCounts(Dictionary<string, int> counts, IEnumerable<string> reasons)
{
foreach (string reason in reasons)
{
counts.TryGetValue(reason, out var value);
counts[reason] = value + 1;
}
}
private static float Round(float value)
{
return Mathf.Round(value * 1000f) / 1000f;
}
private static void ParseMode(IEnumerable<string> args, out bool? dryRun, out bool? reset)
{
dryRun = null;
reset = null;
foreach (string arg in args)
{
if (string.Equals(arg, "dry", StringComparison.OrdinalIgnoreCase) || string.Equals(arg, "dry-run", StringComparison.OrdinalIgnoreCase))
{
dryRun = true;
reset = false;
continue;
}
if (string.Equals(arg, "save", StringComparison.OrdinalIgnoreCase))
{
dryRun = false;
reset = false;
continue;
}
if (string.Equals(arg, "reset", StringComparison.OrdinalIgnoreCase))
{
dryRun = false;
reset = true;
continue;
}
throw new InvalidOperationException("Unknown archive mode '" + arg + "'. Use dry, save, or reset.");
}
}
private static List<long> ResolveTargetPlayerIds(string target, out string targetLabel)
{
if (AutoArchiveStore.TryGetPlayerIdsBySteamId(target, out List<long> playerIds, out string normalizedSteamId))
{
targetLabel = "steamID " + normalizedSteamId + " (playerID " + string.Join(", ", playerIds) + ")";
return playerIds;
}
if (IsLikelySteamId(target))
{
throw new InvalidOperationException("No known playerID is linked to SteamID " + target + ". The player must have joined while ZoneSavior activity tracking was active.");
}
throw new InvalidOperationException("Syntax: zs_archive_player steamID [dry|save|reset]");
}
private static bool IsLikelySteamId(string target)
{
if (string.IsNullOrWhiteSpace(target))
{
return false;
}
string text = target.Trim();
if (text.StartsWith("steam:", StringComparison.OrdinalIgnoreCase))
{
text = text.Substring("steam:".Length);
}
return new string(text.Where(char.IsDigit).ToArray()).Length >= 15;
}
private static void EnsureCommandReady()
{
if ((Object)(object)ZNet.instance == (Object)null)
{
throw new InvalidOperationException("World is not ready.");
}
if (!ZNet.instance.IsServer() && ZRoutedRpc.instance == null)
{
throw new InvalidOperationException("Server RPC is not ready.");
}
if (!ZNet.instance.IsServer() || (ZNet.instance.IsServer() && (Object)(object)Player.m_localPlayer == (Object)null) || ZNet.instance.LocalPlayerIsAdminOrHost())
{
return;
}
throw new InvalidOperationException("Admin only.");
}
private static bool IsAuthorizedSender(long sender)
{
ZNetPeer peer = ZNet.instance.GetPeer(sender);
object obj;
if (peer == null)
{
obj = null;
}
else
{
ZRpc rpc = peer.m_rpc;
if (rpc == null)
{
obj = null;
}
else
{
ISocket socket = rpc.m_socket;
obj = ((socket != null) ? socket.GetHostName() : null);
}
}
if (obj == null)
{
obj = "";
}
string text = (string)obj;
if (text.Length > 0)
{
return ZNet.instance.IsAdmin(text);
}
return false;
}
private static void ShowResult(AutoArchiveCommandResult result, Terminal? terminal)
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
MessageType val = (MessageType)(result.Success ? 1 : 2);
foreach (string item in (result.Messages.Count > 0) ? result.Messages : new List<string>(1) { result.Success ? "Done." : "Command failed." })
{
_logger.LogInfo((object)item);
if (terminal != null)
{
terminal.AddString(item);
}
if ((Object)(object)Player.m_localPlayer != (Object)null)
{
((Character)Player.m_localPlayer).Message(val, item, 0, (Sprite)null);
}
}
}
private static string FormatDate(DateTime value)
{
if (!(value == DateTime.MinValue))
{
return ZoneSaviorTimestamp.Format(value);
}
return "-";
}
private static DateTime GetNextAutoScanUtc(DateTime lastAutoScanUtc)
{
if (AutoArchiveConfig.Enabled && !(lastAutoScanUtc == DateTime.MinValue))
{
return DateTime.SpecifyKind(lastAutoScanUtc, DateTimeKind.Utc) + TimeSpan.FromMinutes(AutoArchiveConfig.ScanIntervalMinutes);
}
return DateTime.MinValue;
}
private static DateTime ParseScheduleDate(IEnumerable<string> values)
{
string text = string.Join(" ", values).Trim();
if (string.IsNullOrWhiteSpace(text))
{
throw new InvalidOperationException("Missing schedule date.");
}
DateTimeStyles styles = ((text.EndsWith("Z", StringComparison.OrdinalIgnoreCase) || HasTimezoneOffset(text)) ? (DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal) : (DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal));
if (!DateTime.TryParse(text, CultureInfo.InvariantCulture, styles, out var result) && !DateTime.TryParse(text, CultureInfo.CurrentCulture, styles, out result))
{
throw new InvalidOperationException("Invalid schedule date '" + text + "'. Use local server time like 2026-05-02 15:00, or UTC like 2026-05-02T06:00:00Z.");
}
return DateTime.SpecifyKind(result, DateTimeKind.Utc);
}
private static bool HasTimezoneOffset(string value)
{
string text = value.Trim();
if (text.Length < 6)
{
return false;
}
int num = text.Length - 6;
char c = text[num];
if ((c == '+' || c == '-') && char.IsDigit(text[num + 1]) && char.IsDigit(text[num + 2]) && text[num + 3] == ':' && char.IsDigit(text[num + 4]))
{
return char.IsDigit(text[num + 5]);
}
return false;
}
}
internal sealed class AutoArchiveCommandRequest
{
public string Command { get; set; } = "";
public List<string> Args { get; set; } = new List<string>();
}
internal sealed class AutoArchiveCommandResult
{
public bool Success { get; set; }
public List<string> Messages { get; set; } = new List<string>();
public static AutoArchiveCommandResult Ok(IEnumerable<string> messages)
{
return new AutoArchiveCommandResult
{
Success = true,
Messages = messages.ToList()
};
}
public static AutoArchiveCommandResult Fail(string message)
{
return new AutoArchiveCommandResult
{
Success = false,
Messages = new List<string>(1) { message }
};
}
}
internal enum AutoArchiveSmallClusterAction
{
Skip,
SaveAnyway,
ResetWithoutSave
}
internal sealed class AutoArchiveState
{
public int Version { get; set; } = 1;
[YamlIgnore]
public DateTime LastScanUtc { get; set; } = DateTime.MinValue;
public string LastScanAt
{
get
{
return ZoneSaviorTimestamp.Format(LastScanUtc);
}
set
{
LastScanUtc = ZoneSaviorTimestamp.ParseUtc(value);
}
}
[YamlIgnore]
public DateTime LastAutoScanUtc { get; set; } = DateTime.MinValue;
public string LastAutoScanAt
{
get
{
return ZoneSaviorTimestamp.Format(LastAutoScanUtc);
}
set
{
LastAutoScanUtc = ZoneSaviorTimestamp.ParseUtc(value);
}
}
public List<PlayerActivityRecord> Players { get; set; } = new List<PlayerActivityRecord>();
public List<ArchiveRunRecord> Runs { get; set; } = new List<ArchiveRunRecord>();
public List<long> IgnoredPlayerIds { get; set; } = new List<long>();
}
internal sealed class PlayerActivityRecord
{
public string PlatformId { get; set; } = "";
public List<long> PlayerIds { get; set; } = new List<long>();
public List<string> Names { get; set; } = new List<string>();
[YamlIgnore]
public DateTime FirstSeenUtc { get; set; } = DateTime.MinValue;
public string FirstSeenAt
{
get
{
return ZoneSaviorTimestamp.Format(FirstSeenUtc);
}
set
{
FirstSeenUtc = ZoneSaviorTimestamp.ParseUtc(value);
}
}
[YamlIgnore]
public DateTime LastSeenUtc { get; set; } = DateTime.MinValue;
public string LastSeenAt
{
get
{
return ZoneSaviorTimestamp.Format(LastSeenUtc);
}
set
{
LastSeenUtc = ZoneSaviorTimestamp.ParseUtc(value);
}
}
}
internal sealed class ArchiveRunRecord
{
public string RunId { get; set; } = "";
[YamlIgnore]
public DateTime CreatedUtc { get; set; } = DateTime.MinValue;
public string CreatedAt
{
get
{
return ZoneSaviorTimestamp.Format(CreatedUtc);
}
set
{
CreatedUtc = ZoneSaviorTimestamp.ParseUtc(value);
}
}
public bool Manual { get; set; }
public bool DryRun { get; set; }
public bool ResetAfterSave { get; set; }
public List<long> TargetPlayerIds { get; set; } = new List<long>();
public int ScannedZdos { get; set; }
public int StructureZdos { get; set; }
public int CandidateZones { get; set; }
public int ProcessedZones { get; set; }
public List<ArchiveClusterRecord> Clusters { get; set; } = new List<ArchiveClusterRecord>();
public List<string> Messages { get; set; } = new List<string>();
}
internal sealed class ArchiveClusterRecord
{
public string Tag { get; set; } = "";
public string Status { get; set; } = "";
public string Reason { get; set; } = "";
public int PieceCount { get; set; }
public int TerrainLoaded { get; set; }
public int TerrainCaptured { get; set; }
public List<long> Creators { get; set; } = new List<long>();
public List<ZoneBundleZone> Zones { get; set; } = new List<ZoneBundleZone>();
}
internal sealed class AutoArchiveScanOptions
{
public bool Manual { get; set; }
public bool DryRun { get; set; }
public bool ResetAfterSave { get; set; }
public List<long> TargetPlayerIds { get; set; } = new List<long>();
}
internal sealed class AutoArchiveCreatorEligibility
{
public long PlayerId { get; set; }
public string PlatformId { get; set; } = "";
public List<string> Names { get; set; } = new List<string>();
public bool RecordedInActivity { get; set; }
public bool UnknownActivityRecord { get; set; }
public bool Ignored { get; set; }
public bool Protected { get; set; }
public bool Eligible { get; set; }
public string Reason { get; set; } = "";
}
internal sealed class ZoneBundleArchiveResult
{
public bool Success { get; set; }
public string Message { get; set; } = "";
public string Tag { get; set; } = "";
public string ManifestPath { get; set; } = "";
public int ZoneCount { get; set; }
public int EntryCount { get; set; }
public int MonsterCount { get; set; }
public int TerrainLoaded { get; set; }
public int TerrainCaptured { get; set; }
}
internal sealed class ZoneBundleResetResult
{
public bool Success { get; set; }
public string Message { get; set; } = "";
public int ZoneCount { get; set; }
public int RemovedCount { get; set; }
public int RemainingWearNTearCount { get; set; }
}
internal sealed class AutoArchiveZoneDebugReport
{
public int Version { get; set; } = 1;
public string World { get; set; } = "";
public string CreatedAt { get; set; } = "";
public ZoneBundleZone Zone { get; set; } = new ZoneBundleZone();
public AutoArchiveZoneDebugSettings Settings { get; set; } = new AutoArchiveZoneDebugSettings();
public AutoArchiveZoneDebugSummary Summary { get; set; } = new AutoArchiveZoneDebugSummary();
public List<AutoArchiveZoneDebugCreator> Creators { get; set; } = new List<AutoArchiveZoneDebugCreator>();
public Dictionary<string, int> ExclusionCounts { get; set; } = new Dictionary<string, int>();
public List<AutoArchiveZoneDebugObject> Objects { get; set; } = new List<AutoArchiveZoneDebugObject>();
}
internal sealed class AutoArchiveZoneDebugSettings
{
public bool DryRun { get; set; }
public bool ResetAfterSave { get; set; }
public int InactiveDays { get; set; }
public int UnknownOwnerGraceDays { get; set; }
public int MinimumPiecesPerCluster { get; set; }
public string SmallClusterAction { get; set; } = "";
public int MaxZonesPerRun { get; set; }
}
internal sealed class AutoArchiveZoneDebugSummary
{
public int TotalZdos { get; set; }
public int InRequestedZone { get; set; }
public int WearNTear { get; set; }
public int PlayerBuildRecipe { get; set; }
public int AutoArchiveCandidatePieces { get; set; }
public int CandidateCreators { get; set; }
public bool ObjectDbReady { get; set; }
public bool WouldBeCandidateZone { get; set; }
public string Reason { get; set; } = "";
}
internal sealed class AutoArchiveZoneDebugCreator
{
public long PlayerId { get; set; }
public string PlatformId { get; set; } = "";
public List<string> Names { get; set; } = new List<string>();
public bool RecordedInActivity { get; set; }
public bool UnknownActivityRecord { get; set; }
public bool Ignored { get; set; }
public bool Protected { get; set; }
public bool Eligible { get; set; }
public string Reason { get; set; } = "";
}
internal sealed class AutoArchiveZoneDebugObject
{
public string ZdoId { get; set; } = "";
public int PrefabHash { get; set; }
public string Prefab { get; set; } = "";
public float[] Position { get; set; } = new float[3];
public ZoneBundleZone ObjectZone { get; set; } = new ZoneBundleZone();
public long CreatorPlayerId { get; set; }
public string CreatorName { get; set; } = "";
public bool HasPrefab { get; set; }
public bool HasZNetView { get; set; }
public bool HasWearNTear { get; set; }
public bool HasPiece { get; set; }
public bool HasBuildRecipe { get; set; }
public bool InRequestedZone { get; set; }
public bool AutoArchiveCandidatePiece { get; set; }
public List<string> ExclusionReasons { get; set; } = new List<string>();
}
internal static class AutoArchiveScanner
{
private sealed class AutoArchiveZoneInfo
{
public Vector2i Zone { get; }
public int PieceCount { get; set; }
public HashSet<long> Creators { get; } = new HashSet<long>();
public Dictionary<long, int> CreatorPieceCounts { get; } = new Dictionary<long, int>();
public AutoArchiveZoneInfo(Vector2i zone)
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
Zone = zone;
}
public void AddCreator(long creator)
{
PieceCount++;
Creators.Add(creator);
CreatorPieceCounts.TryGetValue(creator, out var value);
CreatorPieceCounts[creator] = value + 1;
}
}
[CompilerGenerated]
private sealed class <>c__DisplayClass3_0
{
public ZoneBundleArchiveResult saveResult;
internal void <Run>b__4(ZoneBundleArchiveResult result)
{
saveResult = result;
}
}
[CompilerGenerated]
private sealed class <>c__DisplayClass3_1
{
public ZoneBundleResetResult resetResult;
internal void <Run>b__5(ZoneBundleResetResult result)
{
resetResult = result;
}
}
[CompilerGenerated]
private sealed class <Run>d__3 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public AutoArchiveScanOptions options;
public Action<ArchiveRunRecord> onComplete;
private <>c__DisplayClass3_0 <>8__1;
private <>c__DisplayClass3_1 <>8__2;
private DateTime <utcNow>5__2;
private ArchiveRunRecord <run>5__3;
private Dictionary<Vector2i, AutoArchiveZoneInfo> <zoneInfos>5__4;
private Dictionary<Vector2i, AutoArchiveZoneInfo> <candidates>5__5;
private int <processedZones>5__6;
private int <clusterIndex>5__7;
private IEnumerator<List<Vector2i>> <>7__wrap7;
private List<Vector2i> <cluster>5__9;
private ArchiveClusterRecord <record>5__10;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <Run>d__3(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || (uint)(num - 2) <= 5u)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<>8__1 = null;
<>8__2 = null;
<run>5__3 = null;
<zoneInfos>5__4 = null;
<candidates>5__5 = null;
<>7__wrap7 = null;
<cluster>5__9 = null;
<record>5__10 = null;
<>1__state = -2;
}
private bool MoveNext()
{
try
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<utcNow>5__2 = DateTime.UtcNow;
<run>5__3 = new ArchiveRunRecord
{
RunId = <utcNow>5__2.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture),
CreatedUtc = <utcNow>5__2,
Manual = options.Manual,
DryRun = options.DryRun,
ResetAfterSave = options.ResetAfterSave,
TargetPlayerIds = options.TargetPlayerIds.ToList()
};
if (!IsWorldReady())
{
<run>5__3.Messages.Add("World is not ready.");
onComplete(<run>5__3);
return false;
}
PlayerActivityTracker.TrackOnlinePlayers(<utcNow>5__2);
<zoneInfos>5__4 = new Dictionary<Vector2i, AutoArchiveZoneInfo>();
<>2__current = ScanZdosBySector(<utcNow>5__2, <zoneInfos>5__4, <run>5__3);
<>1__state = 1;
return true;
case 1:
{
<>1__state = -1;
<candidates>5__5 = BuildCandidateZones(<zoneInfos>5__4, <utcNow>5__2, options.TargetPlayerIds, <run>5__3);
<run>5__3.CandidateZones = <candidates>5__5.Count;
List<List<Vector2i>> source = BuildClusters(<candidates>5__5);
<processedZones>5__6 = 0;
<clusterIndex>5__7 = 0;
<>7__wrap7 = (from cluster in source
orderby cluster.Min((Vector2i zone) => zone.x), cluster.Min((Vector2i zone) => zone.y)
select cluster).GetEnumerator();
<>1__state = -3;
break;
}
case 2:
<>1__state = -3;
break;
case 3:
<>1__state = -3;
if (<>8__1.saveResult == null)
{
<record>5__10.Status = "failed";
<record>5__10.Reason = "save failed: archive coroutine did not return a result";
<run>5__3.Clusters.Add(<record>5__10);
_logger.LogError((object)("Auto archive save failed for tag '" + <record>5__10.Tag + "': archive coroutine did not return a result."));
<>2__current = null;
<>1__state = 4;
return true;
}
<record>5__10.TerrainLoaded = <>8__1.saveResult.TerrainLoaded;
<record>5__10.TerrainCaptured = <>8__1.saveResult.TerrainCaptured;
if (!<>8__1.saveResult.Success)
{
<record>5__10.Status = "failed";
<record>5__10.Reason = <>8__1.saveResult.Message;
<run>5__3.Clusters.Add(<record>5__10);
break;
}
<record>5__10.Status = "saved";
<record>5__10.Reason = <>8__1.saveResult.Message;
if (options.ResetAfterSave)
{
if (!AutoArchiveConfig.RequireLoadedTerrainForReset || <>8__1.saveResult.TerrainCaptured >= <cluster>5__9.Count)
{
<>8__2 = new <>c__DisplayClass3_1();
<>8__2.resetResult = null;
<>2__current = ZoneBundleCommands.ResetGeneratedZonesAsync(<cluster>5__9, delegate(ZoneBundleResetResult result)
{
<>8__2.resetResult = result;
});
<>1__state = 5;
return true;
}
<record>5__10.Status = "saved-reset-skipped";
<record>5__10.Reason = $"saved, but reset skipped because usable terrain contacts were captured for {<>8__1.saveResult.TerrainCaptured}/{<cluster>5__9.Count} zones";
}
goto IL_08c2;
case 4:
<>1__state = -3;
break;
case 5:
<>1__state = -3;
if (<>8__2.resetResult == null)
{
<record>5__10.Status = "saved-reset-failed";
<record>5__10.Reason = "reset failed: reset coroutine did not return a result";
<run>5__3.Clusters.Add(<record>5__10);
_logger.LogError((object)("Auto archive reset failed for tag '" + <record>5__10.Tag + "': reset coroutine did not return a result."));
<>2__current = null;
<>1__state = 6;
return true;
}
<record>5__10.Status = (<>8__2.resetResult.Success ? "reset" : "saved-reset-failed");
<record>5__10.Reason = <>8__2.resetResult.Message;
<>8__2 = null;
goto IL_08c2;
case 6:
<>1__state = -3;
break;
case 7:
{
<>1__state = -3;
<>8__1 = null;
<record>5__10 = null;
<cluster>5__9 = null;
break;
}
IL_08c2:
<run>5__3.Clusters.Add(<record>5__10);
<processedZones>5__6 += <cluster>5__9.Count;
<>2__current = null;
<>1__state = 7;
return true;
}
while (<>7__wrap7.MoveNext())
{
<cluster>5__9 = <>7__wrap7.Current;
<>8__1 = new <>c__DisplayClass3_0();
<clusterIndex>5__7++;
<record>5__10 = BuildClusterRecord(<cluster>5__9, <candidates>5__5);
bool flag = <record>5__10.PieceCount < AutoArchiveConfig.MinimumPiecesPerCluster;
if (flag && AutoArchiveConfig.SmallClusterAction == AutoArchiveSmallClusterAction.Skip)
{
<record>5__10.Status = "skipped";
<record>5__10.Reason = $"piece count {<record>5__10.PieceCount} is below minimum {AutoArchiveConfig.MinimumPiecesPerCluster}";
<run>5__3.Clusters.Add(<record>5__10);
continue;
}
if (<processedZones>5__6 + <cluster>5__9.Count > AutoArchiveConfig.MaxZonesPerRun)
{
<record>5__10.Status = "skipped";
<record>5__10.Reason = $"max zones per run would be exceeded ({<processedZones>5__6 + <cluster>5__9.Count}/{AutoArchiveConfig.MaxZonesPerRun})";
<run>5__3.Clusters.Add(<record>5__10);
continue;
}
if (!AllCreatorsEligible(<record>5__10.Creators, <utcNow>5__2, out string reason))
{
<record>5__10.Status = "skipped";
<record>5__10.Reason = reason;
<run>5__3.Clusters.Add(<record>5__10);
continue;
}
if (flag && AutoArchiveConfig.SmallClusterAction == AutoArchiveSmallClusterAction.ResetWithoutSave)
{
if (!options.ResetAfterSave)
{
<record>5__10.Status = "skipped";
<record>5__10.Reason = $"piece count {<record>5__10.PieceCount} is below minimum {AutoArchiveConfig.MinimumPiecesPerCluster}; reset mode is not enabled";
<run>5__3.Clusters.Add(<record>5__10);
continue;
}
if (options.DryRun)
{
<record>5__10.Status = "dry-run-reset-without-save";
<record>5__10.Reason = $"small inactive cluster would be reset without saving ({<record>5__10.PieceCount}/{AutoArchiveConfig.MinimumPiecesPerCluster} pieces)";
<run>5__3.Clusters.Add(<record>5__10);
<processedZones>5__6 += <cluster>5__9.Count;
continue;
}
ZoneBundleResetResult zoneBundleResetResult = ZoneBundleCommands.ResetGeneratedZones(<cluster>5__9);
<record>5__10.Status = (zoneBundleResetResult.Success ? "reset-without-save" : "reset-without-save-failed");
<record>5__10.Reason = zoneBundleResetResult.Message;
<run>5__3.Clusters.Add(<record>5__10);
<processedZones>5__6 += <cluster>5__9.Count;
<>2__current = null;
<>1__state = 2;
return true;
}
<record>5__10.Tag = ZoneBundleCommands.MakeUniqueAutoArchiveTag(BuildTag(<clusterIndex>5__7, <cluster>5__9, <candidates>5__5));
if (options.DryRun)
{
<record>5__10.Status = "dry-run";
<record>5__10.Reason = "candidate only; dry run is enabled";
<run>5__3.Clusters.Add(<record>5__10);
<processedZones>5__6 += <cluster>5__9.Count;
continue;
}
<>8__1.saveResult = null;
<>2__current = ZoneBundleCommands.SaveZonesAsync(<cluster>5__9, <record>5__10.Tag, delegate(ZoneBundleArchiveResult result)
{
<>8__1.saveResult = result;
});
<>1__state = 3;
return true;
}
<>m__Finally1();
<>7__wrap7 = null;
<run>5__3.ProcessedZones = <processedZones>5__6;
onComplete(<run>5__3);
return false;
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<>7__wrap7 != null)
{
<>7__wrap7.Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <ScanZdosBySector>d__5 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public DateTime utcNow;
public Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos;
public ArchiveRunRecord run;
private int <batchSize>5__2;
private int <processedSinceYield>5__3;
private List<ZDO>[] <sectors>5__4;
private int <sectorIndex>5__5;
private List<ZDO> <sector>5__6;
private int <objectIndex>5__7;
private List<List<ZDO>>.Enumerator <>7__wrap7;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ScanZdosBySector>d__5(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 2)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<sectors>5__4 = null;
<sector>5__6 = null;
<>7__wrap7 = default(List<List<ZDO>>.Enumerator);
<>1__state = -2;
}
private bool MoveNext()
{
try
{
List<List<ZDO>> list;
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<batchSize>5__2 = Math.Max(1, AutoArchiveConfig.ScannerBatchSize);
<processedSinceYield>5__3 = 0;
<sectors>5__4 = ZDOMan.instance.m_objectsBySector;
<sectorIndex>5__5 = 0;
goto IL_0138;
case 1:
<>1__state = -1;
goto IL_00fb;
case 2:
{
<>1__state = -3;
goto IL_022d;
}
IL_0138:
if (<sectors>5__4 != null && <sectorIndex>5__5 < <sectors>5__4.Length)
{
<sector>5__6 = <sectors>5__4[<sectorIndex>5__5];
if (<sector>5__6 != null && <sector>5__6.Count != 0)
{
<objectIndex>5__7 = 0;
goto IL_010b;
}
goto IL_0128;
}
list = ZDOMan.instance.m_objectsByOutsideSector?.Values.ToList() ?? new List<List<ZDO>>();
<>7__wrap7 = list.GetEnumerator();
<>1__state = -3;
goto IL_025a;
IL_00fb:
<objectIndex>5__7++;
goto IL_010b;
IL_0128:
<sectorIndex>5__5++;
goto IL_0138;
IL_025a:
do
{
if (<>7__wrap7.MoveNext())
{
<sector>5__6 = <>7__wrap7.Current;
continue;
}
<>m__Finally1();
<>7__wrap7 = default(List<List<ZDO>>.Enumerator);
return false;
}
while (<sector>5__6 == null || <sector>5__6.Count == 0);
<sectorIndex>5__5 = 0;
goto IL_023d;
IL_010b:
if (<objectIndex>5__7 < <sector>5__6.Count)
{
ProcessScanZdo(<sector>5__6[<objectIndex>5__7], utcNow, zoneInfos, run);
<processedSinceYield>5__3++;
if (<processedSinceYield>5__3 >= <batchSize>5__2)
{
<processedSinceYield>5__3 = 0;
<>2__current = null;
<>1__state = 1;
return true;
}
goto IL_00fb;
}
<sector>5__6 = null;
goto IL_0128;
IL_022d:
<sectorIndex>5__5++;
goto IL_023d;
IL_023d:
if (<sectorIndex>5__5 < <sector>5__6.Count)
{
ProcessScanZdo(<sector>5__6[<sectorIndex>5__5], utcNow, zoneInfos, run);
<processedSinceYield>5__3++;
if (<processedSinceYield>5__3 >= <batchSize>5__2)
{
<processedSinceYield>5__3 = 0;
<>2__current = null;
<>1__state = 2;
return true;
}
goto IL_022d;
}
<sector>5__6 = null;
goto IL_025a;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
((IDisposable)<>7__wrap7).Dispose();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private static readonly Vector2i[] NeighborOffsets = (Vector2i[])(object)new Vector2i[8]
{
new Vector2i(-1, -1),
new Vector2i(0, -1),
new Vector2i(1, -1),
new Vector2i(-1, 0),
new Vector2i(1, 0),
new Vector2i(-1, 1),
new Vector2i(0, 1),
new Vector2i(1, 1)
};
private static ManualLogSource _logger = null;
public static void Initialize(ManualLogSource logger)
{
_logger = logger;
}
[IteratorStateMachine(typeof(<Run>d__3))]
public static IEnumerator Run(AutoArchiveScanOptions options, Action<ArchiveRunRecord> onComplete)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <Run>d__3(0)
{
options = options,
onComplete = onComplete
};
}
private static bool TryReadPlayerStructure(ZDO zdo, DateTime utcNow, out Vector2i zone, out long creator)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
zone = default(Vector2i);
creator = 0L;
if (zdo == null || !zdo.IsValid())
{
return false;
}
creator = zdo.GetLong(ZDOVars.s_creator, 0L);
if (creator == 0L)
{
return false;
}
GameObject prefab = ZNetScene.instance.GetPrefab(zdo.GetPrefab());
if ((Object)(object)prefab == (Object)null || !Object.op_Implicit((Object)(object)prefab) || (Object)(object)prefab.GetComponent<WearNTear>() == (Object)null || !ZoneBlueprintCommands.HasBuildRecipe(prefab))
{
return false;
}
string @string = zdo.GetString(ZDOVars.s_creatorName, "");
AutoArchiveStore.RecordUnknownPlayer(creator, utcNow, @string);
zone = ZoneSystem.GetZone(zdo.GetPosition());
return true;
}
[IteratorStateMachine(typeof(<ScanZdosBySector>d__5))]
private static IEnumerator ScanZdosBySector(DateTime utcNow, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, ArchiveRunRecord run)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ScanZdosBySector>d__5(0)
{
utcNow = utcNow,
zoneInfos = zoneInfos,
run = run
};
}
private static void ProcessScanZdo(ZDO zdo, DateTime utcNow, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, ArchiveRunRecord run)
{
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: 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)
run.ScannedZdos++;
if (TryReadPlayerStructure(zdo, utcNow, out var zone, out var creator))
{
run.StructureZdos++;
if (!zoneInfos.TryGetValue(zone, out AutoArchiveZoneInfo value))
{
value = (zoneInfos[zone] = new AutoArchiveZoneInfo(zone));
}
value.AddCreator(creator);
}
}
private static Dictionary<Vector2i, AutoArchiveZoneInfo> BuildCandidateZones(Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, DateTime utcNow, IReadOnlyCollection<long> targetPlayerIds, ArchiveRunRecord run)
{
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: 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)
Dictionary<Vector2i, AutoArchiveZoneInfo> dictionary = new Dictionary<Vector2i, AutoArchiveZoneInfo>();
foreach (AutoArchiveZoneInfo value in zoneInfos.Values)
{
if (targetPlayerIds.Count <= 0 || value.Creators.Any(((IEnumerable<long>)targetPlayerIds).Contains<long>))
{
if (!AllCreatorsEligible(value.Creators, utcNow, out string reason))
{
run.Messages.Add($"Skipped zone ({value.Zone.x},{value.Zone.y}): {reason}");
}
else
{
dictionary[value.Zone] = value;
}
}
}
return dictionary;
}
private static List<List<Vector2i>> BuildClusters(Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: 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_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: 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_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_009f: Unknown result type (might be due to invalid IL or missing references)
List<List<Vector2i>> list = new List<List<Vector2i>>();
HashSet<Vector2i> hashSet = candidates.Keys.ToHashSet();
Vector2i item2 = default(Vector2i);
while (hashSet.Count > 0)
{
Vector2i item = hashSet.First();
hashSet.Remove(item);
List<Vector2i> list2 = new List<Vector2i>();
Queue<Vector2i> queue = new Queue<Vector2i>();
queue.Enqueue(item);
while (queue.Count > 0)
{
Vector2i val = queue.Dequeue();
list2.Add(val);
Vector2i[] neighborOffsets = NeighborOffsets;
foreach (Vector2i val2 in neighborOffsets)
{
((Vector2i)(ref item2))..ctor(val.x + val2.x, val.y + val2.y);
if (hashSet.Contains(item2))
{
hashSet.Remove(item2);
queue.Enqueue(item2);
}
}
}
list.Add(list2);
}
return list;
}
private static ArchiveClusterRecord BuildClusterRecord(List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
{
//IL_008f: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
ArchiveClusterRecord archiveClusterRecord = new ArchiveClusterRecord
{
Zones = (from zone in cluster
orderby zone.y, zone.x
select new ZoneBundleZone
{
X = zone.x,
Z = zone.y
}).ToList()
};
HashSet<long> hashSet = new HashSet<long>();
foreach (Vector2i item in cluster)
{
AutoArchiveZoneInfo autoArchiveZoneInfo = candidates[item];
archiveClusterRecord.PieceCount += autoArchiveZoneInfo.PieceCount;
foreach (long creator in autoArchiveZoneInfo.Creators)
{
hashSet.Add(creator);
}
}
archiveClusterRecord.Creators = hashSet.OrderBy((long id) => id).ToList();
return archiveClusterRecord;
}
private static bool AllCreatorsEligible(IEnumerable<long> creators, DateTime utcNow, out string reason)
{
foreach (long creator in creators)
{
if (!AutoArchiveStore.IsCreatorArchiveEligible(creator, utcNow, AutoArchiveConfig.InactiveDays, AutoArchiveConfig.UnknownOwnerGraceDays, out reason))
{
return false;
}
}
reason = "";
return true;
}
private static string BuildTag(int index, List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
{
string arg = BuildOwnerSegment(BuildCreatorPieceCounts(cluster, candidates));
return $"auto_{arg}_c{index:D3}";
}
private static Dictionary<long, int> BuildCreatorPieceCounts(List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
{
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
Dictionary<long, int> dictionary = new Dictionary<long, int>();
foreach (Vector2i item in cluster)
{
foreach (KeyValuePair<long, int> creatorPieceCount in candidates[item].CreatorPieceCounts)
{
dictionary.TryGetValue(creatorPieceCount.Key, out var value);
dictionary[creatorPieceCount.Key] = value + creatorPieceCount.Value;
}
}
return dictionary;
}
private static string BuildOwnerSegment(IReadOnlyDictionary<long, int> creatorPieceCounts)
{
IReadOnlyDictionary<long, int> creatorPieceCounts2 = creatorPieceCounts;
List<long> list = (from creator in creatorPieceCounts2.Keys.Where((long creator) => creator != 0).Distinct()
orderby creator
select creator).ToList();
if (list.Count == 0)
{
return "unknown";
}
int value;
string text = BuildOwnerToken((from owner in list
orderby creatorPieceCounts2.TryGetValue(owner, out value) ? value : 0 descending, owner
select owner).First());
if (list.Count == 1)
{
return text;
}
return $"{text}_plus{list.Count - 1}_{BuildOwnerHash(list)}";
}
private static string BuildOwnerToken(long playerId)
{
string text = ResolvePlayerName(playerId);
string text2 = ResolveSteamId(playerId);
if (!string.Equals(text2, "unknown", StringComparison.Ordinal))
{
return text + "_s" + text2;
}
return text;
}
private static string ResolvePlayerName(long playerId)
{
if (!AutoArchiveStore.TryGetPlayerRecord(playerId, out PlayerActivityRecord record))
{
return "unknown";
}
return SanitizeTagToken(record.Names.LastOrDefault((string candidate) => !string.IsNullOrWhiteSpace(candidate) && !string.Equals(candidate, "unknown", StringComparison.OrdinalIgnoreCase) && !string.Equals(candidate, "manual", StringComparison.OrdinalIgnoreCase)) ?? "unknown");
}
private static string ResolveSteamId(long playerId)
{
if (!AutoArchiveStore.TryGetPlayerRecord(playerId, out PlayerActivityRecord record) || string.IsNullOrWhiteSpace(record.PlatformId) || !record.PlatformId.StartsWith("steam:", StringComparison.OrdinalIgnoreCase))
{
return "unknown";
}
string source = record.PlatformId.Substring("steam:".Length);
string text = new string(source.Where(char.IsDigit).ToArray());
if (text.Length >= 15)
{
return text;
}
string text2 = new string(source.Where((char character) => char.IsLetterOrDigit(character) || character == '-' || character == '_').ToArray());
if (!string.IsNullOrWhiteSpace(text2))
{
return text2;
}
return "unknown";
}
private static string BuildOwnerHash(IEnumerable<long> ownerIds)
{
uint num = 2166136261u;
foreach (long ownerId in ownerIds)
{
byte[] bytes = BitConverter.GetBytes(ownerId);
foreach (byte b in bytes)
{
num ^= b;
num *= 16777619;
}
}
return num.ToString("x8", CultureInfo.InvariantCulture);
}
private static string SanitizeTagToken(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return "unknown";
}
char[] invalidChars = Path.GetInvalidFileNameChars();
string text = new string(value.Select(delegate(char character)
{
if (invalidChars.Contains(character))
{
return '_';
}
if (char.IsWhiteSpace(character))
{
return '_';
}
return (!char.IsLetterOrDigit(character) && character != '-' && character != '_') ? '_' : character;
}).ToArray());
text = text.Trim(new char[1] { '_' });
while (text.Contains("__"))
{
text = text.Replace("__", "_");
}
if (text.Length > 32)
{
text = text.Substring(0, 32).Trim(new char[1] { '_' });
}
if (!string.IsNullOrWhiteSpace(text))
{
return text;
}
return "unknown";
}
private static bool IsWorldReady()
{
if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null)
{
return (Object)(object)ZoneSystem.instance != (Object)null;
}
return false;
}
private static string FormatDate(DateTime value)
{
if (!(value == DateTime.MinValue))
{
return value.ToString("O", CultureInfo.InvariantCulture);
}
return "-";
}
}
internal static class AutoArchiveService
{
[CompilerGenerated]
private sealed class <>c__DisplayClass13_0
{
public ArchiveRunRecord completed;
internal void <RunScan>b__0(ArchiveRunRecord run)
{
completed = run;
}
}
[CompilerGenerated]
private sealed class <RunScan>d__13 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public AutoArchiveScanOptions options;
private <>c__DisplayClass13_0 <>8__1;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <RunScan>d__13(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>8__1 = null;
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>8__1 = new <>c__DisplayClass13_0();
<>8__1.completed = null;
<>2__current = AutoArchiveScanner.Run(options, delegate(ArchiveRunRecord run)
{
<>8__1.completed = run;
});
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if (<>8__1.completed != null)
{
AutoArchiveStore.RecordRun(<>8__1.completed);
AutoArchiveStore.Flush(force: true);
_logger.LogInfo((object)$"Auto archive scan finished: {<>8__1.completed.CandidateZones} candidate zone(s), {<>8__1.completed.ProcessedZones} processed zone(s), {<>8__1.completed.Clusters.Count} cluster record(s).");
}
_scanRunning = false;
_scanCoroutine = null;
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();
}
}
private static readonly TimeSpan TrackInterval = TimeSpan.FromMinutes(10.0);
private static readonly TimeSpan FlushInterval = TimeSpan.FromMinutes(5.0);
private static ManualLogSource _logger = null;
private static bool _initialized;
private static bool _scanRunning;
private static DateTime _lastTrackUtc = DateTime.MinValue;
private static DateTime _lastFlushUtc = DateTime.MinValue;
private static Coroutine? _scanCoroutine;
public static void Initialize(ManualLogSource logger)
{
if (!_initialized)
{
_initialized = true;
_logger = logger;
PlayerActivityTracker.Initialize(logger);
AutoArchiveScanner.Initialize(logger);
}
}
public static void Update()
{
if (!IsServerReady())
{
return;
}
DateTime utcNow = DateTime.UtcNow;
if (utcNow - _lastTrackUtc >= TrackInterval)
{
_lastTrackUtc = utcNow;
PlayerActivityTracker.TrackOnlinePlayers(utcNow);
}
if (utcNow - _lastFlushUtc >= FlushInterval)
{
_lastFlushUtc = utcNow;
AutoArchiveStore.Flush();
}
if (AutoArchiveConfig.Enabled && !_scanRunning)
{
TimeSpan timeSpan = TimeSpan.FromMinutes(AutoArchiveConfig.ScanIntervalMinutes);
DateTime dateTime = DateTime.SpecifyKind(AutoArchiveStore.State.LastAutoScanUtc, DateTimeKind.Utc);
if (!(dateTime != DateTime.MinValue) || !(utcNow - dateTime < timeSpan))
{
QueueScan(new AutoArchiveScanOptions
{
Manual = false,
DryRun = AutoArchiveConfig.DryRun,
ResetAfterSave = AutoArchiveConfig.ResetAfterSave
});
}
}
}
public static bool QueueManualScan(bool? dryRunOverride = null, bool? resetAfterSaveOverride = null, IEnumerable<long>? targetPlayerIds = null)
{
if (!IsServerReady() || _scanRunning)
{
return false;
}
QueueScan(new AutoArchiveScanOptions
{
Manual = true,
DryRun = (dryRunOverride ?? AutoArchiveConfig.DryRun),
ResetAfterSave = (resetAfterSaveOverride ?? AutoArchiveConfig.ResetAfterSave),
TargetPlayerIds = ((targetPlayerIds == null) ? new List<long>() : new List<long>(targetPlayerIds))
});
return true;
}
public static void Shutdown()
{
if (_scanCoroutine != null && (Object)(object)ZoneSaviorPlugin.Instance != (Object)null)
{
((MonoBehaviour)ZoneSaviorPlugin.Instance).StopCoroutine(_scanCoroutine);
_scanCoroutine = null;
}
_scanRunning = false;
}
private static void QueueScan(AutoArchiveScanOptions options)
{
_scanRunning = true;
_logger.LogInfo((object)$"Starting auto archive scan (manual: {options.Manual}, dry run: {options.DryRun}, reset: {options.ResetAfterSave}, targets: {options.TargetPlayerIds.Count}).");
_scanCoroutine = ((MonoBehaviour)ZoneSaviorPlugin.Instance).StartCoroutine(RunScan(options));
}
[IteratorStateMachine(typeof(<RunScan>d__13))]
private static IEnumerator RunScan(AutoArchiveScanOptions options)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <RunScan>d__13(0)
{
options = options
};
}
private static bool IsServerReady()
{
if ((Object)(object)ZoneSaviorPlugin.Instance != (Object)null && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null)
{
return (Object)(object)ZoneSystem.instance != (Object)null;
}
return false;
}
}
internal static class AutoArchiveStore
{
private const string UnknownPlatformPrefix = "unknown:";
private const string ManualPlatformPrefix = "manual:";
private const int MaxRunHistory = 50;
private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build();
private static readonly IDeserializer Deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().WithNamingConvention(UnderscoredNamingConvention.Instance).Build();
private static readonly object Sync = new object();
private static ManualLogSource _logger = null;
private static bool _initialized;
private static bool _dirty;
private static DateTime _lastFlushUtc = DateTime.MinValue;
private static readonly Dictionary<string, PlayerActivityRecord> PlayersByPlatform = new Dictionary<string, PlayerActivityRecord>(StringComparer.Ordinal);
private static readonly Dictionary<long, PlayerActivityRecord> PlayersById = new Dictionary<long, PlayerActivityRecord>();
public static string FilePath => Path.Combine(ZoneSaviorPlugin.DataStorageFullPath, "activity.yml");
public static AutoArchiveState State { get; private set; } = new AutoArchiveState();
public static void Initialize(ManualLogSource logger)
{
if (!_initialized)
{
_initialized = true;
_logger = logger;
Load();
}
}
public static void Load()
{
lock (Sync)
{
if (!File.Exists(FilePath))
{
State = new AutoArchiveState();
RebuildIndexes();
_dirty = true;
Flush(force: true);
return;
}
try
{
string input = File.ReadAllText(FilePath);
State = Deserializer.Deserialize<AutoArchiveState>(input) ?? new AutoArchiveState();
AutoArchiveState state = State;
if (state.Players == null)
{
List<PlayerActivityRecord> list2 = (state.Players = new List<PlayerActivityRecord>());
}
state = State;
if (state.Runs == null)
{
List<ArchiveRunRecord> list4 = (state.Runs = new List<ArchiveRunRecord>());
}
state = State;
if (state.IgnoredPlayerIds == null)
{
List<long> list6 = (state.IgnoredPlayerIds = new List<long>());
}
RebuildIndexes();
_dirty = false;
}
catch (Exception arg)
{
_logger.LogError((object)$"Failed to load auto archive activity file, using a blank state: {arg}");
State = new AutoArchiveState();
RebuildIndexes();
_dirty = true;
}
}
}
public static void Flush(bool force = false)
{
lock (Sync)
{
if (_dirty || force)
{
DateTime utcNow = DateTime.UtcNow;
if (force || !(utcNow - _lastFlushUtc < TimeSpan.FromSeconds(60.0)))
{
Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
File.WriteAllText(FilePath, Serializer.Serialize(State));
_lastFlushUtc = utcNow;
_dirty = false;
}
}
}
}
public static void RecordPlayerSeen(string platformId, long playerId, string name, DateTime utcNow)
{
if (string.IsNullOrWhiteSpace(platformId))
{
platformId = ((playerId != 0L) ? UnknownPlatformId(playerId) : "unknown");
}
lock (Sync)
{
PlayerActivityRecord value;
PlayerActivityRecord playerActivityRecord = (PlayersByPlatform.TryGetValue(platformId, out value) ? value : null);
if (playerActivityRecord == null)
{
playerActivityRecord = new PlayerActivityRecord
{
PlatformId = platformId,
FirstSeenUtc = utcNow
};
State.Players.Add(playerActivityRecord);
IndexPlayer(playerActivityRecord);
}
if (playerActivityRecord.FirstSeenUtc == DateTime.MinValue)
{
playerActivityRecord.FirstSeenUtc = utcNow;
}
playerActivityRecord.LastSeenUtc = utcNow;
AddDistinct(playerActivityRecord.Names, name);
if (playerId != 0L)
{
AddDistinct(playerActivityRecord.PlayerIds, playerId);
PlayersById[playerId] = playerActivityRecord;
MergePlayerIdRecords(playerActivityRecord, playerId);
}
_dirty = true;
}
}
public static void RecordManualPlayerSeen(long playerId, DateTime utcNow)
{
lock (Sync)
{
PlayerActivityRecord value;
PlayerActivityRecord playerActivityRecord = (PlayersById.TryGetValue(playerId, out value) ? value : new PlayerActivityRecord
{
PlatformId = ManualPlatformId(playerId),
FirstSeenUtc = utcNow,
PlayerIds = new List<long>(1) { playerId }
});
if (!State.Players.Contains(playerActivityRecord))
{
State.Players.Add(playerActivityRecord);
}
if (IsUnknown(playerActivityRecord))
{
playerActivityRecord.PlatformId = ManualPlatformId(playerId);
}
if (playerActivityRecord.FirstSeenUtc == DateTime.MinValue)
{
playerActivityRecord.FirstSeenUtc = utcNow;
}
playerActivityRecord.LastSeenUtc = utcNow;
AddDistinct(playerActivityRecord.Names, "manual");
RebuildPlayerIndexes();
_dirty = true;
}
}
public static void RecordUnknownPlayer(long playerId, DateTime utcNow, string observedName = "")
{
if (playerId == 0L)
{
return;
}
lock (Sync)
{
if (PlayersById.TryGetValue(playerId, out PlayerActivityRecord value))
{
if (AddObservedName(value, observedName))
{
_dirty = true;
}
}
else
{
GetOrCreateUnknownPlayer(playerId, utcNow, observedName);
_dirty = true;
}
}
}
public static bool TryGetPlayerRecord(long playerId, out PlayerActivityRecord record)
{
lock (Sync)
{
record = (PlayersById.TryGetValue(playerId, out PlayerActivityRecord value) ? value : null);
return record != null;
}
}
public static bool TryGetPlayerIdsBySteamId(string steamId, out List<long> playerIds, out string normalizedSteamId)
{
normalizedSteamId = NormalizeSteamId(steamId);
playerIds = new List<long>();
if (string.IsNullOrWhiteSpace(normalizedSteamId))
{
return false;
}
lock (Sync)
{
foreach (PlayerActivityRecord player in State.Players)
{
if (!TryNormalizeSteamPlatformId(player.PlatformId, out string steamId2) || !string.Equals(steamId2, normalizedSteamId, StringComparison.Ordinal))
{
continue;
}
foreach (long item in player.PlayerIds.Where((long id) => id != 0))
{
AddDistinct(playerIds, item);
}
}
}
return playerIds.Count > 0;
}
public static bool IsIgnored(long playerId)
{
lock (Sync)
{
return State.IgnoredPlayerIds.Contains(playerId);
}
}
public static void SetIgnored(long playerId, bool ignored)
{
lock (Sync)
{
if (ignored)
{
AddDistinct(State.IgnoredPlayerIds, playerId);
}
else
{
State.IgnoredPlayerIds.Remove(playerId);
}
_dirty = true;
}
}
public static AutoArchiveCreatorEligibility EvaluateCreatorArchiveEligibility(long playerId, DateTime utcNow, int inactiveDays, int unknownGraceDays, bool recordUnknownPlayer, string observedName = "")
{
AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = new AutoArchiveCreatorEligibility
{
PlayerId = playerId,
Ignored = (playerId != 0L && IsIgnored(playerId))
};
if (playerId == 0L)
{
autoArchiveCreatorEligibility.Reason = "creatorless";
return autoArchiveCreatorEligibility;
}
if (ZoneLimitConfiguration.IsArchiveProtected(playerId, out string reason))
{
autoArchiveCreatorEligibility.Protected = true;
autoArchiveCreatorEligibility.Reason = reason;
return autoArchiveCreatorEligibility;
}
if (autoArchiveCreatorEligibility.Ignored)
{
autoArchiveCreatorEligibility.Reason = $"player {playerId} is ignored";
return autoArchiveCreatorEligibility;
}
if (!TryGetPlayerRecord(playerId, out PlayerActivityRecord record))
{
autoArchiveCreatorEligibility.PlatformId = UnknownPlatformId(playerId);
autoArchiveCreatorEligibility.Names = (string.IsNullOrWhiteSpace(observedName) ? new List<string>(1) { "not_recorded_in_activity" } : new List<string>(1) { observedName.Trim() });
autoArchiveCreatorEligibility.UnknownActivityRecord = true;
if (recordUnknownPlayer)
{
RecordUnknownPlayer(playerId, utcNow, observedName);
autoArchiveCreatorEligibility.Reason = $"player {playerId} was first discovered by scanner";
}
else
{
autoArchiveCreatorEligibility.Reason = $"player {playerId} is not recorded in activity";
}
return autoArchiveCreatorEligibility;
}
autoArchiveCreatorEligibility.RecordedInActivity = true;
autoArchiveCreatorEligibility.PlatformId = record.PlatformId;
autoArchiveCreatorEligibility.Names = record.Names?.ToList() ?? new List<string>();
bool flag2 = (autoArchiveCreatorEligibility.UnknownActivityRecord = IsUnknown(record));
DateTime dateTime = (flag2 ? record.FirstSeenUtc : record.LastSeenUtc);
int num = (flag2 ? unknownGraceDays : inactiveDays);
if (dateTime == DateTime.MinValue)
{
autoArchiveCreatorEligibility.Reason = $"player {playerId} has no usable activity timestamp";
return autoArchiveCreatorEligibility;
}
TimeSpan timeSpan = utcNow - DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
if (timeSpan.TotalDays < (double)num)
{
autoArchiveCreatorEligibility.Reason = (flag2 ? $"unknown player {playerId} first seen {timeSpan.TotalDays:F1}/{num} days ago" : $"player {playerId} last seen {timeSpan.TotalDays:F1}/{num} days ago");
return autoArchiveCreatorEligibility;
}
autoArchiveCreatorEligibility.Eligible = true;
autoArchiveCreatorEligibility.Reason = (flag2 ? $"unknown player {playerId} grace expired" : $"player {playerId} inactive for {timeSpan.TotalDays:F1} days");
return autoArchiveCreatorEligibility;
}
public static bool IsCreatorArchiveEligible(long playerId, DateTime utcNow, int inactiveDays, int unknownGraceDays, out string reason)
{
AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = EvaluateCreatorArchiveEligibility(playerId, utcNow, inactiveDays, unknownGraceDays, recordUnknownPlayer: true);
reason = autoArchiveCreatorEligibility.Reason;
return autoArchiveCreatorEligibility.Eligible;
}
public static void RecordRun(ArchiveRunRecord run)
{
lock (Sync)
{
State.LastScanUtc = run.CreatedUtc;
if (!run.Manual)
{
State.LastAutoScanUtc = run.CreatedUtc;
}
State.Runs.Add(run);
if (State.Runs.Count > 50)
{
State.Runs.RemoveRange(0, State.Runs.Count - 50);
}
_dirty = true;
}
}
public static void SetLastAutoScanUtc(DateTime value)
{
lock (Sync)
{
State.LastAutoScanUtc = ((value == DateTime.MinValue) ? DateTime.MinValue : DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc));
_dirty = true;
}
}
private static PlayerActivityRecord GetOrCreateUnknownPlayer(long playerId, DateTime utcNow, string observedName = "")
{
string text = UnknownPlatformId(playerId);
if (PlayersByPlatform.TryGetValue(text, out PlayerActivityRecord value))
{
AddObservedName(value, observedName);
return value;
}
List<string> list = new List<string>();
AddObservedName(list, observedName);
if (list.Count == 0)
{
list.Add("unknown");
}
value = new PlayerActivityRecord
{
PlatformId = text,
FirstSeenUtc = utcNow,
LastSeenUtc = DateTime.MinValue,
PlayerIds = new List<long>(1) { playerId },
Names = list
};
State.Players.Add(value);
IndexPlayer(value);
return value;
}
private static bool AddObservedName(PlayerActivityRecord record, string observedName)
{
return AddObservedName(record.Names, observedName);
}
private static bool AddObservedName(List<string> names, string observedName)
{
if (string.IsNullOrWhiteSpace(observedName) || string.Equals(observedName, "unknown", StringComparison.OrdinalIgnoreCase))
{
return false;
}
string item = observedName.Trim();
if (names.Contains(item))
{
return false;
}
names.Add(item);
return true;
}
private static void MergePlayerIdRecords(PlayerActivityRecord target, long playerId)
{
PlayerActivityRecord target2 = target;
foreach (PlayerActivityRecord item in State.Players.Where((PlayerActivityRecord player) => player != target2 && player.PlayerIds.Contains(playerId)).ToList())
{
foreach (long playerId2 in item.PlayerIds)
{
AddDistinct(target2.PlayerIds, playerId2);
}
foreach (string name in item.Names)
{
AddDistinct(target2.Names, name);
}
if (target2.FirstSeenUtc == DateTime.MinValue || item.FirstSeenUtc < target2.FirstSeenUtc)
{
target2.FirstSeenUtc = item.FirstSeenUtc;
}
State.Players.Remove(item);
}
RebuildPlayerIndexes();
}
private static void RebuildIndexes()
{
RebuildPlayerIndexes();
}
private static void RebuildPlayerIndexes()
{