将字节数组转换为托管结构

| 更新:这个问题的答案帮助我在GitHub上编写了开源项目AlicanC \'s Modern Warfare 2 Tool。您可以在MW2Packets.cs中看到我如何读取这些数据包,以及在Extensions.cs中我已编码为读取大字节序数据的扩展。 我正在C#应用程序中使用Pcap.Net捕获《使命召唤:现代战争2》的UDP数据包。我从图书馆收到a0ѭ。我试图像字符串一样解析它,但是效果不佳。 我有一个“ 0”,它有一个通用的数据包标题,然后是另一个特定于数据包类型的标题,然后是有关大厅中每个玩家的信息。 一个乐于助人的人为我检查了一些数据包,并提出了以下结构:
// 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
的大小各不相同,因此我无法为玩家输入任何内容。 (玩家条目是最重要的。) 现在,以某种方式,我必须将“ 0”强制转换为这些“ 9”结构。可悲的是,要
(PacketHeader)bytes
并不容易。我尝试了我在互联网上发现的这种方法,但是却花了11英镑:
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader));
我该如何实现?     
已邀请:
        我会将字节数组转换为内存流。然后在该流上实例化一个二进制读取器。然后定义使用二进制读取器并解析单个类的辅助函数。 内置的
BinaryReader
类始终使用小字节序。 我会在这里使用类而不是结构。
class PacketHeader 
{
    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;
    string packet_type; // replaced with a real string
};

PacketHeader ReadPacketHeader(BinaryReader reader)
{
  var result=new PacketHeader();
  result.magic = reader.ReadInt16();
  ...
  result.packet_type=ReadCString();//Some helper function you might need to define yourself
  return result;
}
    
        //我在以下网址找到了它:http://code.cheesydesign.com/?p=572(我尚未测试,但是 //一见钟情。)
    /// <summary>
    /// Reads in a block from a file and converts it to the struct
    /// type specified by the template parameter
    /// </summary>
    /// <typeparam name=\"T\"></typeparam>
    /// <param name=\"reader\"></param>
    /// <returns></returns>
    private static T FromBinaryReader<T>(BinaryReader reader)
    {

        // Read in a byte array
        byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));

        // Pin the managed memory while, copy it out the data, then unpin it
        GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();

        return theStructure;
    }
    
        这是我做的:
using System;
using System.Runtime.InteropServices;
public static object GetObjectFromBytes(byte[] buffer, Type objType)
{
    object obj = null;
    if ((buffer != null) && (buffer.Length > 0))
    {
        IntPtr ptrObj = IntPtr.Zero;
        try
        {
            int objSize = Marshal.SizeOf(objType);
            if (objSize > 0)
            {
                if (buffer.Length < objSize)
                    throw new Exception(String.Format(\"Buffer smaller than needed for creation of object of type {0}\", objType));
                ptrObj = Marshal.AllocHGlobal(objSize);
                if (ptrObj != IntPtr.Zero)
                {
                    Marshal.Copy(buffer, 0, ptrObj, objSize);
                    obj = Marshal.PtrToStructure(ptrObj, objType);
                }
                else
                    throw new Exception(String.Format(\"Couldn\'t allocate memory to create object of type {0}\", objType));
            }
        }
        finally
        {
            if (ptrObj != IntPtr.Zero)
                Marshal.FreeHGlobal(ptrObj);
        }
    }
    return obj;
}
而且在结构定义中,我没有使用任何
fixed
区域,相反,如果标准编组不起作用,我将使用
MarshalAs
属性。这就是您可能需要的字符串。 您将使用以下功能:
PacketHeader ph = (PacketHeader)GetObjectFromBytes(buffer, typeof(PacketHeader));
编辑: 在代码示例中,我没有看到您的BigEndian \“ restriction \”。仅当字节为LittleEndian时,此解决方案才有效。 编辑2: 在示例字符串中,您可以使用以下命令装饰它:
[MarshalAs(UnmanagedType.LPStr)]
在数组中,我将对n大小的数组进行如下处理:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = n)]
    
        好吧,您实际上有两个任务。首先是本质上将byte []解释为结构,其次是处理可能的不同字节序。 因此,它们有些不同。 AFAIK如果要使用封送处理-它将仅将字节解释为托管结构。因此,从一个字节序转换为另一字节序留给您。这并不难,但不会自动完成。 因此,要将byte []解释为struct,您必须具有以下内容:
[StructLayout(LayoutKind.Sequential)]
internal struct X
{
    public int IntValue;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U1)] 
    public byte[] Array;
}

