Decompiled source of ZoneSavior v1.0.7

ZoneSavior.dll

Decompiled a day 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.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ServerSync;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.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.0.7")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.7.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[CompilerGenerated]
internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
{
	private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
	{
		object IEnumerator.Current => _item;

		T IEnumerator<T>.Current => _item;

		public Enumerator(T item)
		{
			_item = item;
		}

		bool IEnumerator.MoveNext()
		{
			if (!_moveNextCalled)
			{
				return _moveNextCalled = true;
			}
			return false;
		}

		void IEnumerator.Reset()
		{
			_moveNextCalled = false;
		}

		void IDisposable.Dispose()
		{
		}
	}

	int ICollection.Count => 1;

	bool ICollection.IsSynchronized => false;

	object ICollection.SyncRoot => this;

	object IList.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	bool IList.IsFixedSize => true;

	bool IList.IsReadOnly => true;

	int IReadOnlyCollection<T>.Count => 1;

	T IReadOnlyList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
	}

	int ICollection<T>.Count => 1;

	bool ICollection<T>.IsReadOnly => true;

	T IList<T>.this[int index]
	{
		get
		{
			if (index != 0)
			{
				throw new IndexOutOfRangeException();
			}
			return _item;
		}
		set
		{
			throw new NotSupportedException();
		}
	}

	public <>z__ReadOnlySingleElementList(T item)
	{
		_item = item;
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection.CopyTo(Array array, int index)
	{
		array.SetValue(_item, index);
	}

	int IList.Add(object value)
	{
		throw new NotSupportedException();
	}

	void IList.Clear()
	{
		throw new NotSupportedException();
	}

	bool IList.Contains(object value)
	{
		return EqualityComparer<T>.Default.Equals(_item, (T)value);
	}

	int IList.IndexOf(object value)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, (T)value))
		{
			return -1;
		}
		return 0;
	}

	void IList.Insert(int index, object value)
	{
		throw new NotSupportedException();
	}

	void IList.Remove(object value)
	{
		throw new NotSupportedException();
	}

	void IList.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}

	IEnumerator<T> IEnumerable<T>.GetEnumerator()
	{
		return new Enumerator(_item);
	}

	void ICollection<T>.Add(T item)
	{
		throw new NotSupportedException();
	}

	void ICollection<T>.Clear()
	{
		throw new NotSupportedException();
	}

	bool ICollection<T>.Contains(T item)
	{
		return EqualityComparer<T>.Default.Equals(_item, item);
	}

	void ICollection<T>.CopyTo(T[] array, int arrayIndex)
	{
		array[arrayIndex] = _item;
	}

	bool ICollection<T>.Remove(T item)
	{
		throw new NotSupportedException();
	}

	int IList<T>.IndexOf(T item)
	{
		if (!EqualityComparer<T>.Default.Equals(_item, item))
		{
			return -1;
		}
		return 0;
	}

	void IList<T>.Insert(int index, T item)
	{
		throw new NotSupportedException();
	}

	void IList<T>.RemoveAt(int index)
	{
		throw new NotSupportedException();
	}
}
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class ExtensionMarkerAttribute : Attribute
	{
		private readonly string <Name>k__BackingField;

		public string Name => <Name>k__BackingField;

		public ExtensionMarkerAttribute(string name)
		{
			<Name>k__BackingField = name;
		}
	}
}
namespace ZoneSavior
{
	internal static class AutoArchiveCommands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__HandleCommand;

			public static Action<long, ZPackage> <1>__RPC_HandleRequest;

