Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of WorldCrawler v1.0.1
Landoria.WorldCrawler.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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