using System.Buffers.Binary;
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
// 3C12700 registers 5FDBDB0 with size 160; factory 3C0EDD0 calls
// 3AD9300. 3B363C0 tests this owned map. The separate pending map used
// by 3B36220 is deliberately excluded.
internal sealed record GalaxyAccountTradeEntry(ulong systemAddress,ulong timestamp);
internal sealed record GalaxyAccountTradeData(ulong tick,ulong entity,ulong lifetime,GalaxyAccountTradeEntry[] items)
{
internal bool complete=>false;
internal string source=>"original-trade-data-manager";
}
static bool CopyGalaxyTradeDataManager(nuint manager,nuint owner,nuint expectedImage,out ulong lifetime,out GalaxyAccountTradeEntry[] items)
{
lifetime=0;items=[];
try
{
if(manager==0||owner==0||Pointer(manager)!=expectedImage+0x552EA70||
Pointer(manager+8)!=expectedImage+0x552EAA0||Pointer(manager+0x58)!=expectedImage+0x552EBA0||
Pointer(manager+0x10)!=owner)return false;
var buckets=Pointer(manager+0x110);var bucketCount=Read<ulong>(manager+0x118);var count=Read<ulong>(manager+0x128);
var ttl=Read<ulong>(manager+0xD0);
if(buckets==0||bucketCount==0||bucketCount>65536||count>16384)return false;
var heads=Platform.ReadBytes(buckets,(int)bucketCount*8);
var result=new GalaxyAccountTradeEntry[(int)count];var index=0;
HashSet<nuint> seen=[];HashSet<ulong> addresses=[];
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==0||index>=result.Length||!seen.Add(node))return false;
var next=Pointer(node);var address=Read<ulong>(node+8);var stamp=Read<ulong>(node+0x10);
if(address==0||address>=0x80000000000000UL||!addresses.Add(address)||stamp==0)return false;
result[index++]=new(address,stamp);node=next;
}
}
if(index!=result.Length||Pointer(manager+0x110)!=buckets||Read<ulong>(manager+0x118)!=bucketCount||
Read<ulong>(manager+0x128)!=count||Read<ulong>(manager+0xD0)!=ttl||Pointer(manager+0x10)!=owner||
Pointer(manager)!=expectedImage+0x552EA70||!Platform.ReadBytes(buckets,heads.Length).AsSpan().SequenceEqual(heads))return false;
// Timestamp and lifetime retain the original service units. The
// consuming layer must not invent a clock epoch or turn a missing
// address into "no data available".
lifetime=ttl;items=result;return true;
}
catch{return false;}
}
static bool TryReadGalaxyAccountTradeData(Snapshot frame,out GalaxyAccountTradeData result)
{
result=new(0,0,0,[]);
if(!Enabled()||!inGame||!frame.valid||frame.physics==0||owningThread==0||GetCurrentThreadId()!=owningThread)return false;
try
{
if(!TryGetGalaxyAccountRegistry(frame,out var registry))return false;
// 3B12EE0 (interface vtable[0]) returns type ID 5F856E0;
// this is the same registered component used by the map predicate.
var component=RegisteredComponent(registry,Read<uint>(image+0x5F856E0));
if(component<0x10058||Pointer(component)!=image+0x552EBA0)return false;
var manager=component-0x58;var owner=Pointer(manager+0x10);if(owner==0)return false;
if(!CopyGalaxyTradeDataManager(manager,owner,image,out var ttl,out var items))return false;
result=new(frame.tick,frame.entity,ttl,items);return true;
}
catch{return false;}
}
}
using System.Buffers.Binary;
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
// 3C12700 registers 5FDBDB0 with size 160; factory 3C0EDD0 calls
// 3AD9300. 3B363C0 tests this owned map. The separate pending map used
// by 3B36220 is deliberately excluded.
internal sealed record GalaxyAccountTradeEntry(ulong systemAddress,ulong timestamp);
internal sealed record GalaxyAccountTradeData(ulong tick,ulong entity,ulong lifetime,GalaxyAccountTradeEntry[] items)
{
internal bool complete=>false;
internal string source=>"original-trade-data-manager";
}
static bool CopyGalaxyTradeDataManager(nuint manager,nuint owner,nuint expectedImage,out ulong lifetime,out GalaxyAccountTradeEntry[] items)
{
lifetime=0;items=[];
try
{
if(manager==0||owner==0||Pointer(manager)!=expectedImage+0x552EA70||
Pointer(manager+8)!=expectedImage+0x552EAA0||Pointer(manager+0x58)!=expectedImage+0x552EBA0||
Pointer(manager+0x10)!=owner)return false;
var buckets=Pointer(manager+0x110);var bucketCount=Read<ulong>(manager+0x118);var count=Read<ulong>(manager+0x128);
var ttl=Read<ulong>(manager+0xD0);
if(buckets==0||bucketCount==0||bucketCount>65536||count>16384)return false;
var heads=Platform.ReadBytes(buckets,(int)bucketCount*8);
var result=new GalaxyAccountTradeEntry[(int)count];var index=0;
HashSet<nuint> seen=[];HashSet<ulong> addresses=[];
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==0||index>=result.Length||!seen.Add(node))return false;
var next=Pointer(node);var address=Read<ulong>(node+8);var stamp=Read<ulong>(node+0x10);
if(address==0||address>=0x80000000000000UL||!addresses.Add(address)||stamp==0)return false;
result[index++]=new(address,stamp);node=next;
}
}
if(index!=result.Length||Pointer(manager+0x110)!=buckets||Read<ulong>(manager+0x118)!=bucketCount||
Read<ulong>(manager+0x128)!=count||Read<ulong>(manager+0xD0)!=ttl||Pointer(manager+0x10)!=owner||
Pointer(manager)!=expectedImage+0x552EA70||!Platform.ReadBytes(buckets,heads.Length).AsSpan().SequenceEqual(heads))return false;
// Timestamp and lifetime retain the original service units. The
// consuming layer must not invent a clock epoch or turn a missing
// address into "no data available".
lifetime=ttl;items=result;return true;
}
catch{return false;}
}
static bool TryReadGalaxyAccountTradeData(Snapshot frame,out GalaxyAccountTradeData result)
{
result=new(0,0,0,[]);
if(!Enabled()||!inGame||!frame.valid||frame.physics==0||owningThread==0||GetCurrentThreadId()!=owningThread)return false;
try
{
if(!TryGetGalaxyAccountRegistry(frame,out var registry))return false;
// 3B12EE0 (interface vtable[0]) returns type ID 5F856E0;
// this is the same registered component used by the map predicate.
var component=RegisteredComponent(registry,Read<uint>(image+0x5F856E0));
if(component<0x10058||Pointer(component)!=image+0x552EBA0)return false;
var manager=component-0x58;var owner=Pointer(manager+0x10);if(owner==0)return false;
if(!CopyGalaxyTradeDataManager(manager,owner,image,out var ttl,out var items))return false;
result=new(frame.tick,frame.entity,ttl,items);return true;
}
catch{return false;}
}
}