Transmitindo uma matriz de bytes para uma estrutura gerenciada

Atualização: As respostas a esta pergunta me ajudaram a codificar a ferramenta Modern Warfare 2 do AlicanC no GitHub . Você pode ver como estou lendo esses pacotes em MW2Packets.cs e as extensões que codifiquei para ler dados big endian em Extensions.cs .

Eu estou capturando pacotes UDP de Call of Duty: Modern Warfare 2 usando Pcap.Net no meu aplicativo c #. Eu recebo um byte[] da biblioteca. Eu tentei analisá-lo como uma corda, mas isso não funcionou bem.

O byte[] eu tenho tem um header de pacote genérico, depois outro header específico para o tipo de pacote e informações sobre cada jogador no lobby.

Uma pessoa prestativa inspecionou alguns pacotes para mim e elaborou essas estruturas:

 // 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. }; 

Eu recriou a estrutura do header do pacote no meu aplicativo c # como este:

 [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; } 

Então tentei criar uma estrutura para o header “partystate”, mas recebi erros dizendo que a palavra-chave fixed é insegura:

 [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; } 

Eu não pude fazer nada pelas inputs dos jogadores por causa do tamanho variável de unknown14 e unknown18 . (Entradas de jogadores são as mais importantes.)

Agora, de alguma forma, tenho que converter o byte[] que tenho para essas estruturas PacketHeader . Infelizmente, não é fácil como (PacketHeader)bytes . Eu tentei esse método que encontrei na internet, mas ele lançou um AccessViolationException :

 GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader)); 

Como posso conseguir isso?

    Eu transformaria a matriz de bytes em um stream de memory. Em seguida, instancie um leitor binário nesse stream. Em seguida, defina as funções auxiliares que usam um leitor binário e analisam uma única class.

    O construído na class BinaryReader sempre usa little endian.

    Eu usaria classs em vez de estruturas aqui.

     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; } 

    // Eu encontrei isso em: http://code.cheesydesign.com/?p=572 (Eu não testei ainda, mas // à primeira vista ele irá funcionar bem.)

      ///  /// Reads in a block from a file and converts it to the struct /// type specified by the template parameter ///  ///  ///  ///  private static T FromBinaryReader(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; } 

    Foi assim que eu fiz:

     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; } 

    E na definição struct não usei nenhuma região fixed , em vez disso, usei o atributo MarshalAs se o padrão de empacotamento não funcionasse. Isto é o que você provavelmente precisará para a string.

    Você usaria essa function assim:

     PacketHeader ph = (PacketHeader)GetObjectFromBytes(buffer, typeof(PacketHeader)); 

    Edit: Eu não vi sua "restrição" BigEndian no exemplo de código. Esta solução só funcionará se os bytes forem LittleEndian.

    Edit 2: Na string do seu exemplo, você decoraria com:

     [MarshalAs(UnmanagedType.LPStr)] 

    Nas matrizes eu iria com algo assim para uma matriz de tamanho n:

     [MarshalAs(UnmanagedType.ByValArray, SizeConst = n)] 

    Bem, você tem duas tarefas aqui realmente. O primeiro é interpretar o byte [] como struct essencialmente e o segundo é lidar com o possível endianness diferente.

    Então, eles são um pouco divergentes. AFAIK se você quiser usar o empacotamento – ele apenas interpretará os bytes como se fossem estruturas gerenciadas. Então, converter de um endian para outro é deixado para você. Não é difícil de fazer, mas não será automático.

    Então, para interpretar o byte [] como struct você precisa ter algo assim:

     [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]); } 

    Então, os primeiros 4 bytes vão para IntValue (1,0,0,0) -> [little endian] -> 1 Next 3 bytes vão diretamente para o array.

    Se você quiser BigEndian você deve fazer você mesmo:

     int LittleToBigEndian(int littleEndian) { byte[] buf = BitConverter.GetBytes(littleEndian).Reverse().ToArray(); return BitConverter.ToInt32(buf, 0); } 

    É um pouco bagunçado assim, então provavelmente você será melhor ficar com seu analisador escrito customizado que toma bytes um por um do byte de origem [] e preenche sua class de dados sem StructLayout e outra interoperabilidade nativa.

    Minha abordagem é diferente. Eu não queria copiar nenhum byte.
    Eu só queria usá-los, modificar alguns deles e usar o array byte [] alterado em outro lugar como byte [].
    Depois de cavar o google e stackoverflow eu decidi entrar em inseguro / fixo.
    Lá brincando com código eu encontrei código rápido sem copiar.
    Este é o código DEBUG / TEST. Verifique isso no modo de debugging.
    Lembre-se que desta forma você não faz uma cópia e está trabalhando em dados brutos de byte [].
    Qualquer alteração na estrutura refletirá na alteração de matriz byte [] e vice-versa.
    ++ TESTED ++ WORKS

     //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 } } } } } 

    Para aqueles que têm access a resources do C # 7.3, eu uso esse pedaço de código não seguro para “serializar” para bytes:

     public static class Serializer { public static unsafe byte[] Serialize(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(byte[] buffer) where T : unmanaged { T result = new T(); fixed (byte* bufferPtr = buffer) { Buffer.MemoryCopy(bufferPtr, &result, sizeof(T), sizeof(T)); } return result; } } 

    Um tipo unmanaged pode ser uma estrutura (estrutura simples sem tipos de referência, aquelas consideradas estruturas gerenciadas) ou um tipo nativo como int , short , etc.

    Para converter uma matriz de bytes em uma string, faça isso;

     byte [] dBytes = ... string str; System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding(); str = enc.GetString(dBytes); 

    E para converter a string de volta para uma matriz de bytes

     public static byte[] StrToByteArray(string str) { System.Text.UTF8Encoding encoding=new System.Text.UTF8Encoding(); return encoding.GetBytes(str); } 

    Agora leia sua string e veja quais são seus dados.