XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Plugins;
using XFEEDToolBox.Core.Session;
using XFEEDToolBox.NativeSupport;
using XFEEDToolBox.Services;

/// <summary>Explicitly requested recovery of the current non-playing client; never trades or flies.</summary>
internal static class RecoverCurrentSession
{
    private sealed record Stage(string Name,DateTimeOffset At,string? Detail=null);
    private sealed record StationarySample(string Window,DateTimeOffset At,int ProcessId,long ProcessStartedUtcTicks,long Sequence,long ObservedAtMilliseconds,
        string EntityId,string ReferenceWorld,long JournalShipId,double Hull,double? Shield,double Speed,bool OriginalEmergency,JsonElement Raw);

    internal static async Task RunAsync(string executable,string report,bool updateStationaryShip=false,bool updateDockedShip=false)
    {
        executable=Path.GetFullPath(executable);report=Path.GetFullPath(report);
        if(File.Exists(report)||File.Exists(report+".entry.json"))throw new IOException("Use a new current-session recovery report.");
        Directory.CreateDirectory(Path.GetDirectoryName(report)!);
        var stages=new List<Stage>();
        GameSessionTarget? initialTarget=null,replacement=null;
        PlayerState? initialPlayer=null,finalPlayer=null;
        JsonElement? initialNativeStatus=null,beforeTerminationStatus=null,finalNativeStatus=null;
        JsonElement? initialInventory=null,beforeTerminationInventory=null,finalInventory=null;
        var stationarySamples=new List<StationarySample>();
        PluginPackage? bundle=null;PluginStatus? previousPlugin=null,installedPlugin=null;
        PluginHandshake? finalPlugin=null;
        string? packageDirectory=null;Exception? failure=null;
        var terminated=false;var oldProcessExited=false;var installed=false;var entered=false;var passed=false;
        bool? cargoUnchanged=null;
        bool? walletUnchanged=null;
        DockedPluginMaintenanceProof? maintenanceProof=null;DockedMaintenanceJournalWatch? maintenanceWatch=null;
        object? Snapshot(PlayerState? value)=>value is null?null:new
        {
            value.CommanderId,value.GameMode,value.LoadGameAt,value.CurrentShipId,value.Ship,
            value.CargoCount,value.CargoReliable,value.CargoAt,value.Inventory,value.InGame,
            value.Docked,value.SystemName,value.StationName,value.MarketId,value.CargoCapacity,value.Credits,value.CreditsReliable
        };
        async Task Save(string phase)
        {
            var temporary=report+".tmp";
            await File.WriteAllTextAsync(temporary,JsonSerializer.Serialize(new
            {
                schema=1,at=DateTimeOffset.UtcNow,phase,passed,executable,packageDirectory,
                initialTarget,replacement,initial=Snapshot(initialPlayer),final=Snapshot(finalPlayer),
                initialNativeStatus,beforeTerminationStatus,finalNativeStatus,previousPlugin,installedPlugin,
                package=bundle,finalPlugin,terminated,oldProcessExited,installed,entered,cargoUnchanged,walletUnchanged,
                entryReport=report+".entry.json",stages,failure=failure?.ToString(),updateDockedShip,updateStationaryShip,
                dockedMaintenanceSamples=maintenanceProof?.Samples,stationarySamples,initialInventory,beforeTerminationInventory,finalInventory,legacyEmergencyCleared=false,
                flightInputsSent=0,tradeOrdersSent=0,screenUse=false,osInputsSent=0
            },ToolboxSettings.JsonOptions));
            File.Move(temporary,report,true);
        }
        async Task Record(string phase,string? detail=null)
        {stages.Add(new(phase,DateTimeOffset.UtcNow,detail));await Save(phase);Console.WriteLine("恢复当前会话:"+phase);}
        static async Task<string> Hash(string path,CancellationToken token)
        {
            await using var stream=new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.ReadWrite|FileShare.Delete);
            return Convert.ToHexStringLower(await SHA256.HashDataAsync(stream,token));
        }
        static void VerifyNative(JsonElement hello,JsonElement status,GameSessionTarget target,string hash,bool expectInGame)
        {
            var now=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            if(hello.GetProperty("processId").GetInt32()!=target.ProcessId||hello.GetProperty("gameSha256").GetString()!=hash
                ||status.GetProperty("schema").GetInt32()!=1||status.GetProperty("name").GetString()!="XFEEDToolBox.Session"
                ||status.GetProperty("processId").GetInt32()!=target.ProcessId||status.GetProperty("gameSha256").GetString()!=hash
                ||status.GetProperty("sequence").GetInt64()<=0)
                throw new InvalidDataException("Original session identity is not the selected game process.");
            var observed=status.GetProperty("observedAtUnixMilliseconds").GetInt64();
            var state=status.GetProperty("state").GetString();var next=status.GetProperty("nextState").GetString();
            if(now-observed>350||observed>now+100||string.IsNullOrEmpty(state)||string.IsNullOrEmpty(next))
                throw new IOException("Original session state is unavailable or stale; recovery did not infer the current screen.");
            if(expectInGame)
            {
                if(!hello.GetProperty("inGame").GetBoolean()||state!="InGame"||next!="InGame")
                    throw new IOException("The replacement client has not confirmed a stable original InGame state.");
            }
            else if(hello.GetProperty("inGame").GetBoolean()||state=="InGame"||next=="InGame")
                throw new InvalidOperationException("The client has reached a playing state; current-session recovery was not restarted.");
        }
        try
        {
            await Record("requested");
            if(updateDockedShip&&updateStationaryShip)throw new ArgumentException("Use exactly one independently verified maintenance mode.");
            using var timeout=new CancellationTokenSource(TimeSpan.FromMinutes(10));var token=timeout.Token;
            var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
            await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync(token);
            if(updateDockedShip)maintenanceProof=new();
            if(updateDockedShip||updateStationaryShip)maintenanceWatch=new(journal);
            initialPlayer=journal.State;
            if(!NativeRelogWorkflow.SupportsMode(initialPlayer.GameMode)||string.IsNullOrWhiteSpace(initialPlayer.CommanderId)||initialPlayer.LoadGameAt is null)
                throw new InvalidOperationException("A previously observed commander and Solo/Open session are required for original mode restoration.");
            var driver=new WindowsGameSessionDriver(()=>journal.State,()=>executable);
            initialTarget=await driver.FindLaunchTargetAsync(token);
            if(initialTarget.ProcessId<=0)throw new InvalidOperationException("There is no unique current game process to recover.");
            await driver.ValidateRestartAsync(initialTarget,token);
            var gameHash=await Hash(executable,token);
            if(gameHash!=NativeRelogChannel.SupportedGameSha256)throw new InvalidDataException("The current game build has no verified original session interface.");
            var pipe=new NavigationPipeClient(initialTarget.ProcessId);
            async Task<JsonElement> ReadInventoryAsync(NavigationPipeClient currentPipe,PlayerState current)
            {
                var raw=await currentPipe.CallAsync(new{op="read"},token);
                var inventory=await currentPipe.CallAsync(new{op="commerce.inventory"},token);
                var after=await currentPipe.CallAsync(new{op="read"},token);
                if(!inventory.GetProperty("verified").GetBoolean()||inventory.GetProperty("entityId").GetString()!=raw.GetProperty("entityId").GetString()
                    ||after.GetProperty("entityId").GetString()!=raw.GetProperty("entityId").GetString()
                    ||after.GetProperty("referenceWorldAddress").GetString()!=raw.GetProperty("referenceWorldAddress").GetString()
                    ||!current.CargoReliable||inventory.GetProperty("count").GetInt32()!=current.CargoCount||inventory.GetProperty("capacity").GetInt32()!=current.CargoCapacity
                    ||!inventory.GetProperty("credits").TryGetInt64(out var credits)||credits<0)
                    throw new InvalidDataException("Stationary maintenance cannot verify the actual main-ship cargo and wallet.");
                return inventory.Clone();
            }
            async Task VerifyStationaryWindowAsync(string window,bool requireStableWindow=true)
            {
                if(maintenanceWatch is null)throw new InvalidOperationException("Stationary maintenance was not explicitly requested.");
                await VerifyStationaryShip(pipe,initialTarget,initialPlayer,()=>journal.State,stationarySamples,
                    driver.FindLaunchTargetAsync,maintenanceWatch.RequireUnchanged,window,token,requireStableWindow);
            }
            async Task VerifyDockedSampleAsync()
            {
                if(maintenanceProof is null||maintenanceWatch is null)throw new InvalidOperationException("Docked maintenance proof was not explicitly requested.");
                    token.ThrowIfCancellationRequested();maintenanceWatch.RequireUnchanged();
                    if(await driver.FindLaunchTargetAsync(token)!=initialTarget)throw new IOException("Docked maintenance process identity changed.");
                    var currentHello=await pipe.CallAsync(new{op="describe"},token);
                    var session=await GameSessionAcceptance.StatusAsync(initialTarget.ProcessId,token);
                    VerifyNative(currentHello,session,initialTarget,gameHash,expectInGame:true);
                    var before=await pipe.CallAsync(new{op="read"},token);var current=journal.State;
                    if(current.MarketId is not >0 or >=uint.MaxValue)throw new InvalidOperationException("Docked maintenance requires the actual current station market.");
                    var docked=await pipe.CallAsync(new{op="commerce.docked.status",marketId=(uint)current.MarketId.Value},token);
                    var raw=await pipe.CallAsync(new{op="read"},token);
                    if(before.GetProperty("entityId").GetString()!=raw.GetProperty("entityId").GetString()
                        ||before.GetProperty("referenceWorldAddress").GetString()!=raw.GetProperty("referenceWorldAddress").GetString()
                        ||before.GetProperty("sequence").GetInt64()>raw.GetProperty("sequence").GetInt64()
                        ||before.GetProperty("observedAtUnixMilliseconds").GetInt64()>raw.GetProperty("observedAtUnixMilliseconds").GetInt64())
                        throw new IOException("The main ship changed around the original docking read.");
                    maintenanceWatch.RequireUnchanged();
                    maintenanceProof.Accept(journal.State,session,raw,docked,DateTimeOffset.UtcNow);
            }
            async Task VerifyDockedWindowAsync(bool initial)
            {
                var window=Stopwatch.StartNew();var beforeCount=maintenanceProof!.Samples.Count;
                do
                {
                    await VerifyDockedSampleAsync();
                    if(initial?maintenanceProof.Ready:maintenanceProof.Samples.Count-beforeCount>=3&&window.Elapsed>=TimeSpan.FromMilliseconds(250))break;
                    if(window.Elapsed>TimeSpan.FromSeconds(5))throw new TimeoutException("No bounded sequence of fresh stationary docked samples was confirmed.");
                    await Task.Delay(120,token);
                }while(true);
                await Record(initial?"independent-docked-maintenance-baseline-verified":"independent-docked-maintenance-rechecked",
                    "Original emergency values retained; no native health flag, flight input, menu or trade state was changed.");
            }
            var hello=await pipe.CallAsync(new{op="describe"},token);
            initialNativeStatus=await GameSessionAcceptance.StatusAsync(initialTarget.ProcessId,token);
            var initialPlaying=(updateStationaryShip||updateDockedShip)&&initialNativeStatus.Value.GetProperty("state").GetString()=="InGame"&&initialNativeStatus.Value.GetProperty("nextState").GetString()=="InGame";
            if(updateDockedShip&&!initialPlaying)throw new InvalidOperationException("Docked maintenance requires stable InGame; Pause/ExitOptions cannot use non-playing recovery.");
            if(updateStationaryShip&&!initialPlaying)throw new InvalidOperationException("Stationary maintenance requires stable InGame; Pause/ExitOptions cannot use non-playing recovery.");
            VerifyNative(hello,initialNativeStatus.Value,initialTarget,gameHash,expectInGame:initialPlaying);
            if(updateDockedShip)await VerifyDockedWindowAsync(initial:true);
            else if(initialPlaying)
            {
                initialInventory=await ReadInventoryAsync(pipe,initialPlayer);
                await VerifyStationaryWindowAsync("initial");
            }
            await Record(updateDockedShip?"current-process-and-docked-maintenance-verified":initialPlaying?"current-process-and-stationary-maintenance-verified":"current-process-and-nonplaying-session-verified");

            packageDirectory=NativeSupportPackage.Directory;
            var manager=new GamePluginManager(packageDirectory);bundle=manager.Package();
            previousPlugin=await manager.InspectAsync(executable,token);
            if(!previousPlugin.Installed||previousPlugin.PendingRemoval||previousPlugin.Installation is null)
                throw new InvalidOperationException("Recovery updates only the existing toolbox-owned active plugin installation.");
            if(bundle.SupportedGameSha256!=gameHash||await Hash(Path.Combine(packageDirectory,bundle.Loader),token)!=bundle.Sha256)
                throw new InvalidDataException("The embedded replacement package failed validation; the game was left running.");
            await Record("embedded-package-and-owned-installation-verified");

            if(await driver.FindLaunchTargetAsync(token)!=initialTarget)throw new IOException("The current game process changed before recovery.");
            await driver.ValidateRestartAsync(initialTarget,token);
            hello=await pipe.CallAsync(new{op="describe"},token);
            beforeTerminationStatus=await GameSessionAcceptance.StatusAsync(initialTarget.ProcessId,token);
            var playingBefore=(updateStationaryShip||updateDockedShip)&&beforeTerminationStatus.Value.GetProperty("state").GetString()=="InGame"&&beforeTerminationStatus.Value.GetProperty("nextState").GetString()=="InGame";
            if(updateDockedShip&&!playingBefore)throw new InvalidOperationException("Docked maintenance cannot fall back to a non-playing termination path.");
            if(updateStationaryShip&&!playingBefore)throw new InvalidOperationException("Stationary maintenance cannot fall back to a non-playing termination path.");
            VerifyNative(hello,beforeTerminationStatus.Value,initialTarget,gameHash,expectInGame:playingBefore);
            if(updateDockedShip)await VerifyDockedWindowAsync(initial:false);
            else if(playingBefore)
            {
                beforeTerminationInventory=await ReadInventoryAsync(pipe,journal.State);
                if(initialInventory is null||!SameInventory(initialInventory.Value,beforeTerminationInventory.Value))
                    throw new IOException("The actual cargo or wallet changed before stationary maintenance termination.");
                await VerifyStationaryWindowAsync("before-termination");
            }
            await Record("before-verified-process-termination");
            if(updateDockedShip)
            {
                // Disk serialization must not create an unchecked gap before
                // termination. Obtain one last different original frame.
                await Task.Delay(30,token);await VerifyDockedSampleAsync();
            }
            else if(playingBefore)
            {
                await VerifyStationaryWindowAsync("final-before-termination",requireStableWindow:false);
                beforeTerminationInventory=await ReadInventoryAsync(pipe,journal.State);
                if(initialInventory is null||!SameInventory(initialInventory.Value,beforeTerminationInventory.Value))
                    throw new IOException("Actual cargo or wallet changed immediately before stationary maintenance termination.");
            }
            maintenanceWatch?.RequireUnchanged();
            await driver.TerminateAsync(initialTarget,token);terminated=true;
            await Record("termination-requested");
            var exitWatch=Stopwatch.StartNew();
            while(!await driver.HasExitedAsync(initialTarget,token))
            {
                if(exitWatch.Elapsed>TimeSpan.FromSeconds(20))throw new TimeoutException("The verified previous client did not exit.");
                await Task.Delay(100,token);
            }
            oldProcessExited=true;await Record("previous-process-exit-confirmed");
            installedPlugin=await manager.InstallAsync(executable,token);
            if(!installedPlugin.Installed||installedPlugin.PendingRemoval||installedPlugin.Installation?.LoaderSha256!=bundle.Sha256)
                throw new InvalidDataException("The replacement plugin installation has not matched its embedded package.");
            installed=true;await Record("embedded-plugin-installed");

            await Record("official-launch-and-original-mode-entry-requested",initialPlayer.GameMode);
            await GameSessionAcceptance.StartAsync(executable,report+".entry.json");
            entered=true;
            replacement=await driver.FindLaunchTargetAsync(token);
            if(replacement.ProcessId<=0||replacement.StartedUtcTicks<=initialTarget.StartedUtcTicks
                ||!string.Equals(replacement.Executable,initialTarget.Executable,StringComparison.OrdinalIgnoreCase))
                throw new IOException("A new same-installation game process has not been confirmed.");
            await driver.ValidateRestartAsync(replacement,token);
            var replacementPipe=new NavigationPipeClient(replacement.ProcessId);
            hello=await replacementPipe.CallAsync(new{op="describe"},token);
            finalNativeStatus=await GameSessionAcceptance.StatusAsync(replacement.ProcessId,token);
            VerifyNative(hello,finalNativeStatus.Value,replacement,gameHash,expectInGame:true);
            finalPlugin=await GamePluginManager.ProbeAsync(replacement.ProcessId,token:token);
            if(!finalPlugin.Loaded||!finalPlugin.Active||!finalPlugin.ObservationStarted||finalPlugin.Version!=bundle.Version)
                throw new IOException("The new client has not confirmed the installed plugin and original observation service.");
            var journalUntil=DateTimeOffset.UtcNow.AddSeconds(5);
            do
            {
                finalPlayer=journal.State;
                if(finalPlayer.InGame&&finalPlayer.LoadGameAt>initialPlayer.LoadGameAt&&(!(updateDockedShip||updateStationaryShip)
                    ||finalPlayer.CargoReliable&&finalPlayer.CreditsReliable&&(!updateDockedShip||finalPlayer.Docked&&finalPlayer.MarketId==initialPlayer.MarketId)))break;
                await Task.Delay(100,token);
            }while(DateTimeOffset.UtcNow<journalUntil);
            if(finalPlayer is null||!finalPlayer.InGame||finalPlayer.LoadGameAt<=initialPlayer.LoadGameAt
                ||finalPlayer.CommanderId!=initialPlayer.CommanderId||finalPlayer.GameMode!=initialPlayer.GameMode
                ||initialPlayer.CurrentShipId is {} ship&&finalPlayer.CurrentShipId!=ship)
                throw new IOException("The new LoadGame has not confirmed the same commander, ship and original mode.");
            static InventoryItem[] Cargo(PlayerState value)=>value.Inventory.OrderBy(row=>row.Key,StringComparer.Ordinal).ThenBy(row=>row.MissionId).ThenBy(row=>row.Stolen).ToArray();
            if(initialPlayer.CargoReliable&&finalPlayer.CargoReliable)
                cargoUnchanged=initialPlayer.CargoCount==finalPlayer.CargoCount&&Cargo(initialPlayer).SequenceEqual(Cargo(finalPlayer));
            if(cargoUnchanged==false)throw new IOException("The session re-entered, but cargo differs from the recorded starting snapshot; no trade or cargo operation was submitted.");
            if(updateDockedShip&&(!finalPlayer.CargoReliable||cargoUnchanged!=true||!finalPlayer.Docked||finalPlayer.MarketId!=initialPlayer.MarketId
                ||finalPlayer.SystemName!=initialPlayer.SystemName||finalPlayer.StationName!=initialPlayer.StationName
                ||finalPlayer.CargoCapacity!=initialPlayer.CargoCapacity||finalPlayer.Credits!=initialPlayer.Credits))
                throw new IOException("The docked maintenance return has not confirmed unchanged station, cargo, ship capacity and recorded wallet.");
            if(updateStationaryShip)
            {
                finalInventory=await ReadInventoryAsync(replacementPipe,finalPlayer);
                walletUnchanged=initialInventory is {} original&&finalInventory.Value.GetProperty("credits").GetInt64()==original.GetProperty("credits").GetInt64();
                if(initialInventory is null||walletUnchanged!=true||cargoUnchanged!=true||!SameInventory(initialInventory.Value,finalInventory.Value)
                    ||finalPlayer.CargoCapacity!=initialPlayer.CargoCapacity||finalPlayer.Docked!=initialPlayer.Docked
                    ||finalPlayer.SystemName!=initialPlayer.SystemName||initialPlayer.Docked&&finalPlayer.MarketId!=initialPlayer.MarketId)
                    throw new IOException("The stationary maintenance return has not confirmed unchanged actual cargo, wallet, ship capacity and location.");
                using var returnedWatch=new DockedMaintenanceJournalWatch(journal);
                await VerifyStationaryShip(replacementPipe,replacement,finalPlayer,()=>journal.State,stationarySamples,
                    driver.FindLaunchTargetAsync,returnedWatch.RequireUnchanged,"after-restart",token);
            }
            passed=true;await Record("same-commander-original-mode-and-plugin-confirmed");
        }
        catch(Exception error){failure=error;}
        finally{maintenanceWatch?.Dispose();await Save(failure is null?"finished":"failed");}
        Console.WriteLine(JsonSerializer.Serialize(new{passed,report,terminated,installed,entered,cargoUnchanged,replacement,failure=failure?.Message},ToolboxSettings.JsonOptions));
        if(failure is not null)throw new InvalidOperationException("Current-session recovery did not fully verify completion; inspect "+report,failure);
    }

    private static bool SameInventory(JsonElement before,JsonElement after)
        =>before.GetProperty("verified").GetBoolean()&&after.GetProperty("verified").GetBoolean()
            &&before.GetProperty("count").GetInt32()==after.GetProperty("count").GetInt32()
            &&before.GetProperty("capacity").GetInt32()==after.GetProperty("capacity").GetInt32()
            &&before.GetProperty("credits").GetInt64()==after.GetProperty("credits").GetInt64();
    private static async Task VerifyStationaryShip(NavigationPipeClient pipe,GameSessionTarget target,PlayerState baseline,Func<PlayerState> player,
        List<StationarySample> samples,Func<CancellationToken,Task<GameSessionTarget>> findTarget,Action requireJournalUnchanged,string window,
        CancellationToken token,bool requireStableWindow=true)
    {
        static InventoryItem[] Cargo(PlayerState value)=>value.Inventory.OrderBy(row=>row.Key,StringComparer.Ordinal).ThenBy(row=>row.MissionId).ThenBy(row=>row.Stolen).ToArray();
        var clock=Stopwatch.StartNew();var start=samples.Count;long? windowAt=null;
        do
        {
            token.ThrowIfCancellationRequested();requireJournalUnchanged();
            if(await findTarget(token)!=target)throw new IOException("The verified stationary maintenance process changed.");
            var current=player();
            if(!baseline.InGame||baseline.CurrentShipId is null||baseline.LoadGameAt is null||!baseline.CargoReliable||!baseline.CreditsReliable
                ||!current.InGame||current.CommanderId!=baseline.CommanderId||current.CurrentShipId!=baseline.CurrentShipId||current.LoadGameAt!=baseline.LoadGameAt
                ||current.GameMode!=baseline.GameMode||current.CargoCapacity!=baseline.CargoCapacity||!current.CargoReliable||current.CargoCount!=baseline.CargoCount
                ||!Cargo(current).SequenceEqual(Cargo(baseline))||!current.CreditsReliable||current.Credits!=baseline.Credits
                ||current.Docked!=baseline.Docked||current.SystemName!=baseline.SystemName||current.MarketId!=baseline.MarketId)
                throw new InvalidOperationException("The stationary main ship, session, cargo, wallet or recorded location changed.");
            var raw=await pipe.CallAsync(new{op="read"},token);var health=raw.GetProperty("shipHealth");
            var observed=raw.GetProperty("observedAtUnixMilliseconds").GetInt64();var now=DateTimeOffset.UtcNow;
            var sequence=raw.GetProperty("sequence").GetInt64();var entity=raw.GetProperty("entityId").GetString()??"";var world=raw.GetProperty("referenceWorldAddress").GetString()??"";
            var hull=health.GetProperty("hullFraction").GetDouble();var speed=raw.GetProperty("speedMetresPerSecond").GetDouble();
            double? shield=health.TryGetProperty("shieldFraction",out var shieldValue)&&shieldValue.ValueKind==JsonValueKind.Number?shieldValue.GetDouble():null;
            if(!raw.GetProperty("inGame").GetBoolean()||!raw.GetProperty("localPlayerShipVerified").GetBoolean()||raw.GetProperty("playerOverride").GetBoolean()
                ||raw.GetProperty("controlApplied").GetBoolean()||raw.GetProperty("fsd").GetProperty("ownedCharging").GetBoolean()
                ||!ulong.TryParse(entity,out var entityId)||entityId==0||!ulong.TryParse(world,out var worldId)||worldId==0||sequence<=0
                ||!health.GetProperty("verified").GetBoolean()||health.GetProperty("entityId").GetString()!=entity||health.GetProperty("sequence").GetInt64()!=sequence
                ||health.GetProperty("observedAtUnixMilliseconds").GetInt64()!=observed||health.GetProperty("damageObserved").GetBoolean()
                ||!double.IsFinite(hull)||hull is <=0 or >1||shield is {} s&&(!double.IsFinite(s)||s is <0 or >1)
                ||!double.IsFinite(speed)||speed is <0 or >1||observed>now.ToUnixTimeMilliseconds()+100||now.ToUnixTimeMilliseconds()-observed>300)
                throw new InvalidOperationException("Authorized maintenance requires fresh stationary main-ship samples without new damage, control or owned FSD charging.");
            // Keep the old emergency bit as evidence. It grants no input or
            // reset authority and does not override these independent samples.
            var emergency=health.GetProperty("emergency").GetBoolean();
            var previous=samples.LastOrDefault(sample=>sample.ProcessId==target.ProcessId&&sample.ProcessStartedUtcTicks==target.StartedUtcTicks);
            if(previous is not null&&(entity!=previous.EntityId||world!=previous.ReferenceWorld||sequence<previous.Sequence||observed<previous.ObservedAtMilliseconds
                ||hull+.000001<previous.Hull||previous.Shield is {} oldShield&&shield is {} nextShield&&nextShield+.000001<oldShield
                ||samples.Count>start&&observed-previous.ObservedAtMilliseconds>500))
                throw new InvalidOperationException("The stationary ship identity, health or continuous observation changed.");
            if(previous is not null&&sequence==previous.Sequence)
            {
                if(observed!=previous.ObservedAtMilliseconds||hull!=previous.Hull||shield!=previous.Shield)
                    throw new InvalidDataException("The original stationary health changed without a new sample identity.");
                if(clock.Elapsed>TimeSpan.FromSeconds(6))throw new TimeoutException("No newer stationary main-ship sample was observed.");
                await Task.Delay(120,token);continue;
            }
            samples.Add(new(window,now,target.ProcessId,target.StartedUtcTicks,sequence,observed,entity,world,current.CurrentShipId.Value,hull,shield,speed,emergency,raw.Clone()));
            windowAt??=observed;requireJournalUnchanged();
            if(!requireStableWindow||samples.Count-start>=12&&observed-windowAt.Value>=3000&&clock.Elapsed>=TimeSpan.FromSeconds(3))return;
            if(clock.Elapsed>TimeSpan.FromSeconds(6))throw new TimeoutException("A continuous three-second stationary maintenance window was not established.");
            await Task.Delay(120,token);
        }while(true);
    }
}