using System.Buffers.Binary;
namespace XFEEDToolBox.Core.Session;
public sealed record StartupCrashEvidence(string Path,int ProcessId,uint ExceptionCode,DateTimeOffset At);
public sealed class StartupClientExitedException(int processId):InvalidOperationException($"新启动的游戏进程 {processId} 在完成载入前结束。");
/// Only an exception dump of this exact newly launched client authorizes another launch.
public static class StartupCrashRecovery
{
private static bool ProtectedFailure(Exception error)=>error is InvalidDataException or NotSupportedException or Navigation.FlightSafetyPauseException
||error is AggregateException aggregate&&aggregate.InnerExceptions.Any(ProtectedFailure)
||error.InnerException is {} inner&&ProtectedFailure(inner);
public static async Task RunAsync(Func,CancellationToken,Task> attempt,
Func> exited,Func evidence,
Func? recovering,CancellationToken token,int maximumRecoveries=2)
{
if(maximumRecoveries is <0 or >3)throw new ArgumentOutOfRangeException(nameof(maximumRecoveries));
for(var retries=0;;retries++)
{
GameSessionTarget? owned=null;
try{await attempt(target=>owned=target,token).ConfigureAwait(false);return;}
catch(Exception error)when(!token.IsCancellationRequested&&!ProtectedFailure(error)&&retries header=stackalloc byte[32];stream.ReadExactly(header);
static uint U32(ReadOnlySpan value,int offset)=>BinaryPrimitives.ReadUInt32LittleEndian(value[offset..]);
if(U32(header,0)!=0x504d444d||target.ProcessId<=0||target.StartedUtcTicks<=0)return null;
var count=U32(header,8);var table=U32(header,12);var at=DateTimeOffset.FromUnixTimeSeconds(U32(header,20));
var began=new DateTimeOffset(target.StartedUtcTicks,TimeSpan.Zero);
if(count is 0 or >128||atnow.AddSeconds(5)||now-at>TimeSpan.FromMinutes(5)||table>stream.Length-count*12L)return null;
uint pid=0,exception=0;Span entry=stackalloc byte[12];Span prefix=stackalloc byte[12];
for(var i=0;istream.Length-size)return null;
stream.Position=offset;stream.ReadExactly(prefix);
if(kind==6)exception=U32(prefix,8);
else if((U32(prefix,4)&1)!=0)pid=U32(prefix,8);
}
return pid==target.ProcessId&&exception!=0?new(Path.GetFullPath(path),(int)pid,exception,at):null;
}
catch(Exception error)when(error is IOException or UnauthorizedAccessException or ArgumentException){return null;}
}
}