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.
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.
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.
- I do like this approach, but some compilers might not support this feature, and this is why I will be sticking with manual approach for now.
- One additional drawback is that every time you compile your game, all the assets need to be re-read and converted. I have not tested this heavily, but this could potentially significantly increase build times when you have a lot of assets.
- Not using Make could be detrimental to incremental builds. But this would also need to be tested. I do not claim that this is a real problem. Test this yourself.
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;
}