I generally stick to the C/C++ way of doing it first demonstrated by bone-you.
i.e.
Code:
typedef packet0x420 struct {
PACKETHEADER Header;
DWORD Argument;
} MSG_TRADE;
So in C#, my code would look like,
Code:
struct MsgTrade
{
public short Size;
public short Type;
public int Argument;
}
Code:
var msg = new MsgTrade();
msg.Size = sizeof(MsgTrade);
msg.Type = PacketTypes.Trade;
msg.Argument = ivar;
Client.Send(&msg);
And then for any packets that have non-static sizes (for instance, the synchronization, message, etc packet) I implement an interface that a GetBytes() function, and a FromBytes() function.
ConquerAI however uses a packet builder for everything, this system was already set up when I joined the project so I decided to just follow suite instead of changing it to work to my liking.