eolib 0.5.0
A core C library for writing applications related to Endless Online
Loading...
Searching...
No Matches
Protocol Struct Interface

Every generated protocol struct and packet type implements the EoSerialize interface (and packets also implement EoPacket). This gives a uniform API for all ~400 generated types without individual exported serialize/deserialize/free symbols per type.

Initialization

Every type has a static inline TypeName_init() function declared in protocol.h. It returns a zero-initialized struct with its internal vtable pointer already set:

Coords coords = Coords_init(); // plain struct
Eif eif = Eif_init(); // pub/map file struct
Eif Eif_init(void)
Creates a new Eif with the serialization vtable initialized.
Definition protocol.c:1330
LoginRequestClientPacket LoginRequestClientPacket_init(void)
Creates a new LoginRequestClientPacket with the serialization vtable initialized.
Definition protocol.c:7152
Coords Coords_init(void)
Creates a new Coords with the serialization vtable initialized.
Definition protocol.c:20
Note
Never use = {0} to initialize a generated struct. That zeroes the vtable pointer, which will cause a null-pointer dereference the first time any eo_* function is called on it.

Dispatch Functions

All operations go through the EoSerialize vtable via the following inline dispatch helpers declared in data.h:

Function Description
eo_deserialize() Read fields from an EoReader
eo_serialize() Write fields to an EoWriter
eo_get_size() Return the exact serialized byte count
eo_free() Release any heap-allocated fields

All four accept a pointer-to-EoSerialize as their first argument. Cast any generated struct or packet to (EoSerialize *) (or (const EoSerialize *) for read-only operations):

// Deserialize
EoResult r = eo_deserialize((EoSerialize *)&pkt, &reader);
// Query size
size_t sz = eo_get_size((const EoSerialize *)&pkt);
// Serialize
eo_serialize((const EoSerialize *)&pkt, &writer);
// Free heap fields when done
eo_writer_free(&writer);
static EoResult eo_serialize(const EoSerialize *serialize, EoWriter *writer)
Definition data.h:428
static size_t eo_get_size(const EoSerialize *serialize)
Definition data.h:440
EoWriter eo_writer_init_with_capacity(size_t capacity)
Definition data.c:221
void eo_writer_free(EoWriter *writer)
Definition data.c:231
static EoResult eo_deserialize(EoSerialize *serialize, EoReader *reader)
Definition data.h:414
static void eo_free(EoSerialize *serialize)
Definition data.h:452
EoResult
Definition result.h:12

Packet-Specific Dispatch

Packet types additionally implement EoPacket, which carries a second vtable with get_family and get_action slots. Use the packet dispatch helpers to query the hard-coded family/action values without inspecting enum constants manually:

uint8_t family = eo_packet_get_family((const EoPacket *)&pkt);
uint8_t action = eo_packet_get_action((const EoPacket *)&pkt);
// family == PACKET_FAMILY_LOGIN, action == PACKET_ACTION_REQUEST
static uint8_t eo_packet_get_action(const EoPacket *packet)
Gets the action of an EO packet.
Definition data.h:492
static uint8_t eo_packet_get_family(const EoPacket *packet)
Gets the family of an EO packet.
Definition data.h:480

Writing Generic Code

Because every struct shares the EoSerialize base, you can write helpers that operate on any struct without knowing its concrete type:

// Serialize any struct to a newly-allocated byte buffer.
static uint8_t *to_bytes(const EoSerialize *s, size_t *out_len) {
size_t sz = eo_get_size(s);
if (eo_serialize(s, &writer) != EO_SUCCESS) {
eo_writer_free(&writer);
return NULL;
}
uint8_t *buf = malloc(writer.length);
if (buf) {
memcpy(buf, writer.data, writer.length);
*out_len = writer.length;
}
eo_writer_free(&writer);
return buf;
}
// Usage with any generated type:
pkt.username = "Admin";
pkt.password = "hunter2";
size_t len;
uint8_t *bytes = to_bytes((const EoSerialize *)&pkt, &len);
// use bytes...
free(bytes);
@ EO_SUCCESS
Definition result.h:14
size_t length
Definition data.h:27
uint8_t * data
Definition data.h:26

Memory Layout

The C "inheritance by first-member" rule guarantees the casts above are safe. The generated struct layout is:

// Plain struct (e.g. Coords):
typedef struct Coords {
EoSerialize _eo; // vtable pointer — must be first
int32_t x;
int32_t y;
// Packet (e.g. LoginRequestClientPacket):
typedef struct LoginRequestClientPacket {
EoPacket _eo; // EoPacket.base is EoSerialize — must be first
char *username;
char *password;
int32_t x
Definition protocol.h:755
EoSerialize _eo
Definition protocol.h:754
int32_t y
Definition protocol.h:756

Since _eo is always the first member, a pointer to the struct and a pointer to its _eo field have the same address. For packets the chain is LoginRequestClientPacketEoPacketEoSerialize, so (EoSerialize *)&pkt resolves all three levels correctly.

Note
The _eo member is an implementation detail. Do not read or write it directly — always use the _init() function and the eo_* dispatch helpers.