Decompiled source of WorldCrawler v1.0.1

Landoria.WorldCrawler.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Landoria.WorldCrawler.Capture;
using Landoria.WorldCrawler.Commands;
using Landoria.WorldCrawler.Protection;
using Landoria.WorldCrawler.Restoration;
using Landoria.WorldCrawler.Restoration.Cleanup;
using Landoria.WorldCrawler.Restoration.Compatibility;
using Landoria.WorldCrawler.Restoration.Objects;
using Landoria.WorldCrawler.Restoration.Persistence;
using Landoria.WorldCrawler.Runtime;
using Landoria.WorldCrawler.Storage;
using Landoria.WorldCrawler.UI;
using Landoria.WorldCrawler.WorldPreparation;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Landoria.WorldCrawler")]
[assembly: AssemblyDescription("Exports observed Valheim zones and restores them into prepared local worlds.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Landoria")]
[assembly: AssemblyProduct("Landoria.WorldCrawler")]
[assembly: AssemblyCopyright("Copyright © 2026 End3rbyte")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("F61A6157-69B4-47D5-876F-21BAFEB344F9")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: AssemblyInformationalVersion("1.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.1.37806")]
namespace Landoria.WorldCrawler
{
	[BepInPlugin("Landoria.WorldCrawler", "World Crawler", "1.0.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal const string PluginGuid = "Landoria.WorldCrawler";

		internal const string PluginName = "World Crawler";

		internal const string PluginVersion = "1.0.1";

		private CrawlController _controller;

		private Harmony _harmony;

		private WorldPreparationController _preparation;

		private RestorationController _restoration;

		private ExportMapOverlay _mapOverlay;

		private RecordingHud _recordingHud;

		private IndestructibleCommand _indestructible;

		private ConfigEntry<bool> _enableCheats;

		private readonly LocalDaylight _daylight = new LocalDaylight();

		private void Awake()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			GameContext.ValidateVersion();
			_enableCheats = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableCheats", false, "Enables god mode, ghost mode, cold immunity, unlimited stamina, and Alt-click map teleportation.");
			CharacterMarkerPolicy.Initialize(((BaseUnityPlugin)this).Logger);
			_harmony = new Harmony("Landoria.WorldCrawler");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			ApplySettings();
			_controller = new CrawlController(((BaseUnityPlugin)this).Logger);
			_preparation = new WorldPreparationController(((BaseUnityPlugin)this).Logger);
			_restoration = new RestorationController(((BaseUnityPlugin)this).Logger);
			_indestructible = new IndestructibleCommand(((BaseUnityPlugin)this).Logger, () => _controller.Busy || _preparation.Busy, _restoration);
			_restoration.SceneryDeletionsPending = _indestructible.DeletionsPending;
			_mapOverlay = new ExportMapOverlay(((BaseUnityPlugin)this).Logger, _controller, _restoration);
			_recordingHud = new RecordingHud(((BaseUnityPlugin)this).Logger);
			_controller.ZoneSaved += _recordingHud.AddReport;
			_restoration.ZoneRestored += _recordingHud.AddRestoreReport;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"World Crawler 1.0.1 is loaded. F8: manual recording. F9: prepare world. F10: manual restoration.");
		}

		private void Update()
		{
			if (_controller != null)
			{
				ApplySettings();
				CharacterMarkerPolicy.Enforce(((Object)(object)Game.instance == (Object)null) ? null : Game.instance.GetPlayerProfile());
				_daylight.Update();
				_preparation.Update();
				HandleShortcut();
				_controller.Update();
				_restoration.Update();
				_recordingHud?.Update(_controller, _restoration);
			}
		}

		private void ApplySettings()
		{
			PlayerProtection.Enabled = _enableCheats != null && _enableCheats.Value;
		}

		private void HandleShortcut()
		{
			try
			{
				int num = ShortcutInput.Action();
				if (num == 0 && !_restoration.Busy && !_preparation.Busy)
				{
					_controller.Toggle();
				}
				if (num == 1 && !_controller.Busy && !_restoration.Busy)
				{
					_preparation.Start();
				}
				if (num == 2 && !_controller.Busy && !_preparation.Busy)
				{
					_restoration.Toggle();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)ex.Message);
			}
		}

		private void LateUpdate()
		{
			_mapOverlay?.Update();
		}

		private void OnDestroy()
		{
			PlayerProtection.Enabled = false;
			_indestructible?.Dispose();
			_daylight.Reset();
			try
			{
				_controller?.Dispose();
				_restoration?.Dispose();
				_preparation?.Dispose();
			}
			finally
			{
				try
				{
					_mapOverlay?.Dispose();
					_recordingHud?.Dispose();
				}
				finally
				{
					Harmony harmony = _harmony;
					if (harmony != null)
					{
						harmony.UnpatchSelf();
					}
				}
			}
		}
	}
}
namespace Landoria.WorldCrawler.Restoration
{
	internal sealed class RestorationController : IDisposable
	{
		private int _cleanupIndex;

		private float _nextCleanup;

		private readonly ManualLogSource _log;

		private RestoreSession _session;

		private RestorePhase _phase;

		private ObjectRestorer _objects;

		private RestoreWarnings _warnings;

		private readonly NearZoneScope _scope = new NearZoneScope();

		private readonly HashSet<string> _visited = new HashSet<string>();

		private readonly HashSet<string> _applied = new HashSet<string>();

		private IEnumerator<CapturedObject> _scan;

		private Task<string> _backup;

		private Task<ZoneImportData> _read;

		private Task _closing;

		private ZoneEntry _zone;

		private ZoneRestorer _writer;

		private uint _saveBefore;

		private bool _pause;

		private bool _initialSave;

		private bool _finalSave;

		private bool _dirty;

		private bool _finalized;

		private bool _teleportTransit;

		private float _waitingSince;

		private float _lastSave;

		private float _retrySaveAt;

		private string _lastSaveUtc = "none";

		private int _restoredObjects;

		public bool Active
		{
			get
			{
				if (_phase != RestorePhase.Idle)
				{
					return _phase != RestorePhase.Stopped;
				}
				return false;
			}
		}

		public bool Busy
		{
			get
			{
				if (!Active)
				{
					if (_closing != null)
					{
						return !_closing.IsCompleted;
					}
					return false;
				}
				return true;
			}
		}

		internal string HudStatus
		{
			get
			{
				if (Active)
				{
					if (!_pause)
					{
						if (!Preparing)
						{
							if (!IsSavePhase())
							{
								return "Restoring...";
							}
							return "Saving restoration...";
						}
						return "Preparing restoration...";
					}
					return "Finishing restoration...";
				}
				return null;
			}
		}

		private bool Preparing
		{
			get
			{
				if (_phase != RestorePhase.Preparing && _phase != RestorePhase.Preflight && _phase != RestorePhase.InitialSave)
				{
					return _phase == RestorePhase.Backup;
				}
				return true;
			}
		}

		internal Func<bool> SceneryDeletionsPending { get; set; }

		internal event Action<ZoneSaveReport> ZoneRestored;

		private void CleanupRestoredZone()
		{
			if (Time.unscaledTime < _nextCleanup)
			{
				return;
			}
			_nextCleanup = Time.unscaledTime + 2f;
			ZoneEntry[] array = _session.Archive.Manifest.Zones.Where((ZoneEntry zone) => _scope.Contains(zone.X, zone.Z) && _visited.Contains(ZoneKey(zone.X, zone.Z)) && (_applied.Contains(ZoneKey(zone.X, zone.Z)) || _session.Journal.State.Completed.Contains(ZoneKey(zone.X, zone.Z)))).ToArray();
			if (array.Length != 0)
			{
				_cleanupIndex %= array.Length;
				ZoneEntry zoneEntry = array[_cleanupIndex++];
				if (NearZoneScope.Ready(zoneEntry.X, zoneEntry.Z) && _objects.CleanupZone(zoneEntry.X, zoneEntry.Z) != 0)
				{
					string item = ZoneKey(zoneEntry.X, zoneEntry.Z);
					_applied.Add(item);
					_dirty = true;
					_session.Journal.Save();
				}
			}
		}

		internal void GetHudStats(out int saved, out int total, out int pending, out int objects)
		{
			saved = (_session?.Journal?.State?.Completed?.Count).GetValueOrDefault();
			total = (_session?.Archive?.PlannedZoneCount).GetValueOrDefault();
			pending = _applied.Count;
			objects = _restoredObjects;
		}

		public RestorationController(ManualLogSource log)
		{
			_log = log;
		}

		public void Toggle()
		{
			try
			{
				if (Active)
				{
					_pause = true;
					_retrySaveAt = 0f;
					Say("Stopping restoration; saving changes. Movement remains unrestricted.");
				}
				else if (Busy)
				{
					Say("Finishing the previous operation.");
				}
				else
				{
					_closing?.GetAwaiter().GetResult();
					Reset();
					_session = new RestoreSession();
					_phase = RestorePhase.Preparing;
					Say("Validating the export and prepared local world...");
				}
			}
			catch (Exception error)
			{
				Fail(error);
			}
		}

		private void Reset()
		{
			_closing = null;
			_zone = null;
			_writer = null;
			_pause = (_dirty = (_finalSave = (_finalized = (_teleportTransit = false))));
			_visited.Clear();
			_applied.Clear();
			_retrySaveAt = 0f;
			_restoredObjects = 0;
			_cleanupIndex = 0;
			_nextCleanup = 0f;
		}

		public void Update()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if (!Active)
			{
				return;
			}
			try
			{
				_session.Check();
				if (!(Time.timeScale <= 0f) && !ManualTransit())
				{
					_scope.Refresh(((Component)Player.m_localPlayer).transform.position);
					if (!ZNet.instance.IsSaving() || IsSavePhase())
					{
						Advance();
					}
				}
			}
			catch (Exception error)
			{
				Fail(error);
			}
		}

		private void Advance()
		{
			if (_pause && CanStopAtBoundary())
			{
				SuspendZone();
				QueueSave(initial: false, final: true);
				return;
			}
			switch (_phase)
			{
			case RestorePhase.Preparing:
				Prepare();
				break;
			case RestorePhase.Preflight:
				Preflight();
				break;
			case RestorePhase.InitialSave:
				InitialSave();
				break;
			case RestorePhase.Backup:
				Backup();
				break;
			case RestorePhase.Reading:
				ReadZone();
				break;
			case RestorePhase.Restoring:
				Restore();
				break;
			case RestorePhase.Connecting:
				Connect();
				break;
			default:
				AdvanceCheckpoint();
				break;
			}
		}

		private void AdvanceCheckpoint()
		{
			switch (_phase)
			{
			case RestorePhase.RequestSave:
				RequestSave();
				break;
			case RestorePhase.Saving:
				FinishSave();
				break;
			case RestorePhase.Finalizing:
				FinalizeObjects();
				break;
			case RestorePhase.Waiting:
				WaitForZone();
				break;
			}
		}

		private bool CanStopAtBoundary()
		{
			if (_phase != RestorePhase.Waiting && _phase != RestorePhase.Restoring && _phase != RestorePhase.Connecting && _phase != RestorePhase.Finalizing)
			{
				if (_phase == RestorePhase.Reading)
				{
					return _read.IsCompleted;
				}
				return false;
			}
			return true;
		}

		private bool IsSavePhase()
		{
			if (_phase != RestorePhase.RequestSave && _phase != RestorePhase.Saving && _phase != RestorePhase.InitialSave)
			{
				return _phase == RestorePhase.Backup;
			}
			return true;
		}

		private void Prepare()
		{
			if (_session.Prepare())
			{
				_warnings = new RestoreWarnings(_session.Journal.State.Warnings, delegate(string message)
				{
					_log.LogWarning((object)message);
				});
				string[] prefabNames = _session.Archive.Cleanup.PrefabNames;
				Say("Restoration source authority: " + prefabNames.Length + " distinct prefabs across " + _session.Archive.Manifest.Zones.Count + " exported zones: " + string.Join(", ", prefabNames) + ".");
				_objects = new ObjectRestorer(_session.World.Uid.ToString(CultureInfo.InvariantCulture), _session.Journal.State, _session.Archive.Cleanup, AddWarning);
				_scan = _session.Archive.Records.GetEnumerator();
				_phase = RestorePhase.Preflight;
			}
		}

