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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using System.Text;

namespace XFEEDToolBox.Plugin;

internal static unsafe partial class Navigation
{
    internal sealed record GalaxyCommodityOption(uint uid,string name,string category,int kind,bool? publisherEligible);
    internal sealed record GalaxyCommodityOptions(ulong tick,ulong entity,GalaxyCommodityOption[] items,int unresolvedCount)
    {
        // This is the currently observed source, not the final ship-dependent
        // PlayerData/CommodityList or a claim about a complete game catalogue.
        internal bool complete=>false;
        internal string source=>"original-commodity-enumeration-and-definitions";
    }
    sealed class GalaxyCommodityCopy
    {
        readonly Dictionary<(nuint,int),byte[]> copies=[];
        int bytes;
        internal byte[] Bytes(nuint pointer,int length)
        {
            if(length==0)return [];
            if(pointer<0x10000||length<0||length>0x100000||pointer>nuint.MaxValue-(nuint)length)throw new InvalidDataException();
            var key=(pointer,length);if(copies.TryGetValue(key,out var old))return old;
            if(bytes>0x800000-length)throw new InvalidDataException();
            var value=Platform.ReadBytes(pointer,length);if(value.Length!=length)throw new InvalidDataException();copies.Add(key,value);bytes+=length;return value;
        }
        internal ulong U64(nuint pointer)=>BinaryPrimitives.ReadUInt64LittleEndian(Bytes(pointer,8));
        internal uint U32(nuint pointer)=>BinaryPrimitives.ReadUInt32LittleEndian(Bytes(pointer,4));
        internal nuint Ptr(nuint pointer)=>(nuint)U64(pointer);
        internal string Name(nuint pointer)
        {
            var length=U64(pointer);if(length is 0 or >256)throw new InvalidDataException();
            return Text(Bytes(pointer+0x14,(int)length));
        }
        internal string Category(nuint definition)
        {
            var reference=Ptr(definition+0x60);if(reference==0)return "";
            var frozen=(nuint)(U64(reference+8)&~3UL);if(frozen<0x10000)throw new InvalidDataException();
            var text=Ptr(frozen+0x30);if(text==0)return "";var chars=Ptr(text+8);if(chars==0)return "";
            var result=new List<byte>();for(nuint index=0;index<=256;index++)
            {var value=Bytes(chars+index,1)[0];if(value==0)return result.Count==0?"":Text(result.ToArray());if(index==256)break;result.Add(value);}
            throw new InvalidDataException();
        }
        static string Text(byte[] value)
        {var text=new UTF8Encoding(false,true).GetString(value);if(string.IsNullOrWhiteSpace(text)||text.Any(char.IsControl))throw new InvalidDataException();return text;}
        internal bool Stable()=>copies.All(pair=>Platform.ReadBytes(pair.Key.Item1,pair.Key.Item2).AsSpan().SequenceEqual(pair.Value));
    }
    static uint GalaxyCommodityHash(uint uid)
    {unchecked{var value=uid*0x1001;value=(value>>22^value)*0x11;value=(value>>9^value)*0x401;value=(value>>2^value)*0x81;return value>>12^value;}}

    static Dictionary<uint,nuint> GalaxyCommodityNodes(GalaxyCommodityCopy copy,nuint map)
    {
        var buckets=copy.Ptr(map);var capacity=copy.U64(map+8);var count=copy.U64(map+0x18);
        if(capacity is 0 or >65536||count>4096)throw new InvalidDataException();
        var heads=copy.Bytes(buckets,checked((int)capacity*8));var seen=new HashSet<nuint>();var result=new Dictionary<uint,nuint>();
        for(nuint index=0;index<(nuint)capacity;index++)
        {
            var sentinel=buckets+index*8;var node=(nuint)BinaryPrimitives.ReadUInt64LittleEndian(heads.AsSpan((int)index*8,8));
            while(node!=sentinel)
            {
                if(node<0x10000||!seen.Add(node)||seen.Count>(long)count)throw new InvalidDataException();
                var uid=copy.U32(node+8);if(uid==uint.MaxValue||GalaxyCommodityHash(uid)%capacity!=(ulong)index||!result.TryAdd(uid,node))throw new InvalidDataException();
                node=copy.Ptr(node);
            }
        }
        if((ulong)result.Count!=count)throw new InvalidDataException();return result;
    }

