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 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 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(messageLengthMaxPayload+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 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; } }