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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

internal static class SessionRecoveryAcceptance
{
    private const string Service="SessionRecovery0514k";
    private sealed record Attempt(string RequestId,string Action,ulong MenuSequence,DateTimeOffset At,string Outcome,JsonElement? Receipt=null);
    internal static async Task ProbeBridgeAsync(int pid,string report)
    {
        if(File.Exists(report))throw new IOException("Use a fresh read-only bridge report.");
        using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(2));
        var value=await new NavigationPipeClient(pid,Service).CallAsync(new{op="docking.reset.probe"},timeout.Token);
        await File.WriteAllTextAsync(report,JsonSerializer.Serialize(value,ToolboxSettings.JsonOptions));
        Console.WriteLine(JsonSerializer.Serialize(new{pid,service=Service,bridgePassed=value.GetProperty("bridgePassed").GetBoolean(),stage=value.GetProperty("stage").GetString(),report},ToolboxSettings.JsonOptions));
        if(!value.GetProperty("bridgePassed").GetBoolean())throw new IOException("Read-only original navigation bridge failed; no input was sent.");
    }
    internal static async Task InspectAsync(int pid,string service,string report,bool deactivate)
    {
        if(service is not("SessionRecovery0514" or "SessionRecovery0514c" or "SessionRecovery0514d" or "SessionRecovery0514e" or "SessionRecovery0514f" or "SessionRecovery0514g" or "SessionRecovery0514h" or "SessionRecovery0514i" or "SessionRecovery0514j" or Service))throw new ArgumentException("Expected a known diagnostic extension.");
        if(File.Exists(report))throw new IOException("Use a fresh extension report.");
        using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var result=await new NavigationPipeClient(pid,service).CallAsync(new{op=deactivate?"deactivate":"read"},timeout.Token);
        await File.WriteAllTextAsync(report,JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,pid,service,deactivate,result},ToolboxSettings.JsonOptions));
        Console.WriteLine(result);
    }
    internal static async Task RunAsync(int pid,string package,string report,bool leave,string? protectionWorkRoot=null)
    {
        package=Path.GetFullPath(package);report=Path.GetFullPath(report);
        if(File.Exists(report))throw new IOException("Use a fresh recovery report.");
        Directory.CreateDirectory(Path.GetDirectoryName(report)!);
        using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(90));var token=timeout.Token;
        using var process=Process.GetProcessById(pid);var started=process.StartTime.ToUniversalTime().Ticks;
        var nav=new NavigationPipeClient(pid);var pipe=new NavigationPipeClient(pid,Service);
        JsonElement? initial=null,current=null,status=null;var masks=Array.Empty<uint>();var resetMasks=Array.Empty<uint>();var telemetryMasks=Array.Empty<uint>();var startMasks=Array.Empty<uint>();var contextMasks=Array.Empty<uint>();
        var attempts=new List<Attempt>();var loaded=false;var reached=false;Exception? failure=null;
        async Task Save(string phase)
        {
            var temp=report+".tmp";
            await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(new{pid,started,at=DateTimeOffset.UtcNow,phase,loaded,fixtureMasks=masks,resetFixtureMasks=resetMasks,resetTelemetryFixtureMasks=telemetryMasks,dockingStartFixtureMasks=startMasks,flightContextFixtureMasks=contextMasks,
                initial,current,status,attempts,actionSent=attempts.Count>0,actualMainMenu=reached,passed=leave?reached:(bool?)null,
                error=failure?.ToString(),clientTerminated=false,gameAxesWritten=false,osInputSent=false,authFieldsWritten=false},ToolboxSettings.JsonOptions));
            File.Move(temp,report,true);
        }
        async Task<JsonElement> Read()
        {
            process.Refresh();if(process.HasExited||process.StartTime.ToUniversalTime().Ticks!=started)throw new IOException("Game identity changed.");
            current=await pipe.CallAsync(new{op="read"},token);return current.Value;
        }
        async Task<JsonElement> WaitReady()
        {
            var until=DateTimeOffset.UtcNow.AddSeconds(12);
            do
            {
                var value=await Read();
                if(value.GetProperty("menuReady").GetBoolean())return value;
                await Task.Delay(25,token);
            }while(DateTimeOffset.UtcNow<until);
            throw new IOException("Original recovery menu is not ready: "+current?.GetProperty("menuReason").GetString());
        }
        try
        {
            var hello=await nav.CallAsync(new{op="describe"},token);
            if(hello.GetProperty("gameSha256").GetString()!=NativeRelogChannel.SupportedGameSha256||hello.GetProperty("processId").GetInt32()!=pid)
                throw new InvalidDataException("Expected the existing pinned game instance.");
            // AOT fixture libraries remain loaded until this tool process exits.
            var fixture=NativeLibrary.Load(Path.Combine(package,"XFEEDInteraction.dll"));
            var selfTest=Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(fixture,"XFESessionRecoverySelfTest"));
            var start=Marshal.GetDelegateForFunctionPointer<Start>(NativeLibrary.GetExport(fixture,"XFEInteractionStart"));
            masks=Enumerable.Range(0,8).Select(_=>selfTest()).ToArray();
            if(masks.Any(value=>value!=255)||start(0)!=0||start(nuint.MaxValue)!=0)throw new InvalidDataException("Purpose-built recovery guard/unsupported fixture checks failed.");
            var resetTest=Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(fixture,"XFEDockingResetSelfTest"));
            resetMasks=Enumerable.Range(0,8).Select(_=>resetTest()).ToArray();
            var telemetryTest=Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(fixture,"XFEDockingResetTelemetrySelfTest"));
            telemetryMasks=Enumerable.Range(0,8).Select(_=>telemetryTest()).ToArray();
            if(resetMasks.Any(value=>value!=255)||telemetryMasks.Any(value=>value!=255))throw new InvalidDataException("Original reset guards or actual optional-interface regression failed their isolated self-tests.");
            var dockingStartTest=Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(fixture,"XFEDockingStartSelfTest"));
            startMasks=Enumerable.Range(0,8).Select(_=>dockingStartTest()).ToArray();
            if(startMasks.Any(value=>value!=255))throw new InvalidDataException("Original docking start counterexample guards failed their isolated self-tests.");
            var contextTest=Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(fixture,"XFEFlightContextSelfTest"));
            contextMasks=Enumerable.Range(0,8).Select(_=>contextTest()).ToArray();
            if(contextMasks.Any(value=>value!=511))throw new InvalidDataException("Original lowest-priority Flight context counterexample guards failed their isolated self-tests.");
            var trialTest=Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(fixture,"XFEFlightThrottleTrialSelfTest"));
            if(Enumerable.Range(0,8).Any(_=>trialTest()!=255))throw new InvalidDataException("Original persistent throttle trial counterexamples failed their isolated self-tests.");
            await RuntimeInteractionModule.LoadAsync(pid,NativeRelogChannel.SupportedGameSha256,hello.GetProperty("owningThread").GetInt32(),package,token);loaded=true;
            var observedUntil=DateTimeOffset.UtcNow.AddSeconds(3);
            do{var sample=await Read();if(sample.GetProperty("menuReady").GetBoolean())break;await Task.Delay(25,token);}while(DateTimeOffset.UtcNow<observedUntil);
            initial=current;await Save("observed");
            if(leave)
            {
                using var exclusivity=new FileStream(Path.Combine(Path.GetDirectoryName(report)!,"docking-fixture-wait.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
                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();var player=journal.State;
                if(!player.InGame||player.CurrentShipId!=5||player.CargoCount!=0||NavigationSafety.UnhealthyHull(player)||NavigationSafety.HasExternalThreat(player))
                    throw new InvalidDataException("Healthy empty diagnostic ship required before leaving through its real menu.");
                using var guardian=await PersistentProtectionProcess.StartOrReuseAsync(pid,protectionWorkRoot??Path.GetDirectoryName(report)!,token,allowStart:false);
                PersistentProtectionProcess.Current=guardian;
                try
                {
                    await using var protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);
                    // Exactly two permitted original menu steps. Persist every
                    // intent before dispatch; reconcile unknown acknowledgements
                    // by ID without submitting the operation again.
                    for(var step=0;step<2&&!reached;step++)
                    {
                        var menu=await WaitReady();
                        var actions=menu.GetProperty("actions").EnumerateArray().Where(row=>row.GetProperty("enabled").GetBoolean()&&row.GetProperty("recoveryAction").GetBoolean()).ToArray();
                        if(actions.Length!=1)throw new InvalidDataException("One exact enabled original recovery item required.");
                        var action=actions[0].GetProperty("name").GetString()!;
                        var id=Guid.NewGuid().ToString("N");var sequence=menu.GetProperty("menuSequence").GetUInt64();
                        guardian.RequireReady(pid);
                        attempts.Add(new(id,action,sequence,DateTimeOffset.UtcNow,"intent-persisted"));await Save("before-dispatch");
                        try
                        {
                            current=await pipe.CallAsync(new{op="session.recovery.leave",requestId=id,action,menuSequence=sequence,ttlMilliseconds=250},token);
                            attempts[^1]=attempts[^1] with{Outcome="acknowledged",Receipt=current};
                        }
                        catch(Exception error)
                        {
                            attempts[^1]=attempts[^1] with{Outcome="acknowledgement-unknown: "+error.Message};await Save("reconciling");
                        }
                        await Save("after-dispatch");
                        var until=DateTimeOffset.UtcNow.AddSeconds(20);var applied=false;
                        do
                        {
                            var value=await Read();status=await nav.CallAsync(new{op="session.status"},token);
                            if(status.Value.GetProperty("state").GetString()=="MainMenu"&&status.Value.GetProperty("nextState").GetString()=="MainMenu")
                            {reached=true;break;}
                            if(value.GetProperty("requestId").GetString()==id)
                            {
                                applied=value.GetProperty("applied").GetBoolean();
                                if(value.GetProperty("failed").GetBoolean())throw new IOException("Original menu request expired or failed; it will not be replayed.");
                            }
                            if(action=="ExitOptions"&&applied&&value.GetProperty("state").GetString()=="ExitOptions"
                                &&value.GetProperty("nextState").GetString()=="ExitOptions"&&value.GetProperty("menuReady").GetBoolean())break;
                            await Task.Delay(25,token);
                        }while(DateTimeOffset.UtcNow<until);
                        attempts[^1]=attempts[^1] with{Outcome=reached?"actual-main-menu":applied?"original-action-applied":"unresolved-no-replay",Receipt=current};
                        await Save("reconciled");
                        if(!reached&&(action!="ExitOptions"||!applied))throw new IOException("Original menu outcome is unresolved; no request was replayed.");
                    }
                }
                finally{PersistentProtectionProcess.Current=null;}
                if(!reached)throw new IOException("Original main menu has not been confirmed; no client was terminated.");
            }
        }
        catch(Exception error){failure=error;}
        finally{await Save(failure==null?"finished":"failed");}
        Console.WriteLine(JsonSerializer.Serialize(new{pid,loaded,actionSent=attempts.Count>0,actualMainMenu=reached,report,error=failure?.Message},ToolboxSettings.JsonOptions));
        if(failure!=null)throw new IOException("Session recovery did not complete; evidence and unresolved intents are preserved.",failure);
    }
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]private delegate uint Probe();
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]private delegate uint Start(nuint thread);
}