diff --git a/tools/fpack.c b/tools/fpack.c new file mode 100644 index 0000000..19153d0 --- /dev/null +++ b/tools/fpack.c @@ -0,0 +1,284 @@ +#include "fpk.h" +#include +#include +#include +#include +#include +#include + +#define FPACK_QUIET (1u << 0) +#define FPACK_VERBOSE (1u << 1) +#define FPACK_PROGRESS (1u << 2) + +static unsigned char flags; +static uint64_t entry_count; +static uint64_t total_bytes; + +static struct option long_opts[] = { + {"output", required_argument, NULL, 'o'}, + {"progress", no_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"verbose", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0} +}; + +static char *dup_str(const char *s) +{ + size_t len = strlen(s) + 1; + char *out = malloc(len); + + 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); + + 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 char *basename_dup(const char *path) +{ + size_t end = strlen(path); + size_t start; + char *out; + + while (end > 0 && path[end - 1] == '/') + end--; + + start = end; + while (start > 0 && path[start - 1] != '/') + start--; + + out = malloc(end - start + 1); + memcpy(out, path + start, end - start); + out[end - start] = '\0'; + return out; +} + +static char *derive_output_name(const char *input_dir) +{ + char *base = basename_dup(input_dir); + size_t len = strlen(base); + char *out = malloc(len + 5); + + memcpy(out, base, len); + memcpy(out + len, ".fpk", 5); + free(base); + 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; + long len; + void *buf; + + fp = fopen(path, "rb"); + if (!fp) + return NULL; + + fseek(fp, 0, SEEK_END); + len = ftell(fp); + fseek(fp, 0, SEEK_SET); + + 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_dir(struct fpk_writer *w, const char *dir, size_t base_len) +{ + DIR *d; + struct dirent *ent; + + d = opendir(dir); + if (!d) { + fprintf(stderr, "fpack: cannot open directory: %s\n", dir); + 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 (stat(path, &st) != 0) { + fprintf(stderr, "fpack: cannot stat: %s\n", path); + free(path); + continue; + } + + if (S_ISDIR(st.st_mode)) { + if (!add_dir(w, path, base_len)) { + free(path); + closedir(d); + return 0; + } + } else if (S_ISREG(st.st_mode)) { + const char *rel_path = path + base_len; + void *data; + size_t size; + char *norm; + + while (*rel_path == '/') + rel_path++; + + norm = dup_str(rel_path); + normalize_path(norm); + + data = read_file(path, &size); + if (!data) { + fprintf(stderr, "fpack: cannot read: %s\n", path); + free(norm); + free(path); + closedir(d); + return 0; + } + + if (fpk_writer_add(w, norm, data, size) != 0) { + fprintf(stderr, "fpack: cannot add: %s\n", norm); + free(data); + free(norm); + free(path); + closedir(d); + return 0; + } + + entry_count++; + total_bytes += size; + + if (flags & FPACK_VERBOSE) + printf(" %s (%lu bytes)\n", norm, (unsigned long)size); + + if (flags & FPACK_PROGRESS) { + printf("\rpacked %lu files (%lu bytes)", + (unsigned long)entry_count, + (unsigned long)total_bytes); + fflush(stdout); + } + + free(data); + free(norm); + } + + free(path); + } + + closedir(d); + return 1; +} + +static void usage(const char *name) +{ + fprintf(stderr, "usage: %s [-o output.fpk] [-p] [-q] [-v] input_dir\n", name); + exit(1); +} + +int main(int argc, char **argv) +{ + const char *output_arg = NULL; + const char *input_dir; + char *output; + size_t base_len; + struct fpk_writer *w; + int c; + + while ((c = getopt_long(argc, argv, "o:pqv", long_opts, NULL)) != -1) { + switch (c) { + case 'o': + output_arg = optarg; + break; + case 'p': + flags |= FPACK_PROGRESS; + break; + case 'q': + flags |= FPACK_QUIET; + break; + case 'v': + flags |= FPACK_VERBOSE; + break; + default: + usage(argv[0]); + } + } + + if (optind != argc - 1) + usage(argv[0]); + + input_dir = argv[optind]; + output = output_arg ? dup_str(output_arg) : derive_output_name(input_dir); + + base_len = strlen(input_dir); + while (base_len > 0 && input_dir[base_len - 1] == '/') + base_len--; + + w = fpk_writer_open(output); + if (!w) { + fprintf(stderr, "fpack: cannot create: %s\n", output); + free(output); + return 1; + } + + if (!add_dir(w, input_dir, base_len)) { + fpk_writer_close(w); + free(output); + return 1; + } + + if (entry_count == 0) { + fprintf(stderr, "fpack: no files found in: %s\n", input_dir); + fpk_writer_close(w); + free(output); + return 1; + } + + if (fpk_writer_finalize(w) != 0) { + fprintf(stderr, "fpack: failed to finalize: %s\n", output); + fpk_writer_close(w); + free(output); + return 1; + } + + fpk_writer_close(w); + + if (flags & FPACK_PROGRESS) + printf("\n"); + + if (!(flags & FPACK_QUIET)) + printf("wrote: %s (%lu entries)\n", output, (unsigned long)entry_count); + + free(output); + return 0; +} diff --git a/tools/funpack.c b/tools/funpack.c new file mode 100644 index 0000000..a3793a4 --- /dev/null +++ b/tools/funpack.c @@ -0,0 +1,263 @@ +#include "fpk.h" +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#define MKDIR(path) _mkdir(path) +#else +#define MKDIR(path) mkdir(path, 0755) +#endif + +#define FUNPACK_LIST (1u << 0) +#define FUNPACK_PROGRESS (1u << 1) +#define FUNPACK_QUIET (1u << 2) +#define FUNPACK_VERBOSE (1u << 3) + +static unsigned char flags; + +static struct option long_opts[] = { + {"dir", required_argument, NULL, 'd'}, + {"list", no_argument, NULL, 'l'}, + {"progress", no_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"verbose", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0} +}; + +static char *dup_str(const char *s) +{ + size_t len = strlen(s) + 1; + char *out = malloc(len); + + 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); + + 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 char *basename_dup(const char *path) +{ + size_t end = strlen(path); + size_t start; + char *out; + + while (end > 0 && path[end - 1] == '/') + end--; + + start = end; + while (start > 0 && path[start - 1] != '/') + start--; + + out = malloc(end - start + 1); + memcpy(out, path + start, end - start); + out[end - start] = '\0'; + return out; +} + +static char *derive_output_dir(const char *input) +{ + char *base = basename_dup(input); + size_t len = strlen(base); + + if (len > 4 && strcmp(base + len - 4, ".fpk") == 0) + base[len - 4] = '\0'; + + return base; +} + +static int mkdir_p(const char *path) +{ + char *copy = dup_str(path); + char *p; + + for (p = copy + 1; *p; p++) { + if (*p != '/') + continue; + + *p = '\0'; + if (MKDIR(copy) != 0 && errno != EEXIST) { + fprintf(stderr, "funpack: cannot create directory: %s\n", copy); + free(copy); + return 0; + } + *p = '/'; + } + + if (MKDIR(copy) != 0 && errno != EEXIST) { + fprintf(stderr, "funpack: cannot create directory: %s\n", copy); + free(copy); + return 0; + } + + free(copy); + return 1; +} + +static int extract_entry(const char *out_dir, const fpk_entry_t *entry, + const void *data, size_t size) +{ + char *full_path = join_path(out_dir, entry->path); + char *dir_part = dup_str(full_path); + char *slash = strrchr(dir_part, '/'); + FILE *fp; + + if (slash) { + *slash = '\0'; + if (!mkdir_p(dir_part)) { + free(dir_part); + free(full_path); + return 0; + } + } + free(dir_part); + + fp = fopen(full_path, "wb"); + if (!fp) { + fprintf(stderr, "funpack: cannot create: %s\n", full_path); + free(full_path); + return 0; + } + + if (fwrite(data, 1, size, fp) != size) { + fprintf(stderr, "funpack: write failed: %s\n", full_path); + fclose(fp); + free(full_path); + return 0; + } + + fclose(fp); + free(full_path); + return 1; +} + +static void usage(const char *name) +{ + fprintf(stderr, "usage: %s [-d output_dir] [-l] [-p] [-q] [-v] input.fpk\n", name); + exit(1); +} + +int main(int argc, char **argv) +{ + const char *dir_arg = NULL; + const char *input; + char *out_dir; + struct fpk *pak; + uint64_t count; + uint64_t i; + int c; + + while ((c = getopt_long(argc, argv, "d:lpqv", long_opts, NULL)) != -1) { + switch (c) { + case 'd': + dir_arg = optarg; + break; + case 'l': + flags |= FUNPACK_LIST; + break; + case 'p': + flags |= FUNPACK_PROGRESS; + break; + case 'q': + flags |= FUNPACK_QUIET; + break; + case 'v': + flags |= FUNPACK_VERBOSE; + break; + default: + usage(argv[0]); + } + } + + if (optind != argc - 1) + usage(argv[0]); + + input = argv[optind]; + + pak = fpk_open(input); + if (!pak) { + fprintf(stderr, "funpack: cannot open: %s\n", input); + return 1; + } + + count = fpk_entry_count(pak); + + if (flags & FUNPACK_LIST) { + for (i = 0; i < count; i++) { + const fpk_entry_t *entry = fpk_entry_at(pak, i); + + printf("%s\t%lu bytes\n", entry->path, + (unsigned long)entry->data_size); + } + + fpk_close(pak); + return 0; + } + + out_dir = dir_arg ? dup_str(dir_arg) : derive_output_dir(input); + + if (!mkdir_p(out_dir)) { + fpk_close(pak); + free(out_dir); + return 1; + } + + for (i = 0; i < count; i++) { + const fpk_entry_t *entry = fpk_entry_at(pak, i); + size_t size; + void *data = fpk_read(pak, entry, &size); + + if (!data) { + fprintf(stderr, "funpack: cannot read: %s\n", entry->path); + fpk_close(pak); + free(out_dir); + return 1; + } + + if (!extract_entry(out_dir, entry, data, size)) { + free(data); + fpk_close(pak); + free(out_dir); + return 1; + } + + if (flags & FUNPACK_VERBOSE) + printf(" %s (%lu bytes)\n", entry->path, (unsigned long)size); + + if (flags & FUNPACK_PROGRESS) { + printf("\runpacking %lu/%lu (%lu%%)", + (unsigned long)(i + 1), (unsigned long)count, + (unsigned long)(((i + 1) * 100) / count)); + fflush(stdout); + } + + free(data); + } + + fpk_close(pak); + + if ((flags & FUNPACK_PROGRESS) && count > 0) + printf("\n"); + + if (!(flags & FUNPACK_QUIET)) + printf("unpacked: %s -> %s (%lu entries)\n", input, out_dir, + (unsigned long)count); + + free(out_dir); + return 0; +}