using System.Buffers.Binary; namespace XFEEDToolBox.Plugin; internal static unsafe partial class Navigation { // Pinned original build: 3AC8AC0 binds these same two interfaces for the // galaxy map. This reader copies existing data on the game owning thread; // it never requests another player's data or invokes a social action. internal sealed record GalaxyAccountSocialEntry(ulong systemAddress,ulong commanderId,int userId, string commanderName,string kind,int status,bool? locationCurrent) { internal string statusName=>kind=="friend"?status switch { 0=>"offline",1=>"main_menu",2=>"solo",3=>"cqc",4=>"privategroup_other",5=>"privategroup_current", 6=>"open",8=>"multicrew",9=>"threepointx",10=>"odyssey",11=>"38in40",12=>"training_simulation", 13=>"operation",_=>"unknown" }:"original-wing-travel-state-"+status; } internal sealed record GalaxyAccountSocial(ulong tick,ulong entity,bool friendsReady,bool wingsReady,GalaxyAccountSocialEntry[] items) { // A structurally readable cache is not proof that all server replies // have arrived. Never turn an empty cache into an exhaustive negative. internal bool complete=>false; internal string source=>"original-friends-and-wing-components"; } static bool GalaxySocialAddress(ulong address)=>address>0&&address<0x80000000000000UL; static bool? GalaxyFriendMarkerCurrent(int status)=>GalaxyFriendMarkerCurrent(status,null,null); static bool CopyGalaxyFriendsManager(nuint manager,nuint owner,nuint expectedImage,out GalaxyAccountSocialEntry[] items,int? mode=null,bool? gameplay=null) { items=[]; try { // 1CFFE00 constructs this component and 1D50740 identifies its // +58 interface as 5F05CBC. 1D6DCA0 feeds its +200 work map to // JSON decoder33FF020. Crucially original marker producer // 3AFF080 uses vfunc28/58DD30, the published +220 map instead. if(manager==0||owner==0||Pointer(manager)!=expectedImage+0x5238F90|| Pointer(manager+8)!=expectedImage+0x5238FC0||Pointer(manager+0x58)!=expectedImage+0x52390C0|| Pointer(manager+0x10)!=owner)return false; var map=manager+0x220;var buckets=Pointer(map);var bucketCount=Read(map+8);var count=Read(map+0x18); if(bucketCount==0||bucketCount>65536||count>16384||buckets<0x10000)return false; var heads=Platform.ReadBytes(buckets,checked((int)bucketCount*8)); List result=[];HashSet nodes=[];HashSet users=[]; for(nuint bucket=0;bucket<(nuint)bucketCount;bucket++) { var sentinel=buckets+bucket*8;var node=(nuint)BinaryPrimitives.ReadUInt64LittleEndian(heads.AsSpan((int)bucket*8)); while(node!=sentinel) { if(node<0x10000||(ulong)nodes.Count>=count||!nodes.Add(node))return false; var row=Platform.ReadBytes(node,0x78); ulong U64(int at)=>BinaryPrimitives.ReadUInt64LittleEndian(row.AsSpan(at)); int I32(int at)=>BinaryPrimitives.ReadInt32LittleEndian(row.AsSpan(at)); var user=I32(8);var relation=I32(0x20);var state=I32(0x28);var address=U64(0x40); if(user<0||I32(0x10)!=user||!users.Add(user)||relation is <0 or >3||row[0x24]>1||state is <0 or >13)return false; // The cache may also contain pending requests and ignored // users. Original 3AFF080 permits relationship2/3. Keep // only those positive rows, retaining historical status. if(relation is 2 or 3&&row[0x24]==0&&GalaxySocialAddress(address)) { if(!GalaxyAccountString((nuint)U64(0x18),out var name))return false; var commander=U64(0x30);if(commander==ulong.MaxValue)commander=0; result.Add(new(address,commander,user,name,"friend",state,GalaxyFriendMarkerCurrent(state,mode,gameplay))); } if(!Platform.ReadBytes(node,0x78).AsSpan().SequenceEqual(row))return false; node=(nuint)U64(0); } } if((ulong)nodes.Count!=count||Pointer(map)!=buckets||Read(map+8)!=bucketCount||Read(map+0x18)!=count|| Pointer(manager+0x10)!=owner||Pointer(manager+0x58)!=expectedImage+0x52390C0|| !Platform.ReadBytes(buckets,heads.Length).AsSpan().SequenceEqual(heads))return false; items=result.ToArray();return true; } catch{return false;} } static bool CopyGalaxyWingManager(nuint manager,nuint owner,nuint expectedImage,uint memberTypeId,out GalaxyAccountSocialEntry[] items,int? ownUserId=null) { items=[]; try { // 25C4580 + 26074D0: six fixed 0x40-byte roster slots. 26075B0 // resolves slot+18's weak life, then the member's registered // 5F771DC interface. 647EF0 proves the life+C8 registry link. if(manager==0||owner==0||Pointer(manager)!=expectedImage+0x52A5498||Pointer(manager+8)!=expectedImage+0x52A54C8|| Pointer(manager+0x58)!=expectedImage+0x52A55C8||Pointer(manager+0x10)!=owner||memberTypeId==0)return false; var slotLimit=Read(manager+0x59C);var phase=Read(manager+0x590); if(slotLimit>6)return false; var roster=Platform.ReadBytes(manager+0x2A8,6*0x40);List result=[];HashSet identities=[]; var any=false; for(var index=0;index(life+0xC0)!=8)continue; var components=Pointer(life+0xC8);if(components<0x10030)continue; var member=RegisteredComponent(components-0x30,memberTypeId); if(member<0x10058||Pointer(member)!=expectedImage+0x52A8668)continue; var obj=member-0x58;var memberOwner=Pointer(obj+0x10); if(memberOwner==0||Pointer(obj)!=expectedImage+0x52A8538||Pointer(obj+8)!=expectedImage+0x52A8568)return false; var rawAddress=Read(obj+0x80);var travelState=Read(obj+0x8C); // 2644BF0 stores the original body/system address then masks // exactly these 55 bits for the galaxy system lookup. var address=rawAddress&0x7FFFFFFFFFFFFFUL; if(rawAddress!=ulong.MaxValue&&GalaxySocialAddress(address)) { // Slot identity has not been proven to be commander_id; // leave that optional identity unknown rather than guess. var user=BinaryPrimitives.ReadInt32LittleEndian(slot[0x10..]); // Original 3AFF080 requires vfunc48 (nonempty roster), // vfunc2C0(2)==false (26178A0, manager+590 !=2), and // excludes the local user ID before appending a marker. bool? current=user<0||!any||phase==2?false:ownUserId is int self?user!=self:null; result.Add(new(address,0,user,"","wing",travelState,current)); } if(Read(obj+0x80)!=rawAddress||Read(obj+0x8C)!=travelState||Pointer(obj+0x10)!=memberOwner|| Pointer(member)!=expectedImage+0x52A8668||Pointer(life+0xC8)!=components||Read(life+0xC0)!=8)return false; } if(Pointer(manager+0x10)!=owner||Pointer(manager+0x58)!=expectedImage+0x52A55C8|| Read(manager+0x59C)!=slotLimit||Read(manager+0x590)!=phase|| !Platform.ReadBytes(manager+0x2A8,roster.Length).AsSpan().SequenceEqual(roster))return false; items=result.ToArray();return true; } catch{return false;} } static bool TryReadGalaxyAccountSocial(Snapshot frame,out GalaxyAccountSocial result) { result=new(0,0,false,false,[]); if(!Enabled()||!inGame||!frame.valid||frame.physics==0||owningThread==0||GetCurrentThreadId()!=owningThread)return false; try { if(!TryGetGalaxyAccountRegistry(frame,out var registry))return false; var friends=RegisteredComponent(registry,Read(image+0x5F05CBC)); var wings=RegisteredComponent(registry,Read(image+0x5F2E7F8)); var flow=RegisteredComponent(registry,Read(image+0x5F05BC4));int? mode=null;bool? gameplay=null; if(!CopyGalaxySocialModes(flow,image,Read(image+0x5F2E7E8),out mode,out gameplay)){mode=null;gameplay=null;} // 26008D0 identifies the original local user interface; its // vfunc28/2600E90 returns +1AC, used by the same marker loop. var user=RegisteredComponent(registry,Read(image+0x5F05BAC));int? ownUserId=null; if(user>=0x10000&&Pointer(user)==image+0x52A0258&&Read(user+0x1AC)>=0)ownUserId=Read(user+0x1AC); GalaxyAccountSocialEntry[] friendItems=[],wingItems=[]; var friendsReady=friends>=0x10058&&Pointer(friends)==image+0x52390C0&& CopyGalaxyFriendsManager(friends-0x58,Pointer(friends-0x48),image,out friendItems,mode,gameplay); var wingsReady=wings>=0x10058&&Pointer(wings)==image+0x52A55C8&& CopyGalaxyWingManager(wings-0x58,Pointer(wings-0x48),image,Read(image+0x5F771DC),out wingItems,ownUserId); if(!friendsReady&&!wingsReady)return false; result=new(frame.tick,frame.entity,friendsReady,wingsReady,[..friendItems,..wingItems]);return true; } catch{return false;} } }