Have you ever needed to package a set of files with a binary executable, but found yourself struggling to keep track of multiple files and directories? Or perhaps you’ve wanted to create self-extracting archives that can be run without needing an external program? If so, then zoe may be just what you need.

Intro

zoe is a simple C library that allows you to access a zip file that has been appended to an executable. This means that your binary can include all the files it needs to function, without needing to rely on external files or archives. And because the zip file is appended to the end of the executable, it’s all contained within a single file. Underneath zoe is the wonderful miniz library should you need to perform more technical zip file work.

For my first use case, I created zoe to help me package sound performance and audio samples with my rototem sound tool. Using zoe, rototem can now find and read the files contained within the appended zip file, without needing to reference any external file. This made it much easier to distribute the rototem, as I only needed to send a single file.

But the usefulness of zoe doesn’t stop there. Because the zip file is attached to the executable, you can use command-line utilities like the zip tool to add, remove, or update the files within the archive. This makes it easy to keep your files up-to-date, and allows users of the binary to do so themselves.

Library overview

Here’s a run-down of the zoe library’s functions:

char *zoe_self(char *exename);
zoe_t *zoe_open(char *zipname);
void zoe_close(zoe_t *zoe);
int zoe_files(zoe_t *zoe);
char *zoe_name_at(zoe_t *zoe, int i);
int zoe_find(zoe_t *zoe, char *name);
zoe_file_t *zoe_fopen_at(zoe_t *zoe, int i);
void zoe_fclose(zoe_file_t *zfile);
#define ZOE_FILE(z) (z->mem)
  • zoe_self resolves a full pathname to the exename provided. This enables zoe to open the attached zip file regardless of where it’s located and without troubles due to symbolic filesystem links.
  • zoe_open finds the appended zip file and returns a zoe_t structure, which is used for further operations.
  • zoe_close frees any allocations created by zoe_open.
  • zoe_files returns the number of files in the appended zip file.
  • zoe_name_at returns the filename using an index number (0 to the number returned from zoe_files()-1).
  • zoe_find returns the index of a file with the given name, or -1 if not found.
  • zoe_fopen_at opens a file in the appended zip using an index number and returns a zoe_file_t structure that contains a FILE pointer that can be used with the C buffered I/O library, along with the provided ZOE_FILE macro.
  • zoe_fclose closes the file opened by zoe_fopen_at.

Usage

Using zoe is straightforward. Simply call zoe_open with a full path to your executable), and zoe will validate and open the appended zip file. You can then use the other functions to retrieve information about the files in the archive, or to open and read the files themselves.

Clone zoe and build it like this

git clone https://github.com/octetta/zoe.git
pushd zoe; make; popd

Create example.c for learning like

// example.c
#include <stdio.h>
#include "zoe/zoe.h"
int main(int argc, char *argv[]) {
    zoe_t *z = zoe_open(zoe_self(argv[0]));
    int n = zoe_files(z);
    printf("%d files in %s\n", n, argv[0]);
    for (int i=0; i<n; i++)
        printf("#%d -> %s\n", i, zoe_name_at(z, i));
    zoe_close(z);
    return 0;
}

Build and run the example without any attached zip file like

$ gcc example.c zoe/libzoe.a -o example
$ ./example
0 files in ./example

Add a starting .zip file like

$ echo "this is the first archive file" > first
$ zip first.zip first
  adding: first (deflated 6%)
$ cat example first.zip > example.zip
$ chmod +x example.zip
$ ./example.zip
1 files in ./example.zip
#0 -> first (size:31)

Add new file to the example.zip binary like

$ zip -A example.zip example.c
Zip entry offsets appear off by 113576 bytes - correcting...
  adding: example.c (deflated 40%)
$ ./example.zip
2 files in ./example.zip
#0 -> first (size:31)
#1 -> example.c (size:358)

Update an existing file in example.zip like

$ echo "updating the first archive file" > first
$ zip -A example.zip first
Zip entry offsets do not need adjusting
updating: first (deflated 3%)
$ ./example.zip
2 files in ./example.zip
#0 -> first (size:32)
#1 -> example.c (size:358)

Remove a file from example.zip like

$ zip -A -d example.zip example.c
Zip entry offsets do not need adjusting
deleting: example.c
$ ./example.zip
1 files in ./example.zip
#0 -> first (size:32)

Where to find out more about zoe

zoe is on my GitHub page at https://github.com/octetta/zoe.git .

If you want to discuss zoe, DISCORD INFO HERE.

Wrapping up

Overall, I’m happy with the results of my work on zoe. It’s a lightweight and versatile library that has already made my life much easier.

If you’re looking for a way to package files with your executable I highly recommend giving zoe a try.