feat: add fpk_writer_add_dir for library-level directory packing

This commit is contained in:
2026-09-16 22:09:03 +02:00
parent cea82dfe50
commit a97b71fea9
3 changed files with 296 additions and 1 deletions
+3 -1
View File
@@ -6,7 +6,9 @@ BIN_DIR = bin
LIB_NAME = fpk LIB_NAME = fpk
STATIC_LIB = lib$(LIB_NAME).a STATIC_LIB = lib$(LIB_NAME).a
SRCS = src/fpk.c SRCS = \
src/fpk.c \
src/fpk_dir.c
TOOL_SRCS = \ TOOL_SRCS = \
tools/fpack.c \ tools/fpack.c \
+4
View File
@@ -56,6 +56,10 @@ int fpk_writer_add(struct fpk_writer *w, const char *path, const void *data,
int fpk_writer_finalize(struct fpk_writer *w); int fpk_writer_finalize(struct fpk_writer *w);
void fpk_writer_close(struct fpk_writer *w); void fpk_writer_close(struct fpk_writer *w);
typedef int (*fpk_add_dir_cb)(const char *path, uint64_t size, void *ctx);
int fpk_writer_add_dir(struct fpk_writer *w, const char *dir_path,
fpk_add_dir_cb on_add, void *ctx);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
+289
View File
@@ -0,0 +1,289 @@
#if !defined(_WIN32)
#define _FILE_OFFSET_BITS 64
#define _POSIX_C_SOURCE 200809L
#endif
#include "fpk.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#include <wchar.h>
#define fpk_dir_fseek64(f, off, whence) _fseeki64((f), (off), (whence))
#define fpk_dir_ftell64(f) _ftelli64((f))
#else
#include <dirent.h>
#include <sys/stat.h>
#define fpk_dir_fseek64(f, off, whence) fseeko((f), (off_t)(off), (whence))
#define fpk_dir_ftell64(f) ftello((f))
#endif
#ifdef _WIN32
#define IS_PATH_SEP(c) ((c) == '/' || (c) == '\\')
#else
#define IS_PATH_SEP(c) ((c) == '/')
#endif
#ifdef _WIN32
static wchar_t *utf8_to_wide(const char *s)
{
int wlen = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
wchar_t *w;
if (wlen <= 0)
return NULL;
w = malloc((size_t)wlen * sizeof(wchar_t));
if (!w)
return NULL;
MultiByteToWideChar(CP_UTF8, 0, s, -1, w, wlen);
return w;
}
static char *wide_to_utf8(const wchar_t *w)
{
int len = WideCharToMultiByte(CP_UTF8, 0, w, -1, NULL, 0, NULL, NULL);
char *s;
if (len <= 0)
return NULL;
s = malloc((size_t)len);
if (!s)
return NULL;
WideCharToMultiByte(CP_UTF8, 0, w, -1, s, len, NULL, NULL);
return s;
}
#endif
static char *dup_str(const char *s)
{
size_t len = strlen(s) + 1;
char *out = malloc(len);
if (out != NULL)
memcpy(out, s, len);
return out;
}
static char *join_path(const char *dir, const char *name)
{
size_t dir_len = strlen(dir);
size_t name_len = strlen(name);
char *out = malloc(dir_len + 1 + name_len + 1);
if (out == NULL)
return NULL;
memcpy(out, dir, dir_len);
out[dir_len] = '/';
memcpy(out + dir_len + 1, name, name_len);
out[dir_len + 1 + name_len] = '\0';
return out;
}
static void normalize_path(char *s)
{
for (; *s; s++) {
if (*s == '\\')
*s = '/';
}
}
static void *read_file(const char *path, size_t *out_size)
{
FILE *fp;
int64_t len;
void *buf;
#ifdef _WIN32
wchar_t *wpath = utf8_to_wide(path);
if (!wpath)
return NULL;
fp = _wfopen(wpath, L"rb");
free(wpath);
#else
fp = fopen(path, "rb");
#endif
if (!fp)
return NULL;
if (fpk_dir_fseek64(fp, 0, SEEK_END) != 0) {
fclose(fp);
return NULL;
}
len = fpk_dir_ftell64(fp);
if (len < 0 || fpk_dir_fseek64(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return NULL;
}
buf = malloc((size_t)len);
if (!buf) {
fclose(fp);
return NULL;
}
if (fread(buf, 1, (size_t)len, fp) != (size_t)len) {
free(buf);
fclose(fp);
return NULL;
}
fclose(fp);
*out_size = (size_t)len;
return buf;
}
static int add_file(struct fpk_writer *w, const char *path, size_t base_len,
fpk_add_dir_cb on_add, void *ctx)
{
const char *rel_path = path + base_len;
void *data;
size_t size;
char *norm;
int ret;
while (*rel_path == '/')
rel_path++;
norm = dup_str(rel_path);
if (norm == NULL)
return 0;
normalize_path(norm);
data = read_file(path, &size);
if (data == NULL) {
free(norm);
return 0;
}
if (fpk_writer_add(w, norm, data, size) != 0) {
free(data);
free(norm);
return 0;
}
ret = on_add == NULL || on_add(norm, (uint64_t)size, ctx) != 0;
free(data);
free(norm);
return ret;
}
#ifdef _WIN32
static int add_dir(struct fpk_writer *w, const char *dir, size_t base_len,
fpk_add_dir_cb on_add, void *ctx)
{
char *pattern = join_path(dir, "*");
wchar_t *wpattern;
WIN32_FIND_DATAW fd;
HANDLE h;
if (pattern == NULL)
return 0;
wpattern = utf8_to_wide(pattern);
free(pattern);
if (wpattern == NULL)
return 0;
h = FindFirstFileW(wpattern, &fd);
free(wpattern);
if (h == INVALID_HANDLE_VALUE)
return 0;
do {
char *name = wide_to_utf8(fd.cFileName);
char *path;
if (name == NULL || name[0] == '.') {
free(name);
continue;
}
path = join_path(dir, name);
free(name);
if (path == NULL) {
FindClose(h);
return 0;
}
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (!add_dir(w, path, base_len, on_add, ctx)) {
free(path);
FindClose(h);
return 0;
}
} else if (!add_file(w, path, base_len, on_add, ctx)) {
free(path);
FindClose(h);
return 0;
}
free(path);
} while (FindNextFileW(h, &fd));
FindClose(h);
return 1;
}
#else
static int add_dir(struct fpk_writer *w, const char *dir, size_t base_len,
fpk_add_dir_cb on_add, void *ctx)
{
DIR *d;
struct dirent *ent;
d = opendir(dir);
if (d == NULL)
return 0;
while ((ent = readdir(d)) != NULL) {
char *path;
struct stat st;
if (ent->d_name[0] == '.')
continue;
path = join_path(dir, ent->d_name);
if (path == NULL) {
closedir(d);
return 0;
}
if (stat(path, &st) != 0) {
free(path);
continue;
}
if (S_ISDIR(st.st_mode)) {
if (!add_dir(w, path, base_len, on_add, ctx)) {
free(path);
closedir(d);
return 0;
}
} else if (S_ISREG(st.st_mode)) {
if (!add_file(w, path, base_len, on_add, ctx)) {
free(path);
closedir(d);
return 0;
}
}
free(path);
}
closedir(d);
return 1;
}
#endif
int fpk_writer_add_dir(struct fpk_writer *w, const char *dir_path,
fpk_add_dir_cb on_add, void *ctx)
{
size_t base_len = strlen(dir_path);
while (base_len > 0 && IS_PATH_SEP(dir_path[base_len - 1]))
base_len--;
return add_dir(w, dir_path, base_len, on_add, ctx) ? 0 : -1;
}