			public static Action<long, ZPackage> <2>__RPC_HandleResult;
		}

		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_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			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, (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, (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, (ConsoleOptionsFetcher)null, false, false, false);
				RegisterRpcs();
			}
		}

		internal static void RegisterRpcs()
		{
			RpcRegistrar.EnsureRegistered(delegate(ZRoutedRpc routedRpc)
			{
				routedRpc.Register<ZPackage>("sighsorry.ZoneSavior_AutoArchiveCommandRequest", (Action<long, ZPackage>)RPC_HandleRequest);
				routedRpc.Register<ZPackage>("sighsorry.ZoneSavior_AutoArchiveCommandResult", (Action<long, ZPackage>)RPC_HandleResult);
			});
		}

		private static void HandleCommand(ConsoleEventArgs args)
		{
			EnsureCommandReady();
			DispatchRequest(new AutoArchiveCommandRequest
			{
				Command = ((args.Args.Length != 0) ? args.Args[0] : ""),
				Args = args.Args.ToList()
			}, args.Context);
		}

		private static void DispatchRequest(AutoArchiveCommandRequest request, Terminal? context)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			Terminal context2 = context;
			if (ZNet.instance.IsServer())
			{
				StartRequest(request, delegate(AutoArchiveCommandResult result)
				{
					ShowResult(result, context2);
				});
				return;
			}
			RegisterRpcs();
			ZPackage val = new ZPackage();
			val.Write(ZoneBundleSerialization.Serialize(request));
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "sighsorry.ZoneSavior_AutoArchiveCommandRequest", new object[1] { val });
			Terminal obj = context2;
			if (obj != null)
			{
				obj.AddString(request.Command + " request sent to server.");
			}
		}

		private static void RPC_HandleRequest(long sender, ZPackage package)
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			try
			{
				if (!IsAuthorizedSender(sender))
				{
					SendResult(sender, AutoArchiveCommandResult.Fail("Admin only."));
					return;
				}
				StartRequest(ZoneBundleSerialization.Deserialize<AutoArchiveCommandRequest>(package.ReadString()), delegate(AutoArchiveCommandResult result)
				{
					SendResult(sender, result);
				});
			}
			catch (Exception ex)
			{
				_logger.LogError((object)$"Auto archive command RPC failed: {ex}");
				SendResult(sender, AutoArchiveCommandResult.Fail(ex.Message));
			}
		}

		private static void SendResult(long target, AutoArchiveCommandResult result)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null)
			{
				ZPackage val = new ZPackage();
				val.Write(ZoneBundleSerialization.Serialize(result));
				ZRoutedRpc.instance.InvokeRoutedRPC(target, "sighsorry.ZoneSavior_AutoArchiveCommandResult", new object[1] { val });
			}
		}

		private static void RPC_HandleResult(long sender, ZPackage package)
		{
			if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())
			{
				ShowResult(ZoneBundleSerialization.Deserialize<AutoArchiveCommandResult>(package.ReadString()), (Terminal?)(object)Console.instance);
			}
		}

		private static void StartRequest(AutoArchiveCommandRequest request, Action<AutoArchiveCommandResult> onComplete)
		{
			onComplete(ExecuteRequest(request));
		}

		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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			Vector2i val = ParseZoneSpec(args.Skip(1));
			AutoArchiveZoneDebugReport autoArchiveZoneDebugReport = BuildZoneDebugReport(val);
			string text = WriteZoneDebugReport(autoArchiveZoneDebugReport);
			messages.Add($"Archive debug zone ({val.x},{val.y}): zdo={autoArchiveZoneDebugReport.Summary.TotalZdos}, candidatePieces={autoArchiveZoneDebugReport.Summary.AutoArchiveCandidatePieces}, creators={autoArchiveZoneDebugReport.Summary.CandidateCreators}, wouldCandidate={autoArchiveZoneDebugReport.Summary.WouldBeCandidateZone}.");
			messages.Add("Reason: " + autoArchiveZoneDebugReport.Summary.Reason);
			messages.Add("Wrote YAML: " + text);
		}

		private static AutoArchiveZoneDebugReport BuildZoneDebugReport(Vector2i zone)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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_0109: Unknown result type (might be due to invalid IL or missing references)
			if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				throw new InvalidOperationException("World ZDO systems are not ready.");
			}
			List<ZDO> list = new List<ZDO>();
			ZDOMan.instance.FindObjects(zone, list);
			DateTime utcNow = DateTime.UtcNow;
			AutoArchiveZoneDebugReport autoArchiveZoneDebugReport = new AutoArchiveZoneDebugReport();
			ZNet instance = ZNet.instance;
			autoArchiveZoneDebugReport.World = ((instance != null) ? instance.GetWorldName() : null) ?? "unknown";
			autoArchiveZoneDebugReport.CreatedAt = ZoneSaviorTimestamp.Format(utcNow);
			autoArchiveZoneDebugReport.Zone = 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) => BuildCreatorDebug(playerId, utcNow)).ToList();
			bool flag = autoArchiveZoneDebugReport2.Creators.Count > 0 && autoArchiveZoneDebugReport2.Creators.All((AutoArchiveZoneDebugCreator creator) => creator.Eligible);
			autoArchiveZoneDebugReport2.Summary = new AutoArchiveZoneDebugSummary
			{
				TotalZdos = autoArchiveZoneDebugReport2.Objects.Count,
				InRequestedZone = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.InRequestedZone),
				WearNTear = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.HasWearNTear),
				PlayerBuildRecipe = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.HasBuildRecipe),
				AutoArchiveCandidatePieces = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.AutoArchiveCandidatePiece),
				CandidateCreators = list2.Count,
				ObjectDbReady = ((Object)(object)ObjectDB.instance != (Object)null),
				WouldBeCandidateZone = (autoArchiveZoneDebugReport2.Objects.Any((AutoArchiveZoneDebugObject entry) => entry.AutoArchiveCandidatePiece) && flag)
			};
			autoArchiveZoneDebugReport2.Summary.Reason = BuildDebugSummaryReason(autoArchiveZoneDebugReport2, flag);
			return autoArchiveZoneDebugReport2;
		}

		private static AutoArchiveZoneDebugObject BuildZoneDebugObject(Vector2i requestedZone, ZDO zdo)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_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 AutoArchiveZoneDebugCreator BuildCreatorDebug(long playerId, DateTime utcNow)
		{
			AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = AutoArchiveStore.EvaluateCreatorArchiveEligibility(playerId, utcNow, AutoArchiveConfig.InactiveDays, recordUnknownPlayer: false);
			return new AutoArchiveZoneDebugCreator
			{
				PlayerId = autoArchiveCreatorEligibility.PlayerId,
				PlatformId = autoArchiveCreatorEligibility.PlatformId,
				Names = autoArchiveCreatorEligibility.Names,
				RecordedInActivity = autoArchiveCreatorEligibility.RecordedInActivity,
				UnknownActivityRecord = autoArchiveCreatorEligibility.UnknownActivityRecord,
				Protected = autoArchiveCreatorEligibility.Protected,
				Eligible = autoArchiveCreatorEligibility.Eligible,
				Reason = autoArchiveCreatorEligibility.Reason
			};
		}

		private static string BuildDebugSummaryReason(AutoArchiveZoneDebugReport report, bool allCreatorsEligible)
		{
			if (report.Summary.AutoArchiveCandidatePieces == 0)
			{
				return "No WearNTear with a non-zero creator and a registered player build recipe was found in this zone.";
			}
			if (!allCreatorsEligible)
			{
				string text = string.Join("; ", from creator in report.Creators
					where !creator.Eligible
					select creator.Reason);
				return "At least one candidate creator is not archive-eligible: " + text;
			}
			if (report.Summary.AutoArchiveCandidatePieces < AutoArchiveConfig.MinimumPiecesPerCluster)
			{
				return $"Zone has candidate pieces, but this single-zone piece count is below Minimum Pieces Per Cluster ({report.Summary.AutoArchiveCandidatePieces}/{AutoArchiveConfig.MinimumPiecesPerCluster}). Cluster adjacency may still change the final action.";
			}
			return "This zone would be an auto archive candidate before cluster adjacency and max-zones-per-run checks.";
		}

		private static string WriteZoneDebugReport(AutoArchiveZoneDebugReport report)
		{
			string text = Path.Combine(ZoneSaviorPlugin.DataStorageFullPath, "Diagnostics");
			Directory.CreateDirectory(text);
			string path = string.Format("archive_debug_zone_{0}_{1}_{2}.yml", report.Zone.X, report.Zone.Z, DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture));
			string text2 = Path.Combine(text, path);
			File.WriteAllText(text2, ZoneBundleSerialization.Serialize(report));
			return text2;
		}

		private static Vector2i ParseZoneSpec(IEnumerable<string> values)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			string input = string.Join(" ", values).Trim();
			Match match = ZoneSpecPattern.Match(input);
			if (!match.Success || !int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
			{
				throw new InvalidOperationException("Syntax: zs_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 bool IsAuthorizedSender(long sender)
		{
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			object obj;
			if (peer == null)
			{
				obj = null;
			}
			else
			{
				ZRpc rpc = peer.m_rpc;
				if (rpc == null)
				{
					obj = null;
				}
				else
				{
					ISocket socket = rpc.m_socket;
					obj = ((socket != null) ? socket.GetHostName() : null);
				}
			}
			if (obj == null)
			{
				obj = "";
			}
			string text = (string)obj;
			if (text.Length > 0)
			{
				return ZNet.instance.IsAdmin(text);
			}
			return false;
		}

		private static void ShowResult(AutoArchiveCommandResult result, Terminal? terminal)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			MessageType val = (MessageType)(result.Success ? 1 : 2);
			foreach (string item in (result.Messages.Count > 0) ? result.Messages : new List<string>(1) { result.Success ? "Done." : "Command failed." })
			{
				_logger.LogInfo((object)item);
				if (terminal != null)
				{
					terminal.AddString(item);
				}
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					((Character)Player.m_localPlayer).Message(val, item, 0, (Sprite)null);
				}
			}
		}
	}
	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 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 TerrainLoaded { get; set; }

		public int TerrainCaptured { get; set; }

		public List<long> Creators { get; set; } = new List<long>();


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

	}
	internal sealed class AutoArchiveScanOptions
	{
		public bool Manual { get; set; }

		public bool DryRun { get; set; }

		public bool ResetAfterSave { get; set; }

		public List<long> TargetPlayerIds { get; set; } = new List<long>();

	}
	internal sealed class AutoArchiveCreatorEligibility
	{
		public long PlayerId { get; set; }

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


		public List<string> Names { get; set; } = new List<string>();


		public bool RecordedInActivity { get; set; }

		public bool UnknownActivityRecord { get; set; }

		public bool Protected { get; set; }

		public bool Eligible { get; set; }

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

	}
	internal sealed class ZoneBundleArchiveResult
	{
		public bool Success { get; set; }

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


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


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


		public int ZoneCount { get; set; }

		public int EntryCount { get; set; }

		public int MonsterCount { get; set; }

		public int TerrainLoaded { get; set; }

		public int TerrainCaptured { get; set; }
	}
	internal sealed class ZoneBundleResetResult
	{
		public bool Success { get; set; }

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


		public int ZoneCount { get; set; }

		public int RemovedCount { get; set; }

		public int RemainingWearNTearCount { get; set; }
	}
	internal sealed class AutoArchiveZoneDebugReport
	{
		public int Version { get; set; } = 1;


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


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


		public ZoneBundleZone Zone { get; set; } = new ZoneBundleZone();


		public AutoArchiveZoneDebugSettings Settings { get; set; } = new AutoArchiveZoneDebugSettings();


		public AutoArchiveZoneDebugSummary Summary { get; set; } = new AutoArchiveZoneDebugSummary();


		public List<AutoArchiveZoneDebugCreator> Creators { get; set; } = new List<AutoArchiveZoneDebugCreator>();


		public Dictionary<string, int> ExclusionCounts { get; set; } = new Dictionary<string, int>();


		public List<AutoArchiveZoneDebugObject> Objects { get; set; } = new List<AutoArchiveZoneDebugObject>();

	}
	internal sealed class AutoArchiveZoneDebugSettings
	{
		public bool DryRun { get; set; }

		public bool ResetAfterSave { get; set; }

		public int InactiveDays { get; set; }

		public int 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 string Reason { get; set; } = "";

	}
	internal sealed class AutoArchiveZoneDebugCreator
	{
		public long PlayerId { get; set; }

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


		public List<string> Names { get; set; } = new List<string>();


		public bool RecordedInActivity { get; set; }

		public bool UnknownActivityRecord { get; set; }

		public bool Protected { get; set; }

		public bool Eligible { get; set; }

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

	}
	internal sealed class AutoArchiveZoneDebugObject
	{
		public string ZdoId { get; set; } = "";


		public int PrefabHash { get; set; }

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


		public float[] Position { get; set; } = new float[3];


		public ZoneBundleZone ObjectZone { get; set; } = new ZoneBundleZone();


		public long CreatorPlayerId { get; set; }

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


		public bool HasPrefab { get; set; }

		public bool HasZNetView { get; set; }

		public bool HasWearNTear { get; set; }

		public bool HasPiece { get; set; }

		public bool HasBuildRecipe { get; set; }

		public bool InRequestedZone { get; set; }

		public bool AutoArchiveCandidatePiece { get; set; }

		public List<string> ExclusionReasons { get; set; } = new List<string>();

	}
	internal sealed class AutoArchiveScanPolicy
	{
		public bool DryRun { get; }

		public bool ResetAfterSave { get; }

		public HashSet<long> TargetPlayerIds { get; }

		public bool IsTargetOverride => TargetPlayerIds.Count > 0;

		public bool UsesMinimumClusterSize => !IsTargetOverride;

		public bool RequiresCreatorEligibility => !IsTargetOverride;

		public bool BlocksMixedOwnerReset
		{
			get
			{
				if (IsTargetOverride)
				{
					return ResetAfterSave;
				}
				return false;
			}
		}

		private AutoArchiveScanPolicy(AutoArchiveScanOptions options)
		{
			DryRun = options.DryRun;
			ResetAfterSave = options.ResetAfterSave;
			TargetPlayerIds = options.TargetPlayerIds.ToHashSet();
		}

		public static AutoArchiveScanPolicy FromOptions(AutoArchiveScanOptions options)
		{
			return new AutoArchiveScanPolicy(options);
		}

		public bool IncludesTargetCreator(IEnumerable<long> creators)
		{
			return creators.Any(TargetPlayerIds.Contains);
		}

		public bool IsSmallCluster(ArchiveClusterRecord record)
		{
			if (UsesMinimumClusterSize)
			{
				return record.PieceCount < AutoArchiveConfig.MinimumPiecesPerCluster;
			}
			return false;
		}

		public string DryRunCandidateReason()
		{
			if (!IsTargetOverride)
			{
				return "candidate only; dry run is enabled";
			}
			return "target override candidate; dry run is enabled";
		}
	}
	internal static class AutoArchiveScanner
	{
		private sealed class AutoArchiveZoneInfo
		{
			public Vector2i Zone { get; }

			public int PieceCount { get; set; }

			public HashSet<long> Creators { get; } = new HashSet<long>();


			public Dictionary<long, int> CreatorPieceCounts { get; } = new Dictionary<long, int>();


			public AutoArchiveZoneInfo(Vector2i zone)
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				Zone = zone;
			}

			public void AddCreator(long creator)
			{
				PieceCount++;
				Creators.Add(creator);
				CreatorPieceCounts.TryGetValue(creator, out var value);
				CreatorPieceCounts[creator] = value + 1;
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass3_0
		{
			public ZoneBundleArchiveResult saveResult;

			internal void <Run>b__4(ZoneBundleArchiveResult result)
			{
				saveResult = result;
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass3_1
		{
			public ZoneBundleResetResult resetResult;

			internal void <Run>b__5(ZoneBundleResetResult result)
			{
				resetResult = result;
			}
		}

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

			private object <>2__current;

			public AutoArchiveScanOptions options;

			public Action<ArchiveRunRecord> onComplete;

			private <>c__DisplayClass3_0 <>8__1;

			private <>c__DisplayClass3_1 <>8__2;

			private DateTime <utcNow>5__2;

			private ArchiveRunRecord <run>5__3;

			private Dictionary<Vector2i, AutoArchiveZoneInfo> <zoneInfos>5__4;

			private AutoArchiveScanPolicy <policy>5__5;

			private Dictionary<Vector2i, AutoArchiveZoneInfo> <candidates>5__6;

			private int <processedZones>5__7;

			private int <clusterIndex>5__8;

			private IEnumerator<List<Vector2i>> <>7__wrap8;

			private List<Vector2i> <cluster>5__10;

			private ArchiveClusterRecord <record>5__11;

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

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

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

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || (uint)(num - 2) <= 5u)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<>8__1 = null;
				<>8__2 = null;
				<run>5__3 = null;
				<zoneInfos>5__4 = null;
				<policy>5__5 = null;
				<candidates>5__6 = null;
				<>7__wrap8 = null;
				<cluster>5__10 = null;
				<record>5__11 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
						<>1__state = -1;
						<utcNow>5__2 = DateTime.UtcNow;
						<run>5__3 = new ArchiveRunRecord
						{
							RunId = <utcNow>5__2.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture),
							CreatedUtc = <utcNow>5__2,
							Manual = options.Manual,
							DryRun = options.DryRun,
							ResetAfterSave = options.ResetAfterSave,
							TargetPlayerIds = options.TargetPlayerIds.ToList()
						};
						if (!IsWorldReady())
						{
							<run>5__3.Messages.Add("World is not ready.");
							onComplete(<run>5__3);
							return false;
						}
						PlayerActivityTracker.TrackOnlinePlayers(<utcNow>5__2);
						<zoneInfos>5__4 = new Dictionary<Vector2i, AutoArchiveZoneInfo>();
						<>2__current = ScanZdosBySector(<utcNow>5__2, <zoneInfos>5__4, <run>5__3);
						<>1__state = 1;
						return true;
					case 1:
					{
						<>1__state = -1;
						<policy>5__5 = AutoArchiveScanPolicy.FromOptions(options);
						<candidates>5__6 = BuildCandidateZones(<zoneInfos>5__4, <utcNow>5__2, <policy>5__5, <run>5__3);
						<run>5__3.CandidateZones = <candidates>5__6.Count;
						List<List<Vector2i>> source = BuildClusters(<candidates>5__6);
						<processedZones>5__7 = 0;
						<clusterIndex>5__8 = 0;
						<>7__wrap8 = (from cluster in source
							orderby cluster.Min((Vector2i zone) => zone.x), cluster.Min((Vector2i zone) => zone.y)
							select cluster).GetEnumerator();
						<>1__state = -3;
						break;
					}
					case 2:
						<>1__state = -3;
						break;
					case 3:
						<>1__state = -3;
						if (<>8__1.saveResult == null)
						{
							<record>5__11.Status = "failed";
							<record>5__11.Reason = "save failed: archive coroutine did not return a result";
							<run>5__3.Clusters.Add(<record>5__11);
							_logger.LogError((object)("Auto archive save failed for tag '" + <record>5__11.Tag + "': archive coroutine did not return a result."));
							<>2__current = null;
							<>1__state = 4;
							return true;
						}
						<record>5__11.TerrainLoaded = <>8__1.saveResult.TerrainLoaded;
						<record>5__11.TerrainCaptured = <>8__1.saveResult.TerrainCaptured;
						if (!<>8__1.saveResult.Success)
						{
							<record>5__11.Status = "failed";
							<record>5__11.Reason = <>8__1.saveResult.Message;
							<run>5__3.Clusters.Add(<record>5__11);
							break;
						}
						<record>5__11.Status = "saved";
						<record>5__11.Reason = <>8__1.saveResult.Message;
						if (options.ResetAfterSave)
						{
							<>8__2 = new <>c__DisplayClass3_1();
							<>8__2.resetResult = null;
							<>2__current = ZoneBundleCommands.ResetGeneratedZonesAsync(<cluster>5__10, delegate(ZoneBundleResetResult result)
							{
								<>8__2.resetResult = result;
							});
							<>1__state = 5;
							return true;
						}
						goto IL_0800;
					case 4:
						<>1__state = -3;
						break;
					case 5:
						<>1__state = -3;
						if (<>8__2.resetResult == null)
						{
							<record>5__11.Status = "saved-reset-failed";
							<record>5__11.Reason = "reset failed: reset coroutine did not return a result";
							<run>5__3.Clusters.Add(<record>5__11);
							_logger.LogError((object)("Auto archive reset failed for tag '" + <record>5__11.Tag + "': reset coroutine did not return a result."));
							<>2__current = null;
							<>1__state = 6;
							return true;
						}
						<record>5__11.Status = (<>8__2.resetResult.Success ? "reset" : "saved-reset-failed");
						<record>5__11.Reason = <>8__2.resetResult.Message;
						<>8__2 = null;
						goto IL_0800;
					case 6:
						<>1__state = -3;
						break;
					case 7:
						{
							<>1__state = -3;
							<>8__1 = null;
							<record>5__11 = null;
							<cluster>5__10 = null;
							break;
						}
						IL_0800:
						<run>5__3.Clusters.Add(<record>5__11);
						<processedZones>5__7 += <cluster>5__10.Count;
						<>2__current = null;
						<>1__state = 7;
						return true;
					}
					while (<>7__wrap8.MoveNext())
					{
						<cluster>5__10 = <>7__wrap8.Current;
						<>8__1 = new <>c__DisplayClass3_0();
						<clusterIndex>5__8++;
						<record>5__11 = BuildClusterRecord(<cluster>5__10, <candidates>5__6);
						bool flag = <policy>5__5.IsSmallCluster(<record>5__11);
						if (<processedZones>5__7 + <cluster>5__10.Count > AutoArchiveConfig.MaxZonesPerRun)
						{
							<record>5__11.Status = "skipped";
							<record>5__11.Reason = $"max zones per run would be exceeded ({<processedZones>5__7 + <cluster>5__10.Count}/{AutoArchiveConfig.MaxZonesPerRun})";
							<run>5__3.Clusters.Add(<record>5__11);
							continue;
						}
						if (<policy>5__5.RequiresCreatorEligibility && !AllCreatorsEligible(<record>5__11.Creators, <utcNow>5__2, out string reason))
						{
							<record>5__11.Status = "skipped";
							<record>5__11.Reason = reason;
							<run>5__3.Clusters.Add(<record>5__11);
							continue;
						}
						if (flag)
						{
							if (!options.ResetAfterSave)
							{
								<record>5__11.Status = "skipped";
								<record>5__11.Reason = $"piece count {<record>5__11.PieceCount} is below minimum {AutoArchiveConfig.MinimumPiecesPerCluster}; reset mode is not enabled";
								<run>5__3.Clusters.Add(<record>5__11);
								continue;
							}
							if (options.DryRun)
							{
								<record>5__11.Status = "dry-run-reset-without-save";
								<record>5__11.Reason = $"small inactive cluster would be reset without saving ({<record>5__11.PieceCount}/{AutoArchiveConfig.MinimumPiecesPerCluster} pieces)";
								<run>5__3.Clusters.Add(<record>5__11);
								<processedZones>5__7 += <cluster>5__10.Count;
								continue;
							}
							ZoneBundleResetResult zoneBundleResetResult = ZoneBundleCommands.ResetGeneratedZones(<cluster>5__10);
							<record>5__11.Status = (zoneBundleResetResult.Success ? "reset-without-save" : "reset-without-save-failed");
							<record>5__11.Reason = zoneBundleResetResult.Message;
							<run>5__3.Clusters.Add(<record>5__11);
							<processedZones>5__7 += <cluster>5__10.Count;
							<>2__current = null;
							<>1__state = 2;
							return true;
						}
						<record>5__11.Tag = ZoneBundleCommands.MakeUniqueAutoArchiveTag(BuildTag(<clusterIndex>5__8, <cluster>5__10, <candidates>5__6));
						if (options.DryRun)
						{
							<record>5__11.Status = "dry-run";
							<record>5__11.Reason = <policy>5__5.DryRunCandidateReason();
							<run>5__3.Clusters.Add(<record>5__11);
							<processedZones>5__7 += <cluster>5__10.Count;
							continue;
						}
						<>8__1.saveResult = null;
						<>2__current = ZoneBundleCommands.SaveZonesAsync(<cluster>5__10, <record>5__11.Tag, delegate(ZoneBundleArchiveResult result)
						{
							<>8__1.saveResult = result;
						}, 0L);
						<>1__state = 3;
						return true;
					}
					<>m__Finally1();
					<>7__wrap8 = null;
					<run>5__3.ProcessedZones = <processedZones>5__7;
					onComplete(<run>5__3);
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

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

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<>7__wrap8 != null)
				{
					<>7__wrap8.Dispose();
				}
			}

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

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

			private object <>2__current;

			public DateTime utcNow;

			public Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos;

			public ArchiveRunRecord run;

			private int <batchSize>5__2;

			private int <processedSinceYield>5__3;

			private List<ZDO>[] <sectors>5__4;

			private int <sectorIndex>5__5;

			private List<ZDO> <sector>5__6;

			private int <objectIndex>5__7;

			private List<List<ZDO>>.Enumerator <>7__wrap7;

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

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

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

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || num == 2)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<sectors>5__4 = null;
				<sector>5__6 = null;
				<>7__wrap7 = default(List<List<ZDO>>.Enumerator);
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					List<List<ZDO>> list;
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
						<>1__state = -1;
						<batchSize>5__2 = Math.Max(1, AutoArchiveConfig.ScannerBatchSize);
						<processedSinceYield>5__3 = 0;
						<sectors>5__4 = ZDOMan.instance.m_objectsBySector;
						<sectorIndex>5__5 = 0;
						goto IL_0138;
					case 1:
						<>1__state = -1;
						goto IL_00fb;
					case 2:
						{
							<>1__state = -3;
							goto IL_022d;
						}
						IL_0138:
						if (<sectors>5__4 != null && <sectorIndex>5__5 < <sectors>5__4.Length)
						{
							<sector>5__6 = <sectors>5__4[<sectorIndex>5__5];
							if (<sector>5__6 != null && <sector>5__6.Count != 0)
							{
								<objectIndex>5__7 = 0;
								goto IL_010b;
							}
							goto IL_0128;
						}
						list = ZDOMan.instance.m_objectsByOutsideSector?.Values.ToList() ?? new List<List<ZDO>>();
						<>7__wrap7 = list.GetEnumerator();
						<>1__state = -3;
						goto IL_025a;
						IL_00fb:
						<objectIndex>5__7++;
						goto IL_010b;
						IL_0128:
						<sectorIndex>5__5++;
						goto IL_0138;
						IL_025a:
						do
						{
							if (<>7__wrap7.MoveNext())
							{
								<sector>5__6 = <>7__wrap7.Current;
								continue;
							}
							<>m__Finally1();
							<>7__wrap7 = default(List<List<ZDO>>.Enumerator);
							return false;
						}
						while (<sector>5__6 == null || <sector>5__6.Count == 0);
						<sectorIndex>5__5 = 0;
						goto IL_023d;
						IL_010b:
						if (<objectIndex>5__7 < <sector>5__6.Count)
						{
							ProcessScanZdo(<sector>5__6[<objectIndex>5__7], utcNow, zoneInfos, run);
							<processedSinceYield>5__3++;
							if (<processedSinceYield>5__3 >= <batchSize>5__2)
							{
								<processedSinceYield>5__3 = 0;
								<>2__current = null;
								<>1__state = 1;
								return true;
							}
							goto IL_00fb;
						}
						<sector>5__6 = null;
						goto IL_0128;
						IL_022d:
						<sectorIndex>5__5++;
						goto IL_023d;
						IL_023d:
						if (<sectorIndex>5__5 < <sector>5__6.Count)
						{
							ProcessScanZdo(<sector>5__6[<sectorIndex>5__5], utcNow, zoneInfos, run);
							<processedSinceYield>5__3++;
							if (<processedSinceYield>5__3 >= <batchSize>5__2)
							{
								<processedSinceYield>5__3 = 0;
								<>2__current = null;
								<>1__state = 2;
								return true;
							}
							goto IL_022d;
						}
						<sector>5__6 = null;
						goto IL_025a;
					}
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

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

			private void <>m__Finally1()
			{
				<>1__state = -1;
				((IDisposable)<>7__wrap7).Dispose();
			}

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

		private static readonly Vector2i[] NeighborOffsets = (Vector2i[])(object)new Vector2i[8]
		{
			new Vector2i(-1, -1),
			new Vector2i(0, -1),
			new Vector2i(1, -1),
			new Vector2i(-1, 0),
			new Vector2i(1, 0),
			new Vector2i(-1, 1),
			new Vector2i(0, 1),
			new Vector2i(1, 1)
		};

		private static ManualLogSource _logger = null;

		public static void Initialize(ManualLogSource logger)
		{
			_logger = logger;
		}

		[IteratorStateMachine(typeof(<Run>d__3))]
		public static IEnumerator Run(AutoArchiveScanOptions options, Action<ArchiveRunRecord> onComplete)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <Run>d__3(0)
			{
				options = options,
				onComplete = onComplete
			};
		}

		private static bool TryReadPlayerStructure(ZDO zdo, DateTime utcNow, out Vector2i zone, out long creator)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			zone = default(Vector2i);
			creator = 0L;
			if (!ZoneStructureClassifier.TryInspectAutoArchiveCandidate(zdo, out ZoneStructureInfo info))
			{
				return false;
			}
			creator = info.CreatorPlayerId;
			AutoArchiveStore.RecordUnknownPlayer(creator, utcNow, info.CreatorName);
			zone = info.ObjectZone;
			return true;
		}

		[IteratorStateMachine(typeof(<ScanZdosBySector>d__5))]
		private static IEnumerator ScanZdosBySector(DateTime utcNow, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, ArchiveRunRecord run)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ScanZdosBySector>d__5(0)
			{
				utcNow = utcNow,
				zoneInfos = zoneInfos,
				run = run
			};
		}

		private static void ProcessScanZdo(ZDO zdo, DateTime utcNow, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, ArchiveRunRecord run)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			run.ScannedZdos++;
			if (TryReadPlayerStructure(zdo, utcNow, out var zone, out var creator))
			{
				run.StructureZdos++;
				if (!zoneInfos.TryGetValue(zone, out AutoArchiveZoneInfo value))
				{
					value = (zoneInfos[zone] = new AutoArchiveZoneInfo(zone));
				}
				value.AddCreator(creator);
			}
		}

		private static Dictionary<Vector2i, AutoArchiveZoneInfo> BuildCandidateZones(Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, DateTime utcNow, AutoArchiveScanPolicy policy, ArchiveRunRecord run)
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: 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_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<Vector2i, AutoArchiveZoneInfo> dictionary = new Dictionary<Vector2i, AutoArchiveZoneInfo>();
			foreach (AutoArchiveZoneInfo value in zoneInfos.Values)
			{
				string reason;
				if (policy.IsTargetOverride)
				{
					if (policy.IncludesTargetCreator(value.Creators))
					{
						if (policy.BlocksMixedOwnerReset && HasNonTargetCreators(value.Creators, policy.TargetPlayerIds, out List<long> nonTargetCreators))
						{
							run.Messages.Add($"Skipped zone ({value.Zone.x},{value.Zone.y}): target reset blocked for mixed-owner zone; non-target creator(s): {FormatCreatorList(nonTargetCreators)}");
						}
						else
						{
							dictionary[value.Zone] = value;
						}
					}
				}
				else if (!AllCreatorsEligible(value.Creators, utcNow, out reason))
				{
					run.Messages.Add($"Skipped zone ({value.Zone.x},{value.Zone.y}): {reason}");
				}
				else
				{
					dictionary[value.Zone] = value;
				}
			}
			return dictionary;
		}

		private static bool HasNonTargetCreators(IEnumerable<long> creators, HashSet<long> targetPlayerIds, out List<long> nonTargetCreators)
		{
			HashSet<long> targetPlayerIds2 = targetPlayerIds;
			nonTargetCreators = (from creator in creators.Where((long creator) => creator != 0L && !targetPlayerIds2.Contains(creator)).Distinct()
				orderby creator
				select creator).ToList();
			return nonTargetCreators.Count > 0;
		}

		private static string FormatCreatorList(IEnumerable<long> creators)
		{
			return string.Join(", ", creators.Select((long creator) => creator.ToString(CultureInfo.InvariantCulture)));
		}

		private static List<List<Vector2i>> BuildClusters(Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			List<List<Vector2i>> list = new List<List<Vector2i>>();
			HashSet<Vector2i> hashSet = candidates.Keys.ToHashSet();
			Vector2i item2 = default(Vector2i);
			while (hashSet.Count > 0)
			{
				Vector2i item = hashSet.First();
				hashSet.Remove(item);
				List<Vector2i> list2 = new List<Vector2i>();
				Queue<Vector2i> queue = new Queue<Vector2i>();
				queue.Enqueue(item);
				while (queue.Count > 0)
				{
					Vector2i val = queue.Dequeue();
					list2.Add(val);
					Vector2i[] neighborOffsets = NeighborOffsets;
					foreach (Vector2i val2 in neighborOffsets)
					{
						((Vector2i)(ref item2))..ctor(val.x + val2.x, val.y + val2.y);
						if (hashSet.Contains(item2))
						{
							hashSet.Remove(item2);
							queue.Enqueue(item2);
						}
					}
				}
				list.Add(list2);
			}
			return list;
		}

		private static ArchiveClusterRecord BuildClusterRecord(List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
		{
			//IL_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)
		{
			foreach (long creator in creators)
			{
				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)
		{
			string arg = BuildOwnerSegment(BuildCreatorPieceCounts(cluster, candidates));
			return $"auto_{arg}_c{index:D3}";
		}

		private static Dictionary<long, int> BuildCreatorPieceCounts(List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<long, int> dictionary = new Dictionary<long, int>();
			foreach (Vector2i item in cluster)
			{
				foreach (KeyValuePair<long, int> creatorPieceCount in candidates[item].CreatorPieceCounts)
				{
					dictionary.TryGetValue(creatorPieceCount.Key, out var value);
					dictionary[creatorPieceCount.Key] = value + creatorPieceCount.Value;
				}
			}
			return dictionary;
		}

		private static string BuildOwnerSegment(IReadOnlyDictionary<long, int> creatorPieceCounts)
		{
			IReadOnlyDictionary<long, int> creatorPieceCounts2 = creatorPieceCounts;
			List<long> list = (from creator in creatorPieceCounts2.Keys.Where((long creator) => creator != 0).Distinct()
				orderby creator
				select creator).ToList();
			if (list.Count == 0)
			{
				return "unknown";
			}
			int value;
			string text = BuildOwnerToken((from owner in list
				orderby creatorPieceCounts2.TryGetValue(owner, out value) ? value : 0 descending, owner
				select owner).First());
			if (list.Count == 1)
			{
				return text;
			}
			return $"{text}_plus{list.Count - 1}_{BuildOwnerHash(list)}";
		}

		private static string BuildOwnerToken(long playerId)
		{
			string text = ResolvePlayerName(playerId);
			string text2 = ResolveSteamId(playerId);
			if (!string.Equals(text2, "unknown", StringComparison.Ordinal))
			{
				return text + "_s" + text2;
			}
			return text;
		}

		private static string ResolvePlayerName(long playerId)
		{
			if (!AutoArchiveStore.TryGetPlayerRecord(playerId, out PlayerActivityRecord record))
			{
				return "unknown";
			}
			return 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;
		}

		private static string FormatDate(DateTime value)
		{
			if (!(value == DateTime.MinValue))
			{
				return value.ToString("O", CultureInfo.InvariantCulture);
			}
			return "-";
		}
	}
	internal static class AutoArchiveService
	{
		[CompilerGenerated]
		private sealed class <>c__DisplayClass15_0
		{
			public ArchiveRunRecord completed;

			internal void <RunScan>b__0(ArchiveRunRecord run)
			{
				completed = run;
			}
		}

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

			private object <>2__current;

			public AutoArchiveScanOptions options;

			private <>c__DisplayClass15_0 <>8__1;

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

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

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

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

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass15_0();
					<>8__1.completed = null;
					<>2__current = AutoArchiveScanner.Run(options, delegate(ArchiveRunRecord run)
					{
						<>8__1.completed = run;
					});
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					if (<>8__1.completed != null)
					{
						AutoArchiveStore.RecordRun(<>8__1.completed);
						AutoArchiveStore.Flush(force: true);
						_logger.LogInfo((object)$"Auto archive scan finished: {<>8__1.completed.CandidateZones} candidate zone(s), {<>8__1.completed.ProcessedZones} processed zone(s), {<>8__1.completed.Clusters.Count} cluster record(s).");
					}
					_scanRunning = false;
					_scanCoroutine = null;
					return false;
				}
			}

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

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

		private static readonly TimeSpan TrackInterval = TimeSpan.FromMinutes(10.0);

		private static readonly TimeSpan FlushInterval = TimeSpan.FromMinutes(5.0);

		private static ManualLogSource _logger = null;

		private static bool _initialized;

		private static bool _scanRunning;

		private static DateTime _lastTrackUtc = DateTime.MinValue;

		private static DateTime _lastFlushUtc = DateTime.MinValue;

		private static Coroutine? _scanCoroutine;

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

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

		private static void QueueScan(AutoArchiveScanOptions options)
		{
			_scanRunning = true;
			_logger.LogInfo((object)$"Starting auto archive scan (manual: {options.Manual}, dry run: {options.DryRun}, reset: {options.ResetAfterSave}, targets: {options.TargetPlayerIds.Count}).");
			_scanCoroutine = ((MonoBehaviour)ZoneSaviorPlugin.Instance).StartCoroutine(RunScan(options));
		}

		[IteratorStateMachine(typeof(<RunScan>d__15))]
		private static IEnumerator RunScan(AutoArchiveScanOptions options)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <RunScan>d__15(0)
			{
				options = options
			};
		}

		private static bool IsServerReady()
		{
			if ((Object)(object)ZoneSaviorPlugin.Instance != (Object)null && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null)
			{
				return (Object)(object)ZoneSystem.instance != (Object)null;
			}
			return false;
		}
	}
	internal static class AutoArchiveStore
	{
		private const string UnknownPlatformPrefix = "unknown:";

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

		public static void Flush(bool force = false)
		{
			lock (Sync)
			{
				if (_dirty || force)
				{
					DateTime utcNow = DateTime.UtcNow;
					if (force || !(utcNow - _lastFlushUtc < TimeSpan.FromSeconds(60.0)))
					{
						Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
						File.WriteAllText(FilePath, Serializer.Serialize(State));
						_lastFlushUtc = utcNow;
						_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;
				}
				State = state;
				RebuildIndexes();
				_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)
				{
					AddDistinct(playerActivityRecord.PlayerIds, playerId);
					PlayersById[playerId] = playerActivityRecord;
					MergePlayerIdRecords(playerActivityRecord, playerId);
				}
				_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;
			}
		}

		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;
			}
			if (ZoneLimitConfiguration.IsArchiveProtected(playerId, out string reason))
			{
				autoArchiveCreatorEligibility.Protected = true;
				autoArchiveCreatorEligibility.Reason = reason;
				return autoArchiveCreatorEligibility;
			}
			if (!TryGetPlayerRecord(playerId, out PlayerActivityRecord record))
			{
				autoArchiveCreatorEligibility.PlatformId = UnknownPlatformId(playerId);
				autoArchiveCreatorEligibility.Names = (string.IsNullOrWhiteSpace(observedName) ? new List<string>(1) { "not_recorded_in_activity" } : new List<string>(1) { observedName.Trim() });
				autoArchiveCreatorEligibility.UnknownActivityRecord = true;
				if (recordUnknownPlayer)
				{
					RecordUnknownPlayer(playerId, utcNow, observedName);
					autoArchiveCreatorEligibility.Reason = $"player {playerId} was first discovered by scanner";
				}
				else
				{
					autoArchiveCreatorEligibility.Reason = $"player {playerId} is not recorded in activity";
				}
				return autoArchiveCreatorEligibility;
			}
			autoArchiveCreatorEligibility.RecordedInActivity = true;
			autoArchiveCreatorEligibility.PlatformId = record.PlatformId;
			autoArchiveCreatorEligibility.Names = record.Names?.ToList() ?? new List<string>();
			bool flag2 = (autoArchiveCreatorEligibility.UnknownActivityRecord = IsUnknown(record));
			DateTime 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 MergePlayerIdRecords(PlayerActivityRecord target, long playerId)
		{
			PlayerActivityRecord target2 = target;
			foreach (PlayerActivityRecord item in State.Players.Where((PlayerActivityRecord player) => player != target2 && player.PlayerIds.Contains(playerId)).ToList())
			{
				foreach (long playerId2 in item.PlayerIds)
				{
					AddDistinct(target2.PlayerIds, playerId2);
				}
				foreach (string name in item.Names)
				{
					AddDistinct(target2.Names, name);
				}
				if (item.LastSeenUtc > target2.LastSeenUtc)
				{
					target2.LastSeenUtc = item.LastSeenUtc;
				}
				State.Players.Remove(item);
			}
			RebuildPlayerIndexes();
		}

		private static void RebuildIndexes()
		{
			RebuildPlayerIndexes();
		}

		private static void RebuildPlayerIndexes()
		{
			PlayersByPlatform.Clear();
			PlayersById.Clear();
			foreach (PlayerActivityRecord player in State.Players)
			{
				IndexPlayer(player);
			}
		}

		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 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();
				NormalizeState(state);
				return true;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
		}

		private static void NormalizeState(AutoArchiveState state)
		{
			AutoArchiveState autoArchiveState = state;
			if (autoArchiveState.Players == null)
			{
				List<PlayerActivityRecord> list2 = (autoArchiveState.Players = new List<PlayerActivityRecord>());
			}
			autoArchiveState = state;
			if (autoArchiveState.Runs == null)
			{
				List<ArchiveRunRecord> list4 = (autoArchiveState.Runs = new List<ArchiveRunRecord>());
			}
		}

		private static void AddDistinct<T>(List<T> values, T value)
		{
			if (value != null && !values.Contains(value))
			{
				values.Add(value);
			}
		}
	}
	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)
		{
			EnsureElements();
			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 references)
			//IL_00e9: 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_0169: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_text != (Object)null) || !((Object)(object)_canvasGroup != (Object)null))
			{
				TMP_Text val = Hud.instance?.m_buildSelection;
				if (!((Object)(object)val == (Object)null))
				{
					GameObject val2 = new GameObject("ZoneSaviorCounter", new Type[4]
					{
						typeof(RectTransform),
						typeof(CanvasRenderer),
						typeof(CanvasGroup),
						typeof(TextMeshProUGUI)
					});
					val2.transform.SetParent(((Component)Hud.instance).transform, false);
					RectTransform val3 = (RectTransform)val2.transform;
					val3.anchorMin = new Vector2(0.5f, 1f);
					val3.anchorMax = new Vector2(0.5f, 1f);
					val3.pivot = new Vector2(0.5f, 1f);
					val3.anchoredPosition = new Vector2(0f, -18f);
					val3.sizeDelta = new Vector2(280f, 34f);
					_canvasGroup = val2.GetComponent<CanvasGroup>();
					_canvasGroup.alpha = 0f;
					_text = val2.GetComponent<TextMeshProUGUI>();
					((TMP_Text)_text).font = val.font;
					((TMP_Text)_text).fontSharedMaterial = val.fontSharedMaterial;
					((TMP_Text)_text).fontSize = val.fontSize;
					((Graphic)_text).color = ((Graphic)val).color;
					((TMP_Text)_text).alignment = (TextAlignmentOptions)514;
					((TMP_Text)_text).textWrappingMode = (TextWrappingModes)0;
					((TMP_Text)_text).overflowMode = (TextOverflowModes)0;
					((Graphic)_text).raycastTarget = false;
					((TMP_Text)_text).text = string.Empty;
				}
			}
		}
	}
	internal static class ZoneSaviorTimestamp
	{
		public static string Now()
		{
			return Format(DateTime.UtcNow);
		}

		public static string Format(DateTime utc)
		{
			if (utc == DateTime.MinValue)
			{
				return "";
			}
			return new DateTimeOffset(DateTime.SpecifyKind(utc, DateTimeKind.Utc)).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture);
		}

		public static DateTime ParseUtc(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return DateTime.MinValue;
			}
			if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var result) || DateTimeOffset.TryParse(value, CultureInfo.CurrentCulture, DateTimeStyles.AllowWhiteSpaces, out result))
			{
				return DateTime.SpecifyKind(result.UtcDateTime, DateTimeKind.Utc);
			}
			if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal, out var result2) || DateTime.TryParse(value, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal, out result2))
			{
				return DateTime.SpecifyKind(result2, DateTimeKind.Utc);
			}
			return DateTime.MinValue;
		}

		public static bool IsExpired(string timestamp, DateTime utcNow)
		{
			DateTime dateTime = ParseUtc(timestamp);
			if (dateTime != DateTime.MinValue)
			{
				return dateTime <= DateTime.SpecifyKind(utcNow, DateTimeKind.Utc);
			}
			return false;
		}
	}
	internal static class VeiledRecipesCompat
	{
		private const string PluginGuid = "sighsorry.VeiledRecipes";

		private const string ApiTypeName = "VeiledRecipes.VeiledRecipesCompat";

		private static readonly BindingFlags PublicStaticFlags = BindingFlags.Static | BindingFlags.Public;

		private static ManualLogSource? _logger;

		private static bool _initialized;

		private static bool _registered;

		private static MethodInfo? _registerKnownPieceOverrideMethod;

		private static readonly Func<Piece, bool> KnownPieceOverride = AdminTerrainTool.IsProxyPiece;

		public static void Initialize(ManualLogSource logger)
		{
			_logger = logger;
			EnsureInitialized();
			RegisterKnownPieceOverride();
		}

		private static void EnsureInitialized()
		{
			if (!_initialized)
			{
				_initialized = true;
				if (Chainloader.PluginInfos.TryGetValue("sighsorry.VeiledRecipes", out var value))
				{
					_registerKnownPieceOverrideMethod = ((((object)value.Instance)?.GetType().Assembly)?.GetType("VeiledRecipes.VeiledRecipesCompat", throwOnError: false))?.GetMethod("RegisterKnownPieceOverride", PublicStaticFlags, null, new Type[1] { typeof(Func<Piece, bool>) }, null);
				}
			}
		}

		private static void RegisterKnownPieceOverride()
		{
			if (_registered || _registerKnownPieceOverrideMethod == null)
			{
				return;
			}
			try
			{
				_registerKnownPieceOverrideMethod.Invoke(null, new object[1] { KnownPieceOverride });
				_registered = true;
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					logger.LogDebug((object)"Registered ZoneSavior admin terrain tools with VeiledRecipes.");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource? logger2 = _logger;
				if (logger2 != null)
				{
					logger2.LogDebug((object)("Could not register ZoneSavior admin terrain tools with VeiledRecipes: " + ex.Message));
				}
			}
		}
	}
	internal static class AdminTerrainInfinityHammerCompat
	{
		private static ManualLogSource? _logger;

		private static bool _available;

		private static bool _patched;

		private static bool _saveDataConfigured;

		private static Type? _configurationType;

		public static void Initialize(ManualLogSource logger, Harmony harmony)
		{
			_logger = logger;
			if (!_patched)
			{
				_configurationType = FindLoadedType("InfinityHammer.Configuration");
				_available = _configurationType != null;
				if (!_available)
				{
					_logger.LogDebug((object)"Infinity Hammer compat skipped: Infinity Hammer is not loaded.");
					return;
				}
				int num = 0;
				num += PatchMethod(harmony, "InfinityHammer.BaseSelection:PostProcessPlaced", new Type[1] { typeof(GameObject) }, "InfinityHammerPostProcessPrefix", prefix: true);
				num += PatchMethod(harmony, "InfinityHammer.BaseSelection:PostProcessPlaced", new Type[1] { typeof(GameObject) }, "InfinityHammerPostProcessPostfix", prefix: false);
				num += PatchMethod(harmony, "InfinityHammer.NoCreator:Set", new Type[2]
				{
					typeof(ZNetView),
					typeof(Piece)
				}, "InfinityHammerNoCreatorPrefix", prefix: true);
				_patched = num > 0;
				EnsureSavesTerrainData();
				_logger.LogInfo((object)$"Infinity Hammer compat initialized. Patched {num} method(s).");
			}
		}

		public static void Update()
		{
			if (_available)
			{
				EnsureSavesTerrainData();
			}
		}

		public static void Shutdown()
		{
			_patched = false;
			_saveDataConfigured = false;
			_configurationType = null;
		}

		private static Type? FindLoadedType(string fullName)
		{
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				Type type = ((object)value.Instance)?.GetType().Assembly.GetType(fullName, throwOnError: false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		private static int PatchMethod(Harmony harmony, string fullMethodName, Type[] parameters, string patchMethodName, bool prefix)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(fullMethodName, parameters, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(AdminTerrainInfinityHammerCompat), patchMethodName, (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					logger.LogDebug((object)("Infinity Hammer compat skipped method: " + fullMethodName));
				}
				return 0;
			}
			HarmonyMethod val = new HarmonyMethod(methodInfo2);
			if (prefix)
			{
				harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			return 1;
		}

		private static void InfinityHammerPostProcessPrefix(GameObject obj)
		{
			AdminTerrainTool.PrepareInfinityHammerPlaced(obj);
		}

		private static void InfinityHammerPostProcessPostfix(GameObject obj)
		{
			AdminTerrainTool.ApplyInfinityHammerPlaced(obj);
		}

		private static bool InfinityHammerNoCreatorPrefix(ZNetView view, Piece piece)
		{
			return AdminTerrainTool.ShouldRunInfinityHammerNoCreator(view, piece);
		}

		private static void EnsureSavesTerrainData()
		{
			if (!_saveDataConfigured && (_configurationType?.GetField("SavedObjectData", BindingFlags.Static | BindingFlags.Public))?.GetValue(null) is HashSet<string> hashSet)
			{
				hashSet.Add("ZoneSaviorTerrainProxy".ToLowerInvariant());
				hashSet.Add("ZoneSaviorTerrainProxySlope".ToLowerInvariant());
				hashSet.Add("ZoneSaviorPaintProxy".ToLowerInvariant());
				_saveDataConfigured = true;
			}
		}
	}
	internal static class AdminTerrainTool
	{
		private enum PreviewKind
		{
			None,
			Circle,
			Width,
			Slope
		}

		private enum TerrainProxyMode
		{
			Circle,
			Slope,
			Paint
		}

		private readonly struct SlopePlacement
		{
			public Vector3 Center { get; }

			public Quaternion Rotation { get; }

			public float Width { get; }

			public float Length { get; }

			public float HeightDelta { get; }

			public SlopePlacement(Vector3 center, Quaternion rotation, float width, float length, float heightDelta)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0002: Unknown result type (might be due to invalid IL or missing references)
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				Center = center;
				Rotation = rotation;
				Width = width;
				Length = length;
				HeightDelta = heightDelta;
			}

			public Vector3 GetWorldPoint(float localX, float localZ)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0013: 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)
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_005e: Unknown result type (might be due to invalid IL or missing references)
				Vector3 result = Center + Rotation * new Vector3(localX, 0f, localZ);
				result.y = Center.y + Mathf.Clamp(localZ / Mathf.Max(Length, 0.001f), -0.5f, 0.5f) * HeightDelta;
				return result;
			}
		}

		private readonly struct TerrainProxySettings
		{
			public TerrainProxyMode Mode { get; }

			public float Radius { get; }

			public float Width { get; }

			public float Length { get; }

			public float SlopeHeightDelta { get; }

			public float TerrainEdgeSoftness { get; }

			public AdminTerrainPaintType PaintType { get; }

			public bool ModifiesHeight
			{
				get
				{
					TerrainProxyMode mode = Mode;
					if ((uint)mode <= 1u)
					{
						return true;
					}
					return false;
				}
			}

			public bool ModifiesPaint => Mode == TerrainProxyMode.Paint;

			public bool HasCircleFootprint => Mode != TerrainProxyMode.Slope;

			public float TerrainRadius
			{
				get
				{
					if (Mode != TerrainProxyMode.Slope)
					{
						return Radius;
					}
					return Mathf.Sqrt(Width * Width + Length * Length) * 0.5f;
				}
			}

			public float SearchRadius => TerrainRadius;

			public TerrainProxySettings(TerrainProxyMode mode, float radius, float width, float length, float slopeHeightDelta, float terrainEdgeSoftness, AdminTerrainPaintType paintType)
			{
				Mode = mode;
				Radius = Mathf.Clamp(radius, 0.5f, 128f);
				Width = Mathf.Clamp(width, 0.5f, 256f);
				Length = Mathf.Clamp(length, 0.5f, 256f);
				SlopeHeightDelta = Mathf.Clamp(slopeHeightDelta, -1024f, 1024f);
				TerrainEdgeSoftness = Mathf.Clamp01(terrainEdgeSoftness);
				PaintType = paintType;
			}

			public float GetNormalizedDistance(float x, float z)
			{
				if (Mode == TerrainProxyMode.Slope)
				{
					return Mathf.Max(Mathf.Abs(x) / (Width * 0.5f), Mathf.Abs(z) / (Length * 0.5f));
				}
				return Mathf.Sqrt(x * x + z * z) / Radius;
			}

			public float GetTargetHeight(Vector3 center, float heightmapWorldY, float localZ)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				float num = center.y;
				if (Mode == TerrainProxyMode.Slope)
				{
					num += Mathf.Clamp(localZ / Length, -0.5f, 0.5f) * SlopeHeightDelta;
				}
				return num - heightmapWorldY;
			}

			public float GetLevelFalloff(float x, float z)
			{
				if (Mode != 0 || TerrainEdgeSoftness <= 0f)