		private void Preflight()
		{
			if (!_objects.IndexIdentities())
			{
				return;
			}
			for (int i = 0; i < 40; i++)
			{
				if (!_scan.MoveNext())
				{
					_scan.Dispose();
					_scan = null;
					QueueSave(initial: true, final: false);
					break;
				}
				if (RestoreRecordPolicy.Include(_scan.Current))
				{
					ObjectRestorer.Validate(_scan.Current);
				}
			}
		}

		private void AddWarning(string warning)
		{
			if (_warnings == null)
			{
				_log.LogWarning((object)warning);
			}
			else
			{
				_warnings.Add(warning);
			}
		}

		private static string ZoneKey(int x, int z)
		{
			return NearZoneScope.Key(x, z);
		}

		private void Fail(Exception error)
		{
			_log.LogError((object)("Restoration phase=" + _phase.ToString() + "; zone=" + ((_zone == null) ? "none" : ZoneKey(_zone.X, _zone.Z)) + ": " + error));
			if (_session != null && GameContext.Ready(allowControlledTransit: true) && GameContext.SameSession(_session.World, Player.m_localPlayer) && _dirty && !IsSavePhase())
			{
				AddWarning("Incomplete work remains pending: " + error.Message);
				_pause = true;
				SuspendZone();
				QueueSave(initial: false, final: true);
			}
			else if (!RetryFailedSave(error))
			{
				bool dirty = _dirty;
				bool num = _session == null || Preparing;
				_phase = RestorePhase.Stopped;
				Close();
				string text = (dirty ? "Restoration interrupted. Unsaved work may need replay." : "Restoration could not continue. No restoration changes are pending.");
				if (num && !dirty)
				{
					text = "Restoration not started. No world data was changed.";
				}
				Say(text + " " + error.Message);
				NativeConfirmation.Report(text + "\n\n" + error.Message);
			}
		}

		private bool RetryFailedSave(Exception error)
		{
			if (_session != null && _dirty && IsSavePhase() && GameContext.Ready(allowControlledTransit: true) && GameContext.SameSession(_session.World, Player.m_localPlayer))
			{
				AddWarning("Native save not checkpointed; changes remain pending. F10 retries stopping: " + error.Message);
				_initialSave = false;
				_retrySaveAt = Time.unscaledTime + 5f;
				_waitingSince = _retrySaveAt;
				_phase = RestorePhase.RequestSave;
				return true;
			}
			return false;
		}

		private void Close()
		{
			RestoreProtection.Clear();
			_scan?.Dispose();
			_scan = null;
			RestoreSession session = _session;
			_session = null;
			if (session == null)
			{
				return;
			}
			Task task = Task.WhenAll(_read ?? Task.CompletedTask, _backup ?? Task.CompletedTask);
			_read = null;
			_backup = null;
			_closing = task.ContinueWith(delegate(Task task2)
			{
				if (task2.IsFaulted)
				{
					_ = task2.Exception;
				}
				session.DisposeResources();
				return session.ReleaseTask;
			}, TaskScheduler.Default).Unwrap();
		}

		private void Say(string message)
		{
			_log.LogInfo((object)message);
		}

		public void Dispose()
		{
			if (_session != null)
			{
				_log.LogWarning((object)("Restoration unloaded. Last confirmed native save: " + _lastSaveUtc + "; unsaved changes may need replay."));
				_phase = RestorePhase.Stopped;
				Close();
			}
		}

		private void InitialSave()
		{
			CheckSaveTimeout();
			if (LatestWorldApi.SaveFinished(_saveBefore, _session.WorldDirectory))
			{
				string source = _session.WorldDirectory;
				string journal = _session.Journal.DirectoryPath;
				_saveBefore = LatestWorldApi.SaveNumber();
				_backup = Task.Run(() => WorldBackup.Create(source, journal));
				_phase = RestorePhase.Backup;
			}
		}

		private void Backup()
		{
			if (_backup.IsCompleted)
			{
				Task<string> backup = _backup;
				_backup = null;
				string result = backup.GetAwaiter().GetResult();
				if (ZNet.instance.IsSaving() || LatestWorldApi.SaveNumber() != _saveBefore)
				{
					throw new IOException("The native world changed during backup. No restoration started; retry.");
				}
				RestoreProtection.Active = true;
				_lastSave = Time.unscaledTime;
				_lastSaveUtc = DateTime.UtcNow.ToString("HH:mm:ss") + " UTC";
				_phase = RestorePhase.Waiting;
				Say("Restoration active. Move manually; F10 stops and saves. Backup: " + result);
			}
		}

		private void ReadZone()
		{
			if (_read.IsCompleted && ZoneReady())
			{
				Task<ZoneImportData> read = _read;
				_read = null;
				ZoneImportData result = read.GetAwaiter().GetResult();
				string item = ZoneKey(_zone.X, _zone.Z);
				_session.Journal.Save();
				_writer = new ZoneRestorer(_objects, result.Records, result.Snapshot, _zone, AddWarning);
				_visited.Add(item);
				_dirty = true;
				_finalized = false;
				_phase = RestorePhase.Restoring;
			}
		}

		private void Restore()
		{
			if (ZoneReady())
			{
				_writer.Step(40);
				if (_writer.Done)
				{
					_applied.Add(ZoneKey(_zone.X, _zone.Z));
					_scan = _session.Archive.Connections.AsEnumerable().GetEnumerator();
					_phase = RestorePhase.Connecting;
				}
			}
		}

		private void Connect()
		{
			for (int i = 0; i < 40; i++)
			{
				if (!_scan.MoveNext())
				{
					FinishZone();
					break;
				}
				CapturedObject current = _scan.Current;
				if (RestoreRecordPolicy.Include(current))
				{
					_dirty |= _objects.Connect(current, _session.Archive);
				}
			}
		}

		private void FinishZone()
		{
			_scan.Dispose();
			_scan = null;
			ZoneSaveReport zoneSaveReport = _writer.Report();
			_restoredObjects += zoneSaveReport.Added.Sum((CaptureCount item) => item.Count);
			_session.Journal.Save();
			_log.LogInfo((object)("Applied zone " + ZoneKey(_zone.X, _zone.Z) + "; shown in green; awaiting native save."));
			this.ZoneRestored?.Invoke(zoneSaveReport);
			_writer = null;
			_zone = null;
			if (!_finalized && _session.Journal.State.Completed.Concat(_applied).Distinct().Count() == _session.Archive.PlannedZoneCount)
			{
				_scan = _session.Archive.Records.GetEnumerator();
				_phase = RestorePhase.Finalizing;
			}
			else
			{
				_phase = RestorePhase.Waiting;
			}
		}

		private void FinalizeObjects()
		{
			for (int i = 0; i < 40; i++)
			{
				if (!_scan.MoveNext())
				{
					_scan.Dispose();
					_scan = null;
					_finalized = true;
					_phase = RestorePhase.Waiting;
					break;
				}
				if (RestoreRecordPolicy.Include(_scan.Current))
				{
					ZDO val = _objects.Resolve(_scan.Current);
					if (val == null)
					{
						AddWarning("Missing imported object at finalization: " + RestoreWarnings.Describe(_scan.Current));
					}
					else if (val.GetBool("WorldCrawler.pending", false))
					{
						val.Set("WorldCrawler.pending", false);
						ObjectRestorer.Refresh(val);
						_dirty = true;
					}
				}
			}
		}

		internal ExportMapOverlayData MapProgress()
		{
			if (_session?.Archive != null)
			{
				return ProgressMapSnapshot.Restore(_session.Archive.Manifest.Zones, _session.Journal.State.Completed.Concat(_applied));
			}
			return null;
		}

		private void WaitForZone()
		{
			if (_dirty && Time.unscaledTime - _lastSave >= 60f)
			{
				QueueSave(initial: false, final: false);
				return;
			}
			HashSet<string> keys = new HashSet<string>(_scope.Zones.Select((ZoneEntry zone) => ZoneKey(zone.X, zone.Z)));
			_visited.RemoveWhere((string key) => !keys.Contains(key));
			CleanupRestoredZone();
			_zone = (from zone in _session.Archive.Manifest.Zones
				where _scope.Contains(zone.X, zone.Z) && !_visited.Contains(ZoneKey(zone.X, zone.Z))
				orderby Restored(ZoneKey(zone.X, zone.Z)) ? 1 : 0
				select zone).FirstOrDefault();
			if (_zone != null)
			{
				ExportArchive archive = _session.Archive;
				ZoneEntry zoneToRead = _zone;
				_read = Task.Run(() => new ZoneImportData
				{
					Snapshot = archive.ReadZone(zoneToRead),
					Records = archive.ZoneObjects(zoneToRead.X, zoneToRead.Z)
				});
				_waitingSince = Time.unscaledTime;
				_phase = RestorePhase.Reading;
			}
		}

		private bool Restored(string key)
		{
			if (!_applied.Contains(key))
			{
				return _session.Journal.State.Completed.Contains(key);
			}
			return true;
		}

