#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; }