using System.Security.Cryptography; using System.Text; namespace XFEEDToolBox.Plugin; internal static unsafe partial class Navigation { private struct CommunityGoalRequest { internal ulong generation,entity,epoch,expires,queued,observed; internal nuint life,request; internal uint market,state,error,kind; internal ulong goal; internal bool mutationSent; } private static readonly object communityGoalLock=new(); private static CommunityGoalRequest communityGoalRequest; private static ulong communityGoalGeneration; private static string communityGoalBody=""; private static readonly byte[] communityGoalBuffer=new byte[1024*1024]; private static readonly Dictionary communityGoalKeys=new(StringComparer.Ordinal); private static string CommunityGoalProof(string body)=>Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(body))); private static bool CommunityGoalReadProofMatches(in Snapshot frame,in CommunityGoalRequest prior,string? proof,ulong generation,ulong now,string body) =>prior.kind==0&&prior.state==3&&prior.generation==generation&&prior.entity==frame.entity&&prior.life==frame.life&&prior.epoch==controllerEpoch &&now>=prior.observed&&now-prior.observed<=60000&&string.Equals(proof,CommunityGoalProof(body),StringComparison.Ordinal); private static bool FreshCommunityGoalDock(in Snapshot frame,uint market) =>Enabled()&&inGame&&frame.valid&&frame.healthValid&&!frame.healthEmergency&&frame.hullFraction>0 &&GetTickCount64()>=frame.tick&&GetTickCount64()-frame.tick<=200&&market!=0&&CommerceDocked(frame,market); private static bool StartCommunityGoalRequest(in Snapshot frame,ref CommunityGoalRequest work) { var route=GameRoute(frame,out _);var context=route!=0?route-0x50:0; if(context==0||Pointer(Pointer(context))!=image+0x541C70||Pointer(context+8)==0)return false; var endpoint=work.kind==1?(nuint)0x5247D00:work.kind==2?(nuint)0x529B270:0x52405E0; ReadOnlySpan expected=work.kind==1?"2.0/elite/initiative/optin\0"u8:work.kind==2?"2.0/elite/initiative/commander/list\0"u8:"2.0/elite/initiative/list\0"u8; if(!Bytes(image+endpoint,expected)||!Prefix(0x3433030,[0x48,0x8B,0xC4,0x48,0x89,0x58,0x10]))return false; var storage=(nuint)Call(image+0x53A690,0xF8);if(storage==0)return false; ClearServiceMemory(storage,0xF8);work.request=storage; Call(image+0x33FCC50,storage+0xA8,context); Call(image+0x99F4F0,storage,work.kind==1?3u:1u,image+endpoint); Write(storage,image+0x511F9D0); if(work.kind==1){work.mutationSent=true;Call(image+0x3433030,storage,work.goal);} else Call(image+0x340E980,storage); return true; } private static void DeleteCommunityGoalRequest(ref CommunityGoalRequest work) { var request=work.request; try{if(request!=0&&Pointer(request)==image+0x511F9D0){var destroy=Pointer(Pointer(request));if(InImage(destroy))Call(destroy,request,1);}} catch{} work.request=0; } private static void PollCommunityGoals(in Snapshot frame) { CommunityGoalRequest work;lock(communityGoalLock)work=communityGoalRequest; if(work.generation==0||work.state>=3||GetCurrentThreadId()!=owningThread)return; string? body=null; try { work.error=1; if(!FreshCommunityGoalDock(frame,work.market)||work.entity!=frame.entity||work.life!=frame.life||work.epoch!=controllerEpoch||work.expires<=GetTickCount64())work.state=4; else { if(work.request==0) { work.error=2; if(work.kind==1&&GetTickCount64()-work.queued>250||!StartCommunityGoalRequest(frame,ref work))work.state=4; else work.state=2; } if(work.state==2) { var request=work.request;work.error=0; var pending=Read(request+0x3C) is 1 or 2; if(!pending||(byte)Call(image+0x9A3250,request)!=0) { if((byte)Call(image+0x9B5480,request)==0){work.error=(uint)Call(image+0x9A8F10,request);work.state=4;} else { nuint text=0;Call(image+0x9A8DC0,request,(ulong)&text); try { var length=text!=0?Read(text):0; if(length==0||length>=(ulong)communityGoalBuffer.Length){work.error=3;work.state=4;} else { fixed(byte* destination=communityGoalBuffer)CopyCommerceMemory(text+0x14,destination,checked((int)length)); body=Encoding.UTF8.GetString(communityGoalBuffer,0,checked((int)length)); work.state=3;work.observed=GetTickCount64(); } } finally{if(text!=0)Call(image+0x204010,(ulong)&text);} } } } } } catch(Exception error){work.error=CommerceExceptionCode(error);work.state=4;} if(work.state>=3)DeleteCommunityGoalRequest(ref work); lock(communityGoalLock) { if(communityGoalRequest.generation!=work.generation)return; communityGoalRequest=work;if(body is not null)communityGoalBody=body; } } private static JsonObject CommunityGoalCall(string op,JsonElement request,in Snapshot frame) { lock(communityGoalLock) { if(op=="cg.operation.status") { var lookupKey=request.Required("operationKey").GetString()!; if(!CommerceOperationKey(lookupKey))throw new InvalidOperationException("Identified CG operation required"); if(!communityGoalKeys.TryGetValue(lookupKey,out var generation))return Obj(("known",false)); return Obj(("known",true),("generation",generation),("receiptAvailable",generation==communityGoalRequest.generation)); } if(op=="cg.status") { var work=communityGoalRequest; if(request.Required("generation").GetUInt64()!=work.generation)throw new InvalidOperationException("CG request generation changed"); return Obj(("generation",work.generation),("state",work.state),("error",work.error),("kind",work.kind),("goalId",work.goal), ("mutationsSent",work.mutationSent?1:0),("responseBody",work.state==3?communityGoalBody:""), ("proof",work.state==3?CommunityGoalProof(communityGoalBody):""),("fresh",work.state==3&&GetTickCount64()-work.observed<=60000)); } var market=request.Required("marketId").GetUInt32(); if(!FreshCommunityGoalDock(frame,market))throw new InvalidOperationException("Fresh healthy docking at the requested CG market required"); var join=op=="cg.join";ulong goal=0;string? key=null; if(join) { key=request.Required("operationKey").GetString()!; if(!CommerceOperationKey(key))throw new InvalidOperationException("Identified CG signup required"); if(communityGoalKeys.TryGetValue(key,out var old))return Obj(("generation",old),("duplicate",true)); goal=request.Required("goalId").GetUInt64(); var proof=request.Required("proof").GetString();var prior=communityGoalRequest; if(goal==0||goal>uint.MaxValue||communityGoalKeys.Count>=32 ||!CommunityGoalReadProofMatches(frame,prior,proof,request.Required("generation").GetUInt64(),GetTickCount64(),communityGoalBody)) throw new InvalidOperationException("Current original CG list proof required before signup"); } if(communityGoalRequest.generation!=0&&communityGoalRequest.state<3)throw new InvalidOperationException("Original CG request already pending"); var next=++communityGoalGeneration; communityGoalRequest=new(){generation=next,entity=frame.entity,life=frame.life,epoch=controllerEpoch,expires=GetTickCount64()+30000,queued=GetTickCount64(), market=market,state=1,kind=join?1u:op=="cg.read.mine"?2u:0u,goal=goal};communityGoalBody=""; if(key is not null)communityGoalKeys.Add(key,next); return Obj(("generation",next),("queued",true),("mutationsSent",0)); } } }