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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;

namespace XFEEDToolBox.Core.Session;

/// <summary>Enters the selected original mode; never edits account or menu availability fields.</summary>
public sealed class NativeSessionEntryWorkflow(INativeRelogChannel channel,Func<PlayerState> player,string mode,string commander)
{
    public TimeSpan Timeout{get;init;}=TimeSpan.FromMinutes(8);
    public TimeSpan PollInterval{get;init;}=TimeSpan.FromMilliseconds(60);
    public TimeSpan ConnectionFailureConfirmation{get;init;}=TimeSpan.FromSeconds(2);
    public DateTimeOffset? ProcessStartedAt{get;init;}
    public long? ExpectedShipId{get;init;}
    public event Action<string>? Progress;
    public async Task RunAsync(CancellationToken token)
    {
        if(!NativeRelogWorkflow.SupportsMode(mode))throw new NotSupportedException("自动恢复只支持已接入的单人或公开模式。");
        var owner=Guid.NewGuid().ToString("N");var watch=Stopwatch.StartNew();string? request=null;var initial=player().LoadGameAt;
        var attempted=false;string? lastState=null;
        var failed=new NativeConnectionFailureObserver{ConfirmationPeriod=ConnectionFailureConfirmation};
        void RequireIdentity()
        {
            token.ThrowIfCancellationRequested();var current=player();
            if(commander.Length>0&&current.CommanderId.Length>0&&current.CommanderId!=commander
                ||ExpectedShipId is {} ship&&current.CurrentShipId is {} actual&&actual!=ship
                ||current.GameMode.Length>0&&current.GameMode!=mode&&current.LoadGameAt is {} loaded
                    &&(ProcessStartedAt is {} started?loaded>=started:initial is null||loaded>initial))
                throw new InvalidDataException("原始载入期间指挥官或飞船身份改变,未自动重启。");
        }
        try
        {
            attempted=true;var begun=false;
            Progress?.Invoke("正在等待游戏原始主菜单就绪");
            while(watch.Elapsed<Timeout)
            {
                token.ThrowIfCancellationRequested();
                RequireIdentity();
                if(channel is INativeSessionEntryStatus status)
                {
                    NativeSessionFrame? observed=null;
                    try{observed=await status.ReadEntryStatusAsync(token);}
                    catch(Exception error)when(!token.IsCancellationRequested&&!NavigationRecovery.HasInvalidDataCause(error)&&error is IOException or OperationCanceledException){}
                    RequireIdentity();
                    if(observed is not null&&failed.Observe(observed,DateTimeOffset.UtcNow))throw new NativeSessionConnectionFailedException(observed);
                    RequireIdentity();var current=player();
                    if(observed is not null&&NativeSessionStartupReadiness.Ready(current,observed,DateTimeOffset.UtcNow,ProcessStartedAt)
                        &&current.GameMode==mode&&current.CommanderId.Length>0&&(ProcessStartedAt is not null||initial is null||current.LoadGameAt>initial))return;
                }
                try{await channel.BeginAsync(owner,token).ConfigureAwait(false);begun=true;break;}
                catch(InvalidOperationException error)when(error.Message.StartsWith("Wait for an idle game and stop automatic flight before relogging",StringComparison.Ordinal)){}
                catch(Exception error)when(!token.IsCancellationRequested&&!NavigationRecovery.HasInvalidDataCause(error)&&error is IOException or OperationCanceledException){}
                await Task.Delay(PollInterval,token).ConfigureAwait(false);
            }
            if(!begun)throw new TimeoutException("游戏原始主菜单未就绪,当前客户端已保留。");
            while(watch.Elapsed<Timeout)
            {
                token.ThrowIfCancellationRequested();NativeSessionFrame frame;
                try{frame=await channel.ReadAsync(owner,token).ConfigureAwait(false);}
                catch(Exception error)when(!token.IsCancellationRequested&&!NavigationRecovery.HasInvalidDataCause(error)&&error is IOException or OperationCanceledException){await Task.Delay(PollInterval,token);continue;}
                RequireIdentity();var age=DateTimeOffset.UtcNow-frame.ObservedAt;
                if(!frame.Available||frame.Sequence<=0||age>TimeSpan.FromMilliseconds(350)||age<TimeSpan.FromMilliseconds(-100)){await Task.Delay(PollInterval,token);continue;}
                if(failed.Observe(frame,DateTimeOffset.UtcNow))throw new NativeSessionConnectionFailedException(frame);
                if(frame.Stable&&frame.State=="FailedToConnect"){await Task.Delay(PollInterval,token);continue;}
                if(frame.State!=lastState){lastState=frame.State;Progress?.Invoke("正在进入游戏:"+frame.State);}
                var current=player();
                if(NativeSessionStartupReadiness.Ready(current,frame,DateTimeOffset.UtcNow,ProcessStartedAt)&&current.LoadGameAt is {} loaded&&(ProcessStartedAt is not null||initial is null||loaded>initial)
                    &&current.GameMode==mode&&current.CommanderId.Length>0)
                {
                    if(commander.Length>0&&current.CommanderId!=commander)throw new InvalidOperationException("重新进入的指挥官与原会话不同。");
                    return;
                }
                if(request is not null)
                {
                    if(frame.LastRequestId==request&&(frame.ActionApplied||frame.ActionFailed))
                    {if(frame.ActionFailed)throw new IOException("原始游戏模式选择失败,未重放选择。");request=null;}
                    else{await Task.Delay(PollInterval,token);continue;}
                }
                var action=frame.Stable?frame.State switch{"MainMenu"=>"ContinueMenu","ContinueMenu"=>mode=="Solo"?"SetupPlaySolo":"SetupPlayWithEveryone",_=>null}:null;
                if(action is not null&&frame.Actions.Count(a=>a.Enabled&&a.Name==action)==1)
                {
                    request=Guid.NewGuid().ToString("N");
                    try{await channel.SubmitAsync(owner,request,frame,action,token).ConfigureAwait(false);}
                    catch(NativeSessionStateChangedException){request=null;}
                    catch(IOException error)when(!NavigationRecovery.HasInvalidDataCause(error)){}catch(OperationCanceledException)when(!token.IsCancellationRequested){}
                }
                await Task.Delay(PollInterval,token).ConfigureAwait(false);
            }
            throw new TimeoutException("游戏原始载入未完成,当前客户端与日志已保留。");
        }
        finally{if(attempted)await channel.EndAsync(owner).ConfigureAwait(false);}
    }
}