eolib 0.5.0
A core C library for writing applications related to Endless Online
Loading...
Searching...
No Matches
Getting Started

This guide walks you through setting up a development environment, downloading a pre-built release of eolib-c, and building two practical example programs.

Prerequisites & Development Environment

To use the pre-built releases you only need a C11-capable compiler to build your own application. To build eolib-c from source you also need CMake 3.16+, libxml2, and json-c (see Option 2 — Build from source below).

Linux (Ubuntu / Debian)

sudo apt-get update
sudo apt-get install gcc cmake build-essential

macOS

xcode-select --install
brew install cmake

Windows

Choose one of the following toolchains:

MSVC (recommended): Install Visual Studio 2022 Community and select the Desktop development with C++ workload. CMake is bundled with Visual Studio; alternatively install it from https://cmake.org/download/.

MinGW via MSYS2: Install MSYS2, then in an MSYS2 MinGW terminal run:

pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake

Getting the library

Option 1 — Pre-built release (recommended)

Platform Download
Linux x86_64 eolib-0.5.0-Linux.x64.zip
Linux ARM64 eolib-0.5.0-Linux.arm64.zip
macOS Apple Silicon eolib-0.5.0-macOS.arm64.zip
macOS Intel eolib-0.5.0-macOS.x64.zip
Windows MSVC x64 eolib-0.5.0-Windows.VS2022.x64.zip
Windows MSVC x86 eolib-0.5.0-Windows.VS2022.x86.zip
Windows MinGW x64 eolib-0.5.0-Windows.MinGW.x64.zip
Windows MinGW x86 eolib-0.5.0-Windows.MinGW.x86.zip

Each archive includes the C library and the Lua bindings:

eolib-0.5.0-{platform}/
include/
eolib/
eolib.h ...
lib/
cmake/eolib/ (CMake find_package support)
lua/5.4/eolib.so (Lua C module)
libeolib.so (Linux)
libeolib.dylib (macOS)
eolib.lib (Windows MSVC import library)
libeolib.dll.a (Windows MinGW import library)
share/
doc/eolib_c/LICENSE.txt
lua/5.4/eolib.d.lua (LuaLS type definitions)

Use directly from the extracted directory (no install needed):

list(APPEND CMAKE_PREFIX_PATH "/path/to/eolib-{version}-{platform}")
find_package(eolib REQUIRED)
target_link_libraries(myapp PRIVATE eolib::eolib)

Or copy to a system prefix:

