Compare commits
13
Commits
bfba351ac2
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9569e7c37a | ||
|
|
430e4f3e68 | ||
|
|
ffc283cd7e | ||
|
|
cce89bb6e4 | ||
|
|
ba77b1d938 | ||
|
|
a97b71fea9 | ||
|
|
cea82dfe50 | ||
|
|
e72df334ef | ||
|
|
fd34aa1956 | ||
|
|
1286fac03f | ||
|
|
a327a7f85c | ||
|
|
d9997184fd | ||
|
|
01aebb3f31 |
@@ -6,3 +6,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Renamed the `fpack`/`funpack` CLI tools into a single `fpk` binary
|
||||||
|
taking `create`/`extract`/`list` subcommands (tar-style, with
|
||||||
|
`c`/`x`/`t` aliases).
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-09-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `fpk_writer_add_dir()` for library-level recursive directory packing.
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-09-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `FPK1` archive format: flat header/data-blobs/string-table/directory layout,
|
||||||
|
fixed little-endian on disk.
|
||||||
|
- Reader API: `fpk_open`/`fpk_close`/`fpk_find`/`fpk_read`/`fpk_entry_count`/
|
||||||
|
`fpk_entry_at`.
|
||||||
|
- Writer API: `fpk_writer_open`/`fpk_writer_add`/`fpk_writer_finalize`/
|
||||||
|
`fpk_writer_close`.
|
||||||
|
- `fpack`/`funpack` CLI tools with full flag sets (`-o`/`-p`/`-q`/`-v`/`-h` and
|
||||||
|
`-d`/`-l`/`-p`/`-q`/`-v`/`-h` respectively).
|
||||||
|
- Graceful Ctrl+C handling in `fpack`, cleaning up partial output.
|
||||||
|
- Elapsed-time reporting on `fpack`/`funpack` completion.
|
||||||
|
- Wide-char Windows filename support in `funpack`.
|
||||||
|
- 64-bit seek/tell throughout, avoiding 32-bit truncation on large archives.
|
||||||
|
|||||||
@@ -14,17 +14,20 @@ build.bat debug release tools # Windows
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fpack [-o output.fpk] [-p] [-q] [-v] input_dir
|
fpk create [-o output.fpk] [-p] [-q] [-v] input_dir
|
||||||
funpack [-d output_dir] [-l] [-p] [-q] [-v] input.fpk
|
fpk extract [-d output_dir] [-p] [-q] [-v] input.fpk
|
||||||
|
fpk list input.fpk
|
||||||
```
|
```
|
||||||
|
|
||||||
`fpack` walks `input_dir` recursively (skipping dotfiles) and writes an
|
`create` (alias `c`) walks `input_dir` recursively (skipping dotfiles) and
|
||||||
archive. If `-o` is omitted, the output name is derived from the input
|
writes an archive. If `-o` is omitted, the output name is derived from the
|
||||||
directory's basename plus `.fpk` (e.g. `assets/` -> `assets.fpk`).
|
input directory's basename plus `.fpk` (e.g. `assets/` -> `assets.fpk`).
|
||||||
|
|
||||||
`funpack` extracts (or, with `-l`, lists) the entries in `input.fpk`. If
|
`extract` (alias `x`) extracts the entries in `input.fpk`. If `-d` is
|
||||||
`-d` is omitted, the output directory is derived from the archive's
|
omitted, the output directory is derived from the archive's basename with
|
||||||
basename with `.fpk` stripped (e.g. `assets.fpk` -> `assets/`).
|
`.fpk` stripped (e.g. `assets.fpk` -> `assets/`).
|
||||||
|
|
||||||
|
`list` (alias `t`) prints the entries in `input.fpk` without extracting.
|
||||||
|
|
||||||
Common flags: `-p` show progress, `-q` suppress the summary line, `-v`
|
Common flags: `-p` show progress, `-q` suppress the summary line, `-v`
|
||||||
print each entry as it's processed.
|
print each entry as it's processed.
|
||||||
|
|||||||
@@ -6,8 +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/fpk.c
|
||||||
tools/funpack.c
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#define FPK_VERSION_H
|
#define FPK_VERSION_H
|
||||||
|
|
||||||
#define FPK_VERSION_MAJOR 0
|
#define FPK_VERSION_MAJOR 0
|
||||||
#define FPK_VERSION_MINOR 1
|
#define FPK_VERSION_MINOR 2
|
||||||
#define FPK_VERSION_PATCH 0
|
#define FPK_VERSION_PATCH 0
|
||||||
|
|
||||||
#define FPK_STRINGIFY_HELPER(x) #x
|
#define FPK_STRINGIFY_HELPER(x) #x
|
||||||
|
|||||||
+289
@@ -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;
|
||||||
|
}
|
||||||
+58
@@ -3,6 +3,17 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <direct.h>
|
||||||
|
#define MKDIR(path) _mkdir(path)
|
||||||
|
#define RMDIR(path) _rmdir(path)
|
||||||
|
#else
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define MKDIR(path) mkdir(path, 0755)
|
||||||
|
#define RMDIR(path) rmdir(path)
|
||||||
|
#endif
|
||||||
|
|
||||||
#define CHECK(cond, msg) \
|
#define CHECK(cond, msg) \
|
||||||
do { \
|
do { \
|
||||||
if (!(cond)) { \
|
if (!(cond)) { \
|
||||||
@@ -13,6 +24,14 @@
|
|||||||
|
|
||||||
static const char *PAK_PATH = "roundtrip.fpk";
|
static const char *PAK_PATH = "roundtrip.fpk";
|
||||||
|
|
||||||
|
static void write_file(const char *path, const char *data)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(path, "wb");
|
||||||
|
|
||||||
|
fwrite(data, 1, strlen(data), fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
struct fpk_writer *w;
|
struct fpk_writer *w;
|
||||||
@@ -66,6 +85,45 @@ int main(void)
|
|||||||
fpk_close(r);
|
fpk_close(r);
|
||||||
remove(PAK_PATH);
|
remove(PAK_PATH);
|
||||||
|
|
||||||
|
CHECK(MKDIR("adddir_src") == 0, "mkdir(adddir_src) failed");
|
||||||
|
CHECK(MKDIR("adddir_src/nested") == 0, "mkdir(adddir_src/nested) failed");
|
||||||
|
write_file("adddir_src/root.txt", "root");
|
||||||
|
write_file("adddir_src/nested/leaf.txt", "leaf");
|
||||||
|
|
||||||
|
w = fpk_writer_open("adddir.fpk");
|
||||||
|
CHECK(w != NULL, "fpk_writer_open(adddir) failed");
|
||||||
|
CHECK(fpk_writer_add_dir(w, "adddir_src", NULL, NULL) == 0,
|
||||||
|
"fpk_writer_add_dir failed");
|
||||||
|
CHECK(fpk_writer_finalize(w) == 0, "fpk_writer_finalize(adddir) failed");
|
||||||
|
fpk_writer_close(w);
|
||||||
|
|
||||||
|
r = fpk_open("adddir.fpk");
|
||||||
|
CHECK(r != NULL, "fpk_open(adddir) failed");
|
||||||
|
CHECK(fpk_entry_count(r) == 2, "adddir entry_count mismatch");
|
||||||
|
|
||||||
|
entry = fpk_find(r, "root.txt");
|
||||||
|
CHECK(entry != NULL, "fpk_find(root.txt) failed");
|
||||||
|
data = fpk_read(r, entry, &out_size);
|
||||||
|
CHECK(data != NULL, "fpk_read(root.txt) failed");
|
||||||
|
CHECK(out_size == 4 && memcmp(data, "root", 4) == 0,
|
||||||
|
"root.txt content mismatch");
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
entry = fpk_find(r, "nested/leaf.txt");
|
||||||
|
CHECK(entry != NULL, "fpk_find(nested/leaf.txt) failed");
|
||||||
|
data = fpk_read(r, entry, &out_size);
|
||||||
|
CHECK(data != NULL, "fpk_read(nested/leaf.txt) failed");
|
||||||
|
CHECK(out_size == 4 && memcmp(data, "leaf", 4) == 0,
|
||||||
|
"nested/leaf.txt content mismatch");
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
fpk_close(r);
|
||||||
|
remove("adddir.fpk");
|
||||||
|
remove("adddir_src/root.txt");
|
||||||
|
remove("adddir_src/nested/leaf.txt");
|
||||||
|
RMDIR("adddir_src/nested");
|
||||||
|
RMDIR("adddir_src");
|
||||||
|
|
||||||
printf("PASS\n");
|
printf("PASS\n");
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
-305
@@ -1,305 +0,0 @@
|
|||||||
#if !defined(_WIN32)
|
|
||||||
#define _FILE_OFFSET_BITS 64
|
|
||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "fpk.h"
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define PROGRAM_NAME "fpack.exe"
|
|
||||||
#define fpack_fseek64(f, off, whence) _fseeki64((f), (off), (whence))
|
|
||||||
#define fpack_ftell64(f) _ftelli64((f))
|
|
||||||
#else
|
|
||||||
#define PROGRAM_NAME "fpack"
|
|
||||||
#define fpack_fseek64(f, off, whence) fseeko((f), (off_t)(off), (whence))
|
|
||||||
#define fpack_ftell64(f) ftello((f))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#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;
|
|
||||||
int64_t len;
|
|
||||||
void *buf;
|
|
||||||
|
|
||||||
fp = fopen(path, "rb");
|
|
||||||
if (!fp)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (fpack_fseek64(fp, 0, SEEK_END) != 0) {
|
|
||||||
fclose(fp);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
len = fpack_ftell64(fp);
|
|
||||||
if (len < 0 || fpack_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_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(void)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "usage: %s [-o output.fpk] [-p] [-q] [-v] input_dir\n", PROGRAM_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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (optind != argc - 1)
|
|
||||||
usage();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
+674
@@ -0,0 +1,674 @@
|
|||||||
|
#if !defined(_WIN32)
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "fpk.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <direct.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
#else
|
||||||
|
#include <time.h>
|
||||||
|
#define MKDIR(path) mkdir(path, 0755)
|
||||||
|
#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 int win_mkdir(const char *path)
|
||||||
|
{
|
||||||
|
wchar_t *wpath = utf8_to_wide(path);
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
if (!wpath)
|
||||||
|
return -1;
|
||||||
|
ok = CreateDirectoryW(wpath, NULL);
|
||||||
|
free(wpath);
|
||||||
|
if (ok)
|
||||||
|
return 0;
|
||||||
|
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||||
|
errno = EEXIST;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#define MKDIR(path) win_mkdir(path)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static double now_seconds(void)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
LARGE_INTEGER freq, count;
|
||||||
|
|
||||||
|
QueryPerformanceFrequency(&freq);
|
||||||
|
QueryPerformanceCounter(&count);
|
||||||
|
return (double)count.QuadPart / (double)freq.QuadPart;
|
||||||
|
#else
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void format_duration(double secs, char *buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
unsigned long total_ms = (unsigned long)(secs * 1000.0 + 0.5);
|
||||||
|
unsigned long ms = total_ms % 1000;
|
||||||
|
unsigned long total_s = total_ms / 1000;
|
||||||
|
unsigned long s = total_s % 60;
|
||||||
|
unsigned long total_m = total_s / 60;
|
||||||
|
unsigned long m = total_m % 60;
|
||||||
|
unsigned long total_h = total_m / 60;
|
||||||
|
unsigned long h = total_h % 24;
|
||||||
|
unsigned long d = total_h / 24;
|
||||||
|
|
||||||
|
if (d > 0)
|
||||||
|
sprintf(buf, "%lud %luh %lum %lus", d, h, m, s);
|
||||||
|
else if (h > 0)
|
||||||
|
sprintf(buf, "%luh %lum %lus", h, m, s);
|
||||||
|
else if (m > 0)
|
||||||
|
sprintf(buf, "%lum %lu.%03lus", m, s, ms);
|
||||||
|
else
|
||||||
|
sprintf(buf, "%lu.%03lus", s, ms);
|
||||||
|
buf[bufsize - 1] = '\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 volatile sig_atomic_t interrupted;
|
||||||
|
|
||||||
|
static void handle_sigint(int sig)
|
||||||
|
{
|
||||||
|
(void)sig;
|
||||||
|
interrupted = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- create ---- */
|
||||||
|
|
||||||
|
#define CREATE_QUIET (1u << 0)
|
||||||
|
#define CREATE_VERBOSE (1u << 1)
|
||||||
|
#define CREATE_PROGRESS (1u << 2)
|
||||||
|
|
||||||
|
static unsigned char create_flags;
|
||||||
|
static uint64_t create_entry_count;
|
||||||
|
static uint64_t create_total_bytes;
|
||||||
|
|
||||||
|
static struct option create_long_opts[] = {
|
||||||
|
{"output", required_argument, NULL, 'o'},
|
||||||
|
{"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 *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 int on_add(const char *path, uint64_t size, void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
create_entry_count++;
|
||||||
|
create_total_bytes += size;
|
||||||
|
|
||||||
|
if (create_flags & CREATE_VERBOSE)
|
||||||
|
printf(" %s (%lu bytes)\n", path, (unsigned long)size);
|
||||||
|
|
||||||
|
if (create_flags & CREATE_PROGRESS) {
|
||||||
|
printf("\rpacked %lu files (%lu bytes)",
|
||||||
|
(unsigned long)create_entry_count, (unsigned long)create_total_bytes);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !interrupted;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage: fpk create [-o output.fpk] [-p] [-q] [-v] input_dir\n");
|
||||||
|
fprintf(stderr, "try 'fpk create --help' for more information\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_help(void)
|
||||||
|
{
|
||||||
|
printf("usage: fpk create [options] input_dir\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("Pack a directory tree into an fpk archive.\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("options:\n");
|
||||||
|
printf(" -o, --output=FILE write the archive to FILE instead of the derived name\n");
|
||||||
|
printf(" -p, --progress print packing progress\n");
|
||||||
|
printf(" -q, --quiet suppress the final summary line\n");
|
||||||
|
printf(" -v, --verbose print each packed entry\n");
|
||||||
|
printf(" -h, --help show this help and exit\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_create(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *output_arg = NULL;
|
||||||
|
const char *input_dir;
|
||||||
|
char *output;
|
||||||
|
struct fpk_writer *w;
|
||||||
|
int c;
|
||||||
|
double start_time;
|
||||||
|
|
||||||
|
while ((c = getopt_long(argc, argv, "o:pqvh", create_long_opts, NULL)) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 'o':
|
||||||
|
output_arg = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
create_flags |= CREATE_PROGRESS;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
create_flags |= CREATE_QUIET;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
create_flags |= CREATE_VERBOSE;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
create_help();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
create_usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind != argc - 1)
|
||||||
|
create_usage();
|
||||||
|
|
||||||
|
input_dir = argv[optind];
|
||||||
|
output = output_arg ? dup_str(output_arg) : derive_output_name(input_dir);
|
||||||
|
|
||||||
|
start_time = now_seconds();
|
||||||
|
|
||||||
|
w = fpk_writer_open(output);
|
||||||
|
if (!w) {
|
||||||
|
fprintf(stderr, "fpk: cannot create: %s\n", output);
|
||||||
|
free(output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal(SIGINT, handle_sigint);
|
||||||
|
|
||||||
|
if (fpk_writer_add_dir(w, input_dir, on_add, NULL) != 0) {
|
||||||
|
fpk_writer_close(w);
|
||||||
|
if (interrupted) {
|
||||||
|
remove(output);
|
||||||
|
fprintf(stderr, "\nfpk: interrupted, removed incomplete archive: %s\n", output);
|
||||||
|
free(output);
|
||||||
|
return 130;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "fpk: failed while packing: %s\n", input_dir);
|
||||||
|
free(output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (create_entry_count == 0) {
|
||||||
|
fprintf(stderr, "fpk: no files found in: %s\n", input_dir);
|
||||||
|
fpk_writer_close(w);
|
||||||
|
free(output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fpk_writer_finalize(w) != 0) {
|
||||||
|
fprintf(stderr, "fpk: failed to finalize: %s\n", output);
|
||||||
|
fpk_writer_close(w);
|
||||||
|
free(output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fpk_writer_close(w);
|
||||||
|
|
||||||
|
if (create_flags & CREATE_PROGRESS)
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
if (!(create_flags & CREATE_QUIET)) {
|
||||||
|
char dur[64];
|
||||||
|
|
||||||
|
format_duration(now_seconds() - start_time, dur, sizeof(dur));
|
||||||
|
printf("wrote: %s (%lu entries) in %s\n", output,
|
||||||
|
(unsigned long)create_entry_count, dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(output);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- extract ---- */
|
||||||
|
|
||||||
|
#define EXTRACT_PROGRESS (1u << 0)
|
||||||
|
#define EXTRACT_QUIET (1u << 1)
|
||||||
|
#define EXTRACT_VERBOSE (1u << 2)
|
||||||
|
|
||||||
|
static unsigned char extract_flags;
|
||||||
|
|
||||||
|
static struct option extract_long_opts[] = {
|
||||||
|
{"dir", required_argument, NULL, 'd'},
|
||||||
|
{"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 *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, "fpk: cannot create directory: %s\n", copy);
|
||||||
|
free(copy);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MKDIR(copy) != 0 && errno != EEXIST) {
|
||||||
|
fprintf(stderr, "fpk: 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);
|
||||||
|
FILE *fp;
|
||||||
|
#ifdef _WIN32
|
||||||
|
wchar_t *wpath;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (strchr(entry->path, '/') != NULL) {
|
||||||
|
char *dir_part = dup_str(full_path);
|
||||||
|
char *slash = strrchr(dir_part, '/');
|
||||||
|
|
||||||
|
*slash = '\0';
|
||||||
|
if (!mkdir_p(dir_part)) {
|
||||||
|
free(dir_part);
|
||||||
|
free(full_path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
free(dir_part);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
wpath = utf8_to_wide(full_path);
|
||||||
|
fp = wpath ? _wfopen(wpath, L"wb") : NULL;
|
||||||
|
free(wpath);
|
||||||
|
#else
|
||||||
|
fp = fopen(full_path, "wb");
|
||||||
|
#endif
|
||||||
|
if (!fp) {
|
||||||
|
fprintf(stderr, "fpk: cannot create: %s\n", full_path);
|
||||||
|
free(full_path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fwrite(data, 1, size, fp) != size) {
|
||||||
|
fprintf(stderr, "fpk: write failed: %s\n", full_path);
|
||||||
|
fclose(fp);
|
||||||
|
free(full_path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
free(full_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void extract_usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage: fpk extract [-d output_dir] [-p] [-q] [-v] input.fpk\n");
|
||||||
|
fprintf(stderr, "try 'fpk extract --help' for more information\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void extract_help(void)
|
||||||
|
{
|
||||||
|
printf("usage: fpk extract [options] input.fpk\n");
|
||||||
|
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(" -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_extract(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *dir_arg = NULL;
|
||||||
|
const char *input;
|
||||||
|
char *out_dir;
|
||||||
|
struct fpk *pak;
|
||||||
|
uint64_t count;
|
||||||
|
uint64_t i = 0;
|
||||||
|
int c;
|
||||||
|
double start_time;
|
||||||
|
|
||||||
|
while ((c = getopt_long(argc, argv, "d:pqvh", extract_long_opts, NULL)) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 'd':
|
||||||
|
dir_arg = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
extract_flags |= EXTRACT_PROGRESS;
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
extract_flags |= EXTRACT_QUIET;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
extract_flags |= EXTRACT_VERBOSE;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
extract_help();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
extract_usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind != argc - 1)
|
||||||
|
extract_usage();
|
||||||
|
|
||||||
|
input = argv[optind];
|
||||||
|
|
||||||
|
start_time = now_seconds();
|
||||||
|
|
||||||
|
pak = fpk_open(input);
|
||||||
|
if (!pak) {
|
||||||
|
fprintf(stderr, "fpk: cannot open: %s\n", input);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = fpk_entry_count(pak);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal(SIGINT, handle_sigint);
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
const fpk_entry_t *entry = fpk_entry_at(pak, i);
|
||||||
|
size_t size;
|
||||||
|
void *data;
|
||||||
|
|
||||||
|
if (interrupted)
|
||||||
|
break;
|
||||||
|
|
||||||
|
data = fpk_read(pak, entry, &size);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
fprintf(stderr, "fpk: 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 (extract_flags & EXTRACT_VERBOSE)
|
||||||
|
printf(" %s (%lu bytes)\n", entry->path, (unsigned long)size);
|
||||||
|
|
||||||
|
if (extract_flags & EXTRACT_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 ((extract_flags & EXTRACT_PROGRESS) && count > 0)
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
if (interrupted) {
|
||||||
|
fprintf(stderr, "fpk: interrupted, %lu of %lu entries extracted\n",
|
||||||
|
(unsigned long)i, (unsigned long)count);
|
||||||
|
free(out_dir);
|
||||||
|
return 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(extract_flags & EXTRACT_QUIET)) {
|
||||||
|
char dur[64];
|
||||||
|
|
||||||
|
format_duration(now_seconds() - start_time, dur, sizeof(dur));
|
||||||
|
printf("unpacked: %s -> %s (%lu entries) in %s\n", input, out_dir,
|
||||||
|
(unsigned long)count, dur);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(out_dir);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- list ---- */
|
||||||
|
|
||||||
|
static struct option list_long_opts[] = {
|
||||||
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{NULL, 0, NULL, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void list_usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage: fpk list input.fpk\n");
|
||||||
|
fprintf(stderr, "try 'fpk list --help' for more information\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void list_help(void)
|
||||||
|
{
|
||||||
|
printf("usage: fpk list input.fpk\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("List the entries in an fpk archive.\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("options:\n");
|
||||||
|
printf(" -h, --help show this help and exit\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_list(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *input;
|
||||||
|
struct fpk *pak;
|
||||||
|
uint64_t count;
|
||||||
|
uint64_t i;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
while ((c = getopt_long(argc, argv, "h", list_long_opts, NULL)) != -1) {
|
||||||
|
switch (c) {
|
||||||
|
case 'h':
|
||||||
|
list_help();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
list_usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind != argc - 1)
|
||||||
|
list_usage();
|
||||||
|
|
||||||
|
input = argv[optind];
|
||||||
|
|
||||||
|
pak = fpk_open(input);
|
||||||
|
if (!pak) {
|
||||||
|
fprintf(stderr, "fpk: cannot open: %s\n", input);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = fpk_entry_count(pak);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- top-level dispatch ---- */
|
||||||
|
|
||||||
|
static void top_usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage: fpk <create|extract|list> [options] ...\n");
|
||||||
|
fprintf(stderr, "try 'fpk --help' for more information\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void top_help(void)
|
||||||
|
{
|
||||||
|
printf("usage: fpk <command> [options] ...\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("commands:\n");
|
||||||
|
printf(" create, c pack a directory tree into an fpk archive\n");
|
||||||
|
printf(" extract, x extract files from an fpk archive\n");
|
||||||
|
printf(" list, t list the entries in an fpk archive\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("run 'fpk <command> --help' for command-specific options\n");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *verb;
|
||||||
|
|
||||||
|
if (argc < 2)
|
||||||
|
top_usage();
|
||||||
|
|
||||||
|
verb = argv[1];
|
||||||
|
|
||||||
|
if (strcmp(verb, "-h") == 0 || strcmp(verb, "--help") == 0)
|
||||||
|
top_help();
|
||||||
|
|
||||||
|
if (strcmp(verb, "create") == 0 || strcmp(verb, "c") == 0)
|
||||||
|
return cmd_create(argc - 1, argv + 1);
|
||||||
|
|
||||||
|
if (strcmp(verb, "extract") == 0 || strcmp(verb, "x") == 0)
|
||||||
|
return cmd_extract(argc - 1, argv + 1);
|
||||||
|
|
||||||
|
if (strcmp(verb, "list") == 0 || strcmp(verb, "t") == 0)
|
||||||
|
return cmd_list(argc - 1, argv + 1);
|
||||||
|
|
||||||
|
fprintf(stderr, "fpk: unknown command: %s\n", verb);
|
||||||
|
top_usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
-269
@@ -1,269 +0,0 @@
|
|||||||
#include "fpk.h"
|
|
||||||
#include <errno.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <direct.h>
|
|
||||||
#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'},
|
|
||||||
{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(void)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "usage: %s [-d output_dir] [-l] [-p] [-q] [-v] input.fpk\n", PROGRAM_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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user