#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 #ifdef _WIN32 #define PROGRAM_NAME "funpack.exe" #else #define PROGRAM_NAME "funpack" #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'}, {"help", no_argument, NULL, 'h'}, {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; } #ifdef _WIN32 #define IS_PATH_SEP(c) ((c) == '/' || (c) == '\\') #else #define IS_PATH_SEP(c) ((c) == '/') #endif static char *basename_dup(const char *path) { size_t end = strlen(path); size_t start; char *out; while (end > 0 && IS_PATH_SEP(path[end - 1])) end--; start = end; while (start > 0 && !IS_PATH_SEP(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(void) { fprintf(stderr, "usage: %s [-d output_dir] [-l] [-p] [-q] [-v] input.fpk\n", PROGRAM_NAME); fprintf(stderr, "try '%s --help' for more information\n", PROGRAM_NAME); exit(1); } static void help(void) { printf("usage: %s [options] input.fpk\n", PROGRAM_NAME); printf("\n"); printf("Extract files from an fpk archive.\n"); printf("\n"); printf("options:\n"); printf(" -d, --dir=DIR extract into DIR instead of the archive's derived name\n"); printf(" -l, --list list archive entries and exit, without extracting\n"); printf(" -p, --progress print extraction progress\n"); printf(" -q, --quiet suppress the final summary line\n"); printf(" -v, --verbose print each extracted entry\n"); printf(" -h, --help show this help and exit\n"); exit(0); } 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:lpqvh", 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; case 'h': help(); break; default: usage(); } } if (optind != argc - 1) usage(); 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; }