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

namespace XFEEDToolBox.Core.Navigation;

public sealed record FlightConnectionBinding(int ProcessId,long StartedUtcTicks,string GameSha256,string CommanderId,long ShipId,string Mode,DateTimeOffset SessionStartedAt,int CargoCount);
/// <summary>A positive original-menu connection failure, not a request to retry flight inputs or replay commerce.</summary>
public sealed class FlightConnectionRecoveryRequiredException(FlightConnectionBinding binding,JsonElement menu,JsonElement navigation,Exception original)
    :InvalidOperationException("原版飞行会话已持续确认连接失败且无实时驾驶场景;保留原订单,等待官方连接恢复。",original)
{
    public FlightConnectionBinding Binding{get;}=binding;
    public JsonElement Menu{get;}=menu.Clone();
    public JsonElement Navigation{get;}=navigation.Clone();
    public static FlightConnectionRecoveryRequiredException? Find(Exception error)
    {
        if(FlightJourneyFailure.HasProtectedCause(error)||FlightConnectionRecovery.HasCancellationCause(error))return null;
        if(error is FlightConnectionRecoveryRequiredException confirmed)return confirmed;
        if(error is AggregateException aggregate)
        {
            // A distinct cancelled, ownership or other unknown cleanup cause
            // cannot be discarded just because another branch has this proof.
            if(aggregate.InnerExceptions.Any(inner=>Find(inner) is null
                &&(inner is OperationCanceledException||!FlightJourneyFailure.HasTransientCause(inner,default))))return null;
            var matches=aggregate.InnerExceptions.Select(Find).Where(value=>value is not null).ToArray();
            return matches.Length==1?matches[0]:null;
        }
        return error.InnerException is {} inner?Find(inner):null;
    }
}
public static class FlightConnectionRecovery
{
    internal static bool HasCancellationCause(Exception error)=>error is OperationCanceledException
        ||error is AggregateException aggregate&&aggregate.InnerExceptions.Any(HasCancellationCause)
        ||error.InnerException is {} inner&&HasCancellationCause(inner);
    public static void RequireCurrentIdentity(FlightConnectionBinding binding,PlayerState current,CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        if(current.CommanderId!=binding.CommanderId||current.CurrentShipId!=binding.ShipId||current.GameMode!=binding.Mode
            ||current.LoadGameAt!=binding.SessionStartedAt||current.CargoCount!=binding.CargoCount)
            throw new InvalidDataException("原飞行会话的身份、模式或实际货物已改变,未自动恢复连接。");
        if(NavigationSafety.UnhealthyHull(current))throw new FlightHealthPauseException();
        if(NavigationSafety.HasExternalThreat(current))throw new FlightThreatPauseException();
    }
    public static void RequireOriginalClient(FlightConnectionBinding binding,GameSessionTarget current,string configuredExecutable)
    {
        if(current.ProcessId!=binding.ProcessId||current.StartedUtcTicks!=binding.StartedUtcTicks
            ||!string.Equals(Path.GetFullPath(current.Executable),Path.GetFullPath(configuredExecutable),StringComparison.OrdinalIgnoreCase))
            throw new InvalidDataException("已确认连接失败的游戏实例或安装目录已改变,未接管其他客户端。");
    }
    public static async Task<Exception> ClassifyAsync(Exception failure,FlightConnectionBinding binding,
        Func<CancellationToken,Task<(JsonElement Menu,JsonElement Navigation)>> read,Func<CancellationToken,Task> verify,CancellationToken token,
        TimeSpan? confirmation=null,TimeSpan? timeout=null,TimeSpan? pollInterval=null)
    {
        token.ThrowIfCancellationRequested();
        if(HasCancellationCause(failure)||FlightConnectionRecoveryRequiredException.Find(failure) is not null||!FlightJourneyFailure.HasTransientCause(failure,token))return failure;
        if(binding.ProcessId<=0||binding.StartedUtcTicks<=0||binding.GameSha256.Length!=64||binding.CommanderId.Length==0
            ||binding.ShipId<0||binding.CargoCount<0||binding.SessionStartedAt==default||!NativeRelogWorkflow.SupportsMode(binding.Mode))throw new InvalidDataException("连接故障缺少原飞行实例或身份绑定,未授权重启。");
        using var budget=CancellationTokenSource.CreateLinkedTokenSource(token);budget.CancelAfter(timeout??TimeSpan.FromSeconds(4));
        var observer=new NativeConnectionFailureObserver{ConfirmationPeriod=confirmation??TimeSpan.FromSeconds(2)};
        try
        {
            while(true)
            {
                budget.Token.ThrowIfCancellationRequested();await verify(budget.Token);budget.Token.ThrowIfCancellationRequested();
                var observed=await read(budget.Token);await verify(budget.Token);budget.Token.ThrowIfCancellationRequested();
                var now=DateTimeOffset.UtcNow;
                if(!NativeSessionStartupReadiness.FailedWithoutLiveFlight(observed.Menu,observed.Navigation,binding.ProcessId,binding.GameSha256,now))return failure;
                if(observer.Observe(NativeRelogChannel.Parse(observed.Menu),now))
                    return new FlightConnectionRecoveryRequiredException(binding,observed.Menu,observed.Navigation,failure);
                await Task.Delay(pollInterval??TimeSpan.FromMilliseconds(100),budget.Token);
            }
        }
        catch(OperationCanceledException)when(!token.IsCancellationRequested&&budget.IsCancellationRequested){return failure;}
        catch(Exception error)when(!token.IsCancellationRequested&&NavigationRecovery.CanRetry(error,token)){return failure;}
    }
}