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)
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>
#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]);
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);
fprintf(stderr, "Deserialize failed: %s\n",
return 1;
}
printf(" password: (hidden, %zu char(s))\n",
fprintf(stderr, "Serialize failed: %s\n",
return 1;
}
printf(
" re-encoded: %zu byte(s)\n", writer.
length);
} else {
printf(" (no decoder for family=%d action=%d)\n", family, action);
}
return 0;
}
static EoResult eo_serialize(const EoSerialize *serialize, EoWriter *writer)
static size_t eo_get_size(const EoSerialize *serialize)
EoWriter eo_writer_init_with_capacity(size_t capacity)
void eo_writer_free(EoWriter *writer)
EoReader eo_reader_init(const uint8_t *data, size_t length)
static EoResult eo_deserialize(EoSerialize *serialize, EoReader *reader)
static void eo_free(EoSerialize *serialize)
LoginRequestClientPacket LoginRequestClientPacket_init(void)
Creates a new LoginRequestClientPacket with the serialization vtable initialized.
const char * eo_result_string(EoResult result)
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>
static const char *item_type_name(
ItemType t) {
switch (t) {
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]);
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);
free(data);
fprintf(stderr, "Failed to parse EIF: %s\n",
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));
if ((
int)item->
type != filter)
continue;
printf(" id=%-5zu type=%-12s name=%s\n",
i, item_type_name(item->
type),
}
return 0;
}
Eif Eif_init(void)
Creates a new Eif with the serialization vtable initialized.
@ ITEM_TYPE_EFFECT_POTION
- 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 = ...;
size_t len = ...;
free(buf);
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.
return result;
}
uint8_t *out = malloc(writer.
length);
EoResult eo_writer_add_short(EoWriter *writer, int32_t value)
EoWriter eo_writer_init(void)
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:
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.
The same pattern applies to pub and map file structs:
- 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