将字节数组转换为托管结构
更新:这个问题的答案帮助我编写了开源项目 AlicanC 的GitHub 上的《现代战争 2》工具。您可以在 MW2Packets.cs 和我编写的用于读取 Extensions.cs。< /strong>
我正在捕获 《使命召唤:现代战争》的 UDP 数据包2 在我的 C# 应用程序中使用 Pcap.Net。我从库收到一个 byte[]
。我尝试像字符串一样解析它,但这效果不佳。
我的 byte[]
有一个通用数据包标头,然后是特定于数据包类型的另一个标头,然后是有关大厅中每个玩家的信息。
一位乐于助人的人为我检查了一些数据包,并提出了这些结构:
// Fields are big endian unless specified otherwise.
struct packet_header
{
uint16_t magic;
uint16_t packet_size;
uint32_t unknown1;
uint32_t unknown2;
uint32_t unknown3;
uint32_t unknown4;
uint16_t unknown5;
uint16_t unknown6;
uint32_t unknown7;
uint32_t unknown8;
cstring_t packet_type; // \0 terminated string
};
// Fields are little endian unless specified otherwise.
struct header_partystate //Header for the "partystate" packet type
{
uint32_t unknown1;
uint8_t unknown2;
uint8_t player_entry_count;
uint32_t unknown4;
uint32_t unknown5;
uint32_t unknown6;
uint32_t unknown7;
uint8_t unknown8;
uint32_t unknown9;
uint16_t unknown10;
uint8_t unknown11;
uint8_t unknown12[9];
uint32_t unknown13;
uint32_t unknown14;
uint16_t unknown15;
uint16_t unknown16;
uint32_t unknown17[10];
uint32_t unknown18;
uint32_t unknown19;
uint8_t unknown20;
uint32_t unknown21;
uint32_t unknown22;
uint32_t unknown23;
};
// Fields are little endian unless specified otherwise.
struct player_entry
{
uint8_t player_id;
// The following fields may not actually exist in the data if it's an empty entry.
uint8_t unknown1[3];
cstring_t player_name;
uint32_t unknown2;
uint64_t steam_id;
uint32_t internal_ip;
uint32_t external_ip;
uint16_t unknown3;
uint16_t unknown4;
uint32_t unknown5;
uint32_t unknown6;
uint32_t unknown7;
uint32_t unknown8;
uint32_t unknown9;
uint32_t unknown10;
uint32_t unknown11;
uint32_t unknown12;
uint16_t unknown13;
uint8_t unknown14[???]; // Appears to be a bit mask, sometimes the length is zero, sometimes it's one. (First entry is always zero?)
uint8_t unknown15;
uint32_t unknown16;
uint16_t unknown17;
uint8_t unknown18[???]; // Most of the time this is 4 bytes, other times it is 3 bytes.
};
我在我的 C# 应用程序中重新创建了数据包标头结构,如下所示:
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PacketHeader
{
public UInt16 magic;
public UInt16 packetSize;
public UInt32 unknown1;
public UInt32 unknown2;
public UInt32 unknown3;
public UInt32 unknown4;
public UInt16 unknown5;
public UInt16 unknown6;
public UInt32 unknown7;
public UInt32 unknown8;
public String packetType;
}
然后我尝试为“partystate”标头创建一个结构,但我收到错误消息 fixed
关键字不安全:
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PartyStateHeader
{
UInt32 unknown1;
Byte unknown2;
Byte playerEntryCount;
UInt32 unknown4;
UInt32 unknown5;
UInt32 unknown6;
UInt32 unknown7;
Byte unknown8;
UInt32 unknown9;
UInt16 unknown10;
Byte unknown11;
fixed Byte unknown12[9];
UInt32 unknown13;
UInt32 unknown14;
UInt16 unknown15;
UInt16 unknown16;
fixed UInt32 unknown17[10];
UInt32 unknown18;
UInt32 unknown19;
Byte unknown20;
UInt32 unknown21;
UInt32 unknown22;
UInt32 unknown23;
}
由于 unknown14
和 unknown18
的大小不同,我无法对播放器条目执行任何操作。 (玩家条目是最重要的。)
现在,不知何故,我必须将我拥有的 byte[]
转换为这些 PacketHeader
结构。遗憾的是,这并不像(PacketHeader)bytes
那么容易。我尝试了在互联网上找到的这种方法,但它抛出了 AccessViolationException
:
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader));
我怎样才能实现这一点?
Update: Answers to this question helped me code the open sourced project AlicanC's Modern Warfare 2 Tool on GitHub. You can see how I am reading these packets in MW2Packets.cs and the extensions I've coded to read big endian data in Extensions.cs.
I am capturing UDP packets of Call of Duty: Modern Warfare 2 using Pcap.Net in my C# application. I receive a byte[]
from the library. I tried to parse it like a string, but that didn't work well.
The byte[]
I have has a generic packet header, then another header specific to the packet type then info about each player in the lobby.
A helpful person inspected some packets for me and came up with these structures:
// Fields are big endian unless specified otherwise.
struct packet_header
{
uint16_t magic;
uint16_t packet_size;
uint32_t unknown1;
uint32_t unknown2;
uint32_t unknown3;
uint32_t unknown4;
uint16_t unknown5;
uint16_t unknown6;
uint32_t unknown7;
uint32_t unknown8;
cstring_t packet_type; // \0 terminated string
};
// Fields are little endian unless specified otherwise.
struct header_partystate //Header for the "partystate" packet type
{
uint32_t unknown1;
uint8_t unknown2;
uint8_t player_entry_count;
uint32_t unknown4;
uint32_t unknown5;
uint32_t unknown6;
uint32_t unknown7;
uint8_t unknown8;
uint32_t unknown9;
uint16_t unknown10;
uint8_t unknown11;
uint8_t unknown12[9];
uint32_t unknown13;
uint32_t unknown14;
uint16_t unknown15;
uint16_t unknown16;
uint32_t unknown17[10];
uint32_t unknown18;
uint32_t unknown19;
uint8_t unknown20;
uint32_t unknown21;
uint32_t unknown22;
uint32_t unknown23;
};
// Fields are little endian unless specified otherwise.
struct player_entry
{
uint8_t player_id;
// The following fields may not actually exist in the data if it's an empty entry.
uint8_t unknown1[3];
cstring_t player_name;
uint32_t unknown2;
uint64_t steam_id;
uint32_t internal_ip;
uint32_t external_ip;
uint16_t unknown3;
uint16_t unknown4;
uint32_t unknown5;
uint32_t unknown6;
uint32_t unknown7;
uint32_t unknown8;
uint32_t unknown9;
uint32_t unknown10;
uint32_t unknown11;
uint32_t unknown12;
uint16_t unknown13;
uint8_t unknown14[???]; // Appears to be a bit mask, sometimes the length is zero, sometimes it's one. (First entry is always zero?)
uint8_t unknown15;
uint32_t unknown16;
uint16_t unknown17;
uint8_t unknown18[???]; // Most of the time this is 4 bytes, other times it is 3 bytes.
};
I recreated the packet header structure in my C# application like this:
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PacketHeader
{
public UInt16 magic;
public UInt16 packetSize;
public UInt32 unknown1;
public UInt32 unknown2;
public UInt32 unknown3;
public UInt32 unknown4;
public UInt16 unknown5;
public UInt16 unknown6;
public UInt32 unknown7;
public UInt32 unknown8;
public String packetType;
}
Then I tried to make a structure for the "partystate" header, but I got errors saying fixed
keyword is unsafe:
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct PartyStateHeader
{
UInt32 unknown1;
Byte unknown2;
Byte playerEntryCount;
UInt32 unknown4;
UInt32 unknown5;
UInt32 unknown6;
UInt32 unknown7;
Byte unknown8;
UInt32 unknown9;
UInt16 unknown10;
Byte unknown11;
fixed Byte unknown12[9];
UInt32 unknown13;
UInt32 unknown14;
UInt16 unknown15;
UInt16 unknown16;
fixed UInt32 unknown17[10];
UInt32 unknown18;
UInt32 unknown19;
Byte unknown20;
UInt32 unknown21;
UInt32 unknown22;
UInt32 unknown23;
}
I couldn't do anything for the player entries because of the varying size of unknown14
and unknown18
. (Player entries are the most important.)
Now, somehow, I have to cast the byte[]
I have to these PacketHeader
structures. Sadly, it's not easy as (PacketHeader)bytes
. I tried this method I've found on the internet but it threw an AccessViolationException
:
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader));
How can I achieve this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
//我在以下位置找到了这个: http://code.cheesydesign.com/?p=572 (我还没有测试过,但是
// 乍一看效果很好。)
//I have found this at: http://code.cheesydesign.com/?p=572 (I have not tested yet, but
// at first sight it will work well.)
我会将字节数组转换为内存流。然后在该流上实例化一个二进制读取器。然后定义使用二进制读取器并解析单个类的辅助函数。
内置的
BinaryReader
类始终使用小尾数法。我会在这里使用类而不是结构。
I'd turn the byte array into a memory stream. Then instantiate a binary reader on that stream. And then define helper functions that take a binary reader and parse a single class.
The built in
BinaryReader
class always uses little endian.I'd use classes instead of structs here.
这就是我的做法:
在结构定义中,我没有使用任何固定区域,而是在标准编组不起作用的情况下使用了 MarshalAs 属性。这就是您可能需要的字符串。
您可以像这样使用这个函数:
编辑:
我在代码示例中没有看到您的 BigEndian“限制”。仅当字节为 LittleEndian 时,此解决方案才有效。
编辑2:
在示例的字符串中,您可以用以下内容来装饰它:
在数组中,对于 n 大小的数组,我会使用类似的内容:
This is how i did:
And in the struct definition I didn't use any
fixed
region, instead I used theMarshalAs
attribute if the standard marshalling didn't worked. This is what you will probally need for the string.You would use this function like this:
Edit:
I didn´t see your BigEndian "restriction" in the code example. This solution will only work if the bytes are LittleEndian.
Edit 2:
In the string of your example you would decorate it with:
In the arrays I would go with something like this for a n-sized array:
对于那些有权访问 C# 7.3 功能的人,我使用这段不安全的代码来“序列化”为字节:
非托管
类型可以是一个结构体(没有引用类型的简单结构体,那些被视为托管结构体) ) 或原生类型,例如int
、short
等。For those who have access to C# 7.3 features, I use this piece of unsafe code to "serialize" to bytes:
A
unmanaged
type can be a struct (simple struct without reference types, those a considered managed structs) or a native type such asint
,short
, etc.如果您想要快速的代码而不需要复制,这就是解决方案。我们正在处理原始
byte[]
,只需将指针转换为unsafe
代码,就像在本机 C/C++ 中一样。因此,不会产生调用昂贵的框架方法、制作副本等的开销。对非托管
struct
的任何更改都将反映在托管byte[]
中,反之亦然。< br>当您使用基元类型的
固定
大小的缓冲区,并且需要将其元素作为带有成员的struct
进行处理时,例如ulong
到MyStruct
,均为 64 位长。If you want fast code without copy, this is the solution. We are working here on raw
byte[]
, simply casting the pointer inunsafe
code, as you would in native C / C++. So there is no overhead calling into expensive framework methods, making copies etc. etc.Any change to the unmanaged
struct
will reflect in managedbyte[]
, and vice versa.This can also be used when you are working with primitive-typed
fixed
-size buffers, and need to work on elements thereof asstruct
s with members, e.g.ulong
toMyStruct
, both 64 bits long.嗯,你实际上有两个任务。首先是将 byte[] 本质上解释为结构体,其次是处理可能的不同字节序。
所以,他们有些分歧。 AFAIK,如果你想使用封送处理 - 它只会将字节解释为托管结构。因此,从一种字节序转换为另一种字节序的工作就交给你了。这并不难做到,但不会是自动的。
因此,要将 byte[] 解释为结构,您必须具有类似的内容:
所以前 4 个字节转到 IntValue (1,0,0,0) -> [小端] -> 1
接下来的 3 个字节直接进入数组。
如果你想要 BigEndian,你应该自己做:
它有点混乱,所以可能你最好坚持使用自定义编写的解析器,它从源 byte[] 中逐一获取字节并填充你的数据类没有 StructLayout 和其他本机互操作。
Well, you have two tasks here really. First is to interpret byte[] as struct essentially and second is to deal with possible different endianness.
So, they are somewhat diverge. AFAIK if you want to use marshaling - it will just interpret bytes as if it were managed structure. So converting from one endian to another is left to you. It is not hard to do but it will not be automatic.
So, to interpret byte[] as struct you have to have something like that:
So first 4 bytes go to IntValue (1,0,0,0) -> [little endian] -> 1
Next 3 bytes go directly to array.
If you want BigEndian you should do it yourself:
It is somewhat messy like that, so probably for you will be better to stick with your custom-written parser that takes bytes one-by-one from source byte[] and fill your data class without StructLayout and other native interop.
要将字节数组转换为字符串,请执行以下操作:
并将字符串转换回字节数组
现在读取您的字符串并查看您的数据是什么。
To convert a byte array to a string you do this;
And to convert the string back to a byte array
Now read your string and see what your data is.