1---
  2title: Embedding game assets within your executable binary
  3url: embedding-game-assets-within-your-binary.html
  4date: 2025-04-08T16:13:13+02:00
  5type: post
  6draft: false
  7tags: []
  8---
  9
 10## Why?
 11
 12Normally, assets live outside the binary, but there is a valid reason to embed
 13them. Specially if you do not want to deal with reading and parsing external
 14files. This makes development and distribution much easier.
 15
 16In this example, I'm using [raylib](https://github.com/raysan5/raylib) and C,
 17but this can also be done with [SDL](https://github.com/libsdl-org/SDL) or
 18other libraries.
 19
 20Code for these notes is available as an
 21[embedding-binary-data.tar.xz](/assets/notes/embedding-binary-data.tar.xz)
 22tarball, but beware that this only includes the Linux build of raylib so please
 23change to appropriate operating system.
 24
 25You can also check code on GitHub
 26[@mitjafelicijan/probe/c-embedding-data](https://github.com/mitjafelicijan/probe/tree/master/c-embedding-data).
 27
 28## Project structure
 29
 30We are going to keep it clean and simple here. I am using pre-build version of
 31raylib just to simplify compilation. All the code is located in the `main.c`
 32file, so refer to that.
 33
 34```
 35├── data                            Contains assets
 36│   ├── armor.png                   Image example
 37│   └── dejavusans-mono.ttf         Font example
 38├── libs
 39│   └── raylib-5.5_linux_amd64      Includes prebuild raylib
 40├── main.c                          Main file
 41└── Makefile                        Build instruction
 42```
 43
 44## Main game loop
 45
 46We first need to setup main game loop and get the game running.
 47
 48```c
 49#include <stdio.h>
 50#include "raylib.h"
 51
 52int main(void) {
 53    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
 54    InitWindow(900, 400, "Embedding assets");
 55    SetTargetFPS(60);
 56
 57    while (!WindowShouldClose()) {
 58        BeginDrawing();
 59        ClearBackground(BLACK);
 60        EndDrawing();
 61    }
 62
 63    CloseWindow();
 64    return 0;
 65}
 66```
 67
 68And then prepare basic `Makefile` to compile this.
 69
 70```make
 71RAYLIB_VER  := 5.5_linux_amd64
 72CC          ?= cc
 73CFLAGS      := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical
 74LDFLAGS     := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm
 75
 76game:
 77	$(CC) $(CFLAGS) -o game main.c $(LDFLAGS)
 78```
 79
 80Now we can compile with `make game` and get the following.
 81
 82![Basic window](/assets/notes/embedding-window.png)
 83
 84## Converting assets to C header files
 85
 86Here we use [`xxd`](https://linux.die.net/man/1/xxd) which is used to make a
 87hexdump or do the reverse. This tool contains an interesting option `-i` which
 88allows us to output the file in C include file style. And this is exactly what
 89we need.
 90
 91Modified `Makefile` now looks like.
 92
 93```make
 94RAYLIB_VER  := 5.5_linux_amd64
 95CC          ?= cc
 96CFLAGS      := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical
 97LDFLAGS     := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm
 98
 99game: embed
100	$(CC) $(CFLAGS) -o game main.c $(LDFLAGS)
101
102embed:
103	xxd -i data/armor.png > data/armor.h
104	xxd -i data/dejavusans-mono.ttf > data/dejavusans-mono.h
105```
106
107This converted binary data files into C header style files which contain the
108array of bytes and the size of the array of bytes. This will be useful later
109with raylib code.
110
111If we execute `make embed` we will create C header files. But running `make
112game` will also call embed as well, so no need to call it separately.
113
114An example of such a file (in our case armor.png) looks like this.
115
116```c
117// data/armor.h
118unsigned char data_armor_png[] = {
119    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
120    0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x40,
121    0x08, 0x06, 0x00, 0x00, 0x00, 0xcd, 0x90, 0xa5, 0xaa, 0x00, 0x00, 0x00,
122    0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
123    0x00, 0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
124    0xa0, 0xbd, 0xa7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
125    0x00, 0x00, 0x17, 0x12, 0x00, 0x00, 0x17, 0x12, 0x01, 0x67, 0x9f, 0xd2,
126    0x52, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xdc, 0x0c,
127    0x1b, 0x07, 0x30, 0x17, 0xb2, 0x1a, 0xee, 0xda, 0x00, 0x00, 0x20, 0x00,
128    ...
129    0x81, 0x98, 0xa6, 0xb9, 0x2d, 0x37, 0xac, 0x6d, 0x57, 0xb0, 0xed, 0xea,
130    0x86, 0xac, 0xa1, 0xa6, 0x6b, 0xf8, 0x54, 0x1f, 0x8e, 0xe3, 0x90, 0xcb,
131    0xe6, 0x36, 0x7d, 0x4e, 0x6b, 0xab, 0xcb, 0x78, 0xbd, 0x5e, 0x6c, 0xc7,
132    0x01, 0x8f, 0xa7, 0x5e, 0x46, 0x22, 0x37, 0x17, 0x76, 0x13, 0x4d, 0x6c,
133    0xab, 0x0c, 0xb0, 0x89, 0x26, 0x9a, 0x68, 0xe2, 0x45, 0xa3, 0x99, 0x2b,
134    0x34, 0xd1, 0x44, 0x13, 0xff, 0xb7, 0xf8, 0x27, 0x9b, 0x96, 0x5c, 0x0d,
135    0x8b, 0xbb, 0x21, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
136    0xae, 0x42, 0x60, 0x82
137};
138unsigned int data_armor_png_len = 118324;
139```
140
141## Embedding and compiling
142
143Now it's time to include this files in our `main.c` code and use them. raylib's
144API fits perfectly with this style of converting binary files.
145
146```c
147#include <stdio.h>
148#include "raylib.h"
149
150#include "data/armor.h"
151#include "data/dejavusans-mono.h"
152
153#define FONT_SIZE 24
154
155int main(void) {
156    SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI);
157    InitWindow(900, 400, "Embedding assets");
158    SetTargetFPS(60);
159
160    // Load font from memory.
161    Font font = LoadFontFromMemory(".ttf", data_dejavusans_mono_ttf, data_dejavusans_mono_ttf_len, FONT_SIZE, NULL, 0);
162    SetTextureFilter(font.texture, TEXTURE_FILTER_TRILINEAR); // Font antialising.
163
164    // Load image from memory and create texture from it.
165    Image armor = LoadImageFromMemory(".png", data_armor_png, data_armor_png_len);
166    Texture2D armor_texture = LoadTextureFromImage(armor);
167    UnloadImage(armor);
168
169    while (!WindowShouldClose()) {
170        BeginDrawing();
171        ClearBackground(BLACK);
172
173        // Draw the armor texture.
174        DrawTexture(armor_texture, 20, 20, WHITE);
175
176        // Draw some text on the screen.
177        DrawTextEx(font, "Hello embedded assets.", (Vector2){ 400, 20 }, FONT_SIZE, 0, WHITE);
178        DrawTextEx(font, "This is example how we can use embedded fonts.", (Vector2){ 400, 50 }, FONT_SIZE - 4, 0, WHITE);
179
180        EndDrawing();
181    }
182
183    UnloadTexture(armor_texture);
184    CloseWindow();
185    return 0;
186}
187```
188
189This makes embedding assets quite straightforward. All we need to do is include
190header files and then provide byte arrays to the appropriate functions. Like I
191said raylib has a bunch of `FromMemory` functions that make this quite easy.
192
193This produces a single binary `game` that includes both assets, so there is no
194need to also copy over these assets. Mind you, these binaries can potentially
195get quite big and if that is the problem, you could always compile this into a
196`so` library and include that. This way you could create data packs for audio,
197graphics, etc. and ship that alongside your game binary.
198
199Run `make -B game` and run the game.
200
201![Basic window](/assets/notes/embedding-assets.png)
202
203## Honorable mention: C23-embed-directive
204
205`#embed` is a preprocessor directive to include (binary) resources in the
206build, where a resource is defined as a source of data accessible from the
207translation environment. This has been introduces with the C23 standard so it's
208quite new.
209
210Speaking plainly, `#embed` allows inclusion of binary data in a program
211executable image, as arrays of unsigned char or other types, without the need
212for an external script run from a Makefile.
213
214- I do like this approach, but some compilers might not support this feature,
215  and this is why I will be sticking with manual approach for now.
216- One additional drawback is that every time you compile your game, all the
217  assets need to be re-read and converted. I have not tested this heavily, but
218  this could potentially significantly increase build times when you have a lot
219  of assets.
220- Not using Make could be detrimental to incremental builds. But this would
221  also need to be tested. I do not claim that this is a real problem. Test this
222  yourself.
223
224Read more about [Binary resource
225inclusion](https://en.cppreference.com/w/c/preprocessor/embed).
226
227Below is a quick example how to use this new directive.
228
229```c
230#include <stdint.h>
231#include <stdio.h>
232
233const uint8_t image_data[] = {
234    #embed "image.png"
235};
236
237int main(void) {
238    printf("Image size: %d bytes\n", sizeof(image_data));
239    return 0;
240}
241```
242
243## Credits
244
245- https://opengameart.org/content/armor-icons-by-equipment-slot-with-transparency
246- https://dejavu-fonts.github.io/