XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
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;}
    }
}