Embedding game assets within your executable binary

post, Apr 8, 2025, on Mitja Felicijan's blog

Why?

Normally, assets live outside the binary, but there is a valid reason to embed them. Specially if you do not want to deal with reading and parsing external files. This makes development and distribution much easier.

In this example, I'm using raylib and C, but this can also be done with SDL or other libraries.

Code for these notes is available as an embedding-binary-data.tar.xz tarball, but beware that this only includes the Linux build of raylib so please change to appropriate operating system.

You can also check code on GitHub @mitjafelicijan/probe/c-embedding-data.

Project structure

We are going to keep it clean and simple here. I am using pre-build version of raylib just to simplify compilation. All the code is located in the main.c file, so refer to that.

├── data                            Contains assets
│   ├── armor.png                   Image example
│   └── dejavusans-mono.ttf         Font example
├── libs
│   └── raylib-5.5_linux_amd64      Includes prebuild raylib
├── main.c                          Main file
└── Makefile                        Build instruction

Main game loop

We first need to setup main game loop and get the game running.

#include <stdio.h>
#include "raylib.h"

int main(void) {
    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
    InitWindow(900, 400, "Embedding assets");
    SetTargetFPS(60);

    while (!WindowShouldClose()) {
        BeginDrawing();
        ClearBackground(BLACK);
        EndDrawing();
    }

    CloseWindow();
    return 0;
}

And then prepare basic Makefile to compile this.

RAYLIB_VER  := 5.5_linux_amd64
CC          ?= cc
CFLAGS      := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical
LDFLAGS     := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm

game:
	$(CC) $(CFLAGS) -o game main.c $(LDFLAGS)

Now we can compile with make game and get the following.

Basic window

Converting assets to C header files

Here we use xxd which is used to make a hexdump or do the reverse. This tool contains an interesting option -i which allows us to output the file in C include file style. And this is exactly what we need.

Modified Makefile now looks like.

RAYLIB_VER  := 5.5_linux_amd64
CC          ?= cc
CFLAGS      := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical
LDFLAGS     := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm

game: embed
	$(CC) $(CFLAGS) -o game main.c $(LDFLAGS)

embed:
	xxd -i data/armor.png > data/armor.h
	xxd -i data/dejavusans-mono.ttf > data/dejavusans-mono.h

This converted binary data files into C header style files which contain the array of bytes and the size of the array of bytes. This will be useful later with raylib code.

If we execute make embed we will create C header files. But running make game will also call embed as well, so no need to call it separately.

An example of such a file (in our case armor.png) looks like this.

// data/armor.h
unsigned char data_armor_png[] = {
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
    0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x40,
    0x08, 0x06, 0x00, 0x00, 0x00, 0xcd, 0x90, 0xa5, 0xaa, 0x00, 0x00, 0x00,
    0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
    0x00, 0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
    0xa0, 0xbd, 0xa7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
    0x00, 0x00, 0x17, 0x12, 0x00, 0x00, 0x17, 0x12, 0x01, 0x67, 0x9f, 0xd2,
    0x52, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xdc, 0x0c,
    0x1b, 0x07, 0x30, 0x17, 0xb2, 0x1a, 0xee, 0xda, 0x00, 0x00, 0x20, 0x00,
    ...
    0x81, 0x98, 0xa6, 0xb9, 0x2d, 0x37, 0xac, 0x6d, 0x57, 0xb0, 0xed, 0xea,
    0x86, 0xac, 0xa1, 0xa6, 0x6b, 0xf8, 0x54, 0x1f, 0x8e, 0xe3, 0x90, 0xcb,
    0xe6, 0x36, 0x7d, 0x4e, 0x6b, 0xab, 0xcb, 0x78, 0xbd, 0x5e, 0x6c, 0xc7,
    0x01, 0x8f, 0xa7, 0x5e, 0x46, 0x22, 0x37, 0x17, 0x76, 0x13, 0x4d, 0x6c,
    0xab, 0x0c, 0xb0, 0x89, 0x26, 0x9a, 0x68, 0xe2, 0x45, 0xa3, 0x99, 0x2b,
    0x34, 0xd1, 0x44, 0x13, 0xff, 0xb7, 0xf8, 0x27, 0x9b, 0x96, 0x5c, 0x0d,
    0x8b, 0xbb, 0x21, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
    0xae, 0x42, 0x60, 0x82
};
unsigned int data_armor_png_len = 118324;

Embedding and compiling

Now it's time to include this files in our main.c code and use them. raylib's API fits perfectly with this style of converting binary files.

#include <stdio.h>
#include "raylib.h"

#include "data/armor.h"
#include "data/dejavusans-mono.h"

#define FONT_SIZE 24

int main(void) {
    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
    InitWindow(900, 400, "Embedding assets");
    SetTargetFPS(60);

    // Load font from memory.
    Font font = LoadFontFromMemory(".ttf", data_dejavusans_mono_ttf, data_dejavusans_mono_ttf_len, FONT_SIZE, NULL, 0);
    SetTextureFilter(font.texture, TEXTURE_FILTER_TRILINEAR); // Font antialising.

    // Load image from memory and create texture from it.
    Image armor = LoadImageFromMemory(".png", data_armor_png, data_armor_png_len);
    Texture2D armor_texture = LoadTextureFromImage(armor);
    UnloadImage(armor);

    while (!WindowShouldClose()) {
        BeginDrawing();
        ClearBackground(BLACK);

        // Draw the armor texture.
        DrawTexture(armor_texture, 20, 20, WHITE);

        // Draw some text on the screen.
        DrawTextEx(font, "Hello embedded assets.", (Vector2){ 400, 20 }, FONT_SIZE, 0, WHITE);
        DrawTextEx(font, "This is example how we can use embedded fonts.", (Vector2){ 400, 50 }, FONT_SIZE - 4, 0, WHITE);

        EndDrawing();
    }

    UnloadTexture(armor_texture);
    CloseWindow();
    return 0;
}

This makes embedding assets quite straightforward. All we need to do is include header files and then provide byte arrays to the appropriate functions. Like I said raylib has a bunch of FromMemory functions that make this quite easy.

This produces a single binary game that includes both assets, so there is no need to also copy over these assets. Mind you, these binaries can potentially get quite big and if that is the problem, you could always compile this into a so library and include that. This way you could create data packs for audio, graphics, etc. and ship that alongside your game binary.

Run make -B game and run the game.

Basic window

Honorable mention: C23-embed-directive

#embed is a preprocessor directive to include (binary) resources in the build, where a resource is defined as a source of data accessible from the translation environment. This has been introduces with the C23 standard so it's quite new.

Speaking plainly, #embed allows inclusion of binary data in a program executable image, as arrays of unsigned char or other types, without the need for an external script run from a Makefile.

Read more about Binary resource inclusion.

Below is a quick example how to use this new directive.

#include <stdint.h>
#include <stdio.h>

const uint8_t image_data[] = {
    #embed "image.png"
};

int main(void) {
    printf("Image size: %d bytes\n", sizeof(image_data));
    return 0;
}

Credits

Other posts

DateTitle
Create placeholder images with sharp Node.js image processing library
What I've learned developing ad server
Getting started with MicroPython and ESP8266
Golang profiling simplified
The strange case of Elasticsearch allocation failure
Wireless Application Protocol and the mobile web before the web
What would DNA sound if synthesized to an audio file
Using custom software with Github Actions to deploy a site
Simple world clock with eInk display and Raspberry Pi Zero
Profiling Python web applications with visual tools
Embedding game assets within your executable binary