    // These are current ED5A table addresses, independently closed against the
    // pinned6891 constructors/getters by commodity-tree/compatibility.json.
    // The source registry IDs are likewise paired; no original method is called.
    static bool CopyGalaxyCommodityOptions(nuint enumeration,nuint definitions,nuint enumerationOwner,nuint definitionsOwner,nuint expectedImage,
        out GalaxyCommodityOption[] items,out int unresolved,GalaxyCommodityPublisherContext? publisher=null)
    {
        items=[];unresolved=0;
        try
        {
            var copy=new GalaxyCommodityCopy();
            if(enumeration<0x10000||definitions<0x10000||enumerationOwner<0x10000||definitionsOwner<0x10000||
               copy.Ptr(enumeration)!=expectedImage+0x52418E8||copy.Ptr(enumeration+8)!=expectedImage+0x5241918||copy.Ptr(enumeration+0x58)!=expectedImage+0x5241A18||copy.Ptr(enumeration+0x10)!=enumerationOwner||copy.Bytes(enumeration+0xA8,1)[0]!=1||
               copy.Ptr(definitions)!=expectedImage+0x5219600||copy.Ptr(definitions+8)!=expectedImage+0x5219630||copy.Ptr(definitions+0x58)!=expectedImage+0x5219730||copy.Ptr(definitions+0x10)!=definitionsOwner||copy.U32(definitions+0x284)!=1)return false;
            // Active resource5F85C40 -> callback1DA7760 -> poller1DF25B0
            // sets A8 only for a successful response before parser1E34530.
            // Own-thread copying cannot interleave that synchronous parser.
            var enumerated=GalaxyCommodityNodes(copy,enumeration+0xB0);var defined=GalaxyCommodityNodes(copy,definitions+0x1B8);
            var result=new List<GalaxyCommodityOption>();
            foreach(var pair in enumerated)
            {
                var kind=unchecked((int)copy.U32(pair.Value+0x14));
                // Original3A603C0 ignores all other kinds. Kind2 uses only a
                // guarded original actor/cargo/owner snapshot, never history.
                if(kind is not(1 or 2))continue;
                if(!defined.TryGetValue(pair.Key,out var node)){unresolved++;continue;}
                var definition=node+0x10;var name=copy.Name(copy.Ptr(definition+8));var category=copy.Category(definition);
                result.Add(new(pair.Key,name,category,kind,kind==1?true:publisher?.Names.Contains(name)));
            }
            if(!copy.Stable()||publisher is not null&&!publisher.Stable())return false;items=result.ToArray();return true;
        }
        catch{items=[];unresolved=0;return false;}
    }
    static bool TryReadGalaxyCommodityOptions(Snapshot frame,out GalaxyCommodityOptions result)
    {
        result=new(0,0,[],0);
        if(!Enabled()||!inGame||!frame.valid||frame.physics==0||owningThread==0||GetCurrentThreadId()!=owningThread||GetTickCount64()-frame.tick>500)return false;
        try
        {
            if(!TryGetGalaxyAccountRegistry(frame,out var registry))return false;
            var enumeration=RegisteredComponent(registry,Read<uint>(image+0x5F05CE8));var definitions=RegisteredComponent(registry,Read<uint>(image+0x5F05C34));
            if(enumeration<0x10058||definitions<0x10058)return false;var a=enumeration-0x58;var b=definitions-0x58;
            var publisher=ReadGalaxyCommodityPublisherContext(registry);
            if(!CopyGalaxyCommodityOptions(a,b,Pointer(a+0x10),Pointer(b+0x10),image,out var items,out var unresolved,publisher)||
               !TryGetGalaxyAccountRegistry(frame,out var currentRegistry)||currentRegistry!=registry||
               RegisteredComponent(registry,Read<uint>(image+0x5F05CE8))!=enumeration||RegisteredComponent(registry,Read<uint>(image+0x5F05C34))!=definitions||Current().entity!=frame.entity)return false;
            result=new(frame.tick,frame.entity,items,unresolved);return true;
        }
        catch{return false;}
    }
}