using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
namespace XFEEDToolBox.Services;
/// <summary>Bounded recovery before the station's departure deadline, shared by station journeys and trade.</summary>
internal static class WindowsFlightRecovery
{
public static async Task RecoverAsync(int pid,string hash,Func<PlayerState> player,string executable,string package,
Func<Task> releaseProtection,Action<string> progress,CancellationToken token)
{
var expected=player();var lifecycle=new WindowsGameSessionDriver(player,()=>executable);
var target=await lifecycle.FindTargetAsync(token);
if(target.ProcessId!=pid||hash!=NativeRelogChannel.SupportedGameSha256||expected.CommanderId.Length==0||expected.CurrentShipId is null
||!expected.CargoReliable||NavigationSafety.UnhealthyHull(expected)||!NativeRelogWorkflow.SupportsMode(expected.GameMode))
throw new InvalidOperationException("离场恢复的实例、身份、货舱或健康状态未确认。");
await lifecycle.ValidateLaunchAsync(target,token);
var began=System.Diagnostics.Stopwatch.StartNew();var totalTimeout=TimeSpan.FromMinutes(8);
var channel=new NativeSessionRecoveryChannel(pid,hash,Path.Combine(package,"DockingControl"));
var workflow=new NativeRelogWorkflow(channel,player){Timeout=totalTimeout,MenuExitTimeout=TimeSpan.FromSeconds(45),OnReachedMainMenu=releaseProtection};
async Task<NativeSessionFrame> ObserveActualMenuAsync()
{
using var read=CancellationTokenSource.CreateLinkedTokenSource(token);read.CancelAfter(TimeSpan.FromSeconds(1));
return await channel.ReadOriginalMenuStateAsync(read.Token).ConfigureAwait(false);
}
async Task RetainAndFinishEntryAsync(NativeSessionFrame actual)
{
progress("已确认游戏处于原始菜单或载入阶段,保留客户端并等待原模式完成载入。");
if(actual.State=="MainMenu"&&actual.Stable)
{
await releaseProtection();token.ThrowIfCancellationRequested();
var remaining=totalTimeout-began.Elapsed;
if(remaining<=TimeSpan.Zero)throw new TimeoutException("原模式载入总期限已到,当前客户端已保留。");
var entry=new NativeSessionEntryWorkflow(new NativeRelogChannel(pid,hash,entryOnly:true),player,expected.GameMode,expected.CommanderId){Timeout=remaining};
entry.Progress+=progress;await entry.RunAsync(token).ConfigureAwait(false);return;
}
while(began.Elapsed<totalTimeout)
{
token.ThrowIfCancellationRequested();var current=player();
if(current.InGame&¤t.LoadGameAt>expected.LoadGameAt)return;
await Task.Delay(100,token).ConfigureAwait(false);
}
throw new TimeoutException("原模式载入总期限已到,当前客户端已保留。");
}
try
{
workflow.Progress+=progress;
progress("离场超时,正在空间站期限前通过原始菜单恢复原航程。");
await workflow.RunAsync(token);
}
catch(Exception error)when(!workflow.ReachedMainMenu&&NativeMenuExitSafety.CanFallback(error,token))
{
var current=player();
if(current.CommanderId!=expected.CommanderId||current.CurrentShipId!=expected.CurrentShipId||current.CargoCount!=expected.CargoCount
||!current.CargoReliable||current.GameMode!=expected.GameMode||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current))throw;
// The original Begin can reach a safe menu at the deadline edge.
// Its independently verified current state outranks an outer timeout.
var actual=await ObserveActualMenuAsync().ConfigureAwait(false);
if(!NativeMenuExitSafety.Fresh(actual,DateTimeOffset.UtcNow))throw;
if(NativeMenuExitSafety.MenuOrLoading(actual.State)||NativeMenuExitSafety.MenuOrLoading(actual.NextState)
||current.InGame&¤t.LoadGameAt>expected.LoadGameAt)
await RetainAndFinishEntryAsync(actual).ConfigureAwait(false);
else
{
if(!NativeMenuExitSafety.CanExitClient(expected,current,actual,DateTimeOffset.UtcNow))throw;
NativeFlightTestProtection.CheckBeforeInput(pid);
// No further flight or trade request is outstanding. Do not leave
// an unattended ship inside the five-minute station exclusion zone.
progress("原始菜单退出未完成,正在保护退出客户端并保留原订单。");
await releaseProtection();token.ThrowIfCancellationRequested();
actual=await ObserveActualMenuAsync().ConfigureAwait(false);current=player();
if(!NativeMenuExitSafety.Fresh(actual,DateTimeOffset.UtcNow))throw;
if(NativeMenuExitSafety.MenuOrLoading(actual.State)||NativeMenuExitSafety.MenuOrLoading(actual.NextState)
||current.InGame&¤t.LoadGameAt>expected.LoadGameAt)
await RetainAndFinishEntryAsync(actual).ConfigureAwait(false);
else
{
if(!NativeMenuExitSafety.CanExitClient(expected,current,actual,DateTimeOffset.UtcNow))throw;
await lifecycle.TerminateAsync(target,token);
var until=DateTimeOffset.UtcNow.AddSeconds(15);
while(!await lifecycle.HasExitedAsync(target,token))
{if(DateTimeOffset.UtcNow>=until)throw new TimeoutException("保护退出未确认,未重复启动。");await Task.Delay(100,token);}
var bootstrap=new WindowsTradingBootstrap(player,executable,package){ResumeExistingCargo=true};
bootstrap.Progress+=progress;await bootstrap.UpgradeAndEnterAsync(token);
}
}
}
var restored=player();
if(restored.CommanderId!=expected.CommanderId||restored.CurrentShipId!=expected.CurrentShipId||restored.CargoCount!=expected.CargoCount
||!restored.CargoReliable||restored.GameMode!=expected.GameMode||NavigationSafety.UnhealthyHull(restored))
throw new InvalidDataException("离场恢复后的身份、货物或健康状态不一致。");
}
}
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
namespace XFEEDToolBox.Services;
/// <summary>Bounded recovery before the station's departure deadline, shared by station journeys and trade.</summary>
internal static class WindowsFlightRecovery
{
public static async Task RecoverAsync(int pid,string hash,Func<PlayerState> player,string executable,string package,
Func<Task> releaseProtection,Action<string> progress,CancellationToken token)
{
var expected=player();var lifecycle=new WindowsGameSessionDriver(player,()=>executable);
var target=await lifecycle.FindTargetAsync(token);
if(target.ProcessId!=pid||hash!=NativeRelogChannel.SupportedGameSha256||expected.CommanderId.Length==0||expected.CurrentShipId is null
||!expected.CargoReliable||NavigationSafety.UnhealthyHull(expected)||!NativeRelogWorkflow.SupportsMode(expected.GameMode))
throw new InvalidOperationException("离场恢复的实例、身份、货舱或健康状态未确认。");
await lifecycle.ValidateLaunchAsync(target,token);
var began=System.Diagnostics.Stopwatch.StartNew();var totalTimeout=TimeSpan.FromMinutes(8);
var channel=new NativeSessionRecoveryChannel(pid,hash,Path.Combine(package,"DockingControl"));
var workflow=new NativeRelogWorkflow(channel,player){Timeout=totalTimeout,MenuExitTimeout=TimeSpan.FromSeconds(45),OnReachedMainMenu=releaseProtection};
async Task<NativeSessionFrame> ObserveActualMenuAsync()
{
using var read=CancellationTokenSource.CreateLinkedTokenSource(token);read.CancelAfter(TimeSpan.FromSeconds(1));
return await channel.ReadOriginalMenuStateAsync(read.Token).ConfigureAwait(false);
}
async Task RetainAndFinishEntryAsync(NativeSessionFrame actual)
{
progress("已确认游戏处于原始菜单或载入阶段,保留客户端并等待原模式完成载入。");
if(actual.State=="MainMenu"&&actual.Stable)
{
await releaseProtection();token.ThrowIfCancellationRequested();
var remaining=totalTimeout-began.Elapsed;
if(remaining<=TimeSpan.Zero)throw new TimeoutException("原模式载入总期限已到,当前客户端已保留。");
var entry=new NativeSessionEntryWorkflow(new NativeRelogChannel(pid,hash,entryOnly:true),player,expected.GameMode,expected.CommanderId){Timeout=remaining};
entry.Progress+=progress;await entry.RunAsync(token).ConfigureAwait(false);return;
}
while(began.Elapsed<totalTimeout)
{
token.ThrowIfCancellationRequested();var current=player();
if(current.InGame&¤t.LoadGameAt>expected.LoadGameAt)return;
await Task.Delay(100,token).ConfigureAwait(false);
}
throw new TimeoutException("原模式载入总期限已到,当前客户端已保留。");
}
try
{
workflow.Progress+=progress;
progress("离场超时,正在空间站期限前通过原始菜单恢复原航程。");
await workflow.RunAsync(token);
}
catch(Exception error)when(!workflow.ReachedMainMenu&&NativeMenuExitSafety.CanFallback(error,token))
{
var current=player();
if(current.CommanderId!=expected.CommanderId||current.CurrentShipId!=expected.CurrentShipId||current.CargoCount!=expected.CargoCount
||!current.CargoReliable||current.GameMode!=expected.GameMode||NavigationSafety.UnhealthyHull(current)||NavigationSafety.HasExternalThreat(current))throw;
// The original Begin can reach a safe menu at the deadline edge.
// Its independently verified current state outranks an outer timeout.
var actual=await ObserveActualMenuAsync().ConfigureAwait(false);
if(!NativeMenuExitSafety.Fresh(actual,DateTimeOffset.UtcNow))throw;
if(NativeMenuExitSafety.MenuOrLoading(actual.State)||NativeMenuExitSafety.MenuOrLoading(actual.NextState)
||current.InGame&¤t.LoadGameAt>expected.LoadGameAt)
await RetainAndFinishEntryAsync(actual).ConfigureAwait(false);
else
{
if(!NativeMenuExitSafety.CanExitClient(expected,current,actual,DateTimeOffset.UtcNow))throw;
NativeFlightTestProtection.CheckBeforeInput(pid);
// No further flight or trade request is outstanding. Do not leave
// an unattended ship inside the five-minute station exclusion zone.
progress("原始菜单退出未完成,正在保护退出客户端并保留原订单。");
await releaseProtection();token.ThrowIfCancellationRequested();
actual=await ObserveActualMenuAsync().ConfigureAwait(false);current=player();
if(!NativeMenuExitSafety.Fresh(actual,DateTimeOffset.UtcNow))throw;
if(NativeMenuExitSafety.MenuOrLoading(actual.State)||NativeMenuExitSafety.MenuOrLoading(actual.NextState)
||current.InGame&¤t.LoadGameAt>expected.LoadGameAt)
await RetainAndFinishEntryAsync(actual).ConfigureAwait(false);
else
{
if(!NativeMenuExitSafety.CanExitClient(expected,current,actual,DateTimeOffset.UtcNow))throw;
await lifecycle.TerminateAsync(target,token);
var until=DateTimeOffset.UtcNow.AddSeconds(15);
while(!await lifecycle.HasExitedAsync(target,token))
{if(DateTimeOffset.UtcNow>=until)throw new TimeoutException("保护退出未确认,未重复启动。");await Task.Delay(100,token);}
var bootstrap=new WindowsTradingBootstrap(player,executable,package){ResumeExistingCargo=true};
bootstrap.Progress+=progress;await bootstrap.UpgradeAndEnterAsync(token);
}
}
}
var restored=player();
if(restored.CommanderId!=expected.CommanderId||restored.CurrentShipId!=expected.CurrentShipId||restored.CargoCount!=expected.CargoCount
||!restored.CargoReliable||restored.GameMode!=expected.GameMode||NavigationSafety.UnhealthyHull(restored))
throw new InvalidDataException("离场恢复后的身份、货物或健康状态不一致。");
}
}