		private bool ManualTransit()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			bool flag = ((Character)Player.m_localPlayer).IsTeleporting();
			_teleportTransit |= flag;
			_scope.Refresh(((Component)Player.m_localPlayer).transform.position);
			if (_zone != null && (_teleportTransit || !_scope.Contains(_zone.X, _zone.Z)) && (_phase == RestorePhase.Reading || _phase == RestorePhase.Restoring || _phase == RestorePhase.Connecting))
			{
				if (_read != null && !_read.IsCompleted)
				{
					return true;
				}
				string text = ZoneKey(_zone.X, _zone.Z);
				if (!_teleportTransit)
				{
					AddWarning("External travel interrupted zone " + text + "; unfinished work remains pending for a later visit.");
				}
				_visited.Remove(text);
				SuspendZone();
				_phase = RestorePhase.Waiting;
			}
			_teleportTransit = flag;
			if (flag)
			{
				return !IsSavePhase();
			}
			return false;
		}

		private bool ZoneReady()
		{
			if (NearZoneScope.Ready(_zone.X, _zone.Z))
			{
				return true;
			}
			if (Time.unscaledTime - _waitingSince >= 120f)
			{
				AddWarning("Zone " + ZoneKey(_zone.X, _zone.Z) + " is still loading. Move freely or press F10 to stop safely.");
				_waitingSince = Time.unscaledTime;
			}
			return false;
		}

		private void SuspendZone()
		{
			if (_read != null && _read.IsCompleted)
			{
				if (_read.IsFaulted)
				{
					_ = _read.Exception;
				}
				_read = null;
			}
			_scan?.Dispose();
			_scan = null;
			_writer = null;
			_zone = null;
		}

		private void QueueSave(bool initial, bool final)
		{
			_initialSave = initial;
			_finalSave = final;
			_waitingSince = Time.unscaledTime;
			_phase = RestorePhase.RequestSave;
		}

		private void RequestSave()
		{
			if (Time.unscaledTime < _retrySaveAt)
			{
				return;
			}
			CheckSaveTimeout();
			if (ZNet.instance.IsSaving())
			{
				return;
			}
			ObjectRestorer objects = _objects;
			if (objects == null || !objects.DeletionsPending())
			{
				Func<bool> sceneryDeletionsPending = SceneryDeletionsPending;
				if (sceneryDeletionsPending == null || !sceneryDeletionsPending())
				{
					_session.Journal.Save();
					_saveBefore = LatestWorldApi.BeginSave();
					_waitingSince = Time.unscaledTime;
					_phase = (_initialSave ? RestorePhase.InitialSave : RestorePhase.Saving);
				}
			}
		}

		private void FinishSave()
		{
			CheckSaveTimeout();
			if (!LatestWorldApi.SaveFinished(_saveBefore, _session.WorldDirectory))
			{
				return;
			}
			foreach (string item in _applied)
			{
				if (!_session.Journal.State.Completed.Contains(item))
				{
					_session.Journal.State.Completed.Add(item);
				}
			}
			_lastSaveUtc = DateTime.UtcNow.ToString("HH:mm:ss") + " UTC";
			_session.Journal.Save();
			_applied.Clear();
			_dirty = false;
			_lastSave = Time.unscaledTime;
			_log.LogInfo((object)("Native restoration checkpoint saved at " + _lastSaveUtc + "."));
			if (_finalSave)
			{
				FinishStop();
			}
			else
			{
				_phase = RestorePhase.Waiting;
			}
		}

		private void CheckSaveTimeout()
		{
			if (Time.unscaledTime - _waitingSince > 300f)
			{
				throw new TimeoutException("Native world save timed out; completion was not checkpointed.");
			}
		}

		internal CleanupSourceIndex ScenerySource()
		{
			if (Busy && (_session?.Archive == null || _session.Journal == null))
			{
				throw new InvalidOperationException("F10 is still preparing or closing. Wait before using indestructible.");
			}
			if (_session == null)
			{
				return null;
			}
			_session.Check();
			return _session.Archive.Cleanup;
		}

		internal void SceneryChanged()
		{
			if (Active)
			{
				_dirty = true;
			}
		}

		private void FinishStop()
		{
			_session.Journal.Save();
			Say("Restoration stopped and saved. Last confirmed save: " + _lastSaveUtc + ". Unfinished zones remain pending.");
			Close();
			_phase = RestorePhase.Stopped;
		}
	}
	internal enum RestorePhase
	{
		Idle,
		Preparing,
		Preflight,
		InitialSave,
		Backup,
		Reading,
		Restoring,
		Connecting,
		RequestSave,
		Saving,
		Finalizing,
		Waiting,
		Stopped
	}
	internal sealed class RestoreSession : IDisposable
	{
		private readonly Player _player;

		private Task<ExportArchive> _loading;

		public WorldIdentity World { get; }

		public string WorldDirectory { get; }

		public RestoreJournal Journal { get; private set; }

		public ExportArchive Archive { get; private set; }

		public Task ReleaseTask { get; private set; } = Task.CompletedTask;

		public RestoreSession()
		{
			LatestWorldApi.RequireCurrent();
			LegacyItemData.Validate();
			GeneratedObjectMatch.Validate();
			_player = Player.m_localPlayer;
			World = ((ZNet.World == null) ? null : GameContext.Identity());
			Check();
			WorldDirectory = LatestWorldApi.DirectoryFor(ZNet.World);
			_loading = Task.Run(() => new ExportArchive(ExportSourceResolver.Resolve(World)));
		}

		public void Check()
		{
			CheckConnection();
			CheckLocalWorld();
			CheckPlayer();
			if (!GameContext.SameSession(World, _player))
			{
				throw new InvalidOperationException("The world or character changed during restoration. Press F10 again in the prepared local world.");
			}
		}

		private unsafe static void CheckConnection()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Invalid comparison between Unknown and I4
			if ((Object)(object)ZNet.instance == (Object)null || ZNet.World == null || (Object)(object)Game.instance == (Object)null)
			{
				throw new InvalidOperationException("No world is loaded. Enter the local world prepared with F9, then press F10.");
			}
			ConnectionStatus connectionStatus = ZNet.GetConnectionStatus();
			if ((int)connectionStatus != 2)
			{
				throw new InvalidOperationException("The world connection is not ready (" + ((object)(*(ConnectionStatus*)(&connectionStatus))/*cast due to .constrained prefix*/).ToString() + "). Wait until you are connected before pressing F10.");
			}
		}

		private unsafe static void CheckLocalWorld()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			if (ZNet.instance.IsDedicated() || !ZNet.instance.IsServer())
			{
				throw new InvalidOperationException("F10 cannot restore a remote or dedicated server. Enter your own local world from the main menu.");
			}
			FileSource fileSource = ZNet.World.m_fileSource;
			if (fileSource != LatestWorldApi.LocalSource)
			{
				string text = ((((object)(*(FileSource*)(&fileSource))/*cast due to .constrained prefix*/).ToString().IndexOf("Cloud", StringComparison.OrdinalIgnoreCase) >= 0) ? "in the cloud" : ("with non-local storage (" + ((object)(*(FileSource*)(&fileSource))/*cast due to .constrained prefix*/).ToString() + ")"));
				throw new InvalidOperationException("World '" + ZNet.World.m_name + "' is saved " + text + ". F10 requires a LOCAL save, even in single-player.\n\nReturn to the main menu > Manage saves > Worlds > Move to local. Then press F9, enter that local world, and press F10.");
			}
			int count = ZNet.instance.GetPeers().Count;
			if (count != 0)
			{
				throw new InvalidOperationException("F10 requires you to be alone. " + count + " other connection(s) are present. Disconnect other players before restoring.");
			}
		}

		private static void CheckPlayer()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				throw new InvalidOperationException("Your character has not spawned yet. Wait until you can move, then press F10.");
			}
			if (((Character)localPlayer).IsDead())
			{
				throw new InvalidOperationException("Your character is dead. Respawn before pressing F10.");
			}
			if (((Character)localPlayer).InCutscene() && !((Character)localPlayer).IsTeleporting())
			{
				throw new InvalidOperationException("Your character is in an arrival or respawn sequence. Wait until it finishes, then press F10.");
			}
			if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				throw new InvalidOperationException("World objects are still loading. Wait until the world is ready, then press F10.");
			}
		}

		public bool Prepare()
		{
			if (!_loading.IsCompleted)
			{
				return false;
			}
			Task<ExportArchive> loading = _loading;
			_loading = null;
			Archive = loading.GetAwaiter().GetResult();
			RestoreWorldIdentity.Require(World, Archive.Manifest.World);
			Journal = new RestoreJournal(World, Archive);
			return true;
		}

		public void Dispose()
		{
			DisposeResources();
		}

		public void DisposeResources()
		{
			Archive?.Dispose();
			Archive = null;
			if (_loading == null)
			{
				return;
			}
			ReleaseTask = _loading.ContinueWith(delegate(Task<ExportArchive> task)
			{
				if (task.Status == TaskStatus.RanToCompletion)
				{
					task.Result.Dispose();
				}
				else if (task.IsFaulted)
				{
					_ = task.Exception;
				}
			}, TaskScheduler.Default);
			_loading = null;
		}
	}
	internal sealed class RestoreState
	{
		public readonly List<string> Completed = new List<string>();

		public readonly List<RestoredObject> Objects = new List<RestoredObject>();

		public readonly List<string> Warnings = new List<string>();
	}
	internal sealed class RestoreWarnings
	{
		private readonly List<string> _messages;

		private readonly Action<string> _log;

		internal RestoreWarnings(List<string> messages, Action<string> log)
		{
			_messages = messages;
			_log = log;
		}

		internal void Add(string message)
		{
			string text = SingleLine(message);
			if (!string.IsNullOrWhiteSpace(text) && !_messages.Contains(text))
			{
				_messages.Add(text);
				_log("Restoration warning: " + text);
			}
		}

		internal static string Describe(CapturedObject source)
		{
			return "zone=" + source.ZoneX + ":" + source.ZoneZ + "; prefab=" + SingleLine(source.PrefabName) + "; source=" + ExportArchive.Key(source);
		}

		private static string SingleLine(string value)
		{
			return value?.Replace('\r', ' ').Replace('\n', ' ');
		}
	}
}
namespace Landoria.WorldCrawler.Restoration.Persistence
{
	internal sealed class ExportArchive : IDisposable
	{
		private readonly FileStream _lock;

		private readonly Dictionary<string, CapturedObject> _latest = new Dictionary<string, CapturedObject>();

		internal CleanupSourceIndex Cleanup { get; } = new CleanupSourceIndex();

		public string DirectoryPath { get; }

		public WorldManifest Manifest { get; }

		public int PlannedZoneCount { get; }

		public IEnumerable<CapturedObject> Records => _latest.Values;

		public CapturedObject[] Connections { get; }