static void Main(string[] args)
{
    byte[] data = {1, 0, 0, 0, 9, 8, 7}; // IntValue = 1, Array = {9,8,7}
    IntPtr ptPoit = Marshal.AllocHGlobal(data.Length);
    Marshal.Copy(data, 0, ptPoit, data.Length);
    var x = (X) Marshal.PtrToStructure(ptPoit, typeof (X));
    Marshal.FreeHGlobal(ptPoit);

    Console.WriteLine(\"x.IntValue = {0}\", x.IntValue);
    Console.WriteLine(\"x.Array = ({0}, {1}, {2})\", x.Array[0], x.Array[1], x.Array[2]);
}
因此,前4个字节进入IntValue(1,0,0,0)-> [little endian]-> 1 接下来的3个字节直接进入数组。 如果您想要BigEndian,则应该自己做:
int LittleToBigEndian(int littleEndian)
{
    byte[] buf = BitConverter.GetBytes(littleEndian).Reverse().ToArray();
    return BitConverter.ToInt32(buf, 0);
}
这样有点混乱,因此对于您来说,最好还是坚持使用自定义编写的解析器,该解析器从源byte []处一个接一个地获取字节,并在不使用StructLayout和其他本机互操作的情况下填充数据类。     
        我的方法是不同的。我不想复制任何字节。 我只是想使用它们,修改其中的一些,并在其他位置使用更改后的byte []数组作为byte []。 在研究google和stackoverflow之后,我决定进入不安全/已修复的状态。 在那里玩代码,我发现没有复制的快速代码。 这是DEBUG / TEST代码。在调试模式下进行检查。 请记住,这样一来您就不会进行复制,并且正在处理原始byte []数据。 结构的任何更改都将反映在byte []数组更改中,反之亦然。 ++测试++工作
//FOR DEBUG/TEST ONLY
using System.Runtime.InteropServices;
namespace ByteStructCast1
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        unsafe struct StructTest//4B
        {
            [MarshalAs(UnmanagedType.U2)]
            public ushort item1;//2B
            public fixed byte item2[2];//2B =2x 1B
        }
        static void Main(string[] args)
        {
            //managed byte array
            byte[] DB1 = new byte[7];//7B more than we need. byte buffer usually is greater.
            DB1[0] = 2;//test data |> LITTLE ENDIAN
            DB1[1] = 0;//test data |
            DB1[2] = 3;//test data
            DB1[3] = 4;//test data
            unsafe //OK we are going to pin unmanaged struct to managed byte array
            {
                fixed(byte* db1 = DB1) //db1 is pinned pointer to DB1 byte[] array
                {
                    //StructTest t1 = *(StructTest*)db1;    //does not change DB1/db1
                    //t1.item1 = 11;                        //does not change DB1/db1
                    db1[0] = 22;                            //does CHANGE DB1/db1
                    DB1[0] = 33;                            //does CHANGE DB1/db1
                    StructTest* ptest = (StructTest*)db1;   //does CHANGE DB1/db1
                    ptest->item1 = 44;                      //does CHANGE DB1/db1
                    ptest->item2[0]++;                      //does CHANGE DB1/db1
                    ptest->item2[1]--;                      //does CHANGE DB1/db1
                }
            }
        }
    }
}
    
        对于那些有权使用C#7.3功能的人,我使用这段不安全的代码来“串行化”到字节:
public static class Serializer
{
    public static unsafe byte[] Serialize<T>(T value) where T : unmanaged
    {
        byte[] buffer = new byte[sizeof(T)];

        fixed (byte* bufferPtr = buffer)
        {
            Buffer.MemoryCopy(&value, bufferPtr, sizeof(T), sizeof(T));
        }

        return buffer;
    }

    public static unsafe T Deserialize<T>(byte[] buffer) where T : unmanaged
    {
        T result = new T();

        fixed (byte* bufferPtr = buffer)
        {
            Buffer.MemoryCopy(bufferPtr, &result, sizeof(T), sizeof(T));
        }

        return result;
    }
}
unmanaged
类型可以是结构(没有引用类型的简单结构,那些是托管结构)或本机类型,例如
int
short
等。     
        要将字节数组转换为字符串,请执行以下操作:
byte [] dBytes = ...
string str;
System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
str = enc.GetString(dBytes);
并将字符串转换回字节数组
public static byte[] StrToByteArray(string str)
{
    System.Text.UTF8Encoding  encoding=new System.Text.UTF8Encoding();
    return encoding.GetBytes(str);
}
现在阅读您的字符串,看看您的数据是什么。     

要回复问题请先登录注册