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.Models;
using XFEEDToolBox.Core.Navigation;

internal static class SteeringCalibration
{
    public static async Task<bool> RunAsync(int pid,string profilePath,string reportPath)
    {
        if(File.Exists(reportPath)||File.Exists(reportPath+".jsonl"))throw new IOException("Use a new evidence path.");
        using var profile=JsonDocument.Parse(await File.ReadAllTextAsync(profilePath));
        var path=profile.RootElement.GetProperty("image").GetString()!;var hash=profile.RootElement.GetProperty("sha256").GetString();
        await using(var file=File.OpenRead(path))if(Convert.ToHexStringLower(await SHA256.HashDataAsync(file))!=hash)throw new InvalidDataException("Game build changed.");
        using var game=Process.GetProcessById(pid);var started=game.StartTime.ToUniversalTime().Ticks;
        if(!string.Equals(game.MainModule?.FileName,path,StringComparison.OrdinalIgnoreCase))throw new InvalidDataException("Wrong selected game.");
        var pipe=new NavigationPipeClient(pid);var owner=Guid.NewGuid().ToString("N");var clock=Stopwatch.StartNew();
        await using var log=new StreamWriter(new FileStream(reportPath+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
        Vector3d Vector(JsonElement row,string name){var a=row.GetProperty(name);return new(a[0].GetDouble(),a[1].GetDouble(),a[2].GetDouble());}
        var reachedOffset=false;var returned=false;var released=false;var maximumDisplacement=0.0;var maximumHeat=0.0;var frames=0;
        double? stableSince=null;string? failure=null;
        try
        {
            var initial=await pipe.CallAsync(new{op="read"});
            var original=Vector(initial,"forward");var position=Vector(initial,"position");
            var target=(original+Vector(initial,"right")*.07+Vector(initial,"up")*.08).Unit();
            using var status=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(new ToolboxSettings().JournalDirectory,"Status.json")));
            var flags=status.RootElement.GetProperty("Flags").GetUInt64();
            if((flags&(1UL|16UL|1048576UL|2097152UL))!=0||status.RootElement.GetProperty("GuiFocus").GetInt32()!=0)throw new InvalidOperationException("Steering calibration requires undocked normal space without danger or UI focus.");
            while(clock.Elapsed.TotalSeconds<65)
            {
                using var currentGame=Process.GetProcessById(pid);
                if(currentGame.StartTime.ToUniversalTime().Ticks!=started)throw new InvalidOperationException("Process identity changed.");
                var frame=await pipe.CallAsync(new{op="read"});
                if(!frame.GetProperty("inGame").GetBoolean()||frame.GetProperty("playerOverride").GetBoolean()||frame.GetProperty("rawFsdState").GetInt32()!=2)throw new InvalidOperationException("Active flight/FSD state changed.");
                maximumHeat=Math.Max(maximumHeat,frame.GetProperty("heatPercent").GetDouble());
                maximumDisplacement=Math.Max(maximumDisplacement,(Vector(frame,"position")-position).Length);
                if(frame.GetProperty("speedMetresPerSecond").GetDouble()>=1||maximumDisplacement>1||maximumHeat>=60)throw new InvalidOperationException("Steering calibration motion/heat bounds exceeded.");
                var input=AttitudeController.Aim(Vector(frame,"forward"),Vector(frame,"up"),reachedOffset?original:target);
                var result=await pipe.CallAsync(new{op="calibrate.axes",owner,sequence=frame.GetProperty("sequence").GetInt64(),ttlMilliseconds=180,
                    axes=new[]{0.0,0,0,input.Yaw,input.Pitch,input.Roll}});
                await log.WriteLineAsync(JsonSerializer.Serialize(new{kind="sample",seconds=clock.Elapsed.TotalSeconds,phase=reachedOffset?"return":"offset",input,frame,result}));await log.FlushAsync();frames++;
                if(input.ErrorDegrees<.25)stableSince??=clock.Elapsed.TotalSeconds;else stableSince=null;
                if(stableSince is {} stable&&clock.Elapsed.TotalSeconds-stable>=.5)
                {
                    if(reachedOffset){returned=true;break;}
                    reachedOffset=true;stableSince=null;
                }
                await Task.Delay(50);
            }
            if(!returned)throw new TimeoutException("Closed-loop aiming did not complete both targets.");
        }
        catch(Exception error){failure=error.ToString();}
        finally
        {
            try{var result=await pipe.CallAsync(new{op="release",owner});released=result.GetProperty("released").GetBoolean();}
            catch(Exception error){failure=(failure??"")+" Release: "+error.Message;}
        }
        var passed=reachedOffset&&returned&&released&&failure is null;
        await File.WriteAllTextAsync(reportPath,JsonSerializer.Serialize(new{pid,passed,frames,reachedOffset,returned,released,maximumHeat,maximumDisplacement,failure,screenUse=false,fsdInputsSent=0,completeAutomaticNavigationAccepted=false},new JsonSerializerOptions{WriteIndented=true}));
        Console.WriteLine(JsonSerializer.Serialize(new{pid,passed,frames,reachedOffset,returned,released,maximumHeat,maximumDisplacement,failure}));return passed;
    }
}