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;} } }