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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

namespace XFEEDToolBox.Plugin;

internal static unsafe partial class Navigation
{
    internal sealed record GalaxyRouteMapFilterRequest(int categoryId,string[] expectedKeys,string[] selectedKeys,bool affectsRoutePlotting);
    internal sealed record GalaxyRouteCheckboxChoice(uint index,string settingsName,string key,bool selected);
    internal sealed record GalaxyRouteCheckboxState(nuint controller,nuint provider,uint mask,bool matches)
    {internal GalaxyRouteCheckboxChoice[] choices {get;init;}=[];internal bool actualAffects {get;init;}}
    static string OriginalRouteKey(string value)=>new(value.Trim().ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray());
    static bool ValidGalaxyRouteMapCategory(int category)=>category is 2 or 3 or 4 or 6 or 7 or 8 or 10;
    static GalaxyRouteMapFilterRequest? ParseGalaxyRouteMapFilter(JsonElement options)
    {
        if(!options.TryGetProperty("mapFilter",out var value)||value.ValueKind==JsonValueKind.Null)return null;
        if(value.ValueKind!=JsonValueKind.Object||!value.TryGetProperty("categoryId",out var category)||category.ValueKind!=JsonValueKind.Number||!category.TryGetInt32(out var id)||!ValidGalaxyRouteMapCategory(id))
            throw new InvalidDataException("Invalid original route-map category");
        string[] Keys(string name,bool required)
        {
            if(!value.TryGetProperty(name,out var input)||input.ValueKind!=JsonValueKind.Array||input.GetArrayLength()>96)
                throw new InvalidDataException("Invalid original route-map identities");
            var result=new List<string>();
            foreach(var item in input.EnumerateArray())
            {
                if(item.ValueKind!=JsonValueKind.String||item.GetString() is not{} text||string.IsNullOrWhiteSpace(text)||text.Length>128||text!=OriginalRouteKey(text)||result.Contains(text))
                    throw new InvalidDataException("Invalid original route-map identity");
                result.Add(text);
            }
            if(required&&result.Count==0)throw new InvalidDataException("Missing original route-map identities");
            return result.ToArray();
        }
        var expected=Keys("expectedKeys",true);var selected=Keys("selectedKeys",false);
        if(selected.Any(key=>!expected.Contains(key))||!value.TryGetProperty("affectsRoutePlotting",out var affects)||affects.ValueKind is not(JsonValueKind.True or JsonValueKind.False))
            throw new InvalidDataException("Invalid original route-map selection");
        return new(id,expected,selected,affects.GetBoolean());
    }

    // Original3B16B00 shared handles, relative to its owner+58 interface.
    static nuint GalaxyRouteCheckboxHandle(int category)=>category switch
    {2=>0x2C0,3=>0x2D0,4=>0x2F0,6=>0x300,7=>0x320,8=>0x370,10=>0x390,_=>0};

