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&¤t.entity==frame.entity&¤t.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;}
}
}
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&¤t.entity==frame.entity&¤t.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;}
}
}