# Linux / macOS
cp -r eolib-{version}-{platform}/include/* /usr/local/include/
* cp -r eolib-{version}-{platform}/lib/* /usr/local/lib/
*

Then find_package(eolib REQUIRED) will find it without any CMAKE_PREFIX_PATH.

Manual compilation (without CMake):

Linux / macOS:

gcc main.c -I/path/to/eolib/include \
-L/path/to/eolib/lib -leolib \
-Wl,-rpath,/path/to/eolib/lib \
-o myapp

Windows — MSVC:

cl main.c /I C:\eolib\include ^
/link C:\eolib\lib\eolib.lib ^
/out:myapp.exe

Windows — MinGW:

gcc main.c -I/c/eolib/include -L/c/eolib/lib -leolib -o myapp.exe

Option 2 — Build from source

Requires CMake 3.16+, libxml2, and json-c in addition to the compiler above.

Linux (Ubuntu / Debian):

sudo apt-get install libxml2-dev libjson-c-dev

macOS:

brew install libxml2 json-c

Windows — MSVC: libxml2 and json-c are handled automatically via the bundled vcpkg configuration.

Windows — MinGW via MSYS2:

pacman -S mingw-w64-x86_64-libxml2 mingw-w64-x86_64-json-c

Then clone and build:

git clone --recurse-submodules https://github.com/sorokya/eolib-c.git
cd eolib-c
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install build
Note
On Linux, if the library is not installed in a standard system path, set LD_LIBRARY_PATH (or DYLD_LIBRARY_PATH on macOS) before running your application, or use a -Wl,-rpath flag as shown above.

Example 1 — Packet Inspector

This program reads raw packet bytes from stdin and decodes a LoginRequestClientPacket (family = PACKET_FAMILY_LOGIN = 4, action = PACKET_ACTION_REQUEST = 1). It then re-encodes the packet and prints the byte count.

Note
In the EO wire format the two-byte family/action header is stripped before the packet body is passed to _deserialize. Your network layer is responsible for stripping those two bytes first.

packet_inspect.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <eolib/eolib.h>
#define MAX_PACKET 65535
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <family> <action>\n", argv[0]);
return 1;
}
int family = atoi(argv[1]);
int action = atoi(argv[2]);
// Read the packet body from stdin (family/action bytes already stripped).
uint8_t buf[MAX_PACKET];
size_t len = fread(buf, 1, sizeof(buf), stdin);
if (len == 0) {
fprintf(stderr, "No data on stdin.\n");
return 1;
}
printf("Received %zu byte(s), family=%d, action=%d\n",
len, family, action);
// Decode a Login_Request packet.
if (family == PACKET_FAMILY_LOGIN && action == PACKET_ACTION_REQUEST) {
EoReader reader = eo_reader_init(buf, len);
// _init() sets up the vtable so eo_* dispatch functions work.
EoResult result = eo_deserialize((EoSerialize *)&pkt, &reader);
if (result != EO_SUCCESS) {
fprintf(stderr, "Deserialize failed: %s\n",
eo_result_string(result));
eo_free((EoSerialize *)&pkt);
return 1;
}
printf(" username: %s\n", pkt.username ? pkt.username : "(null)");
printf(" password: (hidden, %zu char(s))\n",
pkt.password ? strlen(pkt.password) : 0);
// Re-encode and report the output size.
// eo_get_size() queries the vtable for the exact serialized size.
eo_get_size((const EoSerialize *)&pkt));
result = eo_serialize((const EoSerialize *)&pkt, &writer);
if (result != EO_SUCCESS) {
fprintf(stderr, "Serialize failed: %s\n",
eo_result_string(result));
eo_free((EoSerialize *)&pkt);
eo_writer_free(&writer);
return 1;
}
printf(" re-encoded: %zu byte(s)\n", writer.length);
eo_writer_free(&writer);
eo_free((EoSerialize *)&pkt);
} else {
printf(" (no decoder for family=%d action=%d)\n", family, action);
}
return 0;
}
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
EoReader eo_reader_init(const uint8_t *data, size_t length)
Definition data.c:834
static EoResult eo_deserialize(EoSerialize *serialize, EoReader *reader)
Definition data.h:414
static void eo_free(EoSerialize *serialize)
Definition data.h:452
@ PACKET_ACTION_REQUEST
Definition protocol.h:200
@ PACKET_FAMILY_LOGIN
Definition protocol.h:146
LoginRequestClientPacket LoginRequestClientPacket_init(void)
Creates a new LoginRequestClientPacket with the serialization vtable initialized.
Definition protocol.c:7152
const char * eo_result_string(EoResult result)
Definition data.c:175
EoResult
Definition result.h:12
@ EO_SUCCESS
Definition result.h:14
size_t length
Definition data.h:27

Compiling:

# Linux / macOS
gcc packet_inspect.c -I/path/to/eolib/include \
-L/path/to/eolib/lib -leolib \
-Wl,-rpath,/path/to/eolib/lib \
-o packet_inspect
# Windows — MSVC
cl packet_inspect.c /I C:\eolib\include ^
/link C:\eolib\lib\eolib.lib /out:packet_inspect.exe
# Windows — MinGW
gcc packet_inspect.c -I/c/eolib/include \
-L/c/eolib/lib -leolib \
-o packet_inspect.exe

Running (Linux/macOS):

# Pipe raw bytes for family=4 (Login), action=1 (Request)
printf '\x06\x07admin\xFFsecret' | ./packet_inspect 4 1

Example 2 — EIF Pub File Reader

This program reads an EIF (Endless Item File) pub file, parses it, and prints items filtered by type.

The Eif struct holds the file header and an array of EifRecord items. The type field of each EifRecord is an ItemType enum value.

pub_reader.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <eolib/eolib.h>
static const char *item_type_name(ItemType t) {
switch (t) {
case ITEM_TYPE_GENERAL: return "General";
case ITEM_TYPE_CURRENCY: return "Currency";
case ITEM_TYPE_HEAL: return "Heal";
case ITEM_TYPE_TELEPORT: return "Teleport";
case ITEM_TYPE_EXP_REWARD: return "ExpReward";
case ITEM_TYPE_KEY: return "Key";
case ITEM_TYPE_WEAPON: return "Weapon";
case ITEM_TYPE_SHIELD: return "Shield";
case ITEM_TYPE_ARMOR: return "Armor";
case ITEM_TYPE_HAT: return "Hat";
case ITEM_TYPE_BOOTS: return "Boots";
case ITEM_TYPE_GLOVES: return "Gloves";
case ITEM_TYPE_ACCESSORY: return "Accessory";
case ITEM_TYPE_BELT: return "Belt";
case ITEM_TYPE_NECKLACE: return "Necklace";
case ITEM_TYPE_RING: return "Ring";
case ITEM_TYPE_ARMLET: return "Armlet";
case ITEM_TYPE_BRACER: return "Bracer";
case ITEM_TYPE_ALCOHOL: return "Alcohol";
case ITEM_TYPE_EFFECT_POTION: return "EffectPotion";
case ITEM_TYPE_HAIR_DYE: return "HairDye";
case ITEM_TYPE_CURE_CURSE: return "CureCurse";
default: return "Unknown";
}
}
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <file.eif> <item_type_int>\n", argv[0]);
fprintf(stderr, " e.g.: %s dat001.eif 10 (10 = Weapon)\n", argv[0]);
return 1;
}
const char *path = argv[1];
int filter = atoi(argv[2]);
// Read the entire file into memory.
FILE *fp = fopen(path, "rb");
if (!fp) {
perror("fopen");
return 1;
}
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
rewind(fp);
uint8_t *data = (uint8_t *)malloc((size_t)file_size);
if (!data) {
fprintf(stderr, "Out of memory.\n");
fclose(fp);
return 1;
}
fread(data, 1, (size_t)file_size, fp);
fclose(fp);
// Deserialize the EIF file.
EoReader reader = eo_reader_init(data, (size_t)file_size);
// _init() sets up the vtable; eo_deserialize/eo_free work on any struct.
Eif eif = Eif_init();
EoResult result = eo_deserialize((EoSerialize *)&eif, &reader);
free(data);
if (result != EO_SUCCESS) {
fprintf(stderr, "Failed to parse EIF: %s\n",
eo_result_string(result));
eo_free((EoSerialize *)&eif);
return 1;
}
printf("EIF loaded: %zu item(s) total\n", eif.items_length);
printf("Listing items of type %d (%s):\n",
filter, item_type_name((ItemType)filter));
for (size_t i = 0; i < eif.items_length; i++) {
const EifRecord *item = &eif.items[i];
if ((int)item->type != filter)
continue;
// Item IDs in EIF are 1-based (index 0 is the placeholder record).
printf(" id=%-5zu type=%-12s name=%s\n",
i, item_type_name(item->type),
item->name ? item->name : "(null)");
}
eo_free((EoSerialize *)&eif);
return 0;
}
Eif Eif_init(void)
Creates a new Eif with the serialization vtable initialized.
Definition protocol.c:1330
ItemType
Definition protocol.h:267
@ ITEM_TYPE_ALCOHOL
Definition protocol.h:290
@ ITEM_TYPE_EFFECT_POTION
Definition protocol.h:291
@ ITEM_TYPE_ACCESSORY
Definition protocol.h:284
@ ITEM_TYPE_ARMOR
Definition protocol.h:280
@ ITEM_TYPE_ARMLET
Definition protocol.h:288
@ ITEM_TYPE_BRACER
Definition protocol.h:289
@ ITEM_TYPE_SHIELD
Definition protocol.h:279
@ ITEM_TYPE_BELT
Definition protocol.h:285
@ ITEM_TYPE_KEY
Definition protocol.h:277
@ ITEM_TYPE_NECKLACE
Definition protocol.h:286
@ ITEM_TYPE_GLOVES
Definition protocol.h:283
@ ITEM_TYPE_CURE_CURSE
Definition protocol.h:293
@ ITEM_TYPE_RING
Definition protocol.h:287
@ ITEM_TYPE_HEAL
Definition protocol.h:271
@ ITEM_TYPE_WEAPON
Definition protocol.h:278
@ ITEM_TYPE_EXP_REWARD
Definition protocol.h:274
@ ITEM_TYPE_HAT
Definition protocol.h:281
@ ITEM_TYPE_CURRENCY
Definition protocol.h:270
@ ITEM_TYPE_GENERAL
Definition protocol.h:268
@ ITEM_TYPE_BOOTS
Definition protocol.h:282
@ ITEM_TYPE_TELEPORT
Definition protocol.h:272
@ ITEM_TYPE_HAIR_DYE
Definition protocol.h:292
char * name
Definition protocol.h:1080
ItemType type
Definition protocol.h:1082
size_t items_length
Definition protocol.h:1147
EifRecord * items
Definition protocol.h:1148
Note
Item index 0 in the items array is a placeholder record and can safely be skipped. Real item IDs start at 1.

Compiling:

# Linux / macOS
gcc pub_reader.c -I/path/to/eolib/include \
-L/path/to/eolib/lib -leolib \
-Wl,-rpath,/path/to/eolib/lib \
-o pub_reader
# Windows — MSVC
cl pub_reader.c /I C:\eolib\include ^
/link C:\eolib\lib\eolib.lib /out:pub_reader.exe
# Windows — MinGW
gcc pub_reader.c -I/c/eolib/include \
-L/c/eolib/lib -leolib \
-o pub_reader.exe

Running:

./pub_reader dat001.eif 10 # list all weapons
./pub_reader dat001.eif 12 # list all armor

Memory Safety

eolib-c uses explicit ownership rules. Following them prevents leaks and use-after-free bugs.

EoReader — borrows its buffer

EoReader is a stack-allocated struct. It does not own the byte buffer you pass to eo_reader_init() — it merely borrows a pointer. You are responsible for keeping that buffer alive for as long as the reader exists, and for freeing it afterwards if necessary.

uint8_t *buf = ...; // your data
size_t len = ...;
EoReader reader = eo_reader_init(buf, len);
// ... read from reader ...
free(buf); // free *your* buffer when you're done; nothing else to do for the reader itself

EoWriter — owns a heap buffer

EoWriter allocates a growing heap buffer internally. Always call eo_writer_free() when you are done — even on error paths.

EoResult result = eo_writer_add_short(&writer, 42);
if (result != EO_SUCCESS) {
eo_writer_free(&writer); // still required before early return
return result;
}
// transfer ownership: copy writer.data before freeing
uint8_t *out = malloc(writer.length);
memcpy(out, writer.data, writer.length);
eo_writer_free(&writer); // always free the writer itself
EoResult eo_writer_add_short(EoWriter *writer, int32_t value)
Definition data.c:469
EoWriter eo_writer_init(void)
Definition data.c:216
uint8_t * data
Definition data.h:26

Protocol structs — init, then free

Every generated packet and data struct has an _init() function (a zero-cost static inline) that returns a properly initialized struct with its internal vtable pointer set. You must call _init() before passing a struct to any eo_* dispatch function.

After initialization, use the eo_* dispatch functions for all operations:

Operation Function
Deserialize eo_deserialize((EoSerialize *)&s, &reader)
Serialize eo_serialize((const EoSerialize *)&s, &writer)
Get serialized size eo_get_size((const EoSerialize *)&s)
Free heap fields eo_free((EoSerialize *)&s)

Always call eo_free() when you are done, including on error paths. Calling it before any heap fields are set is safe — it only frees what was actually allocated.

EoResult result = eo_deserialize((EoSerialize *)&pkt, &reader);
if (result != EO_SUCCESS) {
eo_free((EoSerialize *)&pkt); // safe even on partial deserialization
return result;
}
printf("username: %s\n", pkt.username);
eo_free((EoSerialize *)&pkt); // free when done

The same pattern applies to pub and map file structs:

Eif eif = Eif_init();
EoResult result = eo_deserialize((EoSerialize *)&eif, &reader);
if (result != EO_SUCCESS) {
eo_free((EoSerialize *)&eif);
return result;
}
// ... use eif.items[] ...
Note
If a struct contains only numeric fields (no strings or arrays), its free vtable slot is NULL and eo_free() is a safe no-op. It is always correct to call eo_free() unconditionally.

Next Steps