    static bool CopyGalaxyRouteCheckboxState(nuint main,nuint expectedImage,GalaxyRouteMapFilterRequest request,out GalaxyRouteCheckboxState? result)
        =>CopyGalaxyRouteCheckboxStateCore(main,expectedImage,request.categoryId,request,out result);
    static bool DiscoverGalaxyRouteCheckboxState(nuint main,nuint expectedImage,int category,out GalaxyRouteCheckboxState? result)
        =>CopyGalaxyRouteCheckboxStateCore(main,expectedImage,category,null,out result);
    static bool CopyGalaxyRouteCheckboxStateCore(nuint main,nuint expectedImage,int category,GalaxyRouteMapFilterRequest? request,out GalaxyRouteCheckboxState? result)
    {
        result=null;
        try
        {
            var offset=GalaxyRouteCheckboxHandle(category);
            if(offset==0||main<0x10000||Pointer(main)!=expectedImage+0x551FFC0||Pointer(expectedImage+0x551FFC0+0x78)!=expectedImage+0x3A68F60||Read<int>(main+0x280)!=-1)return false;
            var type=Pointer(main+0x288);if(type<0x10028||Pointer(type)!=expectedImage+0x552D788)return false;
            var controller=type-0x28;
            if(Pointer(controller)!=expectedImage+0x552D5C0||Pointer(expectedImage+0x552D5C0+0x40)!=expectedImage+0x3B45E30)return false;
            var block=Pointer(controller+offset);var provider=Pointer(controller+offset+8);
            if(block<0x10000||Read<int>(block+8)<=0||Read<int>(block+0xC)<=0||provider<0x10000)return false;
            var table=Pointer(provider);
            // The same actual inherited setter/getter establishes the base
            // checkbox layout; category alone is insufficient to accept it.
            if(table<0x10000||Pointer(table+0x28)!=expectedImage+0x3B8BA80||Pointer(table+0x30)!=expectedImage+0x3B178D0||Read<int>(provider+0x60)!=category||Read<byte>(provider+0x80)!=1)return false;
            var count=Read<ulong>(provider+0x90);var capacity=Read<ulong>(provider+0x98);var storage=Pointer(provider+0xA0);var affects=Read<byte>(provider+0x81);
            if(request is not null&&count!=(ulong)request.expectedKeys.Length||count is <1 or >96||capacity is <16 or >256||count>capacity||(capacity&(capacity-1))!=0||storage<0x10000||affects>1)return false;
            var bits=new List<(nuint at,ulong value)>();var rows=new List<(nuint at,uint index,byte selected,nuint text)>();
            var strings=new List<(nuint at,ulong length,ulong capacity,byte[] bytes)>();
            var names=new HashSet<string>(StringComparer.Ordinal);var indices=new HashSet<uint>();
            var values=request?.selectedKeys.ToHashSet(StringComparer.Ordinal);var expected=request?.expectedKeys.ToHashSet(StringComparer.Ordinal);
            var choices=new List<GalaxyRouteCheckboxChoice>();
            uint assigned=0,mask=0;var matches=request is not null&&(affects!=0)==request.affectsRoutePlotting;
            bool Assign(uint index,bool selected)
            {
                var bit=1u<<(int)(index&31);
                if((assigned&bit)!=0&&((mask&bit)!=0)!=selected)return false;
                assigned|=bit;if(selected)mask|=bit;return true;
            }
            var first=storage+(nuint)(((capacity>>3)+7)&~7UL);
            for(ulong bucket=0;bucket<capacity;bucket++)
            {
                var wordAt=storage+(nuint)(bucket>>6)*8;var word=Read<ulong>(wordAt);
                if((bucket&63)==0)bits.Add((wordAt,word));
                if((word&(1UL<<(int)(bucket&63)))==0)continue;
                var row=first+(nuint)bucket*0x38;var index=Read<uint>(row+8);var selected=Read<byte>(row+0x30);var text=Pointer(row+0x10);
                if(index>=count||!indices.Add(index)||selected>1||text<0x10000)return false;
                var length=Read<ulong>(text);var textCapacity=Read<ulong>(text+8);
                if(length is <1 or >128||textCapacity<length||textCapacity>1024||Read<int>(text+0x10)<=0)return false;
                var bytes=Platform.ReadBytes(text+0x14,(int)length);if(bytes.Length!=(int)length||Read<byte>(text+0x14+(nuint)length)!=0)return false;
                var settingsName=new UTF8Encoding(false,true).GetString(bytes);var name=OriginalRouteKey(settingsName);
                if(name.Length==0||!names.Add(name)||request is not null&&(!expected!.Contains(name)||!Assign(index,values!.Contains(name))))return false;
                if(request is not null)matches&=(selected!=0)==values!.Contains(name);
                choices.Add(new(index,settingsName,name,selected!=0));
                rows.Add((row,index,selected,text));strings.Add((text,length,textCapacity,bytes));
            }
            if(names.Count!=(int)count||request is not null&&!Assign((uint)count,request.affectsRoutePlotting))return false;
            if(Pointer(main)!=expectedImage+0x551FFC0||Read<int>(main+0x280)!=-1||Pointer(main+0x288)!=type||Pointer(type)!=expectedImage+0x552D788||
               Pointer(controller)!=expectedImage+0x552D5C0||Pointer(controller+offset)!=block||Pointer(controller+offset+8)!=provider||Read<int>(block+8)<=0||Read<int>(block+0xC)<=0||
               Pointer(provider)!=table||Pointer(table+0x28)!=expectedImage+0x3B8BA80||Pointer(table+0x30)!=expectedImage+0x3B178D0||Read<int>(provider+0x60)!=category||Read<byte>(provider+0x80)!=1||
               Read<byte>(provider+0x81)!=affects||Read<ulong>(provider+0x90)!=count||Read<ulong>(provider+0x98)!=capacity||Pointer(provider+0xA0)!=storage||
               bits.Any(v=>Read<ulong>(v.at)!=v.value)||rows.Any(v=>Read<uint>(v.at+8)!=v.index||Read<byte>(v.at+0x30)!=v.selected||Pointer(v.at+0x10)!=v.text)||
               strings.Any(v=>Read<ulong>(v.at)!=v.length||Read<ulong>(v.at+8)!=v.capacity||Read<int>(v.at+0x10)<=0||Read<byte>(v.at+0x14+(nuint)v.length)!=0||!Platform.ReadBytes(v.at+0x14,(int)v.length).AsSpan().SequenceEqual(v.bytes)))return false;
            result=new(controller,provider,mask,matches){choices=choices.OrderBy(c=>c.index).ToArray(),actualAffects=affects!=0};return true;
        }
        catch{return false;}
    }

    static bool ApplyGalaxyRouteMapFilter(Snapshot frame,GalaxyRouteMapFilterRequest request)
    {
        try
        {
        if(!Enabled()||!inGame||!frame.valid||frame.entity==0||owningThread==0||GetCurrentThreadId()!=owningThread||!FindGalaxyServices(frame,out var services))return false;
        var type=Read<uint>(image+0x5FDB224);var component=RegisteredComponent(services.registry,type);
        if(!CopyGameRouteOptions(component,image,out var main,out _)||!CopyGalaxyRouteCheckboxState(main,image,request,out var before)||before is null)return false;
        var current=Current();if(!current.valid||current.entity!=frame.entity||current.referenceWorld!=frame.referenceWorld||Read<uint>(image+0x5FDB224)!=type||RegisteredComponent(services.registry,type)!=component)return false;
        ((delegate* unmanaged<nuint,uint,uint,void>)(image+0x3A68F60))(main,(uint)request.categoryId,before.mask);
        current=Current();
        return current.valid&&current.entity==frame.entity&&current.referenceWorld==frame.referenceWorld&&Read<uint>(image+0x5FDB224)==type&&RegisteredComponent(services.registry,type)==component&&
            CopyGameRouteOptions(component,image,out var afterMain,out _)&&afterMain==main&&CopyGalaxyRouteCheckboxState(main,image,request,out var after)&&after is{matches:true}&&
            after.controller==before.controller&&after.provider==before.provider;
        }
        catch{return false;}
    }
}