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 FreshWorld v1.2.0
FreshWorld.dll
Decompiled 3 days 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.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; 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; using System.Xml; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using FreshWorld.API; using FreshWorld.Backend; using FreshWorld.Commands; using FreshWorld.Configuration; using FreshWorld.Core; using FreshWorld.Engine; using FreshWorld.Runtime; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using SoftReferenceableAssets; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("FreshWorld")] [assembly: AssemblyProduct("FreshWorld")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyFileVersion("1.2.0")] [assembly: AssemblyInformationalVersion("1.2.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.2.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace FreshWorld { [BepInPlugin("sighsorry.FreshWorld", "FreshWorld", "1.2.0")] public sealed class FreshWorldPlugin : BaseUnityPlugin { private sealed class ManualRequest { internal readonly ScheduledRun Run; internal readonly CommandRequestContext Context; internal readonly RunOptions Options; internal string? LastWait; internal ManualRequest(ScheduledRun run, CommandRequestContext context, RunOptions options) { Run = run; Context = context; Options = options; } } public const string Author = "sighsorry"; public const string ModName = "FreshWorld"; public const string ModVersion = "1.2.0"; public const string ModGUID = "sighsorry.FreshWorld"; public const string PluginGuid = "sighsorry.FreshWorld"; public const string PluginName = "FreshWorld"; public const string PluginVersion = "1.2.0"; private const float SchedulePollIntervalSeconds = 1f; internal static FreshWorldPlugin? Instance; private FreshWorldConfig configuration; private ConfigSynchronization? configSync; private PathOfValheimAzurite? povAzurite; private RuntimeSettings? settings; private Harmony? harmony; private Harmony? commandHarmony; private FileSystemWatcher? watcher; private int reloadRequested; private int configValuesChanged; private bool supported; private bool compatibilityDrained; private bool sessionFaulted; private ZNet? host; private long? worldUid; private FreshWorldScheduler? scheduler; private GuardedCoroutine? runner; private IDisposable? backendLease; private ScheduleClock lastClock; private float attachedAt; private float nextPoll; private float nextCheckpoint; private float nextReleaseSweep; private ManualRequest? pendingManual; private string? activeRunId; private bool activePovRepair; private string progress = "Idle."; private void Awake() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown Instance = this; MaintenanceCoordination.Initialize(CoordinationReady, PrepareMaintenance, () => GameWorld.HasPendingZoneCleanup); try { EmptyChunkSaveCompatibility.Reset(); configuration = new FreshWorldConfig(((BaseUnityPlugin)this).Config); harmony = new Harmony("sighsorry.FreshWorld"); commandHarmony = new Harmony("sighsorry.FreshWorld.Commands"); foreach (Type item in from type in PatchTypes() where type.Namespace == "FreshWorld.Commands" select type) { commandHarmony.PatchAll(item); } FreshWorldCommands.Register(HandleCommand, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }); } catch (Exception ex) { CleanupPlugin(); ((BaseUnityPlugin)this).Logger.LogError((object)("FreshWorld initialization failed; maintenance is disabled. " + ex)); } } private static Type[] PatchTypes() { return typeof(FreshWorldPlugin).Assembly.GetTypes().Where(delegate(Type type) { if (!(type.Namespace == "FreshWorld")) { string? text = type.Namespace; if (text == null || !text.StartsWith("FreshWorld.", StringComparison.Ordinal)) { return false; } } return type.IsDefined(typeof(HarmonyPatch), inherit: false); }).ToArray(); } private void Start() { if (Instance != this || harmony == null || EmptyChunkSaveCompatibility.Check() == EmptyChunkSaveCompatibility.Strategy.Unsupported) { return; } try { foreach (Type item in from type in PatchTypes() where type.Namespace != "FreshWorld.Commands" select type) { harmony.PatchAll(item); } configSync = new ConfigSynchronization(((BaseUnityPlugin)this).Config, configuration, harmony, delegate { Interlocked.Exchange(ref configValuesChanged, 1); }, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }); povAzurite = PathOfValheimAzurite.TryCreate(delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }); ReloadSettings(reloadFile: false); watcher = new FileSystemWatcher(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath)); watcher.Changed += OnConfigChanged; watcher.Created += OnConfigChanged; watcher.Renamed += OnConfigChanged; watcher.EnableRaisingEvents = true; supported = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("FreshWorld ready. Maintenance runs only on the world host; configure " + ((BaseUnityPlugin)this).Config.ConfigFilePath)); } catch (Exception ex) { CleanupPlugin(); ((BaseUnityPlugin)this).Logger.LogError((object)("FreshWorld initialization failed; maintenance is disabled. " + ex)); } } internal static void LogSaveCompatibility(string message, bool error) { if (Instance != null) { if (error) { ((BaseUnityPlugin)Instance).Logger.LogError((object)message); } else { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)message); } } } private void OnConfigChanged(object sender, FileSystemEventArgs args) { Interlocked.Exchange(ref reloadRequested, 1); } private void ReloadSettings(bool reloadFile) { try { if (reloadFile) { configSync.ReloadFile(); Interlocked.Exchange(ref configValuesChanged, 0); } RuntimeSettings runtimeSettings = configuration.Capture(); try { scheduler?.Reconfigure(runtimeSettings.Schedule, ReadClock()); } catch (Exception ex) { sessionFaulted = true; CloseScheduler("The updated schedule could not be persisted."); ((BaseUnityPlugin)this).Logger.LogError((object)("Could not update the recorded schedule; maintenance is disabled for this world session. " + ex)); return; } settings = runtimeSettings; povAzurite?.Configure(runtimeSettings.PathOfValheimAzuriteVegetationEnabled); if (reloadFile) { configSync?.Publish(0L); } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Configuration applied: automatic={runtimeSettings.AutomaticEnabled}, schedule={runtimeSettings.Schedule.Mode}, timezone={runtimeSettings.Schedule.TimeZone.Id}, safeZones={runtimeSettings.Options.ZoneSafeZones}/{runtimeSettings.Options.VegetationSafeZones}/{runtimeSettings.Options.LocationSafeZones} (zones/resources/locations)."); nextPoll = 0f; } catch (Exception ex2) { settings = null; TryCleanup(delegate { povAzurite?.Configure(enable: false); }, "Could not stop PoV azurite vegetation after invalid configuration."); CloseScheduler("The host configuration is invalid."); ((BaseUnityPlugin)this).Logger.LogError((object)("Invalid FreshWorld configuration; new maintenance is disabled until the cfg is corrected. " + ex2.Message)); } } private void ApplyPendingSettings() { if (runner == null && !MaintenanceGate.IsActive) { bool flag = Interlocked.Exchange(ref reloadRequested, 0) != 0; bool flag2 = Interlocked.Exchange(ref configValuesChanged, 0) != 0; if (flag || flag2) { ReloadSettings(flag); } } } private static bool IsHost(ZNet? net) { if ((Object)(object)net != (Object)null && net.IsServer() && !net.HaveStopped) { return ZNet.World != null; } return false; } private static bool WorldReady() { if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.LocationsGenerated && ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null && WorldGenerator.instance != null && (Object)(object)DungeonDB.instance != (Object)null && (Object)(object)Game.instance != (Object)null && (Object)(object)EnvMan.instance != (Object)null) { return !ZNet.m_loadError; } return false; } private ScheduleClock ReadClock() { if (!IsHost(host) || (Object)(object)EnvMan.instance == (Object)null) { return new ScheduleClock(DateTimeOffset.UtcNow, lastClock.GameDay); } long dayLengthSec = EnvMan.instance.m_dayLengthSec; if (dayLengthSec <= 0) { throw new InvalidOperationException("The game day length must be positive."); } return new ScheduleClock(DateTimeOffset.UtcNow, Math.Max(0.0, host.GetTimeSeconds() / (double)dayLengthSec)); } private void Update() { if (!supported) { return; } try { ZNet instance = ZNet.instance; long? num = (IsHost(instance) ? new long?(instance.GetWorldUID()) : ((long?)null)); if (worldUid.HasValue && (host != instance || worldUid != num || !IsHost(instance))) { StopSession(); } if (EmptyChunkSaveCompatibility.DisabledReason != null) { DrainDisabledMaintenance(); return; } ApplyPendingSettings(); if (!IsHost(instance) || !WorldReady()) { runner?.Dispose(); runner = null; CancelPendingManual("The hosting world is no longer ready."); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= nextReleaseSweep) { int num2 = GameWorld.ProcessDeferredReleases(); if (num2 > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Cleared {num2} deferred maintenance zone release(s)."); } nextReleaseSweep = realtimeSinceStartup + 1f; } AttachHost(instance); if (runner != null) { GuardedCoroutine guardedCoroutine = runner; if (!guardedCoroutine.MoveNext() && runner == guardedCoroutine) { runner = null; } } ApplyPendingSettings(); if (settings == null || sessionFaulted) { return; } lastClock = ReadClock(); EnsureScheduler(); if (pendingManual != null) { if (!IsCurrentRequest(pendingManual.Context)) { CancelPendingManual("The requester disconnected, changed session, or lost administrator authority."); } else if (runner == null) { TryDispatch(pendingManual.Run); } } if (EmptyChunkSaveCompatibility.DisabledReason == null && !(realtimeSinceStartup < nextPoll)) { nextPoll = realtimeSinceStartup + 1f; ScheduledRun due = scheduler.GetDue(lastClock); if (realtimeSinceStartup >= nextCheckpoint) { scheduler.Checkpoint(lastClock); nextCheckpoint = realtimeSinceStartup + 60f; } if (runner == null && due != null) { TryDispatch(due); } } } catch (Exception ex) { sessionFaulted = true; runner?.Dispose(); runner = null; ((BaseUnityPlugin)this).Logger.LogError((object)("FreshWorld stopped maintenance for this world session after an error. " + ex)); CloseScheduler("The host stopped maintenance after an error."); } } private void AttachHost(ZNet current) { long worldUID = current.GetWorldUID(); if (worldUid.HasValue && (host != current || worldUid != worldUID)) { StopSession(); } if (!((Object)(object)host != (Object)null)) { host = current; worldUid = worldUID; attachedAt = Time.realtimeSinceStartup; nextCheckpoint = attachedAt + 60f; sessionFaulted = false; } } private void EnsureScheduler() { if (scheduler == null) { string text = worldUid.Value.ToString(CultureInfo.InvariantCulture); scheduler = FreshWorldScheduler.Open(settings.Schedule, new JsonWorldStateStore(Path.Combine(Paths.ConfigPath, "FreshWorld", "state")), text, lastClock); ScheduledRun pendingRun = scheduler.PendingRun; if (pendingRun != null && pendingRun.IsManual) { scheduler.CancelPendingManualRun(pendingRun.Id, lastClock, "The manual request belonged to an earlier host session."); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Cancelled an earlier session's unstarted manual request: " + pendingRun.Id)); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Scheduler attached to world " + host.GetWorldName() + " (" + text + ").")); MaintenanceCoordination.Attach(text); } } private bool IsCurrentRequest(CommandRequestContext context) { if (context.IsAuthorizedNow && context.Network == ZNet.instance && IsHost(context.Network)) { return context.Network.GetWorldUID() == context.WorldUid; } return false; } internal void HandleCommand(FreshWorldCommandAction action, CommandRequestContext context) { try { if (!IsCurrentRequest(context)) { context.Reply("Request rejected: this host session or your authority is no longer valid."); return; } if (!Enum.IsDefined(typeof(FreshWorldCommandAction), action)) { context.Reply("Invalid command action."); return; } if (EmptyChunkSaveCompatibility.DisabledReason != null) { context.Reply(DisabledStatus()); return; } if (!supported) { context.Reply("FreshWorld initialization is not complete; maintenance is unavailable."); return; } if (!WorldReady()) { context.Reply("Request rejected: the world is not ready."); return; } switch (action) { case FreshWorldCommandAction.Pov: StartPovRepair(context); return; case FreshWorldCommandAction.Invasions: try { foreach (string item in JotunInvasionDiagnostics.Describe()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)item); context.Reply(item); } return; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Jotun invasion inspection failed: " + ex)); context.Reply("Invasion inspection failed; see the host log. Maintenance state is unchanged."); return; } } AttachHost(context.Network); if (action == FreshWorldCommandAction.Status) { context.Reply(StatusMessage()); return; } if (runner != null || activeRunId != null) { context.Reply("Maintenance is already running: " + (activePovRepair ? "PoV altar repair" : activeRunId) + ". " + progress); return; } if (MaintenanceCoordination.HasExternalLease) { context.Reply("Another mod holds the maintenance lease. Try again after it finishes."); return; } if (pendingManual != null) { if (IsCurrentRequest(pendingManual.Context)) { context.Reply("A manual request is already pending: " + pendingManual.Run.Id); return; } CancelPendingManual("The earlier request lost its authority or session."); } Interlocked.Exchange(ref reloadRequested, 0); ReloadSettings(reloadFile: true); if (settings == null) { context.Reply("Request rejected: the host cfg is invalid; see the host log."); return; } if (sessionFaulted) { context.Reply("Request rejected: maintenance is faulted for this world session; see the host log."); return; } if (!IsCurrentRequest(context)) { context.Reply("Request rejected: authority changed before admission."); return; } lastClock = ReadClock(); EnsureScheduler(); ScheduledRun due = scheduler.GetDue(lastClock); if (due != null) { context.Reply("A scheduled maintenance is already pending: " + due.Id); return; } ScheduledRun scheduledRun = scheduler.RequestManualRun(lastClock); pendingManual = new ManualRequest(scheduledRun, context, settings.Options); context.Reply("Accepted " + scheduledRun.Id + ". Host cfg: zones=" + settings.Options.ZonesEnabled + ", vegetation=" + settings.Options.VegetationEnabled + ", locations=" + settings.Options.LocationsEnabled + ", safeZones=" + settings.Options.ZoneSafeZones + "/" + settings.Options.VegetationSafeZones + "/" + settings.Options.LocationSafeZones + " (zones/resources/locations)."); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Manual request accepted from " + context.Actor + ": " + scheduledRun.Id)); TryDispatch(scheduledRun); nextPoll = 0f; } catch (Exception ex2) { sessionFaulted = true; context.Reply("Request failed; see the host log. New maintenance is disabled for this session."); ((BaseUnityPlugin)this).Logger.LogError((object)("FreshWorld command admission failed: " + ex2)); CloseScheduler("Command admission failed."); } } private void StartPovRepair(CommandRequestContext context) { if (runner != null || activeRunId != null || pendingManual != null || scheduler?.PendingRun != null || !MaintenanceGate.IsAvailable || GameWorld.HasPendingMaintenanceCleanup) { context.Reply("PoV altar repair is unavailable while maintenance is running, pending, or cleaning up."); return; } AttachHost(context.Network); if (sessionFaulted || settings == null) { context.Reply("PoV altar repair is unavailable: maintenance is faulted or the host cfg is invalid."); return; } if (Time.realtimeSinceStartup - attachedAt < settings.StartupDelaySeconds || Time.timeScale <= 0f || context.Network.IsSaving()) { context.Reply("PoV altar repair is unavailable during startup, pause, or a world save; try again afterward."); return; } if (EmptyChunkSaveCompatibility.Check() == EmptyChunkSaveCompatibility.Strategy.Unsupported) { DrainDisabledMaintenance(); context.Reply(DisabledStatus()); return; } ZoneSystem activeZones = ZoneSystem.instance; ZDOMan activeManager = ZDOMan.instance; ZNetScene activeScene = ZNetScene.instance; WorldGenerator activeGenerator = WorldGenerator.instance; try { RequireAuthority(); PathOfValheimRepair repair = new PathOfValheimRepair(RequireAuthority, delegate(string message) { progress = message; ((BaseUnityPlugin)this).Logger.LogInfo((object)message); context.Reply(message); }); backendLease = AcquireMaintenanceLease(); activePovRepair = true; progress = "Checking recorded PoV altars."; Exception failure = null; runner = new GuardedCoroutine(repair.Run(), CanContinue, delegate(Exception error) { failure = error; ((BaseUnityPlugin)this).Logger.LogError((object)("PoV altar repair failed: " + error)); }, delegate(bool success) { try { string text = (success ? "Completed. " : ("Stopped: " + (failure?.Message ?? "authority or world session changed") + ". ")) + repair.Summary + (success ? " Changes use the game's normal saves." : " Completed changes remain; no automatic retry."); progress = text; if (success) { ((BaseUnityPlugin)this).Logger.LogInfo((object)text); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)text); } context.Reply(text); } finally { backendLease?.Dispose(); backendLease = null; activePovRepair = false; } }); context.Reply("Starting PoV altar repair. Only missing Warden and Power altars with recorded coordinates will be restored."); } catch (Exception ex) { runner?.Dispose(); runner = null; backendLease?.Dispose(); backendLease = null; activePovRepair = false; ((BaseUnityPlugin)this).Logger.LogWarning((object)("PoV altar repair could not start: " + ex)); context.Reply("PoV altar repair could not start: " + ex.Message); } bool CanContinue() { if (EmptyChunkSaveCompatibility.DisabledReason == null && IsCurrentRequest(context) && WorldReady() && activeZones == ZoneSystem.instance && activeManager == ZDOMan.instance && activeScene == ZNetScene.instance) { return activeGenerator == WorldGenerator.instance; } return false; } void RequireAuthority() { if (!CanContinue()) { throw new InvalidOperationException("PoV repair lost its authority, world session, or compatibility contract."); } } } private string StatusMessage() { if (EmptyChunkSaveCompatibility.DisabledReason != null) { return DisabledStatus(); } if (sessionFaulted) { return "Maintenance is faulted for this world session. " + progress; } if (runner != null) { return "Running " + (activePovRepair ? "PoV altar repair" : activeRunId) + ": " + progress; } if (MaintenanceCoordination.HasExternalLease) { return "Another mod holds the maintenance lease. Automatic resets and configuration application are waiting for its completion or verified cleanup."; } if (pendingManual != null) { return "Pending " + pendingManual.Run.Id + ": " + (pendingManual.LastWait ?? "waiting to start"); } if (settings == null) { return "The host cfg is invalid; new maintenance is disabled."; } ScheduledRun scheduledRun = scheduler?.PendingRun; if (scheduledRun != null) { return "Scheduled maintenance pending: " + scheduledRun.Id; } RunRecord runRecord = scheduler?.Attempts.LastOrDefault(); return string.Concat("Automatic=" + settings.AutomaticEnabled + "; manual commands available; Mode=" + settings.Schedule.Mode.ToString() + "; zones=" + settings.Options.ZonesEnabled + ", resources=" + settings.Options.VegetationEnabled + ", locations=" + settings.Options.LocationsEnabled + "; safeZones=" + settings.Options.ZoneSafeZones + "/" + settings.Options.VegetationSafeZones + "/" + settings.Options.LocationSafeZones + " (zones/resources/locations). ", (runRecord == null) ? "Idle. No maintenance has run in this world's recorded history." : ("Idle. Last run: " + runRecord.Status.ToString() + " (" + runRecord.Run.Id + ").")); } private void TryDispatch(ScheduledRun run) { if (runner != null || settings == null || sessionFaulted) { return; } if (EmptyChunkSaveCompatibility.Check() == EmptyChunkSaveCompatibility.Strategy.Unsupported) { DrainDisabledMaintenance(); } else { if (!run.IsManual && !settings.AutomaticEnabled) { return; } RunOptions options = settings.Options; ManualRequest manualRequest = pendingManual; if (run.IsManual) { if (manualRequest == null || manualRequest.Run.Id != run.Id || !IsCurrentRequest(manualRequest.Context)) { if (manualRequest != null && manualRequest.Run.Id == run.Id) { CancelPendingManual("The requester is no longer authorized in this session."); } else { scheduler.CancelPendingManualRun(run.Id, lastClock, "No authenticated request exists in this session."); } return; } options = manualRequest.Options; } string text = null; if (!WorldReady() || !IsHost(host)) { text = "world readiness"; } else if (Time.realtimeSinceStartup - attachedAt < settings.StartupDelaySeconds) { text = "startup delay"; } else if (Time.timeScale <= 0f) { text = "the host to unpause"; } else if (host.IsSaving()) { text = "the current world save"; } else if (!MaintenanceGate.IsAvailable) { text = "another maintenance operation"; } if (text != null) { if (manualRequest != null && manualRequest.LastWait != text) { manualRequest.LastWait = text; manualRequest.Context.Reply("Waiting for " + text + "."); } } else { StartRun(run, options); } } } private void StartRun(ScheduledRun run, RunOptions options) { FreshWorldScheduler activeScheduler = scheduler; ZNet activeHost = host; long? activeWorldUid = worldUid; CommandRequestContext requester = ((pendingManual?.Run.Id == run.Id) ? pendingManual.Context : null); backendLease = AcquireMaintenanceLease(); try { activeScheduler.BeginRun(run, lastClock); pendingManual = null; activeRunId = run.Id; progress = "Starting."; Exception failure = null; MaintenancePipeline pipeline = new MaintenancePipeline(options, run.IncludeVegetation, delegate(string message) { progress = message; ((BaseUnityPlugin)this).Logger.LogInfo((object)message); requester?.Reply(message); }, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); requester?.Reply(message); }, run.IsManual ? 100 : options.AutomaticResetPercent, run.Id); if (run.IsManual) { requester?.Reply("Manual reset: processing all eligible zones. AutomaticResetPercent does not apply."); } runner = new GuardedCoroutine(pipeline.Run(), CanContinue, delegate(Exception error) { failure = failure ?? error; ((BaseUnityPlugin)this).Logger.LogError((object)("Maintenance error: " + error)); }, delegate(bool success) { try { if (success) { activeScheduler.CompleteRun(run.Id, ReadClock()); string planningExclusionSummary = pipeline.PlanningExclusionSummary; string text = ((planningExclusionSummary == null) ? "Completed" : "Partially completed"); if (planningExclusionSummary == null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Maintenance completed: " + run.Id)); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Maintenance partially completed: " + run.Id + "; " + planningExclusionSummary)); } progress = text + "."; requester?.Reply(text + " " + run.Id + ". " + ((planningExclusionSummary == null) ? "" : (planningExclusionSummary + " ")) + "World changes use the game's normal saves; no extra save was requested."); } else { activeScheduler.FailRun(run.Id, ReadClock(), failure?.Message ?? "World session ended or maintenance was cancelled."); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Maintenance stopped; completed world changes remain. This run will not retry automatically: " + run.Id)); progress = "Stopped: " + (failure?.Message ?? "world session ended"); requester?.Reply("Failed or interrupted " + run.Id + ": " + (failure?.Message ?? "world session ended") + ". Applied changes remain; this run will not retry automatically."); } } catch (Exception ex) { sessionFaulted = true; ((BaseUnityPlugin)this).Logger.LogError((object)("Could not persist the maintenance result; further runs disabled for this session. " + ex)); requester?.Reply("Maintenance ended, but its result could not be recorded. Further runs are disabled; see the host log."); } finally { backendLease?.Dispose(); backendLease = null; activeRunId = null; } }); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Maintenance starting: " + run.Id + "; vegetation=" + run.IncludeVegetation)); requester?.Reply("Starting " + run.Id + "."); bool CanContinue() { if (EmptyChunkSaveCompatibility.DisabledReason != null) { if (failure == null) { failure = new InvalidOperationException(DisabledStatus()); } return false; } if (requester != null && !IsCurrentRequest(requester)) { if (failure == null) { failure = new InvalidOperationException("The requester's connection, session, or authority is no longer valid."); } return false; } if (ZNet.instance == activeHost && IsHost(activeHost) && activeHost.GetWorldUID() == activeWorldUid) { return WorldReady(); } return false; } } catch { backendLease?.Dispose(); backendLease = null; activeRunId = null; requester?.Reply("Maintenance could not start; see the host log."); throw; } } private void CancelPendingManual(string reason) { ManualRequest manualRequest = pendingManual; if (manualRequest != null) { pendingManual = null; try { scheduler?.CancelPendingManualRun(manualRequest.Run.Id, ReadClock(), reason); } catch (Exception ex) { sessionFaulted = true; ((BaseUnityPlugin)this).Logger.LogError((object)("Could not persist cancellation of the manual request: " + ex)); } manualRequest.Context.Reply("Cancelled " + manualRequest.Run.Id + " before execution: " + reason); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Manual request cancelled before execution: " + manualRequest.Run.Id + "; " + reason)); } } private void CloseScheduler(string reason = "The scheduler was closed.") { CancelPendingManual(reason); FreshWorldScheduler freshWorldScheduler = scheduler; scheduler = null; if (freshWorldScheduler == null) { return; } try { freshWorldScheduler.Dispose(); } catch (Exception ex) { sessionFaulted = true; ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to close world state. " + ex)); } } private IDisposable AcquireMaintenanceLease() { IDisposable disposable = MaintenanceGate.Acquire(); try { PrepareMaintenance(); return disposable; } catch { disposable.Dispose(); throw; } } private bool CoordinationReady() { if (supported && settings != null && !sessionFaulted && scheduler != null && EmptyChunkSaveCompatibility.DisabledReason == null && ZNet.instance == host && IsHost(host) && host.GetWorldUID() == worldUid) { return WorldReady(); } return false; } private void PrepareMaintenance() { if (EmptyChunkSaveCompatibility.Check() == EmptyChunkSaveCompatibility.Strategy.Unsupported) { throw new InvalidOperationException(DisabledStatus()); } povAzurite?.BeforeMaintenance(); } internal void StopSession() { runner?.Dispose(); runner = null; backendLease?.Dispose(); backendLease = null; MaintenanceCoordination.CloseSession(); TryCleanup(delegate { povAzurite?.EndSession(); }, "Could not discard the PoV azurite scan at session end."); CloseScheduler("The hosting world session ended."); host = null; worldUid = null; nextPoll = 0f; nextReleaseSweep = 0f; sessionFaulted = false; activeRunId = null; activePovRepair = false; progress = "Idle."; } private void OnDestroy() { CleanupPlugin(); } private static string DisabledStatus() { return "FreshWorld disabled: " + EmptyChunkSaveCompatibility.DisabledReason + " Maintenance and FreshWorld save changes are disabled. World saves are not blocked by FreshWorld. Restart after resolving the mod combination."; } private void DrainDisabledMaintenance() { configSync?.Suspend(); if (MaintenanceCoordination.HasExternalLease) { return; } if (runner != null) { GuardedCoroutine guardedCoroutine = runner; if (!guardedCoroutine.MoveNext() && runner == guardedCoroutine) { runner = null; } } if (runner != null || compatibilityDrained) { return; } CloseScheduler("FreshWorld save compatibility is disabled."); if (IsHost(ZNet.instance) && (Object)(object)ZoneSystem.instance != (Object)null && ZDOMan.instance != null) { GameWorld.ProcessDeferredReleases(); if (GameWorld.HasPendingMaintenanceCleanup) { return; } } DisposeWatcher(); ConfigSynchronization? configSynchronization = configSync; if (configSynchronization == null || !configSynchronization.HasPendingLogin) { configSync?.Dispose(); configSync = null; povAzurite?.Dispose(); povAzurite = null; Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } compatibilityDrained = true; ((BaseUnityPlugin)this).Logger.LogWarning((object)"FreshWorld maintenance cleanup finished; operational patches removed. Only authenticated command diagnostics remain."); } } private void CleanupPlugin() { supported = false; try { TryCleanup(DisposeWatcher, "Could not stop configuration watching."); TryCleanup(delegate { configSync?.Dispose(); configSync = null; }, "Could not stop configuration synchronization."); TryCleanup(StopSession, "Could not stop the active world session."); TryCleanup(delegate { povAzurite?.Dispose(); povAzurite = null; }, "Could not stop PoV azurite compatibility."); TryCleanup(FreshWorldCommands.Unregister, "Could not unregister the console command."); TryCleanup(delegate { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } }, "Could not remove Harmony patches."); TryCleanup(delegate { Harmony? obj = commandHarmony; if (obj != null) { obj.UnpatchSelf(); } }, "Could not remove command authentication patches."); } finally { MaintenanceCoordination.Shutdown(); Instance = null; } } private void DisposeWatcher() { FileSystemWatcher fileSystemWatcher = watcher; watcher = null; if (fileSystemWatcher != null) { fileSystemWatcher.Changed -= OnConfigChanged; fileSystemWatcher.Created -= OnConfigChanged; fileSystemWatcher.Renamed -= OnConfigChanged; fileSystemWatcher.Dispose(); } } private void TryCleanup(Action cleanup, string message) { try { cleanup(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)(message + " " + ex)); } } } [HarmonyPatch(typeof(ZNet), "Shutdown", new Type[] { typeof(bool) })] internal static class ShutdownPatch { [HarmonyPrefix] private static void Prefix() { FreshWorldPlugin.Instance?.StopSession(); } } [HarmonyPatch(typeof(ZNet), "ShutdownWithoutSave", new Type[] { typeof(bool) })] internal static class ShutdownWithoutSavePatch { [HarmonyPrefix] private static void Prefix() { FreshWorldPlugin.Instance?.StopSession(); } } } namespace FreshWorld.Core { public sealed class FreshWorldScheduler : IDisposable { private readonly object gate = new object(); private ScheduleSettings settings; private readonly JsonWorldStateStore.Lease lease; private WorldState state; private bool disposed; private bool faulted; public string WorldId => state.WorldId; public IReadOnlyList<RunRecord> Attempts { get { lock (gate) { return state.Attempts.Select((RunRecord a) => a.Copy()).ToArray(); } } } public RunRecord? ActiveRun { get { lock (gate) { return state.Attempts.FirstOrDefault((RunRecord a) => a.Status == RunStatus.Running)?.Copy(); } } } public ScheduledRun? PendingRun { get { lock (gate) { EnsureUsable(); return state.Pending; } } } private FreshWorldScheduler(ScheduleSettings settings, JsonWorldStateStore.Lease lease, WorldState state) { this.settings = settings; this.lease = lease; this.state = state; } public static FreshWorldScheduler Open(ScheduleSettings settings, JsonWorldStateStore store, string worldId, ScheduleClock now) { if (settings == null) { throw new ArgumentNullException("settings"); } if (store == null) { throw new ArgumentNullException("store"); } JsonWorldStateStore.Lease lease = store.Acquire(worldId); try { WorldState worldState = lease.Read(worldId); if (worldState == null) { worldState = NewState(settings, worldId, now); } else { foreach (RunRecord item in worldState.Attempts.Where((RunRecord a) => a.Status == RunStatus.Running)) { item.Finish(RunStatus.Interrupted, now.UtcNow, "The previous host session ended before completion. This run will not be retried automatically."); } if (worldState.Fingerprint != settings.Fingerprint) { ResetAutomaticSchedule(worldState, settings, now); } else if (!settings.RunMissedOnWorldStart) { Advance(worldState, now); } } if (!settings.AutomaticEnabled) { ScheduledRun? pending = worldState.Pending; if (pending == null || !pending.IsManual) { worldState.Pending = null; } } lease.Write(worldState); return new FreshWorldScheduler(settings, lease, worldState); } catch { lease.Dispose(); throw; } } public void Reconfigure(ScheduleSettings nextSettings, ScheduleClock now) { if (nextSettings == null) { throw new ArgumentNullException("nextSettings"); } lock (gate) { EnsureUsable(); WorldState worldState = state.Copy(); if (worldState.Fingerprint != nextSettings.Fingerprint) { ResetAutomaticSchedule(worldState, nextSettings, now); } if (!nextSettings.AutomaticEnabled) { ScheduledRun? pending = worldState.Pending; if (pending == null || !pending.IsManual) { worldState.Pending = null; } } if (worldState.Fingerprint != state.Fingerprint || worldState.Pending?.Id != state.Pending?.Id) { Persist(worldState); } settings = nextSettings; } } public ScheduledRun? GetDue(ScheduleClock now) { lock (gate) { EnsureUsable(); WorldState worldState = state.Copy(); ScheduledRun candidate = ((!settings.AutomaticEnabled) ? null : ((settings.Mode == ScheduleMode.DailyTimes) ? LatestDaily(worldState, now) : LatestGameDay(worldState, now))); if (!settings.AutomaticEnabled) { ScheduledRun? pending = worldState.Pending; if (pending == null || !pending.IsManual) { worldState.Pending = null; } } Advance(worldState, now); if (candidate != null) { ScheduledRun? pending2 = worldState.Pending; if ((pending2 == null || !pending2.IsManual) && !worldState.Attempts.Any((RunRecord a) => a.Run.Id == candidate.Id)) { worldState.Pending = candidate; } } if (worldState.Pending?.Id != state.Pending?.Id) { Persist(worldState); } else { state = worldState; } return state.Attempts.Any((RunRecord a) => a.Status == RunStatus.Running) ? null : state.Pending; } } public void BeginRun(ScheduledRun run, ScheduleClock now) { if (run == null) { throw new ArgumentNullException("run"); } lock (gate) { EnsureUsable(); if (state.Attempts.Any((RunRecord a) => a.Status == RunStatus.Running)) { throw new InvalidOperationException("A FreshWorld run is already active."); } if (state.Pending == null || state.Pending.Id != run.Id || state.Attempts.Any((RunRecord a) => a.Run.Id == run.Id)) { throw new InvalidOperationException("The run is no longer pending or has already been attempted."); } WorldState worldState = state.Copy(); worldState.Attempts.Add(new RunRecord(worldState.Pending, now.UtcNow)); worldState.Pending = null; while (worldState.Attempts.Count > 128) { worldState.Attempts.RemoveAt(0); } Persist(worldState); } } public ScheduledRun RequestManualRun(ScheduleClock now, bool includeVegetation = true) { lock (gate) { EnsureUsable(); if (state.Attempts.Any((RunRecord a) => a.Status == RunStatus.Running)) { throw new InvalidOperationException("A FreshWorld run is already active."); } if (state.Pending != null) { throw new InvalidOperationException("A FreshWorld run is already pending; a manual request cannot replace it."); } WorldState worldState = state.Copy(); ScheduledRun result = (worldState.Pending = new ScheduledRun("manual:" + Guid.NewGuid().ToString("N"), now.UtcNow, now.GameDay, "Manual", includeVegetation)); Persist(worldState); return result; } } public void CompleteRun(string runId, ScheduleClock now) { FinishRun(runId, now, RunStatus.Completed, ""); } public void FailRun(string runId, ScheduleClock now, string reason) { FinishRun(runId, now, RunStatus.Failed, reason); } public bool CancelPendingManualRun(string runId, ScheduleClock now, string reason) { lock (gate) { EnsureUsable(); if (state.Pending == null || state.Pending.Id != runId || !state.Pending.IsManual) { return false; } WorldState worldState = state.Copy(); RunRecord runRecord = new RunRecord(worldState.Pending, now.UtcNow); runRecord.Finish(RunStatus.Interrupted, now.UtcNow, reason); worldState.Attempts.Add(runRecord); worldState.Pending = null; while (worldState.Attempts.Count > 128) { worldState.Attempts.RemoveAt(0); } Persist(worldState); return true; } } public void Checkpoint(ScheduleClock now) { lock (gate) { EnsureUsable(); GetDue(now); Persist(state.Copy()); } } private void FinishRun(string runId, ScheduleClock now, RunStatus status, string reason) { lock (gate) { EnsureUsable(); WorldState worldState = state.Copy(); (worldState.Attempts.SingleOrDefault((RunRecord a) => a.Run.Id == runId && a.Status == RunStatus.Running) ?? throw new InvalidOperationException("No matching active FreshWorld run exists.")).Finish(status, now.UtcNow, reason); Persist(worldState); } } private ScheduledRun? LatestDaily(WorldState current, ScheduleClock now) { if (now.UtcNow.UtcDateTime.Ticks <= current.CursorUtcTicks) { return null; } DateTime date = TimeZoneInfo.ConvertTime(now.UtcNow, settings.TimeZone).Date; for (int i = 0; i < 3; i++) { DateTime dateTime = date.AddDays(-i); for (int num = settings.DailyTimes.Count - 1; num >= 0; num--) { TimeSpan timeSpan = settings.DailyTimes[num]; DateTime dateTime2 = DateTime.SpecifyKind(dateTime.Add(timeSpan), DateTimeKind.Unspecified); if (!settings.TimeZone.IsInvalidTime(dateTime2)) { TimeSpan offset = (settings.TimeZone.IsAmbiguousTime(dateTime2) ? settings.TimeZone.GetAmbiguousTimeOffsets(dateTime2).Max() : settings.TimeZone.GetUtcOffset(dateTime2)); DateTimeOffset dateTimeOffset = new DateTimeOffset(dateTime2, offset).ToUniversalTime(); if (!(dateTimeOffset > now.UtcNow)) { if (dateTimeOffset.UtcDateTime.Ticks <= current.CursorUtcTicks) { return null; } string text = ScheduleSettings.FormatTime(timeSpan); return new ScheduledRun(current.Fingerprint + ":daily:" + dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) + ":" + text, dateTimeOffset, now.GameDay, text, settings.IncludeVegetation(timeSpan)); } } } } return null; } private ScheduledRun? LatestGameDay(WorldState current, ScheduleClock now) { if (now.GameDay <= current.CursorGameDay) { return null; } double num = Math.Floor((now.GameDay - current.GameDayAnchor) / settings.GameDayInterval); double num2 = Math.Floor((current.CursorGameDay - current.GameDayAnchor) / settings.GameDayInterval); if (num < 1.0 || num <= num2) { return null; } double gameDay = current.GameDayAnchor + num * settings.GameDayInterval; return new ScheduledRun(current.Fingerprint + ":days:" + current.GameDayAnchor.ToString("R", CultureInfo.InvariantCulture) + ":" + num.ToString("0", CultureInfo.InvariantCulture), now.UtcNow, gameDay, "GameDays", vegetation: true); } private void Persist(WorldState next) { try { lease.Write(next); state = next; } catch { faulted = true; throw; } } private static void Advance(WorldState target, ScheduleClock clock) { target.CursorUtcTicks = Math.Max(target.CursorUtcTicks, clock.UtcNow.UtcDateTime.Ticks); target.CursorGameDay = Math.Max(target.CursorGameDay, clock.GameDay); } private static void ResetAutomaticSchedule(WorldState target, ScheduleSettings settings, ScheduleClock now) { target.Fingerprint = settings.Fingerprint; ScheduledRun? pending = target.Pending; if (pending == null || !pending.IsManual) { target.Pending = null; } target.GameDayAnchor = now.GameDay; target.CursorGameDay = now.GameDay; target.CursorUtcTicks = now.UtcNow.UtcDateTime.Ticks; } private static WorldState NewState(ScheduleSettings settings, string worldId, ScheduleClock now) { return new WorldState { WorldId = worldId, Fingerprint = settings.Fingerprint, CursorUtcTicks = now.UtcNow.UtcDateTime.Ticks, CursorGameDay = now.GameDay, GameDayAnchor = now.GameDay }; } private void EnsureUsable() { if (disposed) { throw new ObjectDisposedException("FreshWorldScheduler"); } if (faulted) { throw new InvalidOperationException("State persistence failed; this scheduler is disabled for the session."); } } public void Dispose() { lock (gate) { if (disposed) { return; } try { if (!faulted) { lease.Write(state); } } finally { disposed = true; lease.Dispose(); } } } } public sealed class StateCorruptionException : IOException { public StateCorruptionException(string message) : base(message) { } public StateCorruptionException(string message, Exception inner) : base(message, inner) { } } public sealed class JsonWorldStateStore { internal sealed class Lease : IDisposable { private readonly string path; private readonly FileStream fileLock; private readonly DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(WorldState)); public Lease(string path, FileStream fileLock) { this.path = path; this.fileLock = fileLock; } public WorldState? Read(string worldId) { if (!File.Exists(path)) { return null; } try { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); if (fileStream.Length > 4194304) { throw new StateCorruptionException("FreshWorld state exceeds its size limit."); } WorldState obj = (serializer.ReadObject(fileStream) as WorldState) ?? throw new StateCorruptionException("FreshWorld state is empty."); obj.Validate(worldId); return obj; } catch (StateCorruptionException) { throw; } catch (Exception ex2) when (ex2 is SerializationException || ex2 is XmlException || ex2 is ArgumentException) { throw new StateCorruptionException("FreshWorld state is corrupt. Automatic reset is disabled; a stale backup is never restored automatically.", ex2); } } public void Write(WorldState state) { state.Validate(state.WorldId); string sourceFileName = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { using (FileStream fileStream = new FileStream(sourceFileName, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { serializer.WriteObject(fileStream, state); fileStream.Flush(flushToDisk: true); } if (File.Exists(path)) { File.Replace(sourceFileName, path, null); } else { File.Move(sourceFileName, path); } } finally { if (File.Exists(sourceFileName)) { File.Delete(sourceFileName); } } } public void Dispose() { fileLock.Dispose(); } } private readonly string directory; public JsonWorldStateStore(string directory) { if (string.IsNullOrWhiteSpace(directory)) { throw new ArgumentException("A state directory is required.", "directory"); } this.directory = Path.GetFullPath(directory); } public string GetStatePath(string worldId) { ValidateWorldId(worldId); return Path.Combine(directory, ScheduleSettings.Hash(worldId) + ".json"); } internal Lease Acquire(string worldId) { string statePath = GetStatePath(worldId); Directory.CreateDirectory(directory); return new Lease(statePath, new FileStream(statePath + ".lock", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)); } private static void ValidateWorldId(string worldId) { if (string.IsNullOrWhiteSpace(worldId)) { throw new ArgumentException("Use the persistent world UID, not its display name.", "worldId"); } } } public enum ScheduleMode { DailyTimes, GameDays } public sealed class ScheduleSettings { private readonly HashSet<TimeSpan> vegetationTimes; public ScheduleMode Mode { get; } public bool AutomaticEnabled { get; } public IReadOnlyList<TimeSpan> DailyTimes { get; } public TimeZoneInfo TimeZone { get; } public double GameDayInterval { get; } public bool RunMissedOnWorldStart { get; } internal string Fingerprint { get; } public ScheduleSettings(ScheduleMode mode, string dailyTimesCsv, string timeZoneId, double gameDayInterval, string vegetationDailyTimesCsv = "*", bool runMissedOnWorldStart = false, bool automaticEnabled = true) { if (!Enum.IsDefined(typeof(ScheduleMode), mode)) { throw new ArgumentOutOfRangeException("mode"); } Mode = mode; AutomaticEnabled = automaticEnabled; bool flag = automaticEnabled && mode == ScheduleMode.DailyTimes; bool flag2 = automaticEnabled && mode == ScheduleMode.GameDays; if (flag2) { ValidateInterval(gameDayInterval); } GameDayInterval = (flag2 ? gameDayInterval : 24.0); RunMissedOnWorldStart = automaticEnabled && runMissedOnWorldStart; DailyTimes = Array.AsReadOnly(flag ? ParseTimes(dailyTimesCsv, allowEmpty: false) : Array.Empty<TimeSpan>()); TimeZone = (flag ? ResolveTimeZone(timeZoneId) : TimeZoneInfo.Utc); vegetationTimes = (flag ? ParseVegetationTimes(DailyTimes, vegetationDailyTimesCsv) : new HashSet<TimeSpan>()); string text = mode switch { ScheduleMode.GameDays => gameDayInterval.ToString("R", CultureInfo.InvariantCulture), ScheduleMode.DailyTimes => string.Join("|", TimeZone.Id, string.Join(",", DailyTimes.Select(FormatTime)), string.Join(",", vegetationTimes.OrderBy((TimeSpan t) => t).Select(FormatTime))), _ => "", }; Fingerprint = Hash(automaticEnabled ? ("active-v2|" + mode.ToString() + "|" + text) : "automatic-disabled-v1").Substring(0, 16); } internal bool IncludeVegetation(TimeSpan slot) { return vegetationTimes.Contains(slot); } internal static string FormatTime(TimeSpan time) { return time.ToString("hh\\:mm", CultureInfo.InvariantCulture); } internal static string Hash(string value) { using SHA256 sHA = SHA256.Create(); return string.Concat(from b in sHA.ComputeHash(Encoding.UTF8.GetBytes(value)) select b.ToString("x2", CultureInfo.InvariantCulture)); } private static void ValidateInterval(double interval) { if (double.IsNaN(interval) || double.IsInfinity(interval) || interval <= 0.0) { throw new ArgumentOutOfRangeException("interval", "Game day interval must be finite and greater than zero."); } } private static TimeZoneInfo ResolveTimeZone(string timeZoneId) { if (!string.Equals(timeZoneId, "Local", StringComparison.OrdinalIgnoreCase)) { return TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); } return TimeZoneInfo.Local; } private static HashSet<TimeSpan> ParseVegetationTimes(IReadOnlyList<TimeSpan> times, string text) { if (text == null) { throw new ArgumentNullException("text"); } IReadOnlyList<TimeSpan> collection; if (!(text.Trim() == "*")) { IReadOnlyList<TimeSpan> readOnlyList = ParseTimes(text, allowEmpty: true); collection = readOnlyList; } else { collection = times; } HashSet<TimeSpan> hashSet = new HashSet<TimeSpan>(collection); if (hashSet.Any((TimeSpan t) => !times.Contains(t))) { throw new ArgumentException("Vegetation times must be a subset of daily run times, or '*'.", "text"); } return hashSet; } private static TimeSpan[] ParseTimes(string text, bool allowEmpty) { if (text == null) { throw new ArgumentNullException("text"); } if (allowEmpty && string.IsNullOrWhiteSpace(text)) { return Array.Empty<TimeSpan>(); } HashSet<TimeSpan> hashSet = new HashSet<TimeSpan>(); string[] array = text.Split(','); for (int i = 0; i < array.Length; i++) { if (!TimeSpan.TryParseExact(array[i].Trim(), "hh\\:mm", CultureInfo.InvariantCulture, out var result) || result.TotalHours >= 24.0) { throw new ArgumentException("Daily times must contain HH:mm values separated by commas.", "text"); } if (!hashSet.Add(result)) { throw new ArgumentException("Daily times cannot contain duplicates.", "text"); } } if (hashSet.Count == 0) { throw new ArgumentException("At least one daily run time is required.", "text"); } return hashSet.OrderBy((TimeSpan t) => t).ToArray(); } } public readonly struct ScheduleClock { public DateTimeOffset UtcNow { get; } public double GameDay { get; } public ScheduleClock(DateTimeOffset utcNow, double gameDay) { if (double.IsNaN(gameDay) || double.IsInfinity(gameDay) || gameDay < 0.0) { throw new ArgumentOutOfRangeException("gameDay"); } UtcNow = utcNow.ToUniversalTime(); GameDay = gameDay; } } [DataContract] public sealed class ScheduledRun { internal const string ManualSlot = "Manual"; [DataMember(Order = 2)] private long dueUtcTicks; [DataMember(Order = 1)] public string Id { get; private set; } = ""; [DataMember(Order = 3)] public double GameDay { get; private set; } [DataMember(Order = 4)] public string Slot { get; private set; } = ""; [DataMember(Order = 5)] public bool IncludeVegetation { get; private set; } public DateTimeOffset DueUtc => new DateTimeOffset(dueUtcTicks, TimeSpan.Zero); internal bool IsManual => string.Equals(Slot, "Manual", StringComparison.Ordinal); internal bool Valid { get { if (!string.IsNullOrWhiteSpace(Id) && dueUtcTicks > 0 && dueUtcTicks <= DateTime.MaxValue.Ticks && GameDay >= 0.0 && !double.IsInfinity(GameDay) && !double.IsNaN(GameDay)) { return Slot != null; } return false; } } internal ScheduledRun(string id, DateTimeOffset dueUtc, double gameDay, string slot, bool vegetation) { Id = id; dueUtcTicks = dueUtc.UtcDateTime.Ticks; GameDay = gameDay; Slot = slot; IncludeVegetation = vegetation; } } public enum RunStatus { Running, Completed, Failed, Interrupted } [DataContract] public sealed class RunRecord { [DataMember(Order = 3)] private long startedUtcTicks; [DataMember(Order = 4)] private long finishedUtcTicks; [DataMember(Order = 1)] public ScheduledRun Run { get; private set; } [DataMember(Order = 2)] public RunStatus Status { get; internal set; } [DataMember(Order = 5)] public string Message { get; internal set; } = ""; public DateTimeOffset StartedUtc => new DateTimeOffset(startedUtcTicks, TimeSpan.Zero); public DateTimeOffset? FinishedUtc { get { if (finishedUtcTicks != 0L) { return new DateTimeOffset(finishedUtcTicks, TimeSpan.Zero); } return null; } } internal bool Valid { get { if (Run != null && Run.Valid && Enum.IsDefined(typeof(RunStatus), Status) && startedUtcTicks > 0 && startedUtcTicks <= DateTime.MaxValue.Ticks && finishedUtcTicks >= 0 && finishedUtcTicks <= DateTime.MaxValue.Ticks && ((Status == RunStatus.Running) ? (finishedUtcTicks == 0) : (finishedUtcTicks > 0))) { return Message != null; } return false; } } internal RunRecord(ScheduledRun run, DateTimeOffset start) { Run = run; startedUtcTicks = start.UtcDateTime.Ticks; Status = RunStatus.Running; } internal void Finish(RunStatus status, DateTimeOffset time, string message) { Status = status; finishedUtcTicks = time.UtcDateTime.Ticks; Message = message ?? ""; } internal RunRecord Copy() { return (RunRecord)MemberwiseClone(); } } [DataContract] internal sealed class WorldState { [DataMember(Order = 1)] public int SchemaVersion = 1; [DataMember(Order = 2)] public string WorldId = ""; [DataMember(Order = 3)] public string Fingerprint = ""; [DataMember(Order = 4)] public long CursorUtcTicks; [DataMember(Order = 5)] public double CursorGameDay; [DataMember(Order = 6)] public double GameDayAnchor; [DataMember(Order = 7)] public ScheduledRun? Pending; [DataMember(Order = 8)] public List<RunRecord> Attempts = new List<RunRecord>(); public WorldState Copy() { WorldState obj = (WorldState)MemberwiseClone(); obj.Attempts = Attempts.Select((RunRecord a) => a.Copy()).ToList(); return obj; } public void Validate(string expectedWorldId) { if (SchemaVersion != 1 || WorldId != expectedWorldId || string.IsNullOrWhiteSpace(Fingerprint) || CursorUtcTicks <= 0 || CursorUtcTicks > DateTime.MaxValue.Ticks || !FiniteNonnegative(CursorGameDay) || !FiniteNonnegative(GameDayAnchor) || GameDayAnchor > CursorGameDay || Attempts == null || Attempts.Any((RunRecord a) => a == null || !a.Valid) || Attempts.Select((RunRecord a) => a.Run.Id).Distinct().Count() != Attempts.Count || Attempts.Count((RunRecord a) => a.Status == RunStatus.Running) > 1 || (Pending != null && (!Pending.Valid || Attempts.Any((RunRecord a) => a.Run.Id == Pending.Id)))) { throw new StateCorruptionException("FreshWorld state is inconsistent. Automatic reset is disabled until the state file is recovered."); } } private static bool FiniteNonnegative(double x) { if (x >= 0.0 && !double.IsNaN(x)) { return !double.IsInfinity(x); } return false; } } } namespace FreshWorld.Runtime { public sealed class GuardedCoroutine : IEnumerator, IDisposable { private readonly Stack<IEnumerator> _stack = new Stack<IEnumerator>(); private readonly Func<bool> _canContinue; private readonly Action<Exception> _onError; private readonly Action<bool> _onFinished; private bool _finished; private object? _current; public object? Current => _current; public GuardedCoroutine(IEnumerator root, Func<bool> canContinue, Action<Exception> onError, Action<bool> onFinished) { if (root == null) { throw new ArgumentNullException("root"); } _canContinue = canContinue ?? throw new ArgumentNullException("canContinue"); _onError = onError ?? throw new ArgumentNullException("onError"); _onFinished = onFinished ?? throw new ArgumentNullException("onFinished"); _stack.Push(root); } public bool MoveNext() { if (_finished) { return false; } _current = null; try { while (_stack.Count > 0) { if (!_canContinue()) { Finish(success: false); return false; } IEnumerator enumerator = _stack.Peek(); if (!enumerator.MoveNext()) { _stack.Pop(); (enumerator as IDisposable)?.Dispose(); continue; } object current = enumerator.Current; if (current is IEnumerator enumerator2) { foreach (IEnumerator item in _stack) { if (item == enumerator2) { throw new InvalidOperationException("A coroutine yielded an already active enumerator."); } } _stack.Push(enumerator2); continue; } _current = current; return true; } Finish(success: true); return false; } catch (Exception error) { Report(error); Finish(success: false); return false; } } public void Reset() { throw new NotSupportedException("Coroutines cannot be reset."); } public void Dispose() { Finish(success: false); } private void Finish(bool success) { if (_finished) { return; } _finished = true; _current = null; while (_stack.Count > 0) { IEnumerator enumerator = _stack.Pop(); try { (enumerator as IDisposable)?.Dispose(); } catch (Exception error) { success = false; Report(error); } } try { _onFinished(success); } catch (Exception error2) { Report(error2); } } private void Report(Exception error) { try { _onError(error); } catch (Exception) { } } } } namespace FreshWorld.Engine { internal sealed class AlwaysProtectedObjects { private readonly HashSet<int> _prefabs; private readonly Func<ZDO, bool>? _additionalProtection; private readonly HashSet<Vector2s> _observed = new HashSet<Vector2s>(); public AlwaysProtectedObjects(IEnumerable<string> prefabs, Func<ZDO, bool>? additionalProtection = null) { _prefabs = new HashSet<int>(prefabs.Select((string id) => StringExtensionMethods.GetStableHashCode(id))); _additionalProtection = additionalProtection; } public bool IsProtectedObject(ZDO zdo) { if (zdo.IsValid()) { if (!_prefabs.Contains(zdo.GetPrefab())) { return _additionalProtection?.Invoke(zdo) ?? false; } return true; } return false; } public int Capture() { if (_prefabs.Count == 0 && _additionalProtection == null) { return 0; } foreach (ZDO item in GameWorld.AllZDOs()) { Observe(item); } return _observed.Count; } public bool CanResetZone(Vector2s zone) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (_prefabs.Count == 0 && _additionalProtection == null) { return true; } if (_observed.Contains(zone)) { return false; } foreach (ZDO zDO in GameWorld.GetZDOs(zone)) { if (Observe(zDO)) { return false; } } return true; } private bool Observe(ZDO zdo) { //IL_0012: 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) if (!IsProtectedObject(zdo)) { return false; } _observed.Add(ZoneSystem.GetZone(zdo.GetPosition())); return true; } } internal static class BaseProtection { private static HashSet<int> pieceBlacklist = new HashSet<int>(); private static HashSet<int> unconditionalObjects = new HashSet<int>(); private static HashSet<Vector2s> excluded = new HashSet<Vector2s>(); private static DateTime calculatedAt = DateTime.MinValue; private static int lastSize = -1; public static void Configure(IEnumerable<string> blacklistedPieces, IEnumerable<string> alwaysProtectedObjects) { pieceBlacklist = new HashSet<int>(blacklistedPieces.Select((string id) => StringExtensionMethods.GetStableHashCode(id))); unconditionalObjects = new HashSet<int>(alwaysProtectedObjects.Select((string id) => StringExtensionMethods.GetStableHashCode(id))); InvalidateCache(); } public static HashSet<Vector2s> GetExcluded(int size) { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) if (size < 0) { throw new ArgumentOutOfRangeException("size"); } if (lastSize == size && DateTime.UtcNow - calculatedAt < TimeSpan.FromSeconds(10.0)) { return excluded; } HashSet<Vector2s> hashSet = new HashSet<Vector2s>(); ZNetScene scene; Dictionary<int, bool> playerPrefabs; if (size > 0) { scene = ZNetScene.instance; if ((Object)(object)scene == (Object)null) { throw new InvalidOperationException("Base protection requires the world's prefab registry."); } playerPrefabs = pieceBlacklist.ToDictionary((int result) => result, (int _) => false); HashSet<Vector2s> hashSet2 = new HashSet<Vector2s>(); int num = size - 1; foreach (ZDO item in GameWorld.AllZDOs()) { int prefab = item.GetPrefab(); if (!unconditionalObjects.Contains(prefab) && (!IsPlayerPrefab(prefab) || item.GetLong(ZDOVars.s_creator, 0L) == 0L)) { continue; } Vector2s zone = ZoneSystem.GetZone(item.GetPosition()); if (!hashSet2.Add(zone)) { continue; } for (int num2 = Math.Max(-32768, zone.x - num); num2 <= Math.Min(32767, zone.x + num); num2++) { for (int num3 = Math.Max(-32768, zone.y - num); num3 <= Math.Min(32767, zone.y + num); num3++) { hashSet.Add(new Vector2s(num2, num3)); } } } } excluded = hashSet; lastSize = size; calculatedAt = DateTime.UtcNow; return excluded; bool IsPlayerPrefab(int num4) { if (playerPrefabs.TryGetValue(num4, out var value)) { return value; } GameObject prefab2 = scene.GetPrefab(num4); return playerPrefabs[num4] = (Object)(object)prefab2 != (Object)null && (Object)(object)prefab2.GetComponent<Piece>() != (Object)null; } } public static void InvalidateCache() { calculatedAt = DateTime.MinValue; lastSize = -1; excluded = new HashSet<Vector2s>(); } } internal static class EmptyChunkSaveCompatibility { internal enum Strategy { FreshWorld, UpgradeWorld, Unsupported } private const string UpgradeOwner = "upgrade_world"; private static readonly (MethodInfo Target, string Type, string Method, bool Prefix)[] Targets = new(MethodInfo, string, string, bool)[3] { (Required(typeof(ZDOMan), "AddObjectsPerChunk", typeof(int), typeof(byte), typeof(int[]), typeof(List<Tuple<ChunkIndex, List<ZDO>>>).MakeByRefType()), "UpgradeWorld.FindEmptiedChunks", "Prefix", true), (Required(typeof(ChunkSaveMapping), "Clone"), "UpgradeWorld.DropEmptiedChunks", "Postfix", false), (Required(typeof(ZDOMan), "DeleteOldChunks", typeof(string), typeof(FileSource)), "UpgradeWorld.DeleteEmptiedChunkFiles", "Prefix", true) }; private static string? lastReport; internal static string? DisabledReason { get; private set; } internal static void Reset() { DisabledReason = null; lastReport = null; } private static MethodInfo Required(Type type, string name, params Type[] arguments) { return AccessTools.DeclaredMethod(type, name, arguments, (Type[])null) ?? throw new MissingMethodException(type.FullName, name); } internal static Strategy Inspect(out string detail) { List<string> list = new List<string>(); int num = 0; Assembly assembly = null; bool flag = true; (MethodInfo, string, string, bool)[] targets = Targets; for (int i = 0; i < targets.Length; i++) { (MethodInfo, string, string, bool) tuple = targets[i]; Patches patchInfo = Harmony.GetPatchInfo((MethodBase)tuple.Item1); if (patchInfo == null) { continue; } int num2 = 0; int num3 = 0; (string, ReadOnlyCollection<Patch>)[] array = new(string, ReadOnlyCollection<Patch>)[4] { ("Prefix", patchInfo.Prefixes), ("Postfix", patchInfo.Postfixes), ("Transpiler", patchInfo.Transpilers), ("Finalizer", patchInfo.Finalizers) }; for (int j = 0; j < array.Length; j++) { (string, ReadOnlyCollection<Patch>) tuple2 = array[j]; foreach (Patch item in tuple2.Item2) { MethodInfo patchMethod = item.PatchMethod; Assembly assembly2 = patchMethod.DeclaringType?.Assembly; if (item.owner != "upgrade_world" && assembly2?.GetName().Name != "UpgradeWorld" && patchMethod.DeclaringType?.Namespace != "UpgradeWorld") { continue; } num2++; list.Add(tuple.Item1.DeclaringType.Name + "." + tuple.Item1.Name + ": " + tuple2.Item1 + " " + item.owner + "/" + string.Format("{0}.{1} [priority={2}; before={3}; ", patchMethod.DeclaringType?.FullName, patchMethod.Name, item.priority, string.Join(",", item.before)) + string.Format("after={0}; {1}; MVID={2}]", string.Join(",", item.after), assembly2?.FullName, patchMethod.Module.ModuleVersionId)); if (item.owner == "upgrade_world" && patchMethod.DeclaringType?.FullName == tuple.Item2 && patchMethod.Name == tuple.Item3 && patchMethod.IsStatic && patchMethod.ReturnType == typeof(void) && tuple2.Item1 == (tuple.Item4 ? "Prefix" : "Postfix") && assembly2?.GetName().Name == "UpgradeWorld") { num3++; if (assembly == null) { assembly = assembly2; } else if ((object)assembly != assembly2) { flag = false; } } } } if (num2 == 1 && num3 == 1) { num++; } } detail = string.Join("; ", list); if (list.Count == 0) { return Strategy.FreshWorld; } if (list.Count != Targets.Length || num != Targets.Length || !flag || assembly == null) { return Strategy.Unsupported; } string detail2; bool num4 = UpgradeWorldSaveContract.Recognizes(assembly, out detail2); detail = detail + "; " + detail2; if (!num4) { return Strategy.Unsupported; } return Strategy.UpgradeWorld; } internal static Strategy Check() { if (DisabledReason != null) { return Strategy.Unsupported; } Strategy strategy; string detail; try { strategy = Inspect(out detail); } catch (Exception ex) { strategy = Strategy.Unsupported; detail = ex.GetType().Name + ": " + ex.Message; } if (strategy == Strategy.Unsupported) { DisabledReason = "UpgradeWorld empty-chunk contract was not recognized. " + detail; } string text = strategy.ToString() + ":" + detail; if (text != lastReport) { lastReport = text; FreshWorldPlugin.LogSaveCompatibility(strategy switch { Strategy.FreshWorld => "FreshWorld empty chunk compatibility: FreshWorld strategy active.", Strategy.UpgradeWorld => "FreshWorld empty chunk compatibility: UpgradeWorld cleanup contract recognized; FreshWorld empty replacements are disabled.", _ => "FreshWorld disabled: " + DisabledReason + " New maintenance and FreshWorld save changes are disabled. World saves have NOT been blocked by FreshWorld. External save behavior is not verified; resolve the combination and restart to re-enable FreshWorld.", }, strategy == Strategy.Unsupported); } return strategy; } } [HarmonyPatch(typeof(ZDOMan), "GetSaveClonePerChunk", new Type[] { })] internal static class EmptyChunkSavePatch { private static readonly FieldInfo FileCount = FindCounter("m_numFiles"); private static readonly FieldInfo DirtyFileCount = FindCounter("m_dirtyFiles"); private static FieldInfo FindCounter(string name) { FieldInfo field = (typeof(ZDOMan).GetNestedType("SaveData", BindingFlags.NonPublic) ?? throw new TypeLoadException("ZDOMan.SaveData")).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null || field.FieldType != typeof(int)) { throw new MissingFieldException("ZDOMan.SaveData", name); } return field; } [HarmonyPostfix] internal static void Postfix(List<Tuple<ChunkIndex, List<ZDO>>> __result, ChunkSaveMapping ___m_chunkSaveMapping, HashSet<ChunkIndex>[] ___m_dirtyChunks, List<ZDO>[] ___m_objectsBySector, object ___m_saveData) { //IL_0037: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; if (instance == null || !instance.IsServer() || EmptyChunkSaveCompatibility.Check() != EmptyChunkSaveCompatibility.Strategy.FreshWorld) { return; } HashSet<ChunkIndex> scheduled = new HashSet<ChunkIndex>(); HashSet<ChunkIndex> splitParents = new HashSet<ChunkIndex>(); foreach (Tuple<ChunkIndex, List<ZDO>> item in __result) { Record(item.Item1, scheduled, splitParents); } int num = 0; foreach (KeyValuePair<ChunkIndex, ChunkInfo> chunk in ___m_chunkSaveMapping.Chunks) { ChunkIndex key = chunk.Key; ChunkInfo value = chunk.Value; if (value.SaveChunk && value.m_numZDOs > 0 && IsOrdinaryChunk(key) && ___m_dirtyChunks[0].Contains(key) && !Overlaps(key, scheduled, splitParents) && IsEmpty(key, ___m_objectsBySector)) { __result.Add(Tuple.Create<ChunkIndex, List<ZDO>>(key, new List<ZDO>())); Record(key, scheduled, splitParents); num++; } } if (num != 0) { FileCount.SetValue(___m_saveData, (int)FileCount.GetValue(___m_saveData) + num); DirtyFileCount.SetValue(___m_saveData, (int)DirtyFileCount.GetValue(___m_saveData) + num); } } private static bool IsOrdinaryChunk(ChunkIndex chunk) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!((ChunkIndex)(ref chunk)).Equals(ZoneSystem.ChunkPortal) && chunk.m_chunkSize <= 3 && (chunk.Chunk & 0xFF) < 64 && chunk.Chunk >> 8 < 64) { ChunkIndex val = ZoneSystem.ChunkIndexFromIndexAndSize(chunk, chunk.m_chunkSize); return ((ChunkIndex)(ref val)).Equals(chunk); } return false; } private static void Record(ChunkIndex chunk, HashSet<ChunkIndex> scheduled, HashSet<ChunkIndex> splitParents) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (IsOrdinaryChunk(chunk)) { scheduled.Add(chunk); for (int i = chunk.m_chunkSize + 1; i <= 3; i++) { splitParents.Add(ZoneSystem.ChunkIndexFromIndexAndSize(chunk, (byte)i)); } } } private static bool Overlaps(ChunkIndex chunk, HashSet<ChunkIndex> scheduled, HashSet<ChunkIndex> splitParents) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_0023: Unknown result type (might be due to invalid IL or missing references) if (scheduled.Contains(chunk) || splitParents.Contains(chunk)) { return true; } for (int i = chunk.m_chunkSize + 1; i <= 3; i++) { if (scheduled.Contains(ZoneSystem.ChunkIndexFromIndexAndSize(chunk, (byte)i))) { return true; } } return false; } private static bool IsEmpty(ChunkIndex chunk, List<ZDO>[] sectors) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) ValueTuple<int, int> zoneFromChunk = ZoneSystem.GetZoneFromChunk(chunk); int item = zoneFromChunk.Item1; int item2 = zoneFromChunk.Item2; int num = 8 << (int)chunk.m_chunkSize; for (int i = item2; i < item2 + num; i++) { for (int j = item; j < item + num; j++) { List<ZDO> list = sectors[ZoneSystem.SectorToIndex(j, i).Sector]; if (list != null && list.Count != 0) { return false; } } } return true; } } internal sealed class EpicLootProtection { private static readonly int SpawnController = StringExtensionMethods.GetStableHashCode("EL_SpawnController"); private static readonly int TreasureBiome = StringExtensionMethods.GetStableHashCode("TreasureMapChest.Biome"); private static readonly int TreasureFound = StringExtensionMethods.GetStableHashCode("TreasureMapChest.HasBeenFound"); private static readonly int TreasureSpawn = StringExtensionMethods.GetStableHashCode("treasure_spawn"); private static readonly int BountySpawn = StringExtensionMethods.GetStableHashCode("bount_spawn"); private static readonly int BountyId = StringExtensionMethods.GetStableHashCode("BountyID"); private static readonly int IsBounty = StringExtensionMethods.GetStableHashCode("isBounty"); private static readonly int Placed = StringExtensionMethods.GetStableHashCode("placed"); private readonly HashSet<Vector2s> _observed = new HashSet<Vector2s>(); private readonly bool _protectTreasure; private readonly bool _protectBounties; internal EpicLootProtection(bool protectTreasure, bool protectBounties) { _protectTreasure = protectTreasure; _protectBounties = protectBounties; } internal static bool IsTreasureObject(ZDO zdo) { if (zdo.IsValid()) { if (zdo.GetPrefab() == SpawnController && !zdo.GetBool(IsBounty, false) && !zdo.GetBool(Placed, false)) { byte[] byteArray = zdo.GetByteArray(TreasureSpawn, (byte[])null); if (byteArray != null && byteArray.Length > 0) { return true; } } if (!string.IsNullOrEmpty(zdo.GetString(TreasureBiome, ""))) { return !zdo.GetBool(TreasureFound, false); } return false; } return false; } internal static bool IsBountyObject(ZDO zdo) { if (zdo.IsValid()) { if (zdo.GetPrefab() == SpawnController && zdo.GetBool(IsBounty, false) && !zdo.GetBool(Placed, false)) { byte[] byteArray = zdo.GetByteArray(BountySpawn, (byte[])null); if (byteArray != null && byteArray.Length > 0) { return true; } } return !string.IsNullOrEmpty(zdo.GetString(BountyId, "")); } return false; } internal static bool IsProtectedObject(ZDO zdo, bool protectTreasure, bool protectBounties) { if (!protectTreasure || !IsTreasureObject(zdo)) { if (protectBounties) { return IsBountyObject(zdo); } return false; } return true; } public int Capture() { if (!_protectTreasure && !_protectBounties) { return 0; } foreach (ZDO item in GameWorld.AllZDOs()) { Observe(item); } return _observed.Count; } public bool CanResetZone(Vector2s zone) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_protectTreasure && !_protectBounties) { return true; } if (_observed.Contains(zone)) { return false; } foreach (ZDO zDO in GameWorld.GetZDOs(zone)) { if (Observe(zDO)) { return false; } } return true; } private bool Observe(ZDO zdo) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!IsProtectedObject(zdo, _protectTreasure, _protectBounties)) { return false; } _observed.Add(ZoneSystem.GetZone(zdo.GetPosition())); return true; } } internal static class GameWorld { internal sealed class ZoneDeletionPlan { private sealed class Entry { internal readonly ZDO Object; internal readonly ZDOID Id; internal readonly ZDOID Spawned; internal readonly int Prefab; internal readonly Vector3 Position; internal readonly bool PendingBefore; internal string DeletePath = "not-attempted"; internal Entry(ZDO zdo, HashSet<ZDOID> pending) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) Object = zdo; Id = zdo.m_uid; Prefab = zdo.GetPrefab(); Position = zdo.GetPosition(); Spawned = zdo.GetConnectionZDOID((ConnectionType)3); PendingBefore = pending.Contains(Id); } internal bool Matches(ZDO zdo) { //IL_000a: 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_002b: 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_0034: 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_0048: Unknown result type (might be due to invalid IL or missing references) if (Object == zdo && zdo.m_uid == Id && zdo.GetPrefab() == Prefab) { Vector3 position = zdo.GetPosition(); if (((Vector3)(ref position)).Equals(Position)) { return zdo.GetConnectionZDOID((ConnectionType)3) == Spawned; } } return false; } } private readonly HashSet<Vector2s> zones; private readonly bool epicLoot; private readonly bool bounties; private readonly AlwaysProtectedObjects? absolute; private readonly Dictionary<ZDOID, Entry> entries = new Dictionary<ZDOID, Entry>(); private readonly DateTime plannedUtc = DateTime.UtcNow; private DateTime executionUtc; internal string Stage { get; private set; } = "Plan"; internal ZoneDeletionPlan(HashSet<Vector2s> group, bool protectEpicLoot, bool protectBounties, AlwaysProtectedObjects? alwaysProtected) { zones = new HashSet<Vector2s>(group); epicLoot = protectEpicLoot; bounties = protectBounties; absolute = alwaysProtected; } internal void Capture() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) HashSet<ZDOID> pending = new HashSet<ZDOID>(PendingDestruction.Invoke(ZDOMan.instance)); foreach (Vector2s zone in zones) { foreach (ZDO zDO in GetZDOs(zone)) { Capture(zDO, pending); } } Validate(); } private void Capture(ZDO zdo, HashSet<ZDOID> pending) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (zdo.IsValid() && zones.Contains(ZoneSystem.GetZone(zdo.GetPosition())) && !entries.ContainsKey(zdo.m_uid)) { Entry entry = new Entry(zdo, pending); entries.Add(entry.Id, entry); if (entry.Spawned != ZDOID.None && ObjectsById.Invoke(ZDOMan.instance).TryGetValue(entry.Spawned, out var value)) { Capture(value, pending); } } } internal bool Contains(ZDOID id) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return entries.ContainsKey(id); } internal bool Matches(ZDO zdo) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (entries.TryGetValue(zdo.m_uid, out Entry value)) { return value.Matches(zdo); } return false; } internal IEnumerable<ZDO> Remaining() { foreach (ZDOID key in entries.Keys) { if (ObjectsById.Invoke(ZDOMan.instance).TryGetValue(key, out var value) && value.IsValid()) { yield return value; } } } internal void Validate() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) Stage = "Preflight"; if (zones.Any(HasPendingZoneLoad)) { throw new InvalidOperationException("A location zone group has a pending load before deletion."); } Dictionary<ZDOID, ZDO> dictionary = ObjectsById.Invoke(ZDOMan.instance); foreach (Entry value2 in entries.Values) { if (!dictionary.TryGetValue(value2.Id, out var value) || !value.IsValid() || !value2.Matches(value) || Protection(value) != "none") { throw new InvalidOperationException("A location zone group object changed or is protected before deletion."); } } foreach (Vector2s zone in zones) { foreach (ZDO zDO in GetZDOs(zone)) { if (!Matches(zDO)) { throw new InvalidOperationException("An unplanned object appeared in a location zone group before deletion."); } } } } internal void Delete() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) Stage = "Delete"; executionUtc = DateTime.UtcNow; foreach (Entry value2 in entries.Values) { if (ObjectsById.Invoke(ZDOMan.instance).TryGetValue(value2.Id, out var value) && value.IsValid()) { if (!value2.Matches(value)) { throw new InvalidOperationException("A planned object was replaced during location zone group deletion."); } RemoveZDO(value, null, epicLoot, bounties, absolute, WithinPlan, Deleting); } } Stage = "PostDelete"; CompleteZoneDeletions(zones, epicLoot, bounties, absolute, this); void Deleting(ZDO zdo, string path) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (!WithinPlan(zdo) || Protection(zdo) != "none") { throw new InvalidOperationException("A planned object changed during location zone group deletion."); } entries[zdo.m_uid].DeletePath = path; } bool WithinPlan(ZDO zdo) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (zones.Contains(ZoneSystem.GetZone(zdo.GetPosition()))) { return Matches(zdo); } return false; } } private string Protection(ZDO zdo) { if (IsPlayer(zdo)) { return "player"; } AlwaysProtectedObjects? alwaysProtectedObjects = absolute; if (alwaysProtectedObjects != null && alwaysProtectedObjects.IsProtectedObject(zdo)) { return "absolute-or-PoV"; } if (JotunInvasionProtection.IsProtectedObject(zdo)) { return "jotun-objective"; } if (EpicLootProtection.IsProtectedObject(zdo, epicLoot, bounties)) { return "epicloot"; } return "none"; } internal void ReportFailure(Action<string> warn, string runId, Vector2s anchor, Exception error) { //IL_0028: 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) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_03f8: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_0425: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_047f: Unknown result type (might be due to invalid IL or missing references) //IL_0484: Unknown result type (might be due to invalid IL or missing references) //IL_04ab: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_04fa: Unknown result type (might be due to invalid IL or missing references) try { warn($"Location group stopped: runId={runId}; phase={Stage}; anchor={anchor}; error={error.Message}"); Dictionary<ZDOID, ZDO> manager = ObjectsById.Invoke(ZDOMan.instance); HashSet<ZDOID> pending = new HashSet<ZDOID>(PendingDestruction.Invoke(ZDOMan.instance)); Dictionary<ZDOID, ZDO> dictionary = new Dictionary<ZDOID, ZDO>(); foreach (ZDO item in zones.SelectMany(GetZDOs)) { dictionary[item.m_uid] = item; } foreach (Entry value7 in entries.Values) { if (manager.TryGetValue(value7.Id, out ZDO value)) { dictionary[value7.Id] = value; } } IEnumerable<string> values = from zone in zones.Where((Vector2s zone) => ZoneSystem.instance.m_locationInstances.ContainsKey(zone)).Take(8) select ZoneSystem.instance.m_locationInstances[zone].m_location?.m_prefab.Name ?? "unknown"; warn("Location group state: locations=" + string.Join(",", values) + "; " + string.Format("sectors={0} [{1}]; omittedSectors={2}; plannedUtc={3:O}; executionUtc={4}; ", zones.Count, string.Join(",", zones.Take(16)), Math.Max(0, zones.Count - 16), plannedUtc, (executionUtc == default(DateTime)) ? "not-started" : executionUtc.ToString("O")) + $"planned={entries.Count}; residual={dictionary.Count}; plannedAbsentOrInvalid={entries.Keys.Count((ZDOID id) => !manager.TryGetValue(id, out Entry value6) || !value6.IsValid())}; detailLimit={8}"); KeyValuePair<ZDOID, ZDO>[] array = dictionary.Where((KeyValuePair<ZDOID, ZDO> pair) => !entries.TryGetValue(pair.Key, out value6) || !value6.Matches(pair.Value) || Protection(pair.Value) != "none" || !pending.Contains(pair.Key)).ToArray(); KeyValuePair<ZDOID, ZDO>[] array2 = ((array.Length != 0) ? array : dictionary.ToArray()); foreach (KeyValuePair<ZDOID, ZDO> item2 in array2.Take(8)) { ZDO value2 = item2.Value; entries.TryGetValue(item2.Key, out Entry value3); int prefab = value2.GetPrefab(); GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab); string text = ((prefab2 != null) ? ((Object)prefab2).name : null) ?? "unknown"; ZDO value4; bool flag = manager.TryGetValue(item2.Key, out value4) && value4 == value2; ZNetView value5; bool flag2 = SceneInstances.Invoke(ZNetScene.instance).TryGetValue(value2, out value5) && (Object)(object)value5 != (Object)null; string text2 = Protection(value2); string text3 = ((value3 == null) ? "unplanned" : ((!value3.Matches(value2)) ? "changed" : ((text2 != "none") ? "protected" : ((!pending.Contains(item2.Key)) ? "not-queued" : "queued-native-deletion")))); warn($"Location group object: id={item2.Key}; prefab={text}; hash={prefab}; position={value2.GetPosition()}; zone={ZoneSystem.GetZone(value2.GetPosition())}; " + $"valid={value2.IsValid()}; dictionary={flag}; scene={flag2}; planned={value3 != null}; inScope={zones.Contains(ZoneSystem.GetZone(value2.GetPosition()))}; protection={text2}; exclusion={text3}; " + string.Format("spawned={0}; deletePath={1}; ", value2.GetConnectionZDOID((ConnectionType)3), value3?.DeletePath ?? "not-planned") + string.Format("pendingBefore={0}; pendingNow={1}; newSincePlan={2}", value3?.PendingBefore.ToString() ?? "unknown", pending.Contains(item2.Key), value3 == null)); } if (array2.Length > 8) { warn($"Location group diagnostics: {array2.Length - 8} additional objects omitted."); } } catch { } } } [HarmonyPatch(typeof(ZDOMan), "SendDestroyed")] private static class DestroyBatchPatch { private const int MaxPerPacket = 10000; [HarmonyPrefix] private static bool Prefix(ZDOMan __instance) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) List<ZDOID> list = PendingDestruction.Invoke(__instance); if (list.Count < 10000) { return true; } ZPackage val = new ZPackage(); val.Write(10000); for (int i = 0; i < 10000; i++) { val.Write(list[i]); } list.RemoveRange(0, 10000); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "DestroyZDO", new object[1] { val }); return false; } } private static readonly FieldRef<ZoneSystem, HashSet<Vector2s>> GeneratedZones = AccessTools.FieldRefAccess<ZoneSystem, HashSet<Vector2s>>("m_generatedZones"); private static readonly FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID"); private static readonly FieldRef<ZDOMan, List<ZDOID>> PendingDestruction = AccessTools.FieldRefAccess<ZDOMan, List<ZDOID>>("m_destroySendList"); private static readonly Action<ZDOMan, ZDOID> HandleDestroyed = AccessTools.MethodDelegate<Action<ZDOMan, ZDOID>>(AccessTools.Method(typeof(ZDOMan), "HandleDestroyedZDO", (Type[])null, (Type[])null), (object)null, true); private static readonly FieldRef<ZNetScene, Dictionary<ZDO, ZNetView>> SceneInstances = AccessTools.FieldRefAccess<ZNetScene, Dictionary<ZDO, ZNetView>>("m_instances"); private static readonly FieldRef<ZoneSystem, Dictionary<Vector2s, List<ZDO>>> LoadingObjects = AccessTools.FieldRefAccess<ZoneSystem, Dictionary<Vector2s, List<ZDO>>>("m_loadingObjectsInZones"); private static readonly Func<ZoneSystem, Vector2s, bool> PokeLocalZone = AccessTools.MethodDelegate<Func<ZoneSystem, Vector2s, bool>>(AccessTools.Method(typeof(ZoneSystem), "PokeLocalZone", (Type[])null, (Type[])null), (object)null, true); private static readonly FieldInfo ZoneRoots = AccessTools.Field(typeof(ZoneSystem), "m_zones") ?? throw new MissingFieldException(typeof(ZoneSystem).FullName, "m_zones"); private static readonly FieldInfo RootObject = AccessTools.Field(ZoneRoots.FieldType.GetGenericArguments()[1], "m_root") ?? throw new MissingFieldException("ZoneSystem.ZoneData", "m_root"); private static readonly FieldInfo Heightmaps = AccessTools.Field(typeof(Heightmap), "s_heightmaps") ?? throw new MissingFieldException(typeof(Heightmap).FullName, "s_heightmaps"); private static readonly FieldInfo HeightmapBuildData = AccessTools.Field(typeof(Heightmap), "m_buildData") ?? throw new MissingFieldException(typeof(Heightmap).FullName, "m_buildData"); private static readonly int PlayerPrefab = StringExtensionMethods.GetStableHashCode("Player"); private static readonly Dictionary<Vector2s, GameObject> OwnedLoads = new Dictionary<Vector2s, GameObject>(); private static readonly HashSet<Vector2s> DeferredReleases = new HashSet<Vector2s>(); private static ZoneSystem? _loadWorld; internal static bool HasPendingMaintenanceCleanup { get { EnsureLoadWorld(); if (DeferredReleases.Count == 0 && OwnedLoads.Count == 0) { if (ZDOMan.instance != null) { return PendingDestruction.Invoke(ZDOMan.instance).Count != 0; } return false; } return true; } } internal static bool HasPendingZoneCleanup { get { EnsureLoadWorld(); if (DeferredReleases.Count == 0) { return OwnedLoads.Count != 0; } return true; } } public static Vector2s[] GeneratedSnapshot(HashSet<Vector2s>? candidates = null) { IEnumerable<Vector2s> source = GeneratedZones.Invoke(ZoneSystem.instance); if (candidates != null) { source = source.Where(candidates.Contains); } return source.OrderBy((Vector2s zone) => (long)zone.x * (long)zone.x + (long)zone.y * (long)zone.y).ToArray(); } public static HashSet<Vector2s> GeneratedSetSnapshot() { return new HashSet<Vector2s>(GeneratedZones.Invoke(ZoneSystem.instance)); } public static bool IsGenerated(Vector2s zone) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) return GeneratedZones.Invoke(ZoneSystem.instance).Contains(zone); } public static bool HasPendingZoneLoad(Vector2s zone) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) return LoadingObjects.Invoke(ZoneSystem.instance).ContainsKey(zone); } public static List<ZDO> GetZDOs(Vector2s zone) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) List<ZDO> list = new List<ZDO>(); ZDOMan.instance.FindSectorObjects(zone, new SimulationDistance(0, 0, false), list, (List<ZDO>)null); list.RemoveAll((ZDO zdo) => zdo == null || !zdo.IsValid() || ZoneSystem.GetZone(zdo.GetPosition()) != zone); return list; } public static IEnumerable<ZDO> AllZDOs() { return ObjectsById.Invoke(ZDOMan.instance).Values.ToArray(); } internal static bool HasRemainingPrefabNearZone(Vector2s zone, int prefab, float border) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unkn