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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
using XFEEDToolBox.NativeSupport;

/// <summary>One sustained SCO interval on the already selected, clear SC leg. No launch, trade, target replacement or scene transition.</summary>
internal static class SCOContinuityAcceptance
{
    private const double LightSecond = 299792458;
    private const double RequestedActiveSeconds = 20;
    private static readonly JsonSerializerOptions EvidenceJson = new(ToolboxSettings.JsonOptions)
        { NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals };
    private static readonly JsonSerializerOptions EventJson = new(EvidenceJson) { WriteIndented = false };
    private static bool True(JsonElement value, string key) => value.TryGetProperty(key, out var item) && item.ValueKind == JsonValueKind.True;
    private static Vector3d Vector(JsonElement value, string key)
    { var array = value.GetProperty(key); return new(array[0].GetDouble(), array[1].GetDouble(), array[2].GetDouble()); }
    private static string Cargo(PlayerState state) => JsonSerializer.Serialize(state.Inventory
        .OrderBy(item => item.Key, StringComparer.Ordinal).ThenBy(item => item.MissionId)
        .Select(item => new { item.Key, item.Count, item.Stolen, item.MissionId, item.Group }), ToolboxSettings.JsonOptions);

    internal static async Task RunAsync(int pid, string directory)
    {
        directory = Path.GetFullPath(directory);
        if (Directory.Exists(directory)) throw new IOException("Use a fresh SCO continuity evidence directory.");
        var parent = Path.GetDirectoryName(directory)!; Directory.CreateDirectory(parent);
        using var exclusive = new FileStream(Path.Combine(parent, "docking-fixture-wait.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
        Directory.CreateDirectory(directory);
        var stopFile = Path.Combine(directory, "stop");
        using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(3));
        using var sentinel = new Timer(_ => { if (File.Exists(stopFile)) timeout.Cancel(); }, null, 100, 100);
        var token = timeout.Token;
        using var process = Process.GetProcessById(pid); var processStarted = process.StartTime.ToUniversalTime().Ticks;
        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);
        var initial = journal.State; var initialCargo = Cargo(initial); var start = DateTimeOffset.UtcNow;
        FlightTestProtectionScope? protection = null; NativeSystemFlightControlDriver? driver = null; SystemNavigationTarget? target = null;
        var previousGuardian = PersistentProtectionProcess.Current;
        string phase = "preflight", stopReason = "not-started"; string? failure = null, writerFailure = null, releaseFailure = null;
        var commands = new List<JsonElement>(); var stops = new List<JsonElement>(); var active = new SortedDictionary<long, JsonElement>();
        JsonElement? firstEnable = null, actualOff = null; SystemFlightFrame? firstFrame = null, lastFrame = null;
        JsonElement? initialInputOwnership = null, finalInputOwnership = null;
        var enableCalls = 0; var inputSubmitted = false; var zeroThrottleObserved = false; var targetPreserved = false; var overallPassed = false;
        double initialSpeed = 0, maximumSpeed = 0, maximumHeat = 0, reserve = double.NaN;
        var events = Channel.CreateUnbounded<object>(new() { SingleReader = true });
        await using var log = new StreamWriter(new FileStream(Path.Combine(directory, "frames.jsonl"), FileMode.CreateNew, FileAccess.Write, FileShare.Read));
        var writer = Task.Run(async () =>
        {
            try
            {
                await foreach (var value in events.Reader.ReadAllAsync())
                { await log.WriteLineAsync(JsonSerializer.Serialize(value, EventJson)); await log.FlushAsync(); }
            }
            catch (Exception error) { writerFailure = error.ToString(); timeout.Cancel(); }
        });
        void Record(object value)
        { if (!events.Writer.TryWrite(value)) throw new IOException("SCO evidence writer stopped."); }
        void Session()
        {
            token.ThrowIfCancellationRequested(); process.Refresh(); var state = journal.State;
            if (process.HasExited || process.StartTime.ToUniversalTime().Ticks != processStarted || !state.InGame
                || state.LoadGameAt != initial.LoadGameAt || state.CommanderId != initial.CommanderId || state.CurrentShipId != initial.CurrentShipId
                || state.Navigation.CurrentSystemAddress != initial.Navigation.CurrentSystemAddress || state.Docked
                || state.CargoCount != initial.CargoCount || state.CargoCapacity != initial.CargoCapacity || Cargo(state) != initialCargo
                || NavigationSafety.HasExternalThreat(state) || NavigationSafety.UnhealthyHull(state))
                throw new InvalidOperationException("SCO proof session, cargo, ship, system or safety state changed.");
            if (writerFailure is not null) throw new IOException(writerFailure);
        }
        Task Protection(CancellationToken cancellation)
        {
            cancellation.ThrowIfCancellationRequested(); Session();
            if (protection?.Stopped == true) throw new InvalidOperationException(protection.LastIssue ?? "Flight protection stopped.");
            NativeFlightTestProtection.CheckBeforeInput(pid); return Task.CompletedTask;
        }
        async Task<SystemFlightFrame> Read(CancellationToken cancellation)
        {
            Session(); var frame = await driver!.ReadSystemFlightAsync(cancellation); await Protection(cancellation);
            if (frame.Mode != LocalFlightMode.Supercruise || frame.PlayerOverride || frame.ExternalThreat || frame.TargetObscured || frame.CollisionCourse
                || frame.HeatPercent is not { } heat || !double.IsFinite(heat) || heat >= 90
                || frame.SupercruiseAssist is NativeAssistState.Engaged or NativeAssistState.Requested or NativeAssistState.Aligning)
            {
                Record(new { at = DateTimeOffset.UtcNow, phase, kind = "flight-guard-refused", frame, local = driver.LastTelemetry,
                    raw = await new NavigationPipeClient(pid).CallAsync(new { op = "read" }, cancellation) });
                throw new InvalidOperationException($"The existing clear, healthy SC leg is no longer available: mode={frame.Mode}, override={frame.PlayerOverride}, threat={frame.ExternalThreat}, obscured={frame.TargetObscured}, collision={frame.CollisionCourse}, heat={frame.HeatPercent}, assist={frame.SupercruiseAssist}.");
            }
            var assist = driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
            if (True(assist, "enabled") || True(assist, "engaged")) throw new InvalidDataException("SCA must remain explicitly disabled throughout the proof.");
            lastFrame = frame; maximumSpeed = Math.Max(maximumSpeed, frame.Speed); maximumHeat = Math.Max(maximumHeat, heat);
            return frame;
        }
        async Task Apply(SystemFlightFrame frame, SystemFlightDecision decision, CancellationToken cancellation)
        {
            if (DateTimeOffset.UtcNow - frame.ObservedAt > TimeSpan.FromMilliseconds(150))
                throw new FlightTelemetryUnavailableException("Proof input source is older than 150ms.");
            inputSubmitted = true;
            await driver!.ApplySystemFlightAsync(decision, frame, target!, frame.ObservedAt.AddMilliseconds(250), cancellation);
        }
        try
        {
            if (!initial.InGame || initial.Docked || initial.CurrentShipId is null || initial.GameMode != "Solo" || !initial.CargoReliable
                || initial.Navigation.CurrentSystemAddress <= 0 || NavigationSafety.HasExternalThreat(initial) || NavigationSafety.UnhealthyHull(initial)
                || TravelPreparation.HasDeployedEquipment(initial))
                throw new InvalidOperationException("Enter healthy Solo supercruise with known unchanged cargo and all flight equipment retracted before this proof.");
            using var source = new NativeCurrentSystemSource(pid, NativeSystemLocationReader.GameSha256, () => journal.State);
            var catalogue = await source.ReadLocationsAsync(token);
            var row = catalogue.Locations.SingleOrDefault(location => location.Selected)
                ?? throw new InvalidOperationException("An existing selected local target is required; this proof will not choose another one.");
            if (row.DistanceMetres is not { } distance || !double.IsFinite(distance) || distance < 50 * LightSecond
                || row.Category is not (SystemLocationCategory.Station or SystemLocationCategory.Carrier or SystemLocationCategory.Star or SystemLocationCategory.Planet or SystemLocationCategory.LandablePlanet))
                throw new InvalidOperationException("The already selected target must be a station or celestial body at least 50ls away.");
            target = new(catalogue.SystemAddress, row.WorldAddress, row.Name,
                row.Category is SystemLocationCategory.Station or SystemLocationCategory.Carrier ? SystemTargetKind.Station : SystemTargetKind.Orbit, row.MarketId)
                { NativeKey = row.Key, NativeKind = row.NativeKind, NativeIdentity = row.Identity };
            var rawPipe = new NavigationPipeClient(pid);
            var raw = await rawPipe.CallAsync(new { op = "read" }, token);
            if (!True(raw, "inGame") || raw.GetProperty("rawFsdState").GetInt32() != 2)
                throw new InvalidOperationException("This proof requires a stable existing flight scene; SC is confirmed from the bound local frame before any flight input.");
            // The generic production protection owns its continuous health monitor.
            // The older research guardian is intentionally not used: it pins ShipId 5.
            PersistentProtectionProcess.Current = null;
            protection = await FlightTestProtectionScope.ArmAsync(pid, exitForFailedHandoff: false);
            driver = new(pid, NativeSystemLocationReader.GameSha256, () => journal.State, ensureFlightProtection: Protection, useNativeSupercruiseAssist: false);
            await driver.SelectAsync(target, token); // Bind the same already-selected identity, without replacing the destination.
            firstFrame = await Read(token); initialSpeed = firstFrame.Speed;
            var preparedRaw = await rawPipe.CallAsync(new { op = "read" }, token);
            if (preparedRaw.TryGetProperty("inputOwnershipObservation", out var initialOwnership)) initialInputOwnership = initialOwnership.Clone();
            ClearRay(firstFrame, preparedRaw);
            var localAssist = driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
            if (True(localAssist, "enabled") || True(localAssist, "engaged")) throw new InvalidOperationException("Disable game SCA before verification.");
            await driver.PrepareAsync(token);
            phase = "align-existing-target-at-zero-throttle"; var alignmentUntil = DateTimeOffset.UtcNow.AddSeconds(40);
            while (true)
            {
                var frame = await Read(token); ClearRay(frame, await rawPipe.CallAsync(new { op = "read" }, token));
                var direction = (frame.TargetPosition - frame.Position).Unit();
                Record(new { at = DateTimeOffset.UtcNow, phase, frame });
                await Apply(frame, new(SystemNavigationPhase.Waiting, phase, direction, 0), token);
                if (Vector3d.AngleDegrees(frame.Forward, direction) <= 2 && Math.Abs(frame.Throttle) < .001) break;
                if (DateTimeOffset.UtcNow >= alignmentUntil) throw new TimeoutException("Existing target alignment did not settle.");
                await Task.Delay(40, token);
            }
            var prepared = new NativeCruiseControlChannel(pid, NativeSystemLocationReader.GameSha256);
            await prepared.PrepareAsync(Path.Combine(NativeSupportPackage.Directory, "CruiseControl"), token);
            var ship = initial.Ships.Single(ship => ship.ShipId == initial.CurrentShipId);
            if (ship.FuelMain is not > 8 || !double.IsFinite(ship.FuelMain.Value)) throw new InvalidDataException("A verified main-tank capacity above the required reserve is required.");
            reserve = Math.Max(8, ship.FuelMain.Value * .25);
            await driver.ConfigureOriginalCruiseControlAsync(() => reserve, token: token);
            var control = driver.CruiseControl!;
            control.Observed += value =>
            {
                Record(new { at = DateTimeOffset.UtcNow, kind = "original-sco-status", value });
                if (!True(value, "sampleReady") || !value.TryGetProperty("sample", out var sample) || sample.ValueKind != JsonValueKind.Object) return;
                if (!True(sample, "verified") || sample.GetProperty("entityId").GetString() != firstFrame.ShipEntity.ToString()
                    || sample.GetProperty("referenceWorldAddress").GetString() != firstFrame.ReferenceWorld.ToString()) return;
                var at = sample.GetProperty("observedAtUnixMilliseconds").GetInt64();
                if (firstEnable is { } enabled && sample.GetProperty("state").GetInt32() == 2 && at >= enabled.GetProperty("observedAtUnixMilliseconds").GetInt64())
                    active.TryAdd(at, sample.Clone());
                if (stops.Count > 0 && !True(value, "owned") && sample.GetProperty("state").GetInt32() is 0 or 3
                    && at > stops[^1].GetProperty("observedAtUnixMilliseconds").GetInt64()) actualOff = sample.Clone();
            };
            control.OriginalReceipt += value =>
            {
                commands.Add(value.Clone()); Record(new { at = DateTimeOffset.UtcNow, kind = "original-sco-receipt", value });
                if (True(value, "nativeMethodCalled") && True(value, "argument"))
                { enableCalls++; firstEnable ??= value.Clone(); }
            };
            control.OriginalStopReceipt += value => { stops.Add(value.Clone()); Record(new { at = DateTimeOffset.UtcNow, kind = "original-sco-stop", value }); };
            phase = "sustained-production-sco"; var activationUntil = DateTimeOffset.UtcNow.AddSeconds(10); var navigator = new SystemFlightNavigator();
            while (true)
            {
                var frame = await Read(token);
                Record(new { at = DateTimeOffset.UtcNow, phase, frame, decision = control.LastDecision, observation = control.LastObservation });
                if (enableCalls > 1) throw new InvalidDataException("A second SCO enable is forbidden in this one-interval proof.");
                if (stops.Count > 0) { stopReason = stops[^1].GetProperty("reason").GetString() ?? "native-stop"; break; }
                if (active.Count > 1 && active.Keys.Last() - active.Keys.First() >= RequestedActiveSeconds * 1000)
                { stopReason = "requested-active-duration-complete"; break; }
                if (active.Count == 0 && DateTimeOffset.UtcNow >= activationUntil) throw new TimeoutException("No real Active SCO sample was observed within ten seconds.");
                var decision = navigator.Decide(frame, target, DateTimeOffset.UtcNow);
                if (decision.Phase is not (SystemNavigationPhase.Supercruise or SystemNavigationPhase.Aligning)
                    || (decision.Actions & ~SystemNavigationAction.CancelOwnedAssists) != SystemNavigationAction.None)
                    throw new InvalidOperationException("The one-interval proof reached an approach or scene-change boundary.");
                await Apply(frame, decision, token);
                if (firstEnable is not null && control.LastDecision is { UseOvercharge: false } off)
                { stopReason = "production-policy-" + off.Reason; break; }
                await Task.Delay(40, token);
            }
        }
        catch (Exception error) { failure = error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, failure }); }
        finally
        {
            phase = "release-sco-and-zero-throttle";
            using var cleanup = new CancellationTokenSource(TimeSpan.FromSeconds(8));
            try
            {
                if (driver is not null)
                {
                    if (driver.CruiseControl is { } control) await control.ReleaseAsync(cleanup.Token);
                    if (!token.IsCancellationRequested && protection?.Stopped == false)
                    {
                        var frame = await Read(cleanup.Token);
                        await Apply(frame, new(SystemNavigationPhase.Waiting, phase, null, 0), cleanup.Token);
                        await Task.Delay(80, cleanup.Token);
                        var stopped = await Read(cleanup.Token); zeroThrottleObserved = Math.Abs(stopped.Throttle) < .001;
                    }
                    await driver.ReleaseSystemFlightAsync();
                }
                using var finalSource = new NativeCurrentSystemSource(pid, NativeSystemLocationReader.GameSha256, () => journal.State);
                var finalRaw = await new NavigationPipeClient(pid).CallAsync(new { op = "read" }, cleanup.Token);
                if (finalRaw.TryGetProperty("inputOwnershipObservation", out var finalOwnership)) finalInputOwnership = finalOwnership.Clone();
                var final = await finalSource.ReadLocationsAsync(cleanup.Token);
                var selected = final.Locations.SingleOrDefault(location => location.Selected);
                targetPreserved = target is not null && selected?.WorldAddress == target.BodyAddress && selected.NativeKind == target.NativeKind
                    && selected.Name == target.Name && selected.MarketId == target.MarketId;
            }
            catch (Exception error) { releaseFailure = error.ToString(); Record(new { at = DateTimeOffset.UtcNow, phase, releaseFailure }); }
            try { if (protection is not null) await protection.DisposeAsync(); }
            catch (Exception error) { releaseFailure ??= error.ToString(); }
            PersistentProtectionProcess.Current = previousGuardian;
            events.Writer.TryComplete(); await writer;
            var activeSeconds = active.Count > 1 ? (active.Keys.Last() - active.Keys.First()) / 1000d : 0;
            var cargoPreserved = journal.State.CargoCount == initial.CargoCount && journal.State.CargoCapacity == initial.CargoCapacity && Cargo(journal.State) == initialCargo;
            var firstActiveSpeed = active.Count > 0 ? active.First().Value.GetProperty("speedMetresPerSecond").GetDouble() : 0;
            var maximumActiveSpeed = active.Count > 0 ? active.Values.Max(sample => sample.GetProperty("speedMetresPerSecond").GetDouble()) : 0;
            var continuityVerified = activeSeconds >= 6 && maximumActiveSpeed > firstActiveSpeed;
            var originalOff = firstEnable is { } enable && stops.Any(stop => True(stop, "nativeMethodCalled") && !True(stop, "argument")
                && stop.GetProperty("nativeMethodRva").GetString() == "2eaacd0" && stop.GetProperty("owner").GetString() == enable.GetProperty("owner").GetString()
                && stop.GetProperty("startRequestId").GetString() == enable.GetProperty("requestId").GetString()
                && stop.GetProperty("selectionGeneration").GetUInt64() == enable.GetProperty("selectionGeneration").GetUInt64()
                && stop.GetProperty("entityId").GetString() == enable.GetProperty("entityId").GetString()
                && stop.GetProperty("referenceWorldAddress").GetString() == enable.GetProperty("referenceWorldAddress").GetString()
                && stop.GetProperty("owningThread").GetInt32() == enable.GetProperty("owningThread").GetInt32()
                && (active.Count == 0 || stop.GetProperty("observedAtUnixMilliseconds").GetInt64() >= active.Keys.Last())
                && stop.GetProperty("stateAfter").GetInt32() is 0 or 3);
            var passed = failure is null && releaseFailure is null && writerFailure is null && continuityVerified && enableCalls == 1
                && originalOff && actualOff is not null && cargoPreserved && targetPreserved && zeroThrottleObserved;
            overallPassed = passed;
            var report = new
            {
                passed, pid, processStarted, start, at = DateTimeOffset.UtcNow, phase, stopReason, failure, releaseFailure, writerFailure,
                initialShipId = initial.CurrentShipId, initial.Ship, initial.CargoCount, initial.CargoCapacity, initialCargo, finalCargo = Cargo(journal.State), cargoPreserved,
                target, targetPreserved, reserveFuelTonnes = reserve, requestedActiveSeconds = RequestedActiveSeconds, activeSeconds, continuityVerified,
                requestedDurationReached = activeSeconds >= RequestedActiveSeconds, activeSamples = active.Count, enableCalls, initialSpeed, maximumSpeed, firstActiveSpeed, maximumActiveSpeed, maximumHeat,
                originalOff, actualOff, zeroThrottleObserved, firstFrame, lastFrame, initialInputOwnership, finalInputOwnership, commands, stops,
                inputSubmitted, tradeOrdersSent = 0, serviceOrdersSent = 0, launchCommandsSent = 0, scEntryCommandsSent = 0, targetReplaced = false,
                gameSupercruiseAssistUsed = false, crossSystemFlight = false, clientRestarted = false
            };
            await File.WriteAllTextAsync(Path.Combine(directory, "result.json"), JsonSerializer.Serialize(report, EvidenceJson));
            Console.WriteLine(JsonSerializer.Serialize(new { passed, activeSeconds, maximumSpeed, maximumHeat, stopReason, cargoPreserved, targetPreserved, directory }));
        }
        if (!overallPassed)
            throw new InvalidOperationException("SCO continuity proof stopped; see the preserved report.");
    }

    private static void ClearRay(SystemFlightFrame frame, JsonElement raw)
    {
        var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var geometry = raw.GetProperty("geometry");
        var at = raw.GetProperty("observedAtUnixMilliseconds").GetInt64();
        if (now - at is < 0 or > 250 || !True(geometry, "fresh") || !True(geometry, "referenceFrameVerified")
            || geometry.GetProperty("worldAddress").GetString() != frame.ReferenceWorld.ToString())
            throw new InvalidDataException("A fresh matching reference frame is required for the current-target clearance check.");
        var offset = frame.Position - Vector(geometry, "position"); var direction = (frame.TargetPosition - frame.Position).Unit();
        var horizon = frame.TargetDistance - 10 * LightSecond;
        if (horizon <= 0) throw new InvalidDataException("The selected target is already inside SCO approach range.");
        var bodies = geometry.GetProperty("nearbyBodies");
        if (bodies.ValueKind != JsonValueKind.Array || bodies.GetArrayLength() == 0) throw new InvalidDataException("Original body clearance geometry is unavailable.");
        foreach (var body in bodies.EnumerateArray())
        {
            var centre = Vector(body, "centre") + offset; var radius = body.GetProperty("exclusionRadius").GetDouble();
            if (!centre.Finite || !double.IsFinite(radius) || radius <= 0) throw new InvalidDataException("Invalid original exclusion geometry.");
            var relative = centre - frame.Position; var projected = Math.Clamp(Vector3d.Dot(relative, direction), 0, horizon);
            if ((relative - direction * projected).Length <= radius * 1.2)
                throw new InvalidOperationException("The existing target ray crosses an original exclusion envelope; no alternate target will be selected.");
        }
    }
}