		public ExportArchive(string directory)
		{
			DirectoryPath = Path.GetFullPath(directory);
			_lock = new FileStream(Path.Combine(DirectoryPath, ".worldcrawler.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
			try
			{
				if (File.Exists(Path.Combine(DirectoryPath, "pending-recording.json")))
				{
					throw new InvalidDataException("An interrupted recording flush remains. Resume F8 to recover it before F9 or F10.");
				}
				Manifest = AtomicJson.Read<WorldManifest>(Path.Combine(DirectoryPath, "manifest.json"));
				StoreValidation.Manifest(Manifest, Manifest.World);
				PlannedZoneCount = Manifest.Zones.Count;
				Manifest.Zones = Manifest.Zones.Where((ZoneEntry zone) => zone.Status == "captured").ToList();
				if (!Manifest.InventoryInitialized || Manifest.Zones.Count == 0)
				{
					throw new InvalidDataException("No saved zones are available. Export at least one zone with F8 first.");
				}
				IndexZones();
				Cleanup.IndexCurrentObjects(_latest.Values);
				Connections = _latest.Values.Where((CapturedObject v) => v.ConnectionType != 0).ToArray();
			}
			catch
			{
				_lock.Dispose();
				throw;
			}
		}

		private void IndexZones()
		{
			foreach (ZoneEntry item in from z in Manifest.Zones
				orderby z.Z, z.X
				select z)
			{
				ZoneSnapshot zoneSnapshot = ReadZone(item);
				Cleanup.Observe(zoneSnapshot);
				foreach (CapturedObject @object in zoneSnapshot.Objects)
				{
					Index(@object);
				}
			}
		}

		public ZoneSnapshot ReadZone(ZoneEntry zone)
		{
			ZoneEnvelope zoneEnvelope = AtomicJson.Read<ZoneEnvelope>(Path.Combine(DirectoryPath, StoreValidation.ZoneFileName(zone.X, zone.Z)));
			byte[] payload = StoreValidation.Envelope(zoneEnvelope, Manifest.World, zone.X, zone.Z);
			if (zoneEnvelope.Checksum != zone.Checksum || zoneEnvelope.ObjectCount != zone.ObjectCount || zoneEnvelope.CaptureVersion != zone.CaptureVersion || !SupportedGameVersions.CanExport(zoneEnvelope.CaptureVersion))
			{
				throw new InvalidDataException("The export manifest and its zone file disagree.");
			}
			ZoneSnapshot zoneSnapshot = ZoneSnapshot.Decode(payload);
			if (zoneSnapshot.ZoneX != zone.X || zoneSnapshot.ZoneZ != zone.Z || zoneSnapshot.Objects.Count != zone.ObjectCount)
			{
				throw new InvalidDataException("The zone payload does not match its envelope.");
			}
			if (zoneSnapshot.PayloadVersion < 3 && (!zoneSnapshot.NaturalAbsenceComplete || !zoneSnapshot.TerrainReady || zoneSnapshot.StableSeconds < 2f || (zoneSnapshot.DungeonExpected && !zoneSnapshot.DungeonEvidenceComplete)))
			{
				throw new InvalidDataException("The source zone has incomplete loading evidence; destination unchanged.");
			}
			return zoneSnapshot;
		}

		private void Index(CapturedObject item)
		{
			string key = Key(item);
			if (_latest.TryGetValue(key, out var value))
			{
				if (value.PrefabHash != item.PrefabHash)
				{
					throw new InvalidDataException("A source identity refers to different prefabs.");
				}
				if (value.ObservedUtcTicks <= item.ObservedUtcTicks)
				{
					return;
				}
			}
			item.RawDataBase64 = null;
			_latest[key] = item;
		}

		public List<CapturedObject> ZoneObjects(int x, int z)
		{
			ZoneEntry zone = Manifest.Zones.Single((ZoneEntry v) => v.X == x && v.Z == z);
			return (from v in ReadZone(zone).Objects
				where _latest.ContainsKey(Key(v)) && _latest[Key(v)].ZoneX == x && _latest[Key(v)].ZoneZ == z && _latest[Key(v)].ObservedUtcTicks == v.ObservedUtcTicks
				orderby (!v.Categories.Contains("TerrainComp") && !v.Categories.Contains("TerrainModifier")) ? (v.Categories.Contains("LocationProxy") ? 1 : ((!v.Categories.Contains("Piece")) ? ((!v.Categories.Contains("ItemDrop")) ? 3 : 4) : 2)) : 0
				select v).ThenBy<CapturedObject, string>(Key, StringComparer.Ordinal).ToList();
		}

		public static string Key(CapturedObject item)
		{
			return item.SourceUser + ":" + item.SourceId.ToString(CultureInfo.InvariantCulture);
		}

		internal CapturedObject FindObject(string key)
		{
			_latest.TryGetValue(key, out var value);
			return value;
		}

		public void Dispose()
		{
			_lock.Dispose();
		}
	}
	internal static class ExportSourceResolver
	{
		internal static string Resolve(WorldIdentity world)
		{
			if (!Directory.Exists(CrawlerConstants.ExportRoot))
			{
				throw new InvalidOperationException("The recording folder is missing: " + CrawlerConstants.ExportRoot + ". Record the source world with F8 or copy your complete export folder here before using F9 and F10.");
			}
			List<string> list = new List<string>();
			string[] directories = Directory.GetDirectories(CrawlerConstants.ExportRoot);
			foreach (string text in directories)
			{
				if (Matches(text, world))
				{
					list.Add(text);
				}
			}
			if (list.Count != 1)
			{
				throw (list.Count == 0) ? Missing(world) : new InvalidOperationException("Multiple exports match world UID " + world.Uid + ". Keep only one in the worlds folder.");
			}
			return list[0];
		}

		private static bool Matches(string directory, WorldIdentity world)
		{
			string path = Path.Combine(directory, "manifest.json");
			if (!File.Exists(path))
			{
				return false;
			}
			WorldManifest worldManifest = AtomicJson.Read<WorldManifest>(path);
			if (worldManifest?.World?.Uid != world.Uid)
			{
				return false;
			}
			StoreValidation.Manifest(worldManifest, worldManifest.World);
			RestoreWorldIdentity.Require(world, worldManifest.World);
			if (!worldManifest.Zones.Any((ZoneEntry zone) => zone.Status == "captured"))
			{
				throw new InvalidOperationException("The export for world '" + world.Name + "' contains no saved zones. Record and save at least one source zone with F8 before restoring.");
			}
			return true;
		}

		private static InvalidOperationException Missing(WorldIdentity world)
		{
			return new InvalidOperationException("No saved export matches local world '" + world.Name + "' (UID " + world.Uid + "). Its UID may not have been prepared yet.\n\nReturn to the main menu and press F9 AFTER creating the world or moving it to local storage. Wait for the world-ready confirmation, enter that local world, then press F10. Keep the complete export folder in " + CrawlerConstants.ExportRoot + ".");
		}
	}
	internal static class LatestWorldApi
	{
		public static FileSource LocalSource => (FileSource)Enum.Parse(typeof(FileSource), "Local");

		public static void RequireCurrent()
		{
			if (!SupportedGameVersions.IsCurrent(GameContext.GameVersion))
			{
				throw new NotSupportedException("World preparation and restoration require Valheim 1.0.x.");
			}
			CurrentSaveCompatibility.Validate();
			PropertyInfo property = typeof(FileHelpers).GetProperty("LocalStorageSupportedAndAllowed", BindingFlags.Static | BindingFlags.Public);
			if (property == null || !(bool)property.GetValue(null))
			{
				throw new InvalidOperationException("Local saves are unavailable; cloud restoration is not supported.");
			}
		}

		public static List<World> Worlds()
		{
			Call(typeof(SaveSystem), null, "ClearWorldListCache", new Type[1] { typeof(bool) }, false);
			return (List<World>)Call(typeof(SaveSystem), null, "GetWorldList", Type.EmptyTypes);
		}

		public static string SaveRoot()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			return LocalSavePath.Root((string)Call(typeof(SaveSystem), null, "GetWorldsSaveRootPath", new Type[1] { typeof(FileSource) }, LocalSource), Utils.GetSaveDataPath(LocalSource));
		}

		public static string DirectoryFor(World world)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			return LocalSavePath.World((string)Call(typeof(World), world, "GetSaveDirectory", new Type[1] { typeof(FileSource) }, LocalSource), SaveRoot(), world.m_name);
		}

		public static string MetadataPath(World world)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			if (world.m_fileSource != LocalSource)
			{
				throw new InvalidOperationException("Only local world metadata can be prepared.");
			}
			uint num = (uint)Call(typeof(World), world, "SaveNumber", Type.EmptyTypes);
			return Path.Combine(DirectoryFor(world), "_main." + num + ".fwl2");
		}

		public static World[] LocalWorlds()
		{
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Expected O, but got Unknown
			SaveWithBackups[] source = (SaveWithBackups[])Call(typeof(SaveSystem), null, "GetSavesByType", new Type[1] { typeof(SaveDataType) }, (object)(SaveDataType)0);
			List<World> list = new List<World>();
			foreach (SaveFile item in from file in source.SelectMany((SaveWithBackups save) => save.AllFiles)
				where file.m_source == LocalSource && !(bool)Call(typeof(SaveFile), file, "get_IsBackup", Type.EmptyTypes)
				select file)
			{
				string text = (string)Call(typeof(SaveFile), item, "get_Name", Type.EmptyTypes);
				SaveWithBackups val = new SaveWithBackups(text, new SaveCollection((SaveDataType)0), (Action)null);
				val.AddSaveFile(item.AllPaths, LocalSource);
				World val2 = World.LoadWorld(val);
				if (val2 == null)
				{
					throw new IOException("An unreadable local world prevents UID collision checks: " + text);
				}
				list.Add(val2);
			}
			return list.ToArray();
		}

		public static uint BeginSave()
		{
			if (ZNet.instance.IsSaving())
			{
				throw new InvalidOperationException("Wait for the current world save.");
			}
			uint result = SaveNumber();
			Call(typeof(ZNet), ZNet.instance, "Save", new Type[3]
			{
				typeof(bool),
				typeof(bool),
				typeof(bool)
			}, false, false, false);
			return result;
		}

		public static bool SaveFinished(uint before, string directory)
		{
			if (ZNet.instance.IsSaving())
			{
				return false;
			}
			uint num = SaveNumber();
			string path = Path.Combine(directory, "_main." + num + ".ok");
			if (num <= before || !File.Exists(path))
			{
				throw new IOException("Valheim did not confirm the new world save; restore progress was not committed.");
			}
			using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
			{
				if (binaryReader.ReadInt32() != 41)
				{
					throw new IOException("Invalid native world-save completion marker.");
				}
			}
			return true;
		}

		public static uint SaveNumber()
		{
			return (uint)Call(typeof(SaveSystem), null, "GetSaveNumber", Type.EmptyTypes);
		}

		public static void RefreshMenu()
		{
			if ((Object)(object)FejdStartup.instance != (Object)null)
			{
				Call(typeof(FejdStartup), FejdStartup.instance, "UpdateWorldList", new Type[1] { typeof(bool) }, false);
			}
		}

