72 lines
3.9 KiB
C#
72 lines
3.9 KiB
C#
using System.Buffers.Binary;
|
|
|
|
namespace ICRebuild.Core;
|
|
|
|
// The reader must copy exactly count bytes from the current process or fail.
|
|
public delegate bool PacketMemoryReader(nint address,int count,out byte[] bytes);
|
|
public sealed record CapturedPacket(int Opcode,uint? Target,byte[] Payload,bool Complete,string? Issue=null);
|
|
public sealed record CaptureRead(CapturedPacket? Packet,string? Error=null);
|
|
|
|
public static class NativePacketCapture
|
|
{
|
|
public const string ClientVersion="2026.10.06.0000.0000";
|
|
public const string BaselineClientVersion="2026.09.15.0000.0000";
|
|
public static bool IsSupportedVersion(string? version) => version is ClientVersion or BaselineClientVersion;
|
|
public const int IpcHeaderSize=0x10;
|
|
public const int MaxPayload=65536;
|
|
|
|
public static CaptureRead Up(PacketMemoryReader read,nint data,Func<int,bool> accepts)
|
|
{
|
|
if(!Read(read,data,0,16,out var header)) return new(null,"上行对象头读取失败");
|
|
var opcode=BinaryPrimitives.ReadUInt16LittleEndian(header);
|
|
if(!accepts(opcode)) return new(null);
|
|
return Payload(read,data,0x20,BinaryPrimitives.ReadUInt64LittleEndian(header.AsSpan(8)),opcode,null);
|
|
}
|
|
// Called only AFTER ZoneReceive returned success; holder owns the message until its caller releases it.
|
|
public static CaptureRead Down(PacketMemoryReader read,nint holder,Func<int,bool> accepts)
|
|
{
|
|
if(!Read(read,holder,0x10,8,out var reference)) return new(null,"下行消息引用读取失败");
|
|
var message=Pointer(reference);
|
|
if(!Read(read,message,0x20,0x20,out var descriptor)) return new(null,"下行消息描述读取失败");
|
|
var length=BinaryPrimitives.ReadUInt64LittleEndian(descriptor.AsSpan(0x10));
|
|
var data=Pointer(descriptor.AsSpan(0x18));
|
|
// Even a fallback must not read an opcode beyond the declared message.
|
|
if(length<4 || !Read(read,data,0,4,out var ipc)) return new(null,"下行 IPC 头读取失败或长度不足");
|
|
var opcode=BinaryPrimitives.ReadUInt16LittleEndian(ipc.AsSpan(2));
|
|
if(!accepts(opcode)) return new(null);
|
|
var target=NormalizeTarget(BinaryPrimitives.ReadUInt32LittleEndian(descriptor));
|
|
return Payload(read,data,IpcHeaderSize,length,opcode,target);
|
|
}
|
|
private static CaptureRead Payload(PacketMemoryReader read,nint data,int offset,ulong messageLength,int opcode,uint? target)
|
|
{
|
|
if(!TryPayloadLength(messageLength,out var count))
|
|
return new(new(opcode,target,[],false,$"消息长度无效或超出采集上限:{messageLength}"));
|
|
if(count==0) return new(new(opcode,target,[],true));
|
|
return Read(read,data,offset,count,out var bytes)
|
|
? new(new(opcode,target,bytes,true))
|
|
: new(new(opcode,target,[],false,"载荷读取失败,未保留部分数据"));
|
|
}
|
|
public static bool TryPayloadLength(ulong messageLength,out int count)
|
|
{
|
|
count=0;
|
|
if(messageLength<IpcHeaderSize || messageLength>MaxPayload+IpcHeaderSize) return false;
|
|
count=(int)messageLength-IpcHeaderSize;
|
|
return true;
|
|
}
|
|
// Same reserved-target normalization as the native NetworkModule dispatcher caller.
|
|
public static uint NormalizeTarget(uint target) => (target&0x5FF80000u)==0x5FF80000u ? 0x5FF80002u-(target&1u) : target;
|
|
private static nint Pointer(ReadOnlySpan<byte> bytes)
|
|
{
|
|
var value=BinaryPrimitives.ReadUInt64LittleEndian(bytes);
|
|
return value>(ulong)nint.MaxValue ? 0 : (nint)value;
|
|
}
|
|
private static bool Read(PacketMemoryReader read,nint data,int offset,int count,out byte[] bytes)
|
|
{
|
|
bytes=[];
|
|
if(data<=0 || offset<0 || count<0 || count>MaxPayload || (long)data>long.MaxValue-offset-count) return false;
|
|
if(!read(data+offset,count,out var result) || result==null || result.Length!=count) return false;
|
|
bytes=result;
|
|
return true;
|
|
}
|
|
}
|