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?
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.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.
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:
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.
//I have found this at: http://code.cheesydesign.com/?p=572 (I have not tested yet, but // at first sight it will work well.)
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.