		private static object Call(Type type, object instance, string name, Type[] signature, params object[] args)
		{
			return (type.GetMethod(name, (BindingFlags)(0x30 | ((instance == null) ? 8 : 4)), null, signature, null) ?? throw new MissingMethodException(type.FullName, name)).Invoke(instance, args);
		}
	}
	internal static class LocalSavePath
	{
		internal static string Root(string nativeRoot, string dataRoot)
		{
			string text = Path.Combine(Absolute(dataRoot), "worlds_local");
			string text2 = Absolute(nativeRoot);
			if (!string.Equals(text2, text, StringComparison.OrdinalIgnoreCase))
			{
				throw new IOException("Unexpected local save root: " + text2 + "; expected " + text);
			}
			return text2;
		}

		internal static string World(string directory, string root, string name)
		{
			NativeWorldMetadata.ValidateName(name);
			string text = Absolute(directory);
			if (!string.Equals(text, Path.Combine(root, name), StringComparison.OrdinalIgnoreCase))
			{
				throw new IOException("Unexpected local world directory: " + text);
			}
			return text;
		}

		private static string Absolute(string path)
		{
			if (string.IsNullOrWhiteSpace(path) || !Path.IsPathRooted(path) || (Path.DirectorySeparatorChar == '\\' && (path.Length < 3 || path[1] != ':' || (path[2] != '\\' && path[2] != '/'))))
			{
				throw new IOException("A fully qualified local save path is required: " + path);
			}
			return Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
		}
	}
	internal sealed class RestoreJournal
	{
		internal const string FileName = "restore.json";

		private readonly long _uid;

		public string DirectoryPath { get; }

		public RestoreState State { get; } = new RestoreState();

		public RestoreJournal(WorldIdentity world, ExportArchive archive)
		{
			RestoreWorldIdentity.Require(world, archive.Manifest.World);
			_uid = world.Uid;
			DirectoryPath = archive.DirectoryPath;
			State.Completed.AddRange(Read(DirectoryPath, _uid).RestoredZones);
		}

		public void Save()
		{
			RestoreProgress value = new RestoreProgress
			{
				WorldUid = _uid,
				RestoredZones = State.Completed.Distinct().OrderBy<string, string>((string key) => key, StringComparer.Ordinal).ToList()
			};
			AtomicJson.Write(Path.Combine(DirectoryPath, "restore.json"), value, delegate(RestoreProgress item)
			{
				item.Validate(_uid);
			});
		}

		internal static RestoreProgress Read(string directory, long uid)
		{
			string path = Path.Combine(directory, "restore.json");
			RestoreProgress obj = (File.Exists(path) ? AtomicJson.Read<RestoreProgress>(path) : new RestoreProgress
			{
				WorldUid = uid
			}) ?? throw new InvalidDataException("Restoration map progress is empty.");
			obj.Validate(uid);
			return obj;
		}

		internal static void Reset(string directory, long uid)
		{
			AtomicJson.Write(Path.Combine(directory, "restore.json"), new RestoreProgress
			{
				WorldUid = uid
			}, delegate(RestoreProgress item)
			{
				item.Validate(uid);
			});
		}
	}
	[DataContract]
	internal sealed class RestoreProgress
	{
		[DataMember(Order = 0, IsRequired = true)]
		public long WorldUid;

		[DataMember(Order = 1, IsRequired = true)]
		public List<string> RestoredZones = new List<string>();

		internal void Validate(long uid)
		{
			if (WorldUid != uid || RestoredZones == null || RestoredZones.Distinct().Count() != RestoredZones.Count || RestoredZones.Any((string key) => !ValidZone(key)))
			{
				throw new InvalidDataException("Invalid restoration zone progress or mismatched world UID.");
			}
		}

		private static bool ValidZone(string key)
		{
			string[] array = key?.Split(new char[1] { ':' });
			if (array != null && array.Length == 2 && int.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
			{
				return key == result.ToString(CultureInfo.InvariantCulture) + ":" + result2.ToString(CultureInfo.InvariantCulture);
			}
			return false;
		}
	}
	internal static class RestoreWorldIdentity
	{
		internal static void Require(WorldIdentity target, WorldIdentity source)
		{
			if (target == null || source == null)
			{
				throw new InvalidOperationException("World identity is unavailable. Wait for the local world to finish loading.");
			}
			if (!string.Equals(target.Name, source.Name, StringComparison.Ordinal))
			{
				throw new InvalidOperationException("World name mismatch. Current: '" + target.Name + "'. Export: '" + source.Name + "'. Enter a local world with the exact export name.");
			}
			if (target.Seed != source.Seed || !string.Equals(target.SeedText, source.SeedText, StringComparison.Ordinal))
			{
				throw new InvalidOperationException("World seed mismatch. Current: '" + target.SeedText + "' (" + target.Seed + "). Export: '" + source.SeedText + "' (" + source.Seed + "). Create a local world with the exact export seed, then prepare it with F9.");
			}
			if (target.Uid != source.Uid)
			{
				throw new InvalidOperationException("World UID mismatch. Current: " + target.Uid + ". Export: " + source.Uid + ". Return to the main menu and press F9 to apply the export UID, then enter that local world and press F10.");
			}
		}
	}
	internal static class WorldBackup
	{
		public static string Create(string source, string journalDirectory)
		{
			source = Path.GetFullPath(source);
			string text = Path.Combine(journalDirectory, "backup-" + Guid.NewGuid().ToString("N"));
			string text2 = source.TrimEnd(new char[1] { Path.DirectorySeparatorChar });
			char directorySeparatorChar = Path.DirectorySeparatorChar;
			if (text.StartsWith(text2 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase))
			{
				throw new IOException("The backup must be outside the game world directory.");
			}
			string[] array = Files(source);
			Directory.CreateDirectory(text);
			string[] array2 = array;
			foreach (string text3 in array2)
			{
				if ((File.GetAttributes(text3) & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					throw new IOException("Linked save files are not accepted for backup.");
				}
				string text4 = Path.Combine(text, Path.GetFileName(text3));
				File.Copy(text3, text4, overwrite: false);
				if (Hash(text3) != Hash(text4))
				{
					throw new IOException("A world file changed while its backup was copied.");
				}
			}
			if (!array.SequenceEqual(Files(source)))
			{
				throw new IOException("The world save changed during backup. Retry safely.");
			}
			if (!array.Any((string v) => v.EndsWith(".ok", StringComparison.OrdinalIgnoreCase)))
			{
				throw new IOException("No committed new-format save is available to back up.");
			}
			return text;
		}

		private static string[] Files(string directory)
		{
			if ((File.GetAttributes(directory) & FileAttributes.ReparsePoint) != FileAttributes.None)
			{
				throw new IOException("Linked world folders are not accepted for backup.");
			}
			if (Directory.GetDirectories(directory).Length != 0)
			{
				throw new IOException("Unexpected subdirectories in the native world save; backup stopped.");
			}
			return Directory.GetFiles(directory).OrderBy<string, string>((string v) => v, StringComparer.Ordinal).ToArray();
		}

		private static string Hash(string path)
		{
			using FileStream inputStream = File.OpenRead(path);
			using SHA256 sHA = SHA256.Create();
			return Convert.ToBase64String(sHA.ComputeHash(inputStream));
		}
	}
}
namespace Landoria.WorldCrawler.Restoration.Objects
{
	internal static class GeneratedObjectMatch
	{
		private static readonly MethodInfo WriteRotation = typeof(ZPackage).GetMethod("WriteSmallRotation", new Type[1] { typeof(Vector3) });

		private static readonly MethodInfo ReadRotation = typeof(ZPackage).GetMethod("ReadSmallRotation", Type.EmptyTypes);

		internal static void Validate()
		{
			if (WriteRotation == null || WriteRotation.ReturnType != typeof(void) || ReadRotation == null || ReadRotation.ReturnType != typeof(Vector3))
			{
				throw new NotSupportedException("The current native saved-rotation codec is unavailable.");
			}
		}

		internal static Quaternion SavedRotation(Quaternion rotation)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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)
			ZPackage val = new ZPackage();
			WriteRotation.Invoke(val, new object[1] { ((Quaternion)(ref rotation)).eulerAngles });
			val.SetPos(0);
			return Quaternion.Euler((Vector3)ReadRotation.Invoke(val, null));
		}

		internal unsafe static ZDO Choose(CapturedObject source, List<ZDO> candidates, Action<string> warning)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			if (candidates.Count < 2)
			{
				return candidates.FirstOrDefault();
			}
			List<ZDO> list = (from z in candidates
				orderby ((ZDOID)(ref z.m_uid)).UserID, ((ZDOID)(ref z.m_uid)).ID
				select z).ToList();
			ZDO val = list[0];
			string[] obj = new string[6]
			{
				RestoreWarnings.Describe(source),
				"; ",
				list.Count.ToString(),
				" generated copies at the source pose; reusing target=",
				null,
				null
			};
			ZDOID uid = val.m_uid;
			obj[4] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
			obj[5] = "; unclaimed extra copies will be reconciled against the export.";
			warning(string.Concat(obj));
			return val;
		}
	}
	[HarmonyPatch(typeof(ZNetView), "Awake", new Type[] { })]
	internal static class ImportedViewPatch
	{
		private unsafe static void Postfix(ZNetView __instance)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: 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)
			//IL_0097: 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)
			ZDO zDO = __instance.GetZDO();
			if (zDO == null || string.IsNullOrEmpty(zDO.GetString("WorldCrawler.source", "")) || RestoreProtection.Protected(((Component)__instance).gameObject))
			{
				return;
			}
			Vector3 vec = zDO.GetVec3("WorldCrawler.scale", ((Component)__instance).transform.localScale);
			if (float.IsNaN(((Vector3)(ref vec)).sqrMagnitude) || float.IsInfinity(((Vector3)(ref vec)).sqrMagnitude))
			{
				string[] obj = new string[5] { "World Crawler ignored invalid imported scale: target=", null, null, null, null };
				ZDOID uid = zDO.m_uid;
				obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
				obj[2] = "; position=";
				obj[3] = ((object)zDO.GetPosition()/*cast due to .constrained prefix*/).ToString();
				obj[4] = ". Native scale was kept.";
				Debug.LogWarning((object)string.Concat(obj));
				return;
			}
			((Component)__instance).transform.localScale = vec;
			if (zDO.GetBool("WorldCrawler.pending", false))
			{
				Rigidbody[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<Rigidbody>(true);
				foreach (Rigidbody obj2 in componentsInChildren)
				{
					obj2.constraints = (RigidbodyConstraints)126;
					obj2.useGravity = false;
				}
			}
		}
	}
	[DataContract]
	internal sealed class LocationLayout
	{
		internal const string DataKey = "WorldCrawler.locationLayout";

		[DataMember]
		public int Version = 1;

		[DataMember]
		public List<CapturedSceneNode> Nodes = new List<CapturedSceneNode>();

		public static string Attach(ZDO target, CapturedObject source, ZoneSnapshot snapshot)
		{
			ZNetView val = ZNetScene.instance.FindInstance(target);
			LocationProxy val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponent<LocationProxy>());
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			LocationLayout locationLayout = new LocationLayout
			{
				Nodes = snapshot.SceneNodes.Where((CapturedSceneNode v) => v.Root == ExportArchive.Key(source)).ToList()
			};
			if (locationLayout.Nodes.Count == 0)
			{
				return "Location has no captured static layout: " + source.LocationName;
			}
			GameObject locationInstance = new CaptureApi().GetLocationInstance(val2);
			try
			{
				List<Tuple<Transform, CapturedSceneNode>> plan = locationLayout.Plan(locationInstance);
				byte[] array = locationLayout.Encode();
				target.Set("WorldCrawler.locationLayout", array);
				Apply(plan);
				return null;
			}
			catch (InvalidDataException ex)
			{
				return source.LocationName + ": " + ex.Message;
			}
		}

		public static void Replay(LocationProxy proxy)
		{
			ZNetView component = ((Component)proxy).GetComponent<ZNetView>();
			object obj;
			if (component == null)
			{
				obj = null;
			}
			else
			{
				ZDO zDO = component.GetZDO();
				obj = ((zDO != null) ? zDO.GetByteArray("WorldCrawler.locationLayout", (byte[])null) : null);
			}
			byte[] array = (byte[])obj;
			if (array == null)
			{
				return;
			}
			if (array.Length > 16777216)
			{
				throw new InvalidDataException("Location layout exceeds its size limit.");
			}
			using MemoryStream memoryStream = new MemoryStream(array, writable: false);
			Apply(((LocationLayout)((XmlObjectSerializer)Serializer()).ReadObject((Stream)memoryStream)).Plan(new CaptureApi().GetLocationInstance(proxy)));
		}

		private List<Tuple<Transform, CapturedSceneNode>> Plan(GameObject root)
		{
			if (Version != 1 || (Object)(object)root == (Object)null || Nodes == null || Nodes.Count > 100000)
			{
				throw new InvalidDataException("Unsupported or unavailable location hierarchy.");
			}
			List<Tuple<Transform, CapturedSceneNode>> list = new List<Tuple<Transform, CapturedSceneNode>>();
			foreach (CapturedSceneNode node in Nodes)
			{
				if (node == null || node.Path == null)
				{
					throw new InvalidDataException("Invalid static node.");
				}
				Transform val = root.transform;
				string[] array = node.Path.Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries);
				for (int i = 0; i < array.Length; i++)
				{
					if (!int.TryParse(array[i], out var result) || result < 0 || result >= val.childCount)
					{
						throw new InvalidDataException("Static hierarchy changed; automatic layout changes were skipped.");
					}
					val = val.GetChild(result);
				}
				if (((Object)val).name != node.Name)
				{
					throw new InvalidDataException("Static hierarchy names changed; automatic layout changes were skipped.");
				}
				if (string.IsNullOrEmpty(node.NetworkUser) && !((Object)(object)((Component)val).GetComponent<ZNetView>() != (Object)null) && !RestoreProtection.Protected(((Component)val).gameObject) && ((Component)val).GetComponentsInChildren<ZNetView>(true).Length == 0)
				{
					if (node.ActiveSelf != ((Component)val).gameObject.activeSelf && ((Component)val).GetComponentsInChildren<MonoBehaviour>(true).Length != 0)
					{
						throw new InvalidDataException("A scripted static variant changed; its activation needs review.");
					}
					CheckTransform(node);
					list.Add(Tuple.Create<Transform, CapturedSceneNode>(val, node));
				}
			}
			return list;
		}

		private static void CheckTransform(CapturedSceneNode node)
		{
			if (node.LocalPosition == null || node.LocalPosition.Length != 3 || node.LocalRotation == null || node.LocalRotation.Length != 4 || node.LocalScale == null || node.LocalScale.Length != 3 || node.LocalPosition.Concat(node.LocalRotation).Concat(node.LocalScale).Any((float v) => float.IsNaN(v) || float.IsInfinity(v)) || Math.Abs(node.LocalRotation.Sum((float v) => v * v) - 1f) > 0.05f)
			{
				throw new InvalidDataException("Invalid location transform.");
			}
		}

		private static void Apply(IEnumerable<Tuple<Transform, CapturedSceneNode>> plan)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			foreach (Tuple<Transform, CapturedSceneNode> item2 in plan)
			{
				CapturedSceneNode item = item2.Item2;
				item2.Item1.localPosition = ObjectRestorer.Vector(item.LocalPosition);
				item2.Item1.localRotation = ObjectRestorer.Rotation(item.LocalRotation);
				item2.Item1.localScale = ObjectRestorer.Vector(item.LocalScale);
				((Component)item2.Item1).gameObject.SetActive(item.ActiveSelf);
			}
		}

		private byte[] Encode()
		{
			using MemoryStream memoryStream = new MemoryStream();
			((XmlObjectSerializer)Serializer()).WriteObject((Stream)memoryStream, (object)this);
			return memoryStream.ToArray();
		}

		private static DataContractJsonSerializer Serializer()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			return new DataContractJsonSerializer(typeof(LocationLayout), new DataContractJsonSerializerSettings
			{
				MaxItemsInObjectGraph = int.MaxValue
			});
		}
	}
	[HarmonyPatch(typeof(LocationProxy), "SpawnLocation", new Type[] { })]
	internal static class LocationLayoutPatch
	{
		private static void Postfix(LocationProxy __instance, bool __result)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: 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)
			if (!__result)
			{
				return;
			}
			try
			{
				LocationLayout.Replay(__instance);
			}
			catch (Exception ex)
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				ZDO val = ((component != null) ? component.GetZDO() : null);
				Debug.LogWarning((object)("World Crawler location layout skipped: target=" + (val?.m_uid).ToString() + "; position=" + ((object)((Component)__instance).transform.position/*cast due to .constrained prefix*/).ToString() + "; " + ex.Message));
			}
		}
	}
	internal static class LocationRegistry
	{
		public static void Apply(CapturedObject record)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			if (record.LocationHash == 0)
			{
				return;
			}
			ZoneSystem system = ZoneSystem.instance;
			ZoneLocation val = ((IEnumerable<ZoneLocation>)system.m_locations).SingleOrDefault((Func<ZoneLocation, bool>)((ZoneLocation v) => v.m_prefabName == record.LocationName));
			if (val == null)
			{
				throw new InvalidOperationException("Missing source location: " + record.LocationName);
			}
			BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			IDictionary dictionary = (IDictionary)(typeof(ZoneSystem).GetField("m_locationInstances", flags) ?? throw new MissingFieldException("ZoneSystem.m_locationInstances")).GetValue(system);
			Vector3 val2 = ObjectRestorer.Vector(record.Position);
			object key = typeof(ZoneSystem).GetMethod("GetZone", new Type[1] { typeof(Vector3) }).Invoke(null, new object[1] { val2 });
			MethodInfo methodInfo = typeof(ZoneSystem).GetMethod("RegisterLocation", flags, null, new Type[3]
			{
				typeof(ZoneLocation),
				typeof(Vector3),
				typeof(bool)
			}, null) ?? throw new MissingMethodException("ZoneSystem.RegisterLocation");
			IDictionary[] array = new string[3] { "m_locationIDCache", "m_locationGroupCache", "m_locationMaxGroupCache" }.Select((string name) => (IDictionary)(typeof(ZoneSystem).GetField(name, flags) ?? throw new MissingFieldException("ZoneSystem." + name)).GetValue(system)).ToArray();
			if (dictionary.Contains(key))
			{
				object value = dictionary[key];
				IDictionary[] array2 = array;
				for (int num = 0; num < array2.Length; num++)
				{
					foreach (IList value2 in array2[num].Values)
					{
						value2.Remove(value);
					}
				}
				dictionary.Remove(key);
			}
			methodInfo.Invoke(system, new object[3] { val, val2, true });
		}
	}
	internal sealed class ObjectRestorer
	{
		internal const string IdentityTag = "WorldCrawler.source";

		private readonly string _fingerprint;

		private readonly RestoreState _state;

		private readonly Dictionary<string, RestoredObject> _map;

		private readonly RestoreIdentityIndex _identities;

		private readonly Action<string> _warning;

		private readonly List<ZDO> _zone = new List<ZDO>();

		private readonly HashSet<ZDOID> _claimed = new HashSet<ZDOID>();

		private readonly HashSet<string> _applied = new HashSet<string>();

		private readonly CaptureApi _api = new CaptureApi();

		private readonly GeneratedCleanup _cleanup;

		private static readonly MethodInfo RemoveView = typeof(ZNetScene).GetMethod("OnZDODestroyed", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(ZDO) }, null);

		private static void RememberConnection(ZDO target, CapturedObject source)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			string text = source.ConnectionTargetUser + ":" + source.ConnectionTargetId.ToString(CultureInfo.InvariantCulture);
			bool flag = source.ConnectionType == 0 || text == "0:0";
			target.SetConnection((ConnectionType)(byte)source.ConnectionType, ZDOID.None);
			target.Set("WorldCrawler.connectionType", source.ConnectionType);
			target.Set("WorldCrawler.connectionTarget", flag ? "" : text);
			target.Set("WorldCrawler.connectionReady", flag);
		}

		public bool Connect(CapturedObject source, ExportArchive archive)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: 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_002b: 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_0080: Unknown result type (might be due to invalid IL or missing references)
			ZDO val = Resolve(source);
			if (val == null || val.GetBool("WorldCrawler.connectionReady", true))
			{
				return false;
			}
			ZDOConnection connection = val.GetConnection();
			if (connection != null && connection.m_target != ZDOID.None)
			{
				return CompleteConnection(val);
			}
			ConnectionType val2 = (ConnectionType)(byte)val.GetInt("WorldCrawler.connectionType", 0);
			string text = val.GetString("WorldCrawler.connectionTarget", "");
			if ((int)val2 == 0 || string.IsNullOrEmpty(text) || text == "0:0")
			{
				return CompleteEmptyConnection(val, val2);
			}
			return ConnectEndpoint(source, val, val2, text, archive);
		}

		private bool ConnectEndpoint(CapturedObject source, ZDO target, ConnectionType type, string key, ExportArchive archive)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Invalid comparison between Unknown and I4
			//IL_00f3: 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)
			CapturedObject capturedObject = archive.FindObject(key);
			if (!RestoreRecordPolicy.Include(capturedObject))
			{
				if ((int)type == 3 && (Object)(object)ZNetScene.instance.GetPrefab(source.PrefabHash).GetComponent<CreatureSpawner>() != (Object)null)
				{
					return CompleteEmptyConnection(target, type);
				}
				_warning("Link target " + key + " is absent from the restorable export; " + RestoreWarnings.Describe(source) + ". If the destination still exists, export its area before retrying F10. Zone progress is unaffected.");
				return false;
			}
			ZDO val = ResolveKey(key);
			if (val == null)
			{
				_warning("Link target " + key + " is exported but not restored; " + RestoreWarnings.Describe(source) + "; destination zone=" + capturedObject.ZoneX + ":" + capturedObject.ZoneZ + ". The link will be retried after a zone restoration; no source-zone revisit is required.");
				return false;
			}
			return ApplyConnection(source, target, type, val, capturedObject);
		}

		private unsafe bool ApplyConnection(CapturedObject source, ZDO target, ConnectionType type, ZDO endpoint, CapturedObject destination)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Invalid comparison between Unknown and I4
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Invalid comparison between Unknown and I4
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Invalid comparison between Unknown and I4
			if (target.GetPrefab() != source.PrefabHash || endpoint.GetPrefab() != destination.PrefabHash)
			{
				_warning("Link retained without changes because an imported endpoint's prefab changed; " + RestoreWarnings.Describe(source));
				return false;
			}
			if (target == endpoint)
			{
				_warning("Ignored a self-referencing link; " + RestoreWarnings.Describe(source));
				return CompleteConnection(target);
			}
			EnsureSafe(target, target.GetPrefab());
			EnsureSafe(endpoint, endpoint.GetPrefab());
			if ((int)type == 1)
			{
				return ConnectPortal(source, target, endpoint, destination);
			}
			if ((int)type != 2 && (int)type != 3)
			{
				_warning("Ignored unsupported link type " + ((object)(*(ConnectionType*)(&type))/*cast due to .constrained prefix*/).ToString() + "; " + RestoreWarnings.Describe(source));
				return CompleteConnection(target);
			}
			target.SetConnection(type, endpoint.m_uid);
			return CompleteConnection(target);
		}

		private bool ConnectPortal(CapturedObject source, ZDO target, ZDO endpoint, CapturedObject destination)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			ZDOConnection connection = endpoint.GetConnection();
			if ((connection != null && connection.m_target != ZDOID.None) || endpoint.GetBool("WorldCrawler.connectionReady", true) || target.GetString(ZDOVars.s_tag, "") != endpoint.GetString(ZDOVars.s_tag, "") || destination.ConnectionType != 1 || destination.ConnectionTargetUser != source.SourceUser || destination.ConnectionTargetId != source.SourceId)
			{
				_warning("Original portal link was not forced: the destination is already assigned, local tags differ, or captured endpoints disagree; " + RestoreWarnings.Describe(source) + ". Existing portal state is preserved; Valheim can pair free portals by their current tags.");
				return CompleteConnection(target);
			}
			target.SetConnection((ConnectionType)1, endpoint.m_uid);
			endpoint.SetConnection((ConnectionType)1, target.m_uid);
			CompleteConnection(endpoint);
			return CompleteConnection(target);
		}

		private static bool CompleteEmptyConnection(ZDO target, ConnectionType type)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			EnsureSafe(target, target.GetPrefab());
			ZDOConnection connection = target.GetConnection();
			if (connection == null || (int)connection.m_type == 0)
			{
				target.SetConnection(type, ZDOID.None);
			}
			return CompleteConnection(target);
		}

		private static bool CompleteConnection(ZDO target)
		{
			EnsureSafe(target, target.GetPrefab());
			target.Set("WorldCrawler.connectionReady", true);
			return true;
		}

		public ObjectRestorer(string fingerprint, RestoreState state, CleanupSourceIndex source, Action<string> warning)
		{
			_fingerprint = fingerprint;
			_state = state;
			_map = state.Objects.ToDictionary<RestoredObject, string>((RestoredObject v) => v.Source, StringComparer.Ordinal);
			_identities = new RestoreIdentityIndex();
			_warning = warning;
			_cleanup = new GeneratedCleanup(source, warning);
			if (RemoveView == null)
			{
				throw new MissingMethodException("ZNetScene.OnZDODestroyed(ZDO)");
			}
		}

		public bool IndexIdentities()
		{
			return _identities.Step(256);
		}

		public static void Validate(CapturedObject source)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			GameObject prefab = ZNetScene.instance.GetPrefab(source.PrefabHash);
			if ((Object)(object)prefab == (Object)null || ((Object)prefab).name != source.PrefabName)
			{
				throw new InvalidOperationException("Missing or renamed source prefab: " + source.PrefabName);
			}
			if (RestoreProtection.Protected(prefab))
			{
				throw new InvalidOperationException("Protected player or creature record was rejected: " + source.PrefabName);
			}
			if (Math.Abs(source.Rotation.Sum((float v) => v * v) - 1f) > 0.05f)
			{
				throw new InvalidOperationException("Invalid source object rotation.");
			}
			if (!CaptureTransform.InZone(Vector(source.Position), source.ZoneX, source.ZoneZ))
			{
				throw new InvalidOperationException("An object's position lies outside its captured sector.");
			}
		}

		public void BeginZone(int x, int z)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			_api.FindObjects(new Vector3((float)x * 64f, 0f, (float)z * 64f), _zone);
			HashSet<ZDOID> seen = new HashSet<ZDOID>();
			_zone.RemoveAll((ZDO target) => target == null || !seen.Add(target.m_uid));
			_claimed.Clear();
			_applied.Clear();
		}

		internal bool WasApplied(CapturedObject source)
		{
			return _applied.Contains(ExportArchive.Key(source));
		}

		internal bool DeletionsPending()
		{
			return _cleanup.DeletionsPending();
		}

		public int CleanupZone(int x, int z)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			_api.FindObjects(new Vector3((float)x * 64f, 0f, (float)z * 64f), _zone);
			return _cleanup.Run(x, z, _zone, _claimed);
		}

		public int CleanupMerchantDuplicates(CapturedObject source, GameObject locationRoot)
		{
			return _cleanup.RunMerchant(source, locationRoot);
		}

		public unsafe ZDO Restore(CapturedObject source, string version)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			Validate(source);
			ZDO val = Resolve(source);
			if (val != null)
			{
				_claimed.Add(val.m_uid);
				return val;
			}
			val = Match(source);
			bool flag = val == null;
			val = val ?? CreateTarget(source);
			try
			{
				EnsureSafe(val, source.PrefabHash);
				ApplyWithRollback(val, source, version);
				_applied.Add(ExportArchive.Key(source));
				_claimed.Add(val.m_uid);
				return val;
			}
			catch (Exception ex)
			{
				if (flag)
				{
					_zone.Remove(val);
					val.SetOwner(ZDOMan.GetSessionID());
					val.Persistent = false;
					ZDOMan.instance.DestroyZDO(val);
				}
				string[] obj = new string[10]
				{
					"Could not restore ",
					source.PrefabName,
					"; source=",
					ExportArchive.Key(source),
					"; target=",
					null,
					null,
					null,
					null,
					null
				};
				ZDOID uid = val.m_uid;
				obj[5] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
				obj[6] = "; newlyCreated=";
				obj[7] = flag.ToString();
				obj[8] = ". ";
				obj[9] = ex.Message;
				throw new InvalidOperationException(string.Concat(obj), ex);
			}
		}

		private ZDO CreateTarget(CapturedObject source)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			ZDO val = ZDOMan.instance.CreateNewZDO(Vector(source.Position), source.PrefabHash);
			val.SetPrefab(source.PrefabHash);
			_zone.Add(val);
			return val;
		}

		private void ApplyWithRollback(ZDO target, CapturedObject source, string version)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_000e: 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_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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_004b: Expected O, but got Unknown
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			target.Serialize(val);
			Vector3 position = target.GetPosition();
			Quaternion rotation = target.GetRotation();
			long owner = target.GetOwner();
			try
			{
				Apply(target, source, version);
			}
			catch
			{
				ZDOExtraData.Release(target, target.m_uid);
				target.Deserialize(new ZPackage(val.GetArray()));
				target.SetPosition(position);
				target.SetRotation(rotation);
				target.SetOwner(owner);
				throw;
			}
		}

		private void Apply(ZDO target, CapturedObject source, string version)
		{
			//IL_0008: 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_001e: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			Refresh(target);
			ZDOExtraData.Release(target, target.m_uid);
			ZPackage val = new ZPackage(source.RawDataBase64);
			target.Deserialize(val);
			if (target.GetPrefab() != source.PrefabHash || !target.Persistent || val.GetPos() != val.Size())
			{
				throw new InvalidOperationException("The captured ZDO was not fully decoded with its expected persistent prefab.");
			}
			target.SetOwner(ZDOMan.GetSessionID());
			LegacyMigration.Apply(target, source, version);
			target.SetPosition(Vector(source.Position));
			target.SetRotation(Rotation(source.Rotation));
			if (source.LocalScale != null)
			{
				target.Set("scale", Vector(source.LocalScale));
				target.Set("WorldCrawler.scale", Vector(source.LocalScale));
			}
			target.Set("WorldCrawler.source", Tag(source));
			target.Set("WorldCrawler.pending", true);
			target.Set("WorldCrawler.initialized", false);
			RememberConnection(target, source);
			Record(source, target);
		}

		public ZDO Resolve(CapturedObject source)
		{
			ZDO val = ResolveKey(ExportArchive.Key(source));
			if (val != null)
			{
				Record(source, val);
				return val;
			}
			return null;
		}

		private ZDO ResolveKey(string key)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (_map.TryGetValue(key, out var value))
			{
				ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(long.Parse(value.TargetUser, CultureInfo.InvariantCulture), value.TargetId));
				if (zDO != null && RestoreIdentityIndex.SourceKey(zDO.GetString("WorldCrawler.source", "")) == key)
				{
					return zDO;
				}
			}
			return _identities.Resolve(key);
		}

		private ZDO Match(CapturedObject source)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = Vector(source.Position);
			Quaternion rotation = Rotation(source.Rotation);
			Quaternion savedRotation = GeneratedObjectMatch.SavedRotation(rotation);
			List<ZDO> candidates = _zone.Where((ZDO z) => !_claimed.Contains(z.m_uid) && z.GetPrefab() == source.PrefabHash && string.IsNullOrEmpty(z.GetString("WorldCrawler.source", "")) && Vector3.Distance(z.GetPosition(), position) < 0.02f && (Quaternion.Angle(z.GetRotation(), rotation) < 0.2f || Quaternion.Angle(z.GetRotation(), savedRotation) < 0.2f)).ToList();
			return GeneratedObjectMatch.Choose(source, candidates, _warning);
		}

		private unsafe static void EnsureSafe(ZDO target, int prefab)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = ZNetScene.instance.FindInstance(target);
			GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
			bool flag = RestoreProtection.Protected(prefab2);
			bool flag2 = (Object)(object)val != (Object)null && RestoreProtection.Protected(((Component)val).gameObject);
			if (target.GetPrefab() != prefab || flag || flag2)
			{
				string text = (((Object)(object)prefab2 == (Object)null) ? prefab.ToString() : ((Object)prefab2).name);
				string[] obj = new string[11]
				{
					"Protected or changed target object; prefab=", text, "; target=", null, null, null, null, null, null, null,
					null
				};
				ZDOID uid = target.m_uid;
				obj[3] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
				obj[4] = "; actualPrefab=";
				obj[5] = target.GetPrefab().ToString();
				obj[6] = "; protectedPrefab=";
				obj[7] = flag.ToString();
				obj[8] = "; protectedInstance=";
				obj[9] = flag2.ToString();
				obj[10] = ".";
				throw new InvalidOperationException(string.Concat(obj));
			}
		}

		public static void Refresh(ZDO target)
		{
			EnsureSafe(target, target.GetPrefab());
			RemoveView.Invoke(ZNetScene.instance, new object[1] { target });
		}

		private void Record(CapturedObject source, ZDO target)
		{
			string text = ExportArchive.Key(source);
			if (!_map.TryGetValue(text, out var value))
			{
				value = new RestoredObject
				{
					Source = text
				};
				_map.Add(text, value);
				_state.Objects.Add(value);
			}
			value.TargetUser = ((ZDOID)(ref target.m_uid)).UserID.ToString(CultureInfo.InvariantCulture);
			value.TargetId = ((ZDOID)(ref target.m_uid)).ID;
			value.Prefab = target.GetPrefab();
		}

		private string Tag(CapturedObject source)
		{
			return _fingerprint + ":" + ExportArchive.Key(source);
		}

		public static Vector3 Vector(float[] value)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(value[0], value[1], value[2]);
		}

		public static Quaternion Rotation(float[] value)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			return new Quaternion(value[0], value[1], value[2], value[3]);
		}
	}
	[HarmonyPatch(typeof(Destructible), "RPC_Damage", new Type[]
	{
		typeof(long),
		typeof(HitData)
	})]
	internal static class RestoreDestructibleDamagePatch
	{
		private static bool Prefix(Destructible __instance)
		{
			return !RestoreProtection.Pending(((Component)__instance).GetComponent<ZNetView>());
		}
	}
	internal sealed class RestoredObject
	{
		public string Source;

		public string TargetUser;

		public uint TargetId;

		public int Prefab;
	}
	[HarmonyPatch(typeof(Floating), "TerrainCheck", new Type[] { })]
	internal static class RestoreFloatingTerrainPatch
	{
		private static bool Prefix(Floating __instance)
		{
			return !RestoreProtection.Pending(((Component)__instance).GetComponent<ZNetView>());
		}
	}
	internal sealed class RestoreIdentityIndex
	{
		private readonly List<ZDOID> _pending;

		private readonly Dictionary<string, ZDOID> _identities = new Dictionary<string, ZDOID>(StringComparer.Ordinal);

		private int _index;

		internal RestoreIdentityIndex()
		{
			_pending = ZDOExtraData.GetAllZDOIDsWithHash((Type)5, StringExtensionMethods.GetStableHashCode("WorldCrawler.source"));
		}

		internal bool Step(int budget)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			while (_index < _pending.Count && budget-- > 0)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(_pending[_index++]);
				if (zDO == null)
				{
					continue;
				}
				string text = SourceKey(zDO.GetString("WorldCrawler.source", ""));
				if (text != null)
				{
					if (_identities.TryGetValue(text, out var value) && value != zDO.m_uid)
					{
						throw new InvalidOperationException("Duplicate restored source identity: " + text);
					}
					_identities[text] = zDO.m_uid;
				}
			}
			return _index == _pending.Count;
		}

		internal ZDO Resolve(string tag)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (!_identities.TryGetValue(tag, out var value))
			{
				return null;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(value);
			if (zDO == null || !(SourceKey(zDO.GetString("WorldCrawler.source", "")) == tag))
			{
				return null;
			}
			return zDO;
		}

		internal static string SourceKey(string tag)
		{
			string[] array = tag?.Split(new char[1] { ':' });
			if (array == null || array.Length != 3 || string.IsNullOrEmpty(array[0]) || !long.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !uint.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
			{
				return null;
			}
			return result.ToString(CultureInfo.InvariantCulture) + ":" + result2.ToString(CultureInfo.InvariantCulture);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "TimedDestruction", new Type[] { })]
	internal static class RestoreItemLifetimePatch
	{
		private static bool Prefix(ItemDrop __instance)
		{
			return !RestoreProtection.Pending(((Component)__instance).GetComponent<ZNetView>());
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "CanPickup", new Type[] { typeof(bool) })]
	internal static class RestoreItemPickupPatch
	{
		private static bool Prefix(ItemDrop __instance, ref bool __result)
		{
			if (!RestoreProtection.Pending(((Component)__instance).GetComponent<ZNetView>()))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "AutoStackItems", new Type[] { })]
	internal static class RestoreItemStackPatch
	{
		private static bool Prefix(ItemDrop __instance)
		{
			if (!RestoreProtection.Active)
			{
				return !RestoreProtection.Pending(((Component)__instance).GetComponent<ZNetView>());
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "TerrainCheck", new Type[] { })]
	internal static class RestoreItemTerrainPatch
	{
		private static bool Prefix(ItemDrop __instance)
		{
			return !RestoreProtection.Pending(((Component)__instance).GetComponent<ZNetView>());
		}
	}
	internal static class RestorePositionPolicy
	{
		internal const float FixedTolerance = 0.1f;

		internal static bool IsMovable(bool itemDrop, bool placedItem, bool syncPosition)
		{
			if (!placedItem)
			{
				return itemDrop || syncPosition;
			}
			return false;
		}

		internal static bool Accepts(float distance, bool movable)
		{
			if (!float.IsNaN(distance) && !float.IsInfinity(distance) && distance >= 0f)
			{
				if (!movable)
				{
					return distance <= 0.1f;
				}
				return true;
			}
			return false;
		}
	}
	internal static class RestoreProtection
	{
		private static readonly HashSet<string> Held = new HashSet<string>();

		public static bool Active { get; set; }

		public static bool Protected(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return true;
			}
			Transform val = gameObject.transform;
			while ((Object)(object)val != (Object)null)
			{
				if (CaptureExclusionPolicy.Classify(((Component)val).gameObject) != null)
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		internal static bool Pending(ZNetView view)
		{
			ZDO val = (((Object)(object)view == (Object)null) ? null : view.GetZDO());
			if (val != null)
			{
				return val.GetBool("WorldCrawler.pending", false);
			}
			return false;
		}

		public static void Hold(int x, int z)
		{
			Held.Add(x + ":" + z);
		}

		public static bool HoldWear(Vector3 position)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (Active)
			{
				return Held.Contains(Mathf.FloorToInt((position.x + 32f) / 64f) + ":" + Mathf.FloorToInt((position.z + 32f) / 64f));
			}
			return false;
		}

		public static void Clear()
		{
			Active = false;
			Held.Clear();
		}
	}
	internal static class RestoreRecordPolicy
	{
		internal static bool Include(CapturedObject source)
		{
			if (source != null)
			{
				return source.PrefabName != "_ZoneCtrl";
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(WearNTear), "UpdateWear", new Type[] { typeof(float) })]
	internal static class RestoreWearPatch
	{
		private static bool Prefix(WearNTear __instance)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (!((Object)(object)component != (Object)null) || component.GetZDO() == null || !component.GetZDO().GetBool("WorldCrawler.pending", false))
			{
				return !RestoreProtection.HoldWear(((Component)__instance).transform.position);
			}
			return false;
		}
	}
	internal sealed class ZoneImportData
	{
		public ZoneSnapshot Snapshot;

		public List<CapturedObject> Records;
	}
	internal sealed class ZoneRestorer
	{
		private readonly ObjectRestorer _objects;

		private readonly List<CapturedObject> _records;

		private readonly ZoneSnapshot _snapshot;

		private readonly string _version;

		private readonly Action<string> _warning;

		private readonly Vector3 _center;

		private readonly float _started;

		private int _index;

		private int _verify;

		private float _nextCleanup;

		private float _readySince;

		private readonly HashSet<string> _layouts = new HashSet<string>();

		public bool Done { get; private set; }

		internal ZoneSaveReport Report()
		{
			List<IGrouping<string, CapturedObject>> source = (from item in _records.Where(_objects.WasApplied)
				group item by item.PrefabName into @group
				orderby @group.Key
				select @group).ToList();
			return new ZoneSaveReport
			{
				X = _snapshot.ZoneX,
				Z = _snapshot.ZoneZ,
				Added = source.Select((IGrouping<string, CapturedObject> group) => new CaptureCount
				{
					Name = group.Key,
					Count = group.Count()
				}).ToList(),
				Categories = source.ToDictionary((IGrouping<string, CapturedObject> group) => group.Key, (IGrouping<string, CapturedObject> group) => string.Join(", ", group.SelectMany((CapturedObject item) => item.Categories ?? new string[0]).Distinct())),
				PrefabHashes = source.ToDictionary((IGrouping<string, CapturedObject> group) => group.Key, (IGrouping<string, CapturedObject> group) => group.First().PrefabHash)
			};
		}

		public ZoneRestorer(ObjectRestorer objects, List<CapturedObject> records, ZoneSnapshot snapshot, ZoneEntry zone, Action<string> warning)
		{
			//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)
			_objects = objects;
			_warning = warning;
			_records = records.Where(RestoreRecordPolicy.Include).ToList();
			if (_records.Count != records.Count)
			{
				_warning("Ignored the engine-owned zone controller; Valheim keeps its generated destination controller.");
			}
			_snapshot = snapshot;
			_version = zone.CaptureVersion;
			_center = new Vector3((float)zone.X * 64f, 0f, (float)zone.Z * 64f);
			_started = Time.unscaledTime;
			_objects.BeginZone(zone.X, zone.Z);
			RestoreProtection.Hold(zone.X, zone.Z);
			foreach (CapturedObject record in _records)
			{
				ObjectRestorer.Validate(record);
			}
		}

		public void Step(int budget)
		{
			if (Time.unscaledTime - _started > 240f)
			{
				throw new TimeoutException("Restored zone did not become ready.");
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			while (_index < _records.Count && budget-- > 0 && stopwatch.ElapsedMilliseconds < 4)
			{
				_objects.Restore(_records[_index], _version);
				_index++;
			}
			if (_index != _records.Count)
			{
				return;
			}
			ReconcileSource();
			if (!ReadyForVerification())
			{
				return;
			}
			while (_verify < _records.Count && budget-- > 0 && stopwatch.ElapsedMilliseconds < 4)
			{
				if (!Verify(_records[_verify]))
				{
					return;
				}
				_verify++;
			}
			Done = _verify == _records.Count && Cleanup() == 0 && !_objects.DeletionsPending();
		}

		private void ReconcileSource()
		{
			if (Time.unscaledTime >= _nextCleanup)
			{
				Cleanup();
				_nextCleanup = Time.unscaledTime + 0.5f;
			}
		}

		private int Cleanup()
		{
			int num = _objects.CleanupZone(_snapshot.ZoneX, _snapshot.ZoneZ);
			if (num > 0)
			{
				_readySince = 0f;
			}
			return num;
		}

		private bool ReadyForVerification()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (_objects.DeletionsPending() || !NearZoneScope.Ready(_snapshot.ZoneX, _snapshot.ZoneZ))
			{
				return false;
			}
			Heightmap val = Heightmap.FindHeightmap(_center);
			if ((Object)(object)val == (Object)null || val.IsDistantLod || val.HaveQueuedRebuild())
			{
				_readySince = 0f;
				return false;
			}
			if (_readySince == 0f)
			{
				_readySince = Time.unscaledTime;
			}
			return Time.unscaledTime - _readySince >= 2f;
		}

		private bool Verify(CapturedObject source)
		{
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			ZDO val = _objects.Resolve(source);
			if (val == null)
			{
				throw new InvalidOperationException("Imported identity is missing during validation; " + RestoreWarnings.Describe(source));
			}
			ZNetView val2 = ZNetScene.instance.FindInstance(val);
			if (val.GetBool("WorldCrawler.initialized", true))
			{
				if (source.LocationHash != 0 && (Object)(object)val2 != (Object)null)
				{
					LocationProxy component = ((Component)val2).GetComponent<LocationProxy>();
					_objects.CleanupMerchantDuplicates(source, ((Object)(object)component == (Object)null) ? null : new CaptureApi().GetLocationInstance(component));
				}
				return true;
			}
			if ((Object)(object)val2 == (Object)null)
			{
				return false;
			}
			VerifyPosition(source, val);
			if (source.LocalScale != null)
			{
				val2.SetLocalScale(ObjectRestorer.Vector(source.LocalScale));
			}
			if (!InitializeLocation(source, val, val2))
			{
				return false;
			}
			val.Set("WorldCrawler.initialized", true);
			return true;
		}

		private bool InitializeLocation(CapturedObject source, ZDO target, ZNetView instance)
		{
			if (source.LocationHash != 0 && !_layouts.Contains(ExportArchive.Key(source)))
			{
				LocationProxy component = ((Component)instance).GetComponent<LocationProxy>();
				if ((Object)(object)component == (Object)null || (Object)(object)new CaptureApi().GetLocationInstance(component) == (Object)null)
				{
					return false;
				}
				LocationRegistry.Apply(source);
				_objects.CleanupMerchantDuplicates(source, new CaptureApi().GetLocationInstance(component));
				string text = LocationLayout.Attach(target, source, _snapshot);
				if (text != null)
				{
					_warning(RestoreWarnings.Describe(source) + "; " + text);
				}
				_layouts.Add(ExportArchive.Key(source));
			}
			return true;
		}

		private void VerifyPosition(CapturedObject source, ZDO target)
		{
			//IL_0001: 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)
			float num = Vector3.Distance(target.GetPosition(), ObjectRestorer.Vector(source.Position));
			GameObject prefab = ZNetScene.instance.GetPrefab(source.PrefabHash);
			bool flag = (Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null;
			ZSyncTransform component = prefab.GetComponent<ZSyncTransform>();
			bool placedItem = flag && target.GetBool("piece", false);
			bool movable = RestorePositionPolicy.IsMovable(flag, placedItem, (Object)(object)component != (Object)null && component.m_syncPosition);
			if (!RestorePositionPolicy.Accepts(num, movable))
			{
				throw new InvalidOperationException("Imported position is invalid; " + RestoreWarnings.Describe(source) + "; displacement=" + num.ToString("F3", CultureInfo.InvariantCulture) + "m; movable=" + movable + "; itemDrop=" + flag + "; placedItem=" + placedItem + ".");
			}
			if (num > 0.1f)
			{
				_warning(RestoreWarnings.Describe(source) + "; dynamic object settled " + num.ToString("F3", CultureInfo.InvariantCulture) + "m from its captured position; identity is intact.");
			}
		}
	}
}
namespace Landoria.WorldCrawler.Restoration.Compatibility
{
	internal static class CurrentSaveCompatibility
	{
		internal static void Validate()
		{
			FieldInfo field = typeof(Player).Assembly.GetType("Version", throwOnError: true).GetField("c_WorldVersion", BindingFlags.Static | BindingFlags.Public);
			if (field == null || !field.IsLiteral || Convert.ToInt32(field.GetRawConstantValue()) != 41)
			{
				throw new NotSupportedException("This Valheim 1.0.x save format is not supported; expected format 41.");
			}
			Method(typeof(SaveSystem), "ClearWorldListCache", true, typeof(void), typeof(bool));
			Method(typeof(SaveSystem), "GetWorldList", true, typeof(List<World>));
			Method(typeof(SaveSystem), "GetSavesByType", true, typeof(SaveWithBackups[]), typeof(SaveDataType));
			Method(typeof(SaveFile), "get_IsBackup", false, typeof(bool));
			Method(typeof(SaveFile), "get_Name", false, typeof(string));
			Method(typeof(SaveSystem), "GetWorldsSaveRootPath", true, typeof(string), typeof(FileSource));
			Method(typeof(SaveSystem), "GetSaveNumber", true, typeof(uint));
			Method(typeof(World), "GetSaveDirectory", false, typeof(string), typeof(FileSource));
			Method(typeof(World), "SaveNumber", false, typeof(uint));
			Method(typeof(ZNet), "Save", false, typeof(void), typeof(bool), typeof(bool), typeof(bool));
			Method(typeof(FejdStartup), "UpdateWorldList", false, typeof(void), typeof(bool));
		}

		private static void Method(Type owner, string name, bool isStatic, Type result, params Type[] arguments)
		{
			MethodInfo method = owner.GetMethod(name, (BindingFlags)(0x30 | (isStatic ? 8 : 4)), null, arguments, null);
			if (method == null || method.ReturnType != result)
			{
				throw new NotSupportedException("Incompatible Valheim 1.0.x API: " + owner.Name + "." + name);
			}
		}
	}
	internal static class LegacyItemData
	{
		private static MethodInfo _convert;

		private static object _worldVersion;

		internal static void Validate()
		{
			if (!(_convert != null))
			{
				Type type = typeof(ZDOMan).Assembly.GetType("Version+World", throwOnError: true);
				MethodInfo method = typeof(ZDOMan).GetMethod("ConvertInventories", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[3]
				{
					typeof(List<ZDOID>),
					type,
					typeof(int)
				}, null);
				if (method == null || method.ReturnType != typeof(int))
				{
					throw new NotSupportedException("Incompatible native legacy item converter: ZDOMan.ConvertInventories.");
				}
				_worldVersion = Enum.ToObject(type, 37);
				_convert = method;
			}
		}

		internal static void Apply(ZDO target)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			Validate();
			List<ZDOID> list = new List<ZDOID> { target.m_uid };
			for (int i = -1; i < 32; i++)
			{
				string text = ((i < 0) ? "" : (i + "_"));
				if (target.GetInt(text + "stack", int.MinValue) != int.MinValue && target.GetByteArray(text + "itemData", (byte[])null) == null && ((int)_convert.Invoke(ZDOMan.instance, new object[3] { list, _worldVersion, i }) != 1 || target.GetByteArray(text + "itemData", (byte[])null) == null))
				{
					throw new InvalidOperationException("Native legacy item conversion did not produce the expected item data.");
				}
			}
		}
	}
	internal static class LegacyMigration
	{
		public static void Apply(ZDO target, CapturedObject source, string version)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			if (!(version != "0.221.12"))
			{
				string text = target.GetString("items", "");
				if (!string.IsNullOrEmpty(text))
				{
					target.Set("items", new ZPackage(text).GetArray());
				}
				ConvertItem(target, "item");
				for (int i = 0; i < 32; i++)
				{
					ConvertItem(target, i + "_item");
				}
				LegacyItemData.Apply(target);
			}
		}

		private static void ConvertItem(ZDO target, string field)
		{
			string text = target.GetString(field, "");
			if (!string.IsNullOrEmpty(text))
			{
				if ((Object)(object)ZNetScene.instance.GetPrefab(StringExtensionMethods.GetStableHashCode(text)) == (Object)null)
				{
					throw new InvalidOperationException("A legacy displayed item is missing from this game: " + text);
				}
				target.Set(field, StringExtensionMethods.GetStableHashCode(text));
			}
		}
	}
}
namespace Landoria.WorldCrawler.Restoration.Cleanup
{
	internal sealed class CleanupSourceIndex
	{
		private readonly Dictionary<int, HashSet<string>> _prefabs = new Dictionary<int, HashSet<string>>();

		private readonly Dictionary<int, HashSet<string>> _locations = new Dictionary<int, HashSet<string>>();

		private readonly Dictionary<string, List<CapturedObject>> _positions = new Dictionary<string, List<CapturedObject>>();

		private readonly HashSet<string> _zones = new HashSet<string>();

		internal string[] PrefabNames => _prefabs.Values.SelectMany((HashS