Decompiled source of ZoneSavior v1.3.0

ZoneSavior.dll

Decompiled 5 days ago
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.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
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.3.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.0.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]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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]
	[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]
	[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;
		}
	}
}
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;
		}

		private const string ScanCommand = "zs_scan";

		private const string StatusCommand = "zs_status";

		private const string DebugZoneCommand = "zs_debugzone";

		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_0043: 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_0078: 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_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_00ad: 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_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: 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_scan", "[steamID] [dry|save|reset] - Runs an auto archive scan, optionally filtered to one Steam owner.", (ConsoleEvent)obj, false, 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_status", "- Writes a YAML report with recent auto archive runs.", (ConsoleEvent)obj2, false, 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_debugzone", "(x,z) - Writes a YAML report explaining auto archive eligibility for one zone.", (ConsoleEvent)obj3, false, 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_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			if (ZNet.instance.IsServer())
			{
				ShowResult(ExecuteRequest(request), context);
				return;
			}
			RegisterRpcs();
			ZPackage val = new ZPackage();
			val.Write(ZoneBundleSerialization.Serialize(request));
			ZRoutedRpc.instance.InvokeRoutedRPC("sighsorry.ZoneSavior_AutoArchiveCommandRequest", new object[1] { val });
			if (context != null)
			{
				context.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 (!ZoneBundleCommands.IsAuthorizedSender(sender))
				{
					SendResult(sender, AutoArchiveCommandResult.Fail("Admin only."));
					return;
				}
				AutoArchiveCommandRequest request = ZoneBundleSerialization.Deserialize<AutoArchiveCommandRequest>(package.ReadString());
				SendResult(sender, ExecuteRequest(request));
			}
			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 (!ZoneRpcRegistrar.IsServerSender(sender))
			{
				return;
			}
			try
			{
				ShowResult(ZoneBundleSerialization.Deserialize<AutoArchiveCommandResult>(package.ReadString()), (Terminal?)(object)Console.instance);
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("Failed to read auto archive command result: " + ex.Message));
			}
		}

		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_scan":
					ExecuteScan(array, messages);
					break;
				case "zs_status":
					ExecuteStatus(messages);
					break;
				case "zs_debugzone":
					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)
		{
			IEnumerable<string> args2 = args.Skip(1);
			List<long> list = null;
			string targetLabel = "";
			if (args.Length > 1 && !IsArchiveModeToken(args[1]))
			{
				list = ResolveTargetPlayerIds(args[1], out targetLabel);
				args2 = args.Skip(2);
			}
			ParseMode(args2, out var dryRun, out var reset);
			if (!((list == null) ? AutoArchiveService.QueueManualScan(dryRun, reset) : AutoArchiveService.QueueManualScan(dryRun, reset, list)))
			{
				messages.Add("Auto archive scan could not be started. World may not be ready or another scan is running.");
			}
			else
			{
				messages.Add((list == null) ? "Auto archive scan started." : ("Auto archive scan started for " + targetLabel + "."));
			}
		}

		private static void ExecuteStatus(List<string> messages)
		{
			AutoArchiveStatusReport autoArchiveStatusReport = BuildStatusReport();
			string text = WriteStatusReport(autoArchiveStatusReport);
			messages.Add($"Archive status report: {autoArchiveStatusReport.Runs.Count}/{autoArchiveStatusReport.TotalRuns} run(s).");
			if (autoArchiveStatusReport.Runs.Count > 0)
			{
				ArchiveRunRecord archiveRunRecord = autoArchiveStatusReport.Runs[0];
				messages.Add($"Latest: {archiveRunRecord.RunId}, dry={archiveRunRecord.DryRun}, reset={archiveRunRecord.ResetAfterSave}, candidates={archiveRunRecord.CandidateZones}, processed={archiveRunRecord.ProcessedZones}, clusters={archiveRunRecord.Clusters.Count}.");
			}
			messages.Add("Wrote YAML: " + text);
		}

		private static AutoArchiveStatusReport BuildStatusReport()
		{
			List<ArchiveRunRecord> runs = AutoArchiveStore.State.Runs.OrderByDescending((ArchiveRunRecord run) => run.CreatedUtc).ToList();
			AutoArchiveStatusReport autoArchiveStatusReport = new AutoArchiveStatusReport();
			ZNet instance = ZNet.instance;
			autoArchiveStatusReport.World = ((instance != null) ? instance.GetWorldName() : null) ?? "unknown";
			autoArchiveStatusReport.CreatedAt = ZoneSaviorTimestamp.Format(DateTime.UtcNow);
			autoArchiveStatusReport.TotalRuns = AutoArchiveStore.State.Runs.Count;
			autoArchiveStatusReport.Runs = runs;
			return autoArchiveStatusReport;
		}

		private static string WriteStatusReport(AutoArchiveStatusReport report)
		{
			string text = Path.Combine(ZoneSaviorPlugin.DataStorageFullPath, "Diagnostics");
			Directory.CreateDirectory(text);
			string path = "archive_status_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture) + ".yml";
			string text2 = Path.Combine(text, path);
			File.WriteAllText(text2, ZoneBundleSerialization.Serialize(report));
			return text2;
		}

		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_0012: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			Vector2i val = ParseZoneSpec(args.Skip(1));
			ZoneSaviorBuildRecipeRules.RefreshIndex();
			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}, resetCandidate={autoArchiveZoneDebugReport.Summary.WouldBeCandidateZone}, mayBeBackupOnly={autoArchiveZoneDebugReport.Summary.MayBeBackupOnlyZone}.");
			messages.Add("Reason: " + autoArchiveZoneDebugReport.Summary.Reason);
			messages.Add("Wrote YAML: " + text);
		}

		private static AutoArchiveZoneDebugReport BuildZoneDebugReport(Vector2i zone)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: 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>();
			ZoneSaviorZones.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 = ZoneSaviorZones.ToModel(zone);
			autoArchiveZoneDebugReport.Settings = new AutoArchiveZoneDebugSettings
			{
				DryRun = AutoArchiveConfig.DryRun,
				ResetAfterSave = AutoArchiveConfig.ResetAfterSave,
				InactiveDays = AutoArchiveConfig.InactiveDays,
				MinimumPiecesPerCluster = AutoArchiveConfig.MinimumPiecesPerCluster,
				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) => AutoArchiveStore.EvaluateCreatorArchiveEligibility(playerId, utcNow, AutoArchiveConfig.InactiveDays, recordUnknownPlayer: false)).ToList();
			HashSet<long> onlineCreatorIds = AutoArchiveScanner.GetOnlineCreatorIds();
			foreach (AutoArchiveCreatorEligibility creator in autoArchiveZoneDebugReport2.Creators)
			{
				if (onlineCreatorIds.Contains(creator.PlayerId))
				{
					creator.Eligible = false;
					creator.Reason = $"player {creator.PlayerId} is online";
				}
			}
			bool flag = autoArchiveZoneDebugReport2.Creators.Count > 0 && autoArchiveZoneDebugReport2.Creators.All((AutoArchiveCreatorEligibility 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),
				MayBeBackupOnlyZone = (!flag && autoArchiveZoneDebugReport2.Creators.Any((AutoArchiveCreatorEligibility creator) => creator.Eligible))
			};
			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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			ZoneStructureInfo zoneStructureInfo = ZoneStructureClassifier.Inspect(zdo, requestedZone);
			AutoArchiveZoneDebugObject autoArchiveZoneDebugObject = new AutoArchiveZoneDebugObject();
			autoArchiveZoneDebugObject.ZdoId = zoneStructureInfo.ZdoId;
			autoArchiveZoneDebugObject.PrefabHash = zoneStructureInfo.PrefabHash;
			autoArchiveZoneDebugObject.Prefab = zoneStructureInfo.Prefab;
			autoArchiveZoneDebugObject.Position = new float[3]
			{
				Round(zoneStructureInfo.Position.x),
				Round(zoneStructureInfo.Position.y),
				Round(zoneStructureInfo.Position.z)
			};
			autoArchiveZoneDebugObject.ObjectZone = ZoneSaviorZones.ToModel(zoneStructureInfo.ObjectZone);
			autoArchiveZoneDebugObject.CreatorPlayerId = zoneStructureInfo.CreatorPlayerId;
			autoArchiveZoneDebugObject.CreatorName = zoneStructureInfo.CreatorName;
			autoArchiveZoneDebugObject.HasPrefab = zoneStructureInfo.HasPrefab;
			autoArchiveZoneDebugObject.HasZNetView = zoneStructureInfo.HasZNetView;
			autoArchiveZoneDebugObject.HasWearNTear = zoneStructureInfo.HasWearNTear;
			autoArchiveZoneDebugObject.HasPiece = zoneStructureInfo.HasPiece;
			autoArchiveZoneDebugObject.HasBuildRecipe = zoneStructureInfo.HasBuildRecipe;
			autoArchiveZoneDebugObject.InRequestedZone = zoneStructureInfo.InRequestedZone;
			autoArchiveZoneDebugObject.AutoArchiveCandidatePiece = zoneStructureInfo.AutoArchiveCandidatePiece;
			autoArchiveZoneDebugObject.ExclusionReasons = zoneStructureInfo.ExclusionReasons;
			return autoArchiveZoneDebugObject;
		}

		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);
				if (report.Summary.MayBeBackupOnlyZone)
				{
					return "This mixed-owner zone can be saved without reset when it connects to an eligible core through shared eligible creators. Protected creators: " + text;
				}
				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_debugzone (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 (IsArchiveModeToken(arg, "dry") || IsArchiveModeToken(arg, "dry-run"))
				{
					dryRun = true;
					reset = false;
					continue;
				}
				if (IsArchiveModeToken(arg, "save"))
				{
					dryRun = false;
					reset = false;
					continue;
				}
				if (IsArchiveModeToken(arg, "reset"))
				{
					dryRun = false;
					reset = true;
					continue;
				}
				throw new InvalidOperationException("Unknown archive mode '" + arg + "'. Use dry, save, or reset.");
			}
		}

		private static bool IsArchiveModeToken(string arg)
		{
			if (!IsArchiveModeToken(arg, "dry") && !IsArchiveModeToken(arg, "dry-run") && !IsArchiveModeToken(arg, "save"))
			{
				return IsArchiveModeToken(arg, "reset");
			}
			return true;
		}

		private static bool IsArchiveModeToken(string arg, string expected)
		{
			return string.Equals(arg, expected, StringComparison.OrdinalIgnoreCase);
		}

		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 (ZoneSaviorSteamIds.LooksLikeSteamId(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_scan [steamID] [dry|save|reset]");
		}

		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 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, 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 sealed class AutoArchiveState
	{
		public const int CurrentVersion = 1;

		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>();
	}
	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 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 AutoArchiveStatusReport
	{
		public int Version { get; set; } = 1;

		public string World { get; set; } = "";

		public string CreatedAt { get; set; } = "";

		public int TotalRuns { get; set; }

		public List<ArchiveRunRecord> Runs { get; set; } = new List<ArchiveRunRecord>();
	}
	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 EligiblePieceCount { 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>();

		public List<ZoneBundleZone> ResetZones { get; set; } = new List<ZoneBundleZone>();

		public List<ZoneBundleZone> BackupOnlyZones { get; set; } = new List<ZoneBundleZone>();

		public List<ZoneBundleZone> ResetCompletedZones { get; set; } = new List<ZoneBundleZone>();

		public List<ZoneBundleZone> ResetSkippedZones { 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 Protected { get; set; }

		public bool Eligible { get; set; }

		public string Reason { 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<AutoArchiveCreatorEligibility> Creators { get; set; } = new List<AutoArchiveCreatorEligibility>();

		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 MinimumPiecesPerCluster { 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 bool MayBeBackupOnlyZone { 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;
			}
		}

		private sealed class AutoArchivePlan
		{
			public List<Vector2i> Zones { get; }

			public List<Vector2i> ResetZones { get; }

			public AutoArchivePlan(List<Vector2i> zones, List<Vector2i> resetZones)
			{
				Zones = zones;
				ResetZones = resetZones;
			}
		}

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

		public static IEnumerator Run(AutoArchiveScanOptions options, Action<ArchiveRunRecord> onComplete)
		{
			DateTime utcNow = DateTime.UtcNow;
			ArchiveRunRecord run = new ArchiveRunRecord
			{
				RunId = utcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture),
				CreatedUtc = utcNow,
				Manual = options.Manual,
				DryRun = options.DryRun,
				ResetAfterSave = options.ResetAfterSave,
				TargetPlayerIds = options.TargetPlayerIds.ToList()
			};
			if (!IsWorldReady())
			{
				run.Messages.Add("World is not ready.");
				onComplete(run);
				yield break;
			}
			ZoneSaviorBuildRecipeRules.RefreshIndex();
			PlayerActivityTracker.TrackOnlinePlayers(utcNow);
			Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos = new Dictionary<Vector2i, AutoArchiveZoneInfo>();
			yield return ScanZdosBySector(utcNow, zoneInfos, run);
			HashSet<long> targetPlayerIds = options.TargetPlayerIds.ToHashSet();
			bool isTargetOverride = targetPlayerIds.Count > 0;
			HashSet<long> eligibleCreators = FindEligibleCreators(zoneInfos, utcNow, targetPlayerIds);
			List<AutoArchivePlan> list = BuildArchivePlans(zoneInfos, eligibleCreators, isTargetOverride && !options.ResetAfterSave);
			run.CandidateZones = list.Sum((AutoArchivePlan autoArchivePlan) => autoArchivePlan.Zones.Count);
			int processedZones = 0;
			int clusterIndex = 0;
			foreach (AutoArchivePlan plan in list)
			{
				List<Vector2i> cluster = plan.Zones;
				clusterIndex++;
				ArchiveClusterRecord record = BuildClusterRecord(cluster, zoneInfos);
				record.ResetZones = plan.ResetZones.Select(ZoneSaviorZones.ToModel).ToList();
				record.BackupOnlyZones = cluster.Except(plan.ResetZones).Select(ZoneSaviorZones.ToModel).ToList();
				record.EligiblePieceCount = cluster.Sum((Vector2i zone) => zoneInfos[zone].CreatorPieceCounts.Where((KeyValuePair<long, int> pair) => eligibleCreators.Contains(pair.Key)).Sum((KeyValuePair<long, int> pair) => pair.Value));
				bool flag = !isTargetOverride && record.EligiblePieceCount < AutoArchiveConfig.MinimumPiecesPerCluster;
				if (flag && (!options.ResetAfterSave || record.BackupOnlyZones.Count > 0))
				{
					record.Status = "skipped";
					record.Reason = $"eligible piece count {record.EligiblePieceCount} is below minimum {AutoArchiveConfig.MinimumPiecesPerCluster}" + ((record.BackupOnlyZones.Count > 0) ? "; mixed-owner archives are never reset without saving" : "");
					run.Clusters.Add(record);
					continue;
				}
				if (!isTargetOverride && !AllCreatorsEligible(record.Creators.Where(eligibleCreators.Contains), DateTime.UtcNow, out string reason))
				{
					record.Status = "skipped";
					record.Reason = reason;
					run.Clusters.Add(record);
					continue;
				}
				if (processedZones + cluster.Count > AutoArchiveConfig.MaxZonesPerRun)
				{
					record.Status = "skipped";
					record.Reason = $"max zones per run would be exceeded ({processedZones + cluster.Count}/{AutoArchiveConfig.MaxZonesPerRun})";
					run.Clusters.Add(record);
					continue;
				}
				processedZones += cluster.Count;
				if (flag)
				{
					if (options.DryRun)
					{
						record.Status = "dry-run-reset-without-save";
						record.Reason = $"small inactive cluster would be reset without saving ({record.PieceCount}/{AutoArchiveConfig.MinimumPiecesPerCluster} pieces)";
						run.Clusters.Add(record);
						continue;
					}
					ZoneBundleResetResult resetOnlyResult = null;
					string reason2;
					yield return ZoneBundleCommands.ResetGeneratedZonesAsync(cluster, delegate(ZoneBundleResetResult result)
					{
						resetOnlyResult = result;
					}, (Vector2i zone) => CanResetZone(zone, eligibleCreators, targetPlayerIds, out reason2));
					if (resetOnlyResult == null)
					{
						record.Status = "reset-without-save-failed";
						record.Reason = "reset failed: reset coroutine did not return a result";
						run.Clusters.Add(record);
						_logger.LogError((object)"Auto archive small-cluster reset failed: reset coroutine did not return a result.");
						yield return null;
						continue;
					}
					record.Status = (resetOnlyResult.Success ? "reset-without-save" : "reset-without-save-failed");
					record.Reason = resetOnlyResult.Message;
					if (resetOnlyResult.Success)
					{
						record.ResetCompletedZones = cluster.Select(ZoneSaviorZones.ToModel).ToList();
					}
					run.Clusters.Add(record);
					yield return null;
					continue;
				}
				record.Tag = ZoneBundleCommands.MakeUniqueAutoArchiveTag(BuildTag(clusterIndex, cluster, zoneInfos, eligibleCreators));
				if (options.DryRun)
				{
					record.Status = "dry-run";
					record.Reason = $"Would save {cluster.Count} zone(s), keep {record.BackupOnlyZones.Count} mixed-owner zone(s), " + $"and reset {(options.ResetAfterSave ? plan.ResetZones.Count : 0)} zone(s); dry run is enabled.";
					run.Clusters.Add(record);
					continue;
				}
				ZoneBundleArchiveResult saveResult = null;
				Dictionary<Vector2i, Dictionary<ZDOID, string>> resetSnapshots = new Dictionary<Vector2i, Dictionary<ZDOID, string>>();
				Exception ex = null;
				if (options.ResetAfterSave)
				{
					try
					{
						resetSnapshots = plan.ResetZones.ToDictionary((Vector2i zone) => zone, CaptureResetObjects);
					}
					catch (Exception ex2)
					{
						ex = ex2;
					}
				}
				if (ex != null)
				{
					record.Status = "skipped";
					record.Reason = "Could not verify all player-owned reset objects: " + ex.Message;
					run.Clusters.Add(record);
					continue;
				}
				Dictionary<Vector2i, HashSet<ZDOID>> capturedIds = plan.ResetZones.ToDictionary((Vector2i zone) => zone, (Vector2i _) => new HashSet<ZDOID>());
				yield return ZoneBundleCommands.SaveZonesAsync(cluster, record.Tag, delegate(ZoneBundleArchiveResult result)
				{
					saveResult = result;
				}, 0L, (plan.ResetZones.Count > 0) ? plan.ResetZones : null, options.ResetAfterSave, options.ResetAfterSave ? new Func<Vector2i, ZDO, bool>(VerifyCapturedObject) : null);
				if (saveResult == null)
				{
					record.Status = "failed";
					record.Reason = "save failed: archive coroutine did not return a result";
					run.Clusters.Add(record);
					_logger.LogError((object)("Auto archive save failed for tag '" + record.Tag + "': archive coroutine did not return a result."));
					yield return null;
					continue;
				}
				record.TerrainLoaded = saveResult.TerrainLoaded;
				record.TerrainCaptured = saveResult.TerrainCaptured;
				if (!saveResult.Success)
				{
					record.Status = "failed";
					record.Reason = saveResult.Message;
					run.Clusters.Add(record);
					continue;
				}
				record.Status = "saved";
				record.Reason = saveResult.Message;
				if (options.ResetAfterSave)
				{
					yield return ResetSavedZones(plan, record, zoneInfos, eligibleCreators, targetPlayerIds, resetSnapshots, capturedIds);
				}
				run.Clusters.Add(record);
				yield return null;
				bool VerifyCapturedObject(Vector2i zone, ZDO zdo)
				{
					//IL_0018: Unknown result type (might be due to invalid IL or missing references)
					//IL_0043: 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)
					if (!options.ResetAfterSave || !resetSnapshots.TryGetValue(zone, out Dictionary<ZDOID, string> value) || (zdo.GetLong(ZDOVars.s_creator, 0L) == 0L && !zdo.GetBool(ZDOVars.s_tamed, false)))
					{
						return true;
					}
					if (!value.TryGetValue(zdo.m_uid, out var value2) || value2 != ZoneBundleCommands.GetAutoArchiveResetFingerprint(zdo))
					{
						return false;
					}
					capturedIds[zone].Add(zdo.m_uid);
					return true;
				}
			}
			run.ProcessedZones = processedZones;
			onComplete(run);
		}

		private static IEnumerator ResetSavedZones(AutoArchivePlan plan, ArchiveClusterRecord record, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, HashSet<long> eligibleCreators, HashSet<long> targetPlayerIds, Dictionary<Vector2i, Dictionary<ZDOID, string>> snapshots, Dictionary<Vector2i, HashSet<ZDOID>> capturedIds)
		{
			List<string> messages = new List<string>();
			foreach (Vector2i resetZone in plan.ResetZones)
			{
				Vector2i zone = resetZone;
				string reason = "";
				HashSet<ZDOID> destroyedIds = new HashSet<ZDOID>();
				bool mutationStarted = false;
				if (!CanReset())
				{
					record.ResetSkippedZones.Add(ZoneSaviorZones.ToModel(zone));
					messages.Add($"Kept ({zone.x},{zone.y}): {reason}.");
					continue;
				}
				ZoneBundleResetResult resetResult = null;
				Exception resetError = null;
				yield return ZoneSaviorCoroutines.RunSafely(ZoneBundleCommands.ResetGeneratedZonesAsync((IEnumerable<Vector2i>)(object)new Vector2i[1] { zone }, delegate(ZoneBundleResetResult value)
				{
					resetResult = value;
				}, (Vector2i _) => CanReset(), destroyedIds, BeforeFirstMutation), delegate(Exception? ex3)
				{
					resetError = ex3;
				});
				if (resetError != null || resetResult == null || !resetResult.Success)
				{
					if (!mutationStarted)
					{
						record.ResetSkippedZones.Add(ZoneSaviorZones.ToModel(zone));
						messages.Add($"Kept ({zone.x},{zone.y}) before any mutation: " + (resetError?.Message ?? resetResult?.Message ?? "Reset returned no result.") + " " + reason);
						continue;
					}
					record.Status = "saved-reset-failed";
					record.Reason = $"Reset incomplete at ({zone.x},{zone.y}); pending marker retained. " + (resetError?.Message ?? resetResult?.Message ?? "Reset returned no result.") + (string.IsNullOrWhiteSpace(reason) ? "" : (" " + reason));
					_logger.LogWarning((object)record.Reason);
					yield break;
				}
				Exception ex = null;
				try
				{
					ZoneBundleStore.MarkResetCompleted(record.Tag, zone);
				}
				catch (Exception ex2)
				{
					ex = ex2;
				}
				if (ex != null)
				{
					record.Status = "saved-reset-failed";
					record.Reason = $"Reset finished at ({zone.x},{zone.y}), but completion could not be recorded; pending marker retained: {ex.Message}";
					yield break;
				}
				record.ResetCompletedZones.Add(ZoneSaviorZones.ToModel(zone));
				messages.Add(resetResult.Message);
				void BeforeFirstMutation()
				{
					//IL_001b: Unknown result type (might be due to invalid IL or missing references)
					ZoneBundleStore.MarkResetStarted(record.Tag, zone);
					mutationStarted = true;
				}
				bool CanReset()
				{
					//IL_0016: Unknown result type (might be due to invalid IL or missing references)
					//IL_00aa: 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_00f6: Unknown result type (might be due to invalid IL or missing references)
					//IL_0106: Unknown result type (might be due to invalid IL or missing references)
					try
					{
						if (snapshots[zone].Keys.Any((ZDOID id) => !capturedIds[zone].Contains(id)))
						{
							reason = "Not all player-owned objects were captured in the archive";
							return false;
						}
						if (targetPlayerIds.Count == 0 && !AllCreatorsEligible(zoneInfos[zone].Creators, DateTime.UtcNow, out reason))
						{
							return false;
						}
						if (!CanResetZone(zone, eligibleCreators, targetPlayerIds, out reason))
						{
							return false;
						}
						return ResetSnapshotMatches(snapshots[zone], CaptureResetObjects(zone), destroyedIds, out reason);
					}
					catch (Exception ex3)
					{
						reason = "Could not verify reset objects: " + ex3.Message;
						return false;
					}
				}
			}
			record.Status = ((record.ResetSkippedZones.Count == 0) ? "reset" : ((record.ResetCompletedZones.Count > 0) ? "saved-partial-reset" : "saved-reset-skipped"));
			record.Reason = $"Saved {plan.Zones.Count} zone(s), reset {record.ResetCompletedZones.Count}, " + $"kept {record.BackupOnlyZones.Count} backup-only zone(s). " + string.Join(" ", messages);
		}

		private static bool CanResetZone(Vector2i zone, HashSet<long> plannedCreators, HashSet<long> targetPlayerIds, out string reason)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (!IsWorldReady())
			{
				reason = "World is no longer ready";
				return false;
			}
			PlayerActivityTracker.TrackOnlinePlayers(DateTime.UtcNow);
			List<ZDO> list = new List<ZDO>();
			ZoneSaviorZones.FindObjects(zone, list);
			HashSet<long> hashSet = new HashSet<long>();
			foreach (ZDO item in list)
			{
				if (!item.IsValid())
				{
					continue;
				}
				long num = item.GetLong(ZDOVars.s_creator, 0L);
				if (num != 0L)
				{
					if (!plannedCreators.Contains(num) || (targetPlayerIds.Count > 0 && !targetPlayerIds.Contains(num)))
					{
						reason = $"creator {num} is outside the approved reset owners";
						return false;
					}
					hashSet.Add(num);
				}
			}
			reason = "";
			if (targetPlayerIds.Count <= 0)
			{
				return AllCreatorsEligible(hashSet, DateTime.UtcNow, out reason);
			}
			return true;
		}

		private static Dictionary<ZDOID, string> CaptureResetObjects(Vector2i zone)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			List<ZDO> list = new List<ZDO>();
			ZoneSaviorZones.FindObjects(zone, list);
			Dictionary<ZDOID, string> dictionary = new Dictionary<ZDOID, string>();
			foreach (ZDO item in list)
			{
				if (item.IsValid() && (item.GetLong(ZDOVars.s_creator, 0L) != 0L || item.GetBool(ZDOVars.s_tamed, false)))
				{
					dictionary[item.m_uid] = ZoneBundleCommands.GetAutoArchiveResetFingerprint(item);
				}
			}
			return dictionary;
		}

		private static bool ResetSnapshotMatches(Dictionary<ZDOID, string> saved, Dictionary<ZDOID, string> current, ISet<ZDOID> destroyedIds, out string reason)
		{
			if (saved.Keys.Any((ZDOID id) => !current.ContainsKey(id) && !destroyedIds.Contains(id)) || current.Any<KeyValuePair<ZDOID, string>>((KeyValuePair<ZDOID, string> pair) => !saved.TryGetValue(pair.Key, out string value) || value != pair.Value))
			{
				reason = "player-owned objects or their saved contents changed during the archive operation";
				return false;
			}
			reason = "";
			return true;
		}

		internal static HashSet<long> GetOnlineCreatorIds()
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			HashSet<long> hashSet = new HashSet<long>();
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				hashSet.Add(Player.m_localPlayer.GetPlayerID());
			}
			if ((Object)(object)ZNet.instance != (Object)null && ZDOMan.instance != null)
			{
				foreach (ZNetPeer peer in ZNet.instance.GetPeers())
				{
					if (peer != null && !((ZDOID)(ref peer.m_characterID)).IsNone())
					{
						ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
						long num = ((zDO != null) ? zDO.GetLong(ZDOVars.s_playerID, 0L) : 0);
						if (num != 0L)
						{
							hashSet.Add(num);
						}
					}
				}
			}
			long[] array = hashSet.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (AutoArchiveStore.TryGetPlayerRecord(array[i], out PlayerActivityRecord record))
				{
					hashSet.UnionWith(record.PlayerIds.Where((long creator) => creator != 0));
				}
			}
			return hashSet;
		}

		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)
			zone = default(Vector2i);
			creator = 0L;
			if (!ZoneStructureClassifier.TryGetAutoArchiveCandidate(zdo, out zone, out creator, out string creatorName))
			{
				return false;
			}
			AutoArchiveStore.RecordUnknownPlayer(creator, utcNow, creatorName);
			return true;
		}

		private static IEnumerator ScanZdosBySector(DateTime utcNow, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, ArchiveRunRecord run)
		{
			int batchSize = Math.Max(1, AutoArchiveConfig.ScannerBatchSize);
			int processedSinceYield = 0;
			List<ZDO>[] sectors = ZoneSaviorGameAccess.Sectors.Invoke(ZDOMan.instance);
			int sectorIndex = 0;
			while (sectors != null && sectorIndex < sectors.Length)
			{
				List<ZDO> sector = sectors[sectorIndex];
				if (sector != null && sector.Count != 0)
				{
					for (int objectIndex = 0; objectIndex < sector.Count; objectIndex++)
					{
						ProcessScanZdo(sector[objectIndex], utcNow, zoneInfos, run);
						processedSinceYield++;
						if (processedSinceYield >= batchSize)
						{
							processedSinceYield = 0;
							yield return null;
						}
					}
				}
				sectorIndex++;
			}
			List<List<ZDO>> list = ZDOMan.instance.GetPortals().Values.ToList();
			foreach (List<ZDO> sector in list)
			{
				if (sector == null || sector.Count == 0)
				{
					continue;
				}
				for (sectorIndex = 0; sectorIndex < sector.Count; sectorIndex++)
				{
					ProcessScanZdo(sector[sectorIndex], utcNow, zoneInfos, run);
					processedSinceYield++;
					if (processedSinceYield >= batchSize)
					{
						processedSinceYield = 0;
						yield return null;
					}
				}
			}
		}

		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 HashSet<long> FindEligibleCreators(Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, DateTime utcNow, HashSet<long> targetPlayerIds)
		{
			HashSet<long> online = GetOnlineCreatorIds();
			string reason;
			return (from creator in zoneInfos.Values.SelectMany((AutoArchiveZoneInfo info) => info.Creators).Distinct()
				where (targetPlayerIds.Count <= 0) ? (!online.Contains(creator) && AutoArchiveStore.IsCreatorArchiveEligible(creator, utcNow, AutoArchiveConfig.InactiveDays, out reason)) : targetPlayerIds.Contains(creator)
				select creator).ToHashSet();
		}

		private static List<AutoArchivePlan> BuildArchivePlans(Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, HashSet<long> eligibleCreators, bool allowMixedOnly)
		{
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: 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)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			List<AutoArchivePlan> list = new List<AutoArchivePlan>();
			HashSet<Vector2i> hashSet = (from info in zoneInfos.Values
				where info.Creators.Any(eligibleCreators.Contains)
				select info.Zone).ToHashSet();
			HashSet<Vector2i> hashSet2 = hashSet.Where((Vector2i zone) => zoneInfos[zone].Creators.All(eligibleCreators.Contains)).ToHashSet();
			while (hashSet.Count > 0)
			{
				Vector2i item = (from zone in hashSet
					orderby zone.x, zone.y
					select zone).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 neighbor = new Vector2i(val.x + val2.x, val.y + val2.y);
						if (hashSet.Contains(neighbor) && ((hashSet2.Contains(val) && hashSet2.Contains(neighbor)) || zoneInfos[val].Creators.Any((long creator) => eligibleCreators.Contains(creator) && zoneInfos[neighbor].Creators.Contains(creator))))
						{
							hashSet.Remove(neighbor);
							queue.Enqueue(neighbor);
						}
					}
				}
				List<Vector2i> list3 = (from zone in list2
					orderby zone.y, zone.x
					select zone).ToList();
				List<Vector2i> list4 = list3.Where(hashSet2.Contains).ToList();
				if (list4.Count > 0 || allowMixedOnly)
				{
					list.Add(new AutoArchivePlan(list3, list4));
				}
			}
			return (from plan in list
				orderby plan.Zones.Min((Vector2i zone) => zone.x), plan.Zones.Min((Vector2i zone) => zone.y)
				select plan).ToList();
		}

		private static ArchiveClusterRecord BuildClusterRecord(List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
		{
			//IL_008b: 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_0092: 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 zone).Select(ZoneSaviorZones.ToModel).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)
		{
			HashSet<long> onlineCreatorIds = GetOnlineCreatorIds();
			foreach (long creator in creators)
			{
				if (onlineCreatorIds.Contains(creator))
				{
					reason = $"player {creator} is online";
					return false;
				}
				if (!AutoArchiveStore.IsCreatorArchiveEligible(creator, utcNow, AutoArchiveConfig.InactiveDays, out reason))
				{
					return false;
				}
			}
			reason = "";
			return true;
		}

		private static string BuildTag(int index, List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates, HashSet<long> eligibleCreators)
		{
			Dictionary<long, int> dictionary = BuildCreatorPieceCounts(cluster, candidates);
			foreach (long item in dictionary.Keys.Where((long creator) => !eligibleCreators.Contains(creator)).ToList())
			{
				dictionary.Remove(item);
			}
			string arg = BuildOwnerSegment(dictionary);
			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)
		{
			List<long> list = (from creator in creatorPieceCounts.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 creatorPieceCounts.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 ZoneSaviorPaths.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 text = ZoneSaviorSteamIds.Normalize(record.PlatformId);
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			string text2 = new string((from character in record.PlatformId.Substring("steam:".Length)
				where char.IsLetterOrDigit(character) || character == '-' || character == '_'
				select 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 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;
		}
	}
	internal static class AutoArchiveService
	{
		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;

		private static int _scanOperationToken;

		public static bool IsScanRunning => _scanRunning;

		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;
			}
			return QueueScan(new AutoArchiveScanOptions
			{
				Manual = true,
				DryRun = (dryRunOverride ?? AutoArchiveConfig.DryRun),
				ResetAfterSave = (resetAfterSaveOverride ?? AutoArchiveConfig.ResetAfterSave),
				TargetPlayerIds = ((targetPlayerIds == null) ? new List<long>() : new List<long>(targetPlayerIds))
			});
		}

		public static void Shutdown()
		{
			if (_scanCoroutine != null && (Object)(object)ZoneSaviorPlugin.Instance != (Object)null)
			{
				((MonoBehaviour)ZoneSaviorPlugin.Instance).StopCoroutine(_scanCoroutine);
				_scanCoroutine = null;
			}
			ZoneBundleCommands.EndOperation(_scanOperationToken);
			_scanOperationToken = 0;
			_scanRunning = false;
		}

		private static bool QueueScan(AutoArchiveScanOptions options)
		{
			if (!ZoneBundleCommands.TryBeginOperation("auto archive scan", out int token, out string _))
			{
				return false;
			}
			_scanOperationToken = token;
			_scanRunning = true;
			_logger.LogInfo((object)$"Starting auto archive scan (manual: {options.Manual}, dry run: {options.DryRun}, reset: {options.ResetAfterSave}, targets: {options.TargetPlayerIds.Count}).");
			try
			{
				_scanCoroutine = ((MonoBehaviour)ZoneSaviorPlugin.Instance).StartCoroutine(RunScan(options, token));
				if (_scanCoroutine == null)
				{
					throw new InvalidOperationException("Unity did not start the auto archive coroutine.");
				}
				return true;
			}
			catch (Exception ex)
			{
				ZoneBundleCommands.EndOperation(token);
				_scanOperationToken = 0;
				_scanRunning = false;
				_scanCoroutine = null;
				_logger.LogError((object)$"Auto archive scan could not start: {ex}");
				try
				{
					AutoArchiveStore.RecordRun(CreateFailedRun(options, DateTime.UtcNow, "Auto archive scan could not start: " + ex.Message));
					AutoArchiveStore.Flush(force: true);
				}
				catch (Exception arg)
				{
					_logger.LogError((object)$"Failed to record auto archive start failure: {arg}");
				}
				return false;
			}
		}

		private static IEnumerator RunScan(AutoArchiveScanOptions options, int operationToken)
		{
			ArchiveRunRecord completed = null;
			Exception failure = null;
			try
			{
				yield return ZoneSaviorCoroutines.RunSafely(AutoArchiveScanner.Run(options, delegate(ArchiveRunRecord run)
				{
					completed = run;
				}), delegate(Exception? exception)
				{
					failure = exception;
				});
				if (failure != null || completed == null)
				{
					DateTime utcNow = DateTime.UtcNow;
					string text = ((failure != null) ? ("Auto archive scan failed: " + failure.Message) : "Auto archive scanner did not return a result.");
					completed = CreateFailedRun(options, utcNow, text);
					_logger.LogError((object)((failure != null) ? $"Auto archive scan failed unexpectedly: {failure}" : text));
				}
				AutoArchiveStore.RecordRun(completed);
				AutoArchiveStore.Flush(force: true);
				_logger.LogInfo((object)$"Auto archive scan finished: {completed.CandidateZones} candidate zone(s), {completed.ProcessedZones} processed zone(s), {completed.Clusters.Count} cluster record(s).");
			}
			finally
			{
				ZoneBundleCommands.EndOperation(operationToken);
				if (_scanOperationToken == operationToken)
				{
					_scanOperationToken = 0;
				}
				_scanRunning = false;
				_scanCoroutine = null;
			}
		}

		private static ArchiveRunRecord CreateFailedRun(AutoArchiveScanOptions options, DateTime failedAt, string failureMessage)
		{
			return new ArchiveRunRecord
			{
				RunId = failedAt.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture),
				CreatedUtc = failedAt,
				Manual = options.Manual,
				DryRun = options.DryRun,
				ResetAfterSave = options.ResetAfterSave,
				TargetPlayerIds = new List<long>(options.TargetPlayerIds),
				Messages = new List<string>(1) { failureMessage }
			};
		}

		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 int MaxRunHistory = 50;

		private static readonly TimeSpan InternalWriteReloadGrace = TimeSpan.FromSeconds(2.0);

		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 DateTime _lastInternalWriteUtc = 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))
				{
					ReplaceState(new AutoArchiveState());
					_dirty = true;
					Flush(force: true);
					return;
				}
				try
				{
					if (!TryReadStateFromDisk(out AutoArchiveState state, out string error))
					{
						throw new InvalidDataException(error);
					}
					ReplaceState(state);
					_dirty = false;
				}
				catch (Exception arg)
				{
					BackupInvalidActivityFile();
					_logger.LogError((object)$"Failed to load auto archive activity file, using a blank state: {arg}");
					ReplaceState(new AutoArchiveState());
					_dirty = true;
				}
			}
		}

		private static void BackupInvalidActivityFile()
		{
			try
			{
				string? directoryName = Path.GetDirectoryName(FilePath);
				string text = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture);
				string text2 = Path.Combine(directoryName, "activity.invalid." + text + ".yml");
				File.Copy(FilePath, text2, overwrite: false);
				_logger.LogWarning((object)("Backed up invalid auto archive activity data to '" + text2 + "'."));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("Failed to back up invalid auto archive activity data: " + ex.Message));
			}
		}

		public static void Flush(bool force = false)
		{
			lock (Sync)
			{
				if (_dirty || force)
				{
					DateTime utcNow = DateTime.UtcNow;
					if (force || !(utcNow - _lastFlushUtc < TimeSpan.FromSeconds(60.0)))
					{
						ZoneSaviorFiles.WriteAllTextAtomic(FilePath, Serializer.Serialize(State));
						_lastFlushUtc = utcNow;
						_lastInternalWriteUtc = utcNow;
						_dirty = false;
					}
				}
			}
		}

		public static bool TryReloadFromDiskIfSafe(bool scanRunning, out string message)
		{
			lock (Sync)
			{
				if (_dirty)
				{
					message = "Activity reload skipped: runtime activity state has unsaved changes.";
					return false;
				}
				if (scanRunning)
				{
					message = "Activity reload skipped: auto archive scan is running.";
					return false;
				}
				if (DateTime.UtcNow - _lastInternalWriteUtc < InternalWriteReloadGrace)
				{
					message = "Activity reload skipped: change was caused by a recent ZoneSavior activity flush.";
					return false;
				}
				if (!File.Exists(FilePath))
				{
					message = "Activity reload skipped: activity.yml does not exist.";
					return false;
				}
				if (!TryReadStateFromDisk(out AutoArchiveState state, out string error))
				{
					message = "Activity reload skipped: " + error;
					return false;
				}
				ReplaceState(state);
				_dirty = false;
				message = "Activity reload complete.";
				return true;
			}
		}

		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
					};
					State.Players.Add(playerActivityRecord);
					IndexPlayer(playerActivityRecord);
				}
				playerActivityRecord.LastSeenUtc = utcNow;
				AddDistinct(playerActivityRecord.Names, name);
				if (playerId != 0L)
				{
					PlayerActivityRecord value2;
					PlayerActivityRecord playerActivityRecord2 = (PlayersById.TryGetValue(playerId, out value2) ? value2 : null);
					AddDistinct(playerActivityRecord.PlayerIds, playerId);
					PlayersById[playerId] = playerActivityRecord;
					if (playerActivityRecord2 != null && playerActivityRecord2 != playerActivityRecord)
					{
						MergePlayerRecords(playerActivityRecord, playerActivityRecord2);
						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 (value.LastSeenUtc == DateTime.MinValue)
					{
						value.LastSeenUtc = utcNow;
						_dirty = true;
					}
					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;
			}
		}

		internal static bool TryResolveLastKnownPlayerName(long playerId, out string name)
		{
			name = "";
			if (playerId == 0L)
			{
				return false;
			}
			lock (Sync)
			{
				if (!PlayersById.TryGetValue(playerId, out PlayerActivityRecord value) || value.Names == null)
				{
					return false;
				}
				for (int num = value.Names.Count - 1; num >= 0; num--)
				{
					string text = value.Names[num]?.Trim() ?? "";
					if (text.Length != 0 && !string.Equals(text, "unknown", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "manual", StringComparison.OrdinalIgnoreCase))
					{
						name = text;
						return true;
					}
				}
			}
			return false;
		}

		public static bool TryGetPlayerIdsBySteamId(string steamId, out List<long> playerIds, out string normalizedSteamId)
		{
			normalizedSteamId = ZoneSaviorSteamIds.Normalize(steamId);
			playerIds = new List<long>();
			if (string.IsNullOrWhiteSpace(normalizedSteamId))
			{
				return false;
			}
			lock (Sync)
			{
				foreach (PlayerActivityRecord player in State.Players)
				{
					if (!ZoneSaviorSteamIds.TryNormalizePlatformId(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 AutoArchiveCreatorEligibility EvaluateCreatorArchiveEligibility(long playerId, DateTime utcNow, int inactiveDays, bool recordUnknownPlayer, string observedName = "")
		{
			AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = new AutoArchiveCreatorEligibility
			{
				PlayerId = playerId
			};
			if (playerId == 0L)
			{
				autoArchiveCreatorEligibility.Reason = "creatorless";
				return autoArchiveCreatorEligibility;
			}
			PlayerActivityRecord record;
			bool flag = TryGetPlayerRecord(playerId, out record);
			string platformId = (flag ? record.PlatformId : "");
			IEnumerable<string> playerNames = (flag ? record.Names.ToList() : new List<string>());
			if (ZoneLimitConfiguration.IsArchiveProtected(playerId, platformId, playerNames, out string reason))
			{
				autoArchiveCreatorEligibility.Protected = true;
				autoArchiveCreatorEligibility.Reason = reason;
				return autoArchiveCreatorEligibility;
			}
			if (!flag)
			{
				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 lastSeenUtc = record.LastSeenUtc;
			if (lastSeenUtc == DateTime.MinValue)
			{
				autoArchiveCreatorEligibility.Reason = $"player {playerId} has no usable activity timestamp";
				return autoArchiveCreatorEligibility;
			}
			TimeSpan timeSpan = utcNow - DateTime.SpecifyKind(lastSeenUtc, DateTimeKind.Utc);
			if (timeSpan.TotalDays < (double)inactiveDays)
			{
				autoArchiveCreatorEligibility.Reason = (flag2 ? $"unknown player {playerId} discovered {timeSpan.TotalDays:F1}/{inactiveDays} days ago" : $"player {playerId} last seen {timeSpan.TotalDays:F1}/{inactiveDays} days ago");
				return autoArchiveCreatorEligibility;
			}
			autoArchiveCreatorEligibility.Eligible = true;
			autoArchiveCreatorEligibility.Reason = (flag2 ? $"unknown player {playerId} inactive since discovery for {timeSpan.TotalDays:F1} days" : $"player {playerId} inactive for {timeSpan.TotalDays:F1} days");
			return autoArchiveCreatorEligibility;
		}

		public static bool IsCreatorArchiveEligible(long playerId, DateTime utcNow, int inactiveDays, out string reason)
		{
			AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = EvaluateCreatorArchiveEligibility(playerId, utcNow, inactiveDays, 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;
			}
		}

		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,
				LastSeenUtc = utcNow,
				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 MergePlayerRecords(PlayerActivityRecord target, PlayerActivityRecord duplicate)
		{
			MergePlayerRecordContents(target, duplicate);
			State.Players.Remove(duplicate);
		}

		private static void MergePlayerRecordContents(PlayerActivityRecord target, PlayerActivityRecord duplicate)
		{
			foreach (long playerId in duplicate.PlayerIds)
			{
				AddDistinct(target.PlayerIds, playerId);
			}
			foreach (string name in duplicate.Names)
			{
				AddDistinct(target.Names, name);
			}
			if (duplicate.LastSeenUtc > target.LastSeenUtc)
			{
				target.LastSeenUtc = duplicate.LastSeenUtc;
			}
		}

		private static void RebuildPlayerIndexes()
		{
			BuildPlayerIndexes(State, out Dictionary<string, PlayerActivityRecord> playersByPlatform, out Dictionary<long, PlayerActivityRecord> playersById);
			ReplaceIndexes(playersByPlatform, playersById);
		}

		private static void ReplaceState(AutoArchiveState state)
		{
			NormalizePlayerRecords(state);
			BuildPlayerIndexes(state, out Dictionary<string, PlayerActivityRecord> playersByPlatform, out Dictionary<long, PlayerActivityRecord> playersById);
			State = state;
			ReplaceIndexes(playersByPlatform, playersById);
		}

		private static void NormalizePlayerRecords(AutoArchiveState state)
		{
			bool flag;
			do
			{
				flag = false;
				for (int i = 0; i < state.Players.Count; i++)
				{
					if (flag)
					{
						break;
					}
					PlayerActivityRecord playerActivityRecord = state.Players[i];
					for (int j = i + 1; j < state.Players.Count; j++)
					{
						PlayerActivityRecord duplicate = state.Players[j];
						if (playerActivityRecord.PlayerIds.Any((long id) => id != 0L && duplicate.PlayerIds.Contains(id)))
						{
							bool flag2 = GetPlatformIdentityPriority(duplicate) > GetPlatformIdentityPriority(playerActivityRecord);
							PlayerActivityRecord target = (flag2 ? duplicate : playerActivityRecord);
							PlayerActivityRecord duplicate2 = (flag2 ? playerActivityRecord : duplicate);
							MergePlayerRecordContents(target, duplicate2);
							state.Players.RemoveAt(flag2 ? i : j);
							flag = true;
							break;
						}
					}
				}
			}
			while (flag);
		}

		private static void BuildPlayerIndexes(AutoArchiveState state, out Dictionary<string, PlayerActivityRecord> playersByPlatform, out Dictionary<long, PlayerActivityRecord> playersById)
		{
			playersByPlatform = new Dictionary<string, PlayerActivityRecord>(StringComparer.Ordinal);
			playersById = new Dictionary<long, PlayerActivityRecord>();
			foreach (PlayerActivityRecord player in state.Players)
			{
				if (!string.IsNullOrWhiteSpace(player.PlatformId))
				{
					playersByPlatform[player.PlatformId] = player;
				}
				foreach (long playerId in player.PlayerIds)
				{
					if (playerId != 0L)
					{
						playersById[playerId] = player;
					}
				}
			}
		}

		private static void ReplaceIndexes(Dictionary<string, PlayerActivityRecord> playersByPlatform, Dictionary<long, PlayerActivityRecord> playersById)
		{
			PlayersByPlatform.Clear();
			foreach (KeyValuePair<string, PlayerActivityRecord> item in playersByPlatform)
			{
				PlayersByPlatform[item.Key] = item.Value;
			}
			PlayersById.Clear();
			foreach (KeyValuePair<long, PlayerActivityRecord> item2 in playersById)
			{
				PlayersById[item2.Key] = item2.Value;
			}
		}

		private static void IndexPlayer(PlayerActivityRecord record)
		{
			if (!string.IsNullOrWhiteSpace(record.PlatformId))
			{
				PlayersByPlatform[record.PlatformId] = record;
			}
			foreach (long playerId in record.PlayerIds)
			{
				if (playerId != 0L)
				{
					PlayersById[playerId] = record;
				}
			}
		}

		private static string UnknownPlatformId(long playerId)
		{
			return "unknown:" + playerId;
		}

		private static bool IsUnknown(PlayerActivityRecord record)
		{
			return record.PlatformId.StartsWith("unknown:", StringComparison.Ordinal);
		}

		private static int GetPlatformIdentityPriority(PlayerActivityRecord record)
		{
			if (ZoneSaviorSteamIds.TryNormalizePlatformId(record.PlatformId, out string _))
			{
				return 2;
			}
			if (!string.IsNullOrWhiteSpace(record.PlatformId) && !IsUnknown(record))
			{
				return 1;
			}
			return 0;
		}

		private static bool TryReadStateFromDisk(out AutoArchiveState state, out string error)
		{
			state = new AutoArchiveState();
			error = "";
			try
			{
				string input = File.ReadAllText(FilePath);
				state = Deserializer.Deserialize<AutoArchiveState>(input) ?? new AutoArchiveState();
				ValidateState(state);
				return true;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
		}

		private static void ValidateState(AutoArchiveState state)
		{
			if (state.Version != 1)
			{
				throw new InvalidDataException($"Unsupported activity version {state.Version}.");
			}
			if (state.Players == null || state.Runs == null)
			{
				throw new InvalidDataException("Activity players or runs list is missing.");
			}
			for (int i = 0; i < state.Players.Count; i++)
			{
				PlayerActivityRecord playerActivityRecord = state.Players[i];
				if (playerActivityRecord == null || playerActivityRecord.PlatformId == null || playerActivityRecord.PlayerIds == null || playerActivityRecord.Names == null)
				{
					throw new InvalidDataException($"Activity player record {i} is invalid.");
				}
			}
			for (int j = 0; j < state.Runs.Count; j++)
			{
				ArchiveRunRecord archiveRunRecord = state.Runs[j];
				if (archiveRunRecord == null || archiveRunRecord.TargetPlayerIds == null || archiveRunRecord.Clusters == null || archiveRunRecord.Messages == null)
				{
					throw new InvalidDataException($"Activity run record {j} is invalid.");
				}
				for (int k = 0; k < archiveRunRecord.Clusters.Count; k++)
				{
					ArchiveClusterRecord archiveClusterRecord = archiveRunRecord.Clusters[k];
					if (archiveClusterRecord == null || archiveClusterRecord.Creators == null || archiveClusterRecord.Zones == null || archiveClusterRecord.Zones.Any((ZoneBundleZone zone) => zone == null) || archiveClusterRecord.ResetZones == null || archiveClusterRecord.ResetZones.Any((ZoneBundleZone zone) => zone == null) || archiveClusterRecord.BackupOnlyZones == null || archiveClusterRecord.BackupOnlyZones.Any((ZoneBundleZone zone) => zone == null) || archiveClusterRecord.ResetCompletedZones == null || archiveClusterRecord.ResetCompletedZones.Any((ZoneBundleZone zone) => zone == null) || archiveClusterRecord.ResetSkippedZones == null || archiveClusterRecord.ResetSkippedZones.Any((ZoneBundleZone zone) => zone == null))
					{
						throw new InvalidDataException($"Activity run {j} cluster {k} is invalid.");
					}
				}
			}
		}

		private static void AddDistinct<T>(List<T> values, T value)
		{
			if (value != null && !values.Contains(value))
			{
				values.Add(value);
			}
		}
	}
	public static class ZoneSaviorCreatorNameLookupProtocol
	{
		public const int Version = 1;

		public const int RequestBytes = 28;

		public const int MaxResponseBytes = 512;

		public const int MaxNameCharacters = 128;

		public const int StatusSuccess = 0;

		public const int StatusUnsupportedVersion = 1;

		public const int StatusMalformedRequest = 2;

		public const int StatusUnauthorized = 3;

		public const int StatusRateLimited = 4;

		public const int StatusWorldMismatch = 5;

		public const int StatusServerError = 6;

		public const string RequestRpcName = "sighsorry.ZoneSavior_CreatorNameLookupRequest";

		public const string ResponseRpcName = "sighsorry.ZoneSavior_CreatorNameLookupResponse";
	}
	internal static class CreatorNameLookupRpc
	{
		private sealed class RequestRateWindow
		{
			internal DateTime StartedUtc { get; }

			internal int RequestCount { get; set; }

			internal RequestRateWindow(DateTime startedUtc, int requestCount)
			{
				StartedUtc = startedUtc;
				RequestCount = requestCount;
			}
		}

		private const int MaxRequestsPerWindow = 8;

		private static readonly TimeSpan RateWindowDuration = TimeSpan.FromSeconds(5.0);

		private static readonly Dictionary<long, RequestRateWindow> RequestWindows = new Dictionary<long, RequestRateWindow>();

		private static ZRoutedRpc? _registeredRpc;

		internal static void Register()
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (!((Object)(object)instance == (Object)null) && instance.IsServer() && instance2 != null && _registeredRpc != instance2)
			{
				_registeredRpc = instance2;
				RequestWindows.Clear();
				instance2.Register<ZPackage>("sighsorry.ZoneSavior_CreatorNameLookupRequest", (Action<long, ZPackage>)RPC_HandleRequest);
			}
		}

		internal static void ClearRateLimits()
		{
			RequestWindows.Clear();
		}

		private static void RPC_HandleRequest(long sender, ZPackage package)
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || ZRoutedRpc.instance == null || !TryReadRequest(package, out var version, out var requestId, out var worldUid, out var playerId))
			{
				return;
			}
			bool flag;
			try
			{
				flag = IsAuthorizedSender(sender);
			}
			catch
			{
				return;
			}
			if (!flag)
			{
				SendResponse(sender, requestId, worldUid, 3, playerId, found: false, "");
				return;
			}
			if (!TryConsumeRequest(sender, DateTime.UtcNow))
			{
				SendResponse(sender, requestId, worldUid, 4, playerId, found: false, "");
				return;
			}
			if (requestId == 0L || playerId == 0L)
			{
				SendResponse(sender, requestId, worldUid, 2, playerId, found: false, "");
				return;
			}
			if (version != 1)
			{
				SendResponse(sender, requestId, worldUid, 1, playerId, found: false, "");
				return;
			}
			long worldUID;
			try
			{
				worldUID = instance.GetWorldUID();
			}
			catch
			{
				SendResponse(sender, requestId, worldUid, 6, playerId, found: false, "");
				return;
			}
			if (worldUid != worldUID)
			{
				SendResponse(sender, requestId, worldUid, 5, playerId, found: false, "");
				return;
			}
			try
			{
				string name;
				string text = (AutoArchiveStore.TryResolveLastKnownPlayerName(playerId, out name) ? NormalizeName(name) : "");
				SendResponse(sender, requestId, worldUid, 0, playerId, text.Length > 0, text);
			}
			catch (Exception ex)
			{
				ZoneSaviorPlugin.ZoneSaviorLogger.LogWarning((object)("Creator name lookup failed: " + ex.Message));
				SendResponse(sender, requestId, worldUid, 6, playerId, found: false, "");
			}
		}

		private static bool TryReadRequest(ZPackage package, out int version, out long requestId, out long worldUid, out long playerId)
		{
			version = 0;
			requestId = 0L;
			worldUid = 0L;
			playerId = 0L;
			if (package == null || package.Size() != 28)
			{
				return false;
			}
			try
			{
				version = package.ReadInt();
				requestId = package.ReadLong();
				worldUid = package.ReadLong();
				playerId = package.ReadLong();
				return package.GetPos() == package.Size();
			}
			catch
			{
				return false;
			}
		}

		private static bool IsAuthorizedSender(long sender)
		{
			if (!((Object)(object)Player.m_localPlayer != (Object)null) || !((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer() || ZDOMan.instance == null || sender != ZDOMan.GetSessionID())
			{
				return ZoneBundleCommands.IsAuthorizedSender(sender);
			}
			return true;
		}

		private static bool TryConsumeRequest(long sender, DateTime utcNow)
		{
			if (!RequestWindows.TryGetValue(sender, out RequestRateWindow value) || utcNow - value.StartedUtc >= RateWindowDuration)
			{
				RequestWindows[sender] = new RequestRateWindow(utcNow, 1);
				PruneExpiredWindows(utcNow);
				return true;
			}
			if (value.RequestCount >= 8)
			{
				return false;
			}
			value.RequestCount++;
			return true;
		}

		private static void PruneExpiredWindows(DateTime utcNow)
		{
			if (RequestWindows.Count <= 32)
			{
				return;
			}
			List<long> list = new List<long>();
			foreach (KeyValuePair<long, RequestRateWindow> requestWindow in RequestWindows)
			{
				if (utcNow - requestWindow.Value.StartedUtc >= RateWindowDuration)
				{
					list.Add(requestWindow.Key);
				}
			}
			foreach (long item in list)
			{
				RequestWindows.Remove(item);
			}
		}

		private static void SendResponse(long target, long requestId, long worldUid, int status, long playerId, bool found, string name)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || target == 0L)
			{
				return;
			}
			ZPackage val = CreateResponse(requestId, worldUid, status, playerId, found, name);
			if (val.Size() > 512)
			{
				val = CreateResponse(requestId, worldUid, 6, playerId, found: false, "");
			}
			try
			{
				instance.InvokeRoutedRPC(target, "sighsorry.ZoneSavior_CreatorNameLookupResponse", new object[1] { val });
			}
			catch (Exception ex)
			{
				ZoneSaviorPlugin.ZoneSaviorLogger.LogDebug((object)("Could not send creator name lookup response: " + ex.Message));
			}
		}

		private static ZPackage CreateResponse(long requestId, long worldUid, int status, long playerId, bool found, string name)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(requestId);
			val.Write(worldUid);
			val.Write(status);
			val.Write(playerId);
			val.Write(found);
			val.Write(name);
			return val;
		}

		private static string NormalizeName(string value)
		{
			string text = value?.Trim() ?? "";
			if (text.Length == 0)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, 128));
			bool flag = false;
			string text2 = text;
			foreach (char c in text2)
			{
				char c2 = ((char.IsControl(c) || char.IsWhiteSpace(c)) ? ' ' : c);
				if (!(c2 == ' ' && flag))
				{
					if (stringBuilder.Length >= 128)
					{
						break;
					}
					stringBuilder.Append(c2);
					flag = c2 == ' ';
				}
			}
			if (stringBuilder.Length > 0 && char.IsHighSurrogate(stringBuilder[stringBuilder.Length - 1]))
			{
				stringBuilder.Length--;
			}
			return stringBuilder.ToString().Trim();
		}
	}
	internal sealed class BuildCounterHud : MonoBehaviour
	{
		private const float FadeDuration = 0.15f;

		private static BuildCounterHud? _instance;

		private CanvasGroup? _canvasGroup;

		private TextMeshProUGUI? _text;

		private float _hideAt = float.MinValue;

		public static void ShowCount(int count, int limit)
		{
			if (!((Object)(object)Hud.instance == (Object)null))
			{
				EnsureInstance();
				_instance?.Show($"{count}/{limit}");
			}
		}

		private static void EnsureInstance()
		{
			if ((Object)(object)Hud.instance == (Object)null)
			{
				return;
			}
			if ((Object)(object)_instance != (Object)null && Object.op_Implicit((Object)(object)_instance))
			{
				_instance.EnsureElements();
				return;
			}
			_instance = ((Component)Hud.instance).GetComponent<BuildCounterHud>();
			if ((Object)(object)_instance == (Object)null)
			{
				_instance = ((Component)Hud.instance).gameObject.AddComponent<BuildCounterHud>();
			}
			_instance.EnsureElements();
		}

		private void Update()
		{
			if ((Object)(object)_canvasGroup == (Object)null)
			{
				return;
			}
			if (Time.unscaledTime <= _hideAt)
			{
				_canvasGroup.alpha = 1f;
				return;
			}
			_canvasGroup.alpha = Mathf.MoveTowards(_canvasGroup.alpha, 0f, Time.unscaledDeltaTime / 0.15f);
			if (_canvasGroup.alpha <= 0f && (Object)(object)_text != (Object)null)
			{
				((TMP_Text)_text).text = string.Empty;
			}
		}

		private void Show(string value)
		{
			if (!((Object)(object)_text == (Object)null) && !((Object)(object)_canvasGroup == (Object)null))
			{
				((TMP_Text)_text).text = value;
				_canvasGroup.alpha = 1f;
				_hideAt = Time.unscaledTime + ClientConfig.CounterVisibleSeconds;
			}
		}

		private void EnsureElements()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			//IL_009a: 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)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing