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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text.Json;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Views;

namespace XFEEDToolBox;

public partial class App
{
    // Explicit developer acceptance command. Exercises the same viewmodel commands as the page;
    // no screen capture, OS input, fabricated Journal events, or automatic invocation at normal launch.
    private static async Task RunNavigationAcceptanceAsync(string[] args,string reportPath)
    {
        if(File.Exists(reportPath)||File.Exists(reportPath+".jsonl"))throw new IOException("Use a new navigation acceptance report path.");
        var destination=GetArgument(args,"--accept-destination")??throw new ArgumentException("An explicit acceptance destination is required.");
        var reserve=double.Parse(GetArgument(args,"--accept-reserve")??"0.5",System.Globalization.CultureInfo.InvariantCulture);
        var start=DateTimeOffset.UtcNow;var passed=false;string? failure=null;long? expectedSystem=null;var ready=false;var enabled=false;var frames=0;var peak=0d;
        var gameProcessId=0;DateTime? processStart=null;var gate=new object();
        await using var log=new StreamWriter(new FileStream(reportPath+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
        void Log(object value){lock(gate){log.WriteLine(JsonSerializer.Serialize(value,new JsonSerializerOptions{PropertyNamingPolicy=JsonNamingPolicy.CamelCase}));log.Flush();}}
        void Progress(NavigationProgress progress)=>Log(new{kind="application-progress",at=DateTimeOffset.UtcNow,progress});
        void Inputs(GameInputState state)=>Log(new{kind="application-inputs",at=DateTimeOffset.UtcNow,state,statusFlags=Runtime.Player.Status?.Flags});
        void ChargeInput(NativeChargeRequest request)=>Log(new{kind="application-charge-input",at=DateTimeOffset.UtcNow,request});
        void Observation(FlightObservation observation,FlightDecision? decision)
        {frames++;peak=Math.Max(peak,observation.Frame.HeatPercent??0);var player=Runtime.Player;
            var raw=(Runtime.FlightDriver as NativeFlightControlDriver)?.LastRawTelemetry;
            int? Snapshot()=>raw is {} r&&r.TryGetProperty("rawFsdState",out var value)?value.GetInt32():null;
            Log(new{kind="application-frame",at=DateTimeOffset.UtcNow,frame=observation.Frame,decision,applied=decision is null?null:(Runtime.FlightDriver as NativeFlightControlDriver)?.LastAppliedControl,
                snapshotFsdState=Snapshot(),
                journalContext=new{player.InGame,player.Docked,player.InDanger,player.StatusAt,statusFlags=player.Status?.Flags}});}
        NativeFlightControlDriver? native=null;
        XFEEDToolBox.Core.Session.NativeFlightTestProtection? protection=null;
        Runtime.Navigator.Progress+=Progress;
        try
        {
            ViewModel.DemoMode=false;ViewModel.NavigationDestination=destination;ViewModel.NavigationFuelReserve=reserve;
            AppShellPage.Current!.Navigate(typeof(NavigationPage));
            await ViewModel.PlanNavigationCommand.ExecuteAsync(null);await ViewModel.CheckNavigationCommand.ExecuteAsync(null);await Task.Delay(100);
            ready=ViewModel.PluginNavigationReady;enabled=ViewModel.StartNavigationCommand.CanExecute(null);
            expectedSystem=ViewModel.NavigationLegs.LastOrDefault()?.Record.SystemAddress;
            native=Runtime.FlightDriver as NativeFlightControlDriver;
            if(!ready||!enabled||expectedSystem is null||native is null)throw new InvalidOperationException("Application navigation command is not ready: "+ViewModel.NavigationStatus+" / "+ViewModel.NavigationNativeStatus);
            gameProcessId=native.ProcessId;using(var process=System.Diagnostics.Process.GetProcessById(gameProcessId))processStart=process.StartTime.ToUniversalTime();
            var hello=await new NavigationPipeClient(gameProcessId).CallAsync(new{op="describe"});
            protection=await XFEEDToolBox.Core.Session.NativeFlightTestProtection.ArmAsync(gameProcessId,hello.GetProperty("gameSha256").GetString()!,
                new Services.WindowsGameSessionDriver(()=>Runtime.Player,()=>Runtime.Settings.PluginGameExecutable),()=>Runtime.Player,Runtime.Navigator.Stop);
            Runtime.Journal.JournalEvent+=protection.ObserveJournal;
            native.Observed+=Observation;
            native.GameInputsObserved+=Inputs;
            native.OriginalChargeRequested+=ChargeInput;
            Log(new{kind="application-preflight",start,destination,reserve,expectedSystem,ready,enabled,gameProcessId,processStart,Runtime.DataDirectory,initialSystem=Runtime.Player.Navigation.CurrentSystemAddress});
            using var timer=new Timer(_=>{if(File.Exists(reportPath+".stop")||DateTimeOffset.UtcNow-start>TimeSpan.FromMinutes(45))Runtime.Navigator.Stop();},null,100,100);
            await ViewModel.StartNavigationCommand.ExecuteAsync(null);
            using(var process=System.Diagnostics.Process.GetProcessById(gameProcessId))if(process.StartTime.ToUniversalTime()!=processStart)throw new InvalidOperationException("Game process changed during application acceptance.");
            passed=Runtime.Navigator.State==NavigationRunState.Completed&&Runtime.Player.Navigation.CurrentSystemAddress==expectedSystem
                &&Runtime.Player.Navigation.LastJumpAt>=DateTimeOffset.FromUnixTimeSeconds(start.ToUnixTimeSeconds())&&frames>0;
            if(!passed)throw new InvalidOperationException("Application navigation did not reach the expected system: "+ViewModel.NavigationStatus);
        }
        catch(Exception error){failure=error.ToString();passed=false;}
        finally
        {
            Runtime.Navigator.Progress-=Progress;
            if(protection is not null){Runtime.Journal.JournalEvent-=protection.ObserveJournal;await protection.DisposeAsync();}
            if(native is not null){native.Observed-=Observation;native.GameInputsObserved-=Inputs;native.OriginalChargeRequested-=ChargeInput;try{await native.ReleaseAsync();}catch(Exception error){failure=(failure??"")+" Release: "+error.Message;passed=false;}}
        }
        await File.WriteAllTextAsync(reportPath,JsonSerializer.Serialize(new{start,completedAt=DateTimeOffset.UtcNow,destination,reserve,expectedSystem,gameProcessId,processStart,ready,enabled,frames,peak,
            actualSystem=Runtime.Player.Navigation.CurrentSystemAddress,actualArrivalAt=Runtime.Player.Navigation.LastJumpAt,navigationState=Runtime.Navigator.State,passed,failure,
            viewModelStartCommandUsed=true,realJournalUsed=true,screenUse=false,osInputSent=false,journalWrites=0,navRouteWrites=0},new JsonSerializerOptions{WriteIndented=true}));
        MainWindow.Close();
    }
}