diff --git a/config.mk b/config.mk index b44e631..2d4f15a 100644 --- a/config.mk +++ b/config.mk @@ -6,7 +6,9 @@ BIN_DIR = bin LIB_NAME = fpk STATIC_LIB = lib$(LIB_NAME).a -SRCS = src/fpk.c +SRCS = \ + src/fpk.c \ + src/fpk_dir.c TOOL_SRCS = \ tools/fpack.c \ diff --git a/include/fpk.h b/include/fpk.h index 2332454..a1a5739 100644 --- a/include/fpk.h +++ b/include/fpk.h @@ -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); 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 } #endif diff --git a/src/fpk_dir.c b/src/fpk_dir.c new file mode 100644 index 0000000..25dd6e2 --- /dev/null +++ b/src/fpk_dir.c @@ -0,0 +1,289 @@ +#if !defined(_WIN32) +#define _FILE_OFFSET_BITS 64 +#define _POSIX_C_SOURCE 200809L +#endif + +#include "fpk.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#define fpk_dir_fseek64(f, off, whence) _fseeki64((f), (off), (whence)) +#define fpk_dir_ftell64(f) _ftelli64((f)) +#else +#include +#include +#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; +}