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<ulong>(map+8);var count=Read<ulong>(map+0x18);
if(bucketCount==0||bucketCount>65536||count>16384||buckets<0x10000)return false;
var heads=Platform.ReadBytes(buckets,checked((int)bucketCount*8));
List<GalaxyAccountSocialEntry> result=[];HashSet<nuint> nodes=[];HashSet<int> 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<ulong>(map+8)!=bucketCount||Read<ulong>(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<uint>(manager+0x59C);var phase=Read<byte>(manager+0x590);
if(slotLimit>6)return false;
var roster=Platform.ReadBytes(manager+0x2A8,6*0x40);List<GalaxyAccountSocialEntry> result=[];HashSet<ulong> identities=[];
var any=false;
for(var index=0;index<slotLimit;index++)
any|=BinaryPrimitives.ReadUInt64LittleEndian(roster.AsSpan(index*0x40))!=ulong.MaxValue;
for(var index=0;index<6;index++)
{
var slot=roster.AsSpan(index*0x40,0x40);var identity=BinaryPrimitives.ReadUInt64LittleEndian(slot);
if(identity==ulong.MaxValue)continue;
if(!identities.Add(identity))return false;
var life=(nuint)BinaryPrimitives.ReadUInt64LittleEndian(slot[0x18..]);
// A not-yet-loaded member is a partial answer, not evidence
// that the member has no location.
if(life<0x10000||Read<uint>(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<ulong>(obj+0x80);var travelState=Read<int>(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<ulong>(obj+0x80)!=rawAddress||Read<int>(obj+0x8C)!=travelState||Pointer(obj+0x10)!=memberOwner||
Pointer(member)!=expectedImage+0x52A8668||Pointer(life+0xC8)!=components||Read<uint>(life+0xC0)!=8)return false;
}
if(Pointer(manager+0x10)!=owner||Pointer(manager+0x58)!=expectedImage+0x52A55C8||
Read<uint>(manager+0x59C)!=slotLimit||Read<byte>(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<uint>(image+0x5F05CBC));
var wings=RegisteredComponent(registry,Read<uint>(image+0x5F2E7F8));
var flow=RegisteredComponent(registry,Read<uint>(image+0x5F05BC4));int? mode=null;bool? gameplay=null;
if(!CopyGalaxySocialModes(flow,image,Read<uint>(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<uint>(image+0x5F05BAC));int? ownUserId=null;
if(user>=0x10000&&Pointer(user)==image+0x52A0258&&Read<int>(user+0x1AC)>=0)ownUserId=Read<int>(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<uint>(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;}
}
}
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<ulong>(map+8);var count=Read<ulong>(map+0x18);
if(bucketCount==0||bucketCount>65536||count>16384||buckets<0x10000)return false;
var heads=Platform.ReadBytes(buckets,checked((int)bucketCount*8));
List<GalaxyAccountSocialEntry> result=[];HashSet<nuint> nodes=[];HashSet<int> 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<ulong>(map+8)!=bucketCount||Read<ulong>(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<uint>(manager+0x59C);var phase=Read<byte>(manager+0x590);
if(slotLimit>6)return false;
var roster=Platform.ReadBytes(manager+0x2A8,6*0x40);List<GalaxyAccountSocialEntry> result=[];HashSet<ulong> identities=[];
var any=false;
for(var index=0;index<slotLimit;index++)
any|=BinaryPrimitives.ReadUInt64LittleEndian(roster.AsSpan(index*0x40))!=ulong.MaxValue;
for(var index=0;index<6;index++)
{
var slot=roster.AsSpan(index*0x40,0x40);var identity=BinaryPrimitives.ReadUInt64LittleEndian(slot);
if(identity==ulong.MaxValue)continue;
if(!identities.Add(identity))return false;
var life=(nuint)BinaryPrimitives.ReadUInt64LittleEndian(slot[0x18..]);
// A not-yet-loaded member is a partial answer, not evidence
// that the member has no location.
if(life<0x10000||Read<uint>(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<ulong>(obj+0x80);var travelState=Read<int>(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<ulong>(obj+0x80)!=rawAddress||Read<int>(obj+0x8C)!=travelState||Pointer(obj+0x10)!=memberOwner||
Pointer(member)!=expectedImage+0x52A8668||Pointer(life+0xC8)!=components||Read<uint>(life+0xC0)!=8)return false;
}
if(Pointer(manager+0x10)!=owner||Pointer(manager+0x58)!=expectedImage+0x52A55C8||
Read<uint>(manager+0x59C)!=slotLimit||Read<byte>(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<uint>(image+0x5F05CBC));
var wings=RegisteredComponent(registry,Read<uint>(image+0x5F2E7F8));
var flow=RegisteredComponent(registry,Read<uint>(image+0x5F05BC4));int? mode=null;bool? gameplay=null;
if(!CopyGalaxySocialModes(flow,image,Read<uint>(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<uint>(image+0x5F05BAC));int? ownUserId=null;
if(user>=0x10000&&Pointer(user)==image+0x52A0258&&Read<int>(user+0x1AC)>=0)ownUserId=Read<int>(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<uint>(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;}
}
}