using System.Diagnostics; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Navigation; namespace XFEEDToolBox.Core.Session; /// Enters the selected original mode; never edits account or menu availability fields. public sealed class NativeSessionEntryWorkflow(INativeRelogChannel channel,Func 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? 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&¤t.CommanderId.Length>0&¤t.CommanderId!=commander ||ExpectedShipId is {} ship&¤t.CurrentShipId is {} actual&&actual!=ship ||current.GameMode.Length>0&¤t.GameMode!=mode&¤t.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.Elapsed0&&(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.ElapsedTimeSpan.FromMilliseconds(350)||ageinitial) &¤t.GameMode==mode&¤t.CommanderId.Length>0) { if(commander.Length>0&¤t.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);} } }