c# - Casting una matriz de bytes a una estructura administrada
bytearray structure (7)
// Lo he encontrado en: http://code.cheesydesign.com/?p=572 (no lo he probado todavía, pero // a primera vista funcionará bien).
/// <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;
}
Actualización: Las respuestas a esta pregunta me ayudaron a codificar el proyecto de código abierto AlicanC''s Modern Warfare 2 Tool en GitHub . Puede ver cómo estoy leyendo estos paquetes en MW2Packets.cs y las extensiones que he codificado para leer datos de big endian en Extensions.cs .
Estoy capturando paquetes UDP de Call of Duty: Modern Warfare 2 usando Pcap.Net en mi aplicación C #. Recibo un byte[]
de la biblioteca. Traté de analizarlo como una cuerda, pero eso no funcionó bien.
El byte[]
Tengo un encabezado de paquete genérico, luego otro encabezado específico para el tipo de paquete y luego información sobre cada jugador en el lobby.
Una persona útil inspeccionó algunos paquetes para mí y creó estas estructuras:
// 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.
};
Recreé la estructura del encabezado del paquete en mi aplicación C # de esta manera:
[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;
}
Luego traté de hacer una estructura para el encabezado "partystate", pero obtuve errores diciendo que la palabra clave fixed
no es segura:
[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;
}
No pude hacer nada por las entradas de los jugadores debido al tamaño variable de unknown14
y unknown18
. (Las entradas de los jugadores son las más importantes.)
Ahora, de alguna manera, tengo que lanzar el byte[]
que tengo a estas estructuras PacketHeader
. Lamentablemente, no es tan fácil como (PacketHeader)bytes
. Intenté este método que encontré en Internet, pero arrojé una AccessViolationException
:
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
PacketHeader packetHeader = (PacketHeader)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PacketHeader));
¿Cómo puedo conseguir esto?
Así es como lo hice:
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;
}
Y en la definición de la estructura no MarshalAs
ninguna región fixed
, en lugar de eso, utilicé el atributo MarshalAs
si el ordenamiento estándar no funcionó. Esto es lo que probablemente necesitarás para la cadena.
Usarías esta función así:
PacketHeader ph = (PacketHeader)GetObjectFromBytes(buffer, typeof(PacketHeader));
Edit: No vi tu "restricción" de BigEndian en el ejemplo de código. Esta solución solo funcionará si los bytes son LittleEndian.
Edición 2: En la cadena de tu ejemplo, lo decorarías con:
[MarshalAs(UnmanagedType.LPStr)]
En las matrices iría con algo como esto para una matriz de tamaño n:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = n)]
Bueno, tienes dos tareas aquí realmente. Primero es interpretar el byte [] como una estructura esencialmente y el segundo es tratar con posibles endianías diferentes.
Por lo tanto, son algo divergentes. AFAIK si desea utilizar el cálculo de referencias, solo interpretará los bytes como si fuera una estructura administrada. Así que la conversión de un endian a otro se deja a usted. No es difícil de hacer pero no será automático.
Entonces, para interpretar el byte [] como una estructura, debes tener algo así:
[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]);
}
Así que los primeros 4 bytes van a IntValue (1,0,0,0) -> [little endian] -> 1 Los siguientes 3 bytes van directamente a la matriz.
Si quieres BigEndian debes hacerlo tú mismo:
int LittleToBigEndian(int littleEndian)
{
byte[] buf = BitConverter.GetBytes(littleEndian).Reverse().ToArray();
return BitConverter.ToInt32(buf, 0);
}
Es un tanto desordenado así, así que probablemente para ti será mejor seguir con tu analizador personalizado que toma bytes uno por uno desde el byte de origen [] y rellena tu clase de datos sin StructLayout y otras interoperaciones nativas.
Me gustaría convertir la matriz de bytes en una secuencia de memoria. Luego crea una instancia de un lector binario en esa secuencia. Y luego defina las funciones de ayuda que toman un lector binario y analizan una sola clase.
La clase incorporada en BinaryReader
siempre usa little endian.
Yo usaría clases en lugar de estructuras aquí.
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;
}
Mi enfoque es diferente. No quería copiar ningún byte.
Solo quería usarlos, modificar algunos de ellos y usar la matriz de byte [] modificada en otro lugar como byte [].
Después de excavar google y , decidí ir a inseguro / arreglado.
Allí jugando con el código he encontrado código rápido sin copia.
Este es el código DEBUG / TEST. Compruebe esto en el modo de depuración.
Recuerde que de esta manera no hace una copia y está trabajando en datos de byte [] en bruto.
Cualquier cambio en la estructura se reflejará en el cambio de la matriz del byte [] y viceversa.
++ 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 aquellos que tienen acceso a las funciones de C # 7.3, utilizo este código inseguro para "serializar" a bytes:
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;
}
}
Un tipo unmanaged
puede ser una estructura (estructura simple sin tipos de referencia, aquellas consideradas estructuras administradas) o un tipo nativo como int
, short
, etc.
Para convertir una matriz de bytes en una cadena, haga esto;
byte [] dBytes = ...
string str;
System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
str = enc.GetString(dBytes);
Y para convertir la cadena de nuevo a una matriz de bytes.
public static byte[] StrToByteArray(string str)
{
System.Text.UTF8Encoding encoding=new System.Text.UTF8Encoding();
return encoding.GetBytes(str);
}
Ahora lea su cadena y vea cuáles son sus datos.