Compare commits
3
Commits
4af011233d
...
a0a08cb73a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0a08cb73a | ||
|
|
11675f8cd4 | ||
|
|
8287541447 |
@@ -21,8 +21,9 @@ funpack output.fpk [unpacked/] # dir defaults to "output" if omitted
|
|||||||
```c
|
```c
|
||||||
#include <fpk/fpk.h>
|
#include <fpk/fpk.h>
|
||||||
|
|
||||||
fpk_t *pak = fpk_open("assets.fpk");
|
struct fpk *pak = fpk_open("assets.fpk");
|
||||||
fpk_find(pak, "some/file.bin", &data, &size);
|
fpk_entry_t *entry = fpk_find(pak, "some/file.bin");
|
||||||
|
void *data = fpk_read(pak, entry, &size);
|
||||||
fpk_close(pak);
|
fpk_close(pak);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# FPK1 format
|
||||||
|
|
||||||
|
```
|
||||||
|
[ header ][ data blobs ][ string table ][ directory entries ]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Header (28 bytes)
|
||||||
|
|
||||||
|
| Field | Type | Size | Offset |
|
||||||
|
|-----------------|-----------|------|--------|
|
||||||
|
| `magic` | char[4] | 4 | 0 |
|
||||||
|
| `entry_count` | uint64_t | 8 | 4 |
|
||||||
|
| `dir_offset` | uint64_t | 8 | 12 |
|
||||||
|
| `strtab_offset` | uint64_t | 8 | 20 |
|
||||||
|
|
||||||
|
`magic` = `"FPK1"`.
|
||||||
|
|
||||||
|
`FPK_HEADER_SIZE` (28): field-by-field, not `sizeof()`.
|
||||||
|
|
||||||
|
## Data blobs
|
||||||
|
|
||||||
|
Raw bytes, back-to-back, no padding, writer add order.
|
||||||
|
|
||||||
|
## String table
|
||||||
|
|
||||||
|
Paths, `/`-separated, NUL-terminated, back-to-back, entry order.
|
||||||
|
|
||||||
|
`strtab_offset` points here.
|
||||||
|
|
||||||
|
## Directory (25 bytes per entry)
|
||||||
|
|
||||||
|
| Field | Type | Size | Offset |
|
||||||
|
|---------------|----------|------|--------|
|
||||||
|
| `path_offset` | uint64_t | 8 | 0 |
|
||||||
|
| `data_offset` | uint64_t | 8 | 8 |
|
||||||
|
| `data_size` | uint64_t | 8 | 16 |
|
||||||
|
| `method` | uint8_t | 1 | 24 |
|
||||||
|
|
||||||
|
`path_offset` relative to `strtab_offset`. `data_offset` absolute.
|
||||||
|
|
||||||
|
`FPK_DIR_ENTRY_SIZE` (25): field-by-field, not `sizeof()`.
|
||||||
|
|
||||||
|
`dir_offset` points here, last region. File size = `dir_offset +
|
||||||
|
entry_count * FPK_DIR_ENTRY_SIZE`.
|
||||||
|
|
||||||
|
## Compression
|
||||||
|
|
||||||
|
`method`: reserved, v1 = `FPK_METHOD_STORE` (0) only.
|
||||||
|
|
||||||
|
## Endianness
|
||||||
|
|
||||||
|
Little-endian on disk, always. Big-endian host swaps on read/write.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#ifndef FPK_H
|
||||||
|
#define FPK_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define FPK_MAGIC "FPK1"
|
||||||
|
#define FPK_MAGIC_SIZE 4
|
||||||
|
#define FPK_VERSION 1
|
||||||
|
|
||||||
|
#define FPK_HEADER_SIZE 28
|
||||||
|
/*
|
||||||
|
* magic 4
|
||||||
|
* entry_count 8
|
||||||
|
* dir_offset 8
|
||||||
|
* strtab_offset 8
|
||||||
|
* --
|
||||||
|
* 28
|
||||||
|
*/
|
||||||
|
#define FPK_DIR_ENTRY_SIZE 25
|
||||||
|
/*
|
||||||
|
* path_offset 8
|
||||||
|
* data_offset 8
|
||||||
|
* data_size 8
|
||||||
|
* method 1
|
||||||
|
* --
|
||||||
|
* 25
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define FPK_METHOD_STORE 0
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *path;
|
||||||
|
uint64_t data_offset;
|
||||||
|
uint64_t data_size;
|
||||||
|
uint8_t method;
|
||||||
|
} fpk_entry_t;
|
||||||
|
|
||||||
|
struct fpk;
|
||||||
|
struct fpk_writer;
|
||||||
|
|
||||||
|
struct fpk *fpk_open(const char *path);
|
||||||
|
void fpk_close(struct fpk *fpk);
|
||||||
|
fpk_entry_t *fpk_find(struct fpk *fpk, const char *path);
|
||||||
|
void *fpk_read(struct fpk *fpk, const fpk_entry_t *entry, size_t *out_size);
|
||||||
|
uint64_t fpk_entry_count(const struct fpk *fpk);
|
||||||
|
const fpk_entry_t *fpk_entry_at(const struct fpk *fpk, uint64_t index);
|
||||||
|
|
||||||
|
struct fpk_writer *fpk_writer_open(const char *path);
|
||||||
|
int fpk_writer_add(struct fpk_writer *w, const char *path, const void *data,
|
||||||
|
size_t size);
|
||||||
|
int fpk_writer_finalize(struct fpk_writer *w);
|
||||||
|
void fpk_writer_close(struct fpk_writer *w);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* FPK_H */
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef FPK_VERSION_H
|
||||||
|
#define FPK_VERSION_H
|
||||||
|
|
||||||
|
#define FPK_VERSION_MAJOR 0
|
||||||
|
#define FPK_VERSION_MINOR 1
|
||||||
|
#define FPK_VERSION_PATCH 0
|
||||||
|
|
||||||
|
#define FPK_STRINGIFY_HELPER(x) #x
|
||||||
|
#define FPK_STRINGIFY(x) FPK_STRINGIFY_HELPER(x)
|
||||||
|
|
||||||
|
#define FPK_VERSION_STRING \
|
||||||
|
FPK_STRINGIFY(FPK_VERSION_MAJOR) \
|
||||||
|
"." FPK_STRINGIFY(FPK_VERSION_MINOR) "." FPK_STRINGIFY( \
|
||||||
|
FPK_VERSION_PATCH)
|
||||||
|
|
||||||
|
#endif /* FPK_VERSION_H */
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
#include "fpk.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define FPK_HEADER_FIELD_ENTRY_COUNT 4
|
||||||
|
#define FPK_HEADER_FIELD_DIR_OFFSET 12
|
||||||
|
#define FPK_HEADER_FIELD_STRTAB_OFFSET 20
|
||||||
|
|
||||||
|
#define FPK_DIR_ENTRY_FIELD_PATH_OFFSET 0
|
||||||
|
#define FPK_DIR_ENTRY_FIELD_DATA_OFFSET 8
|
||||||
|
#define FPK_DIR_ENTRY_FIELD_DATA_SIZE 16
|
||||||
|
#define FPK_DIR_ENTRY_FIELD_METHOD 24
|
||||||
|
|
||||||
|
struct fpk {
|
||||||
|
FILE *file;
|
||||||
|
uint64_t entry_count;
|
||||||
|
fpk_entry_t *entries;
|
||||||
|
char *strtab;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fpk_writer_entry {
|
||||||
|
char *path;
|
||||||
|
uint64_t data_offset;
|
||||||
|
uint64_t data_size;
|
||||||
|
uint8_t method;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fpk_writer {
|
||||||
|
FILE *file;
|
||||||
|
struct fpk_writer_entry *entries;
|
||||||
|
uint64_t count;
|
||||||
|
uint64_t capacity;
|
||||||
|
uint64_t cursor;
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t get_u32le(const unsigned char *p)
|
||||||
|
{
|
||||||
|
return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 |
|
||||||
|
(uint32_t)p[3] << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t get_u64le(const unsigned char *p)
|
||||||
|
{
|
||||||
|
return (uint64_t)get_u32le(p) | (uint64_t)get_u32le(p + 4) << 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u32le(unsigned char *p, uint32_t v)
|
||||||
|
{
|
||||||
|
p[0] = (unsigned char)(v);
|
||||||
|
p[1] = (unsigned char)(v >> 8);
|
||||||
|
p[2] = (unsigned char)(v >> 16);
|
||||||
|
p[3] = (unsigned char)(v >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u64le(unsigned char *p, uint64_t v)
|
||||||
|
{
|
||||||
|
put_u32le(p, (uint32_t)v);
|
||||||
|
put_u32le(p + 4, (uint32_t)(v >> 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *dup_path(const char *path)
|
||||||
|
{
|
||||||
|
size_t len = strlen(path) + 1;
|
||||||
|
char *copy = malloc(len);
|
||||||
|
|
||||||
|
if (copy != NULL)
|
||||||
|
memcpy(copy, path, len);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fpk *fpk_open(const char *path)
|
||||||
|
{
|
||||||
|
struct fpk *fpk;
|
||||||
|
unsigned char header[FPK_HEADER_SIZE];
|
||||||
|
uint64_t dir_offset, strtab_offset, strtab_size;
|
||||||
|
uint64_t i;
|
||||||
|
|
||||||
|
fpk = malloc(sizeof(*fpk));
|
||||||
|
if (fpk == NULL)
|
||||||
|
goto out;
|
||||||
|
fpk->file = NULL;
|
||||||
|
fpk->entries = NULL;
|
||||||
|
fpk->strtab = NULL;
|
||||||
|
|
||||||
|
fpk->file = fopen(path, "rb");
|
||||||
|
if (fpk->file == NULL)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (fread(header, 1, FPK_HEADER_SIZE, fpk->file) != FPK_HEADER_SIZE)
|
||||||
|
goto fail;
|
||||||
|
if (memcmp(header, FPK_MAGIC, FPK_MAGIC_SIZE) != 0)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
fpk->entry_count = get_u64le(header + FPK_HEADER_FIELD_ENTRY_COUNT);
|
||||||
|
dir_offset = get_u64le(header + FPK_HEADER_FIELD_DIR_OFFSET);
|
||||||
|
strtab_offset = get_u64le(header + FPK_HEADER_FIELD_STRTAB_OFFSET);
|
||||||
|
if (dir_offset < strtab_offset)
|
||||||
|
goto fail;
|
||||||
|
strtab_size = dir_offset - strtab_offset;
|
||||||
|
|
||||||
|
fpk->strtab = malloc((size_t)strtab_size);
|
||||||
|
if (fpk->strtab == NULL)
|
||||||
|
goto fail;
|
||||||
|
if (fseek(fpk->file, (long)strtab_offset, SEEK_SET) != 0)
|
||||||
|
goto fail;
|
||||||
|
if (fread(fpk->strtab, 1, (size_t)strtab_size, fpk->file) !=
|
||||||
|
strtab_size)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
fpk->entries = malloc((size_t)fpk->entry_count * sizeof(fpk_entry_t));
|
||||||
|
if (fpk->entries == NULL)
|
||||||
|
goto fail;
|
||||||
|
if (fseek(fpk->file, (long)dir_offset, SEEK_SET) != 0)
|
||||||
|
goto fail;
|
||||||
|
for (i = 0; i < fpk->entry_count; i++) {
|
||||||
|
unsigned char rec[FPK_DIR_ENTRY_SIZE];
|
||||||
|
uint64_t path_offset;
|
||||||
|
|
||||||
|
if (fread(rec, 1, FPK_DIR_ENTRY_SIZE, fpk->file) !=
|
||||||
|
FPK_DIR_ENTRY_SIZE)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
path_offset = get_u64le(rec + FPK_DIR_ENTRY_FIELD_PATH_OFFSET);
|
||||||
|
if (path_offset >= strtab_size)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
fpk->entries[i].path = fpk->strtab + path_offset;
|
||||||
|
fpk->entries[i].data_offset =
|
||||||
|
get_u64le(rec + FPK_DIR_ENTRY_FIELD_DATA_OFFSET);
|
||||||
|
fpk->entries[i].data_size =
|
||||||
|
get_u64le(rec + FPK_DIR_ENTRY_FIELD_DATA_SIZE);
|
||||||
|
fpk->entries[i].method = rec[FPK_DIR_ENTRY_FIELD_METHOD];
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
fpk_close(fpk);
|
||||||
|
fpk = NULL;
|
||||||
|
out:
|
||||||
|
return fpk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fpk_close(struct fpk *fpk)
|
||||||
|
{
|
||||||
|
if (fpk == NULL)
|
||||||
|
return;
|
||||||
|
if (fpk->file != NULL)
|
||||||
|
fclose(fpk->file);
|
||||||
|
free(fpk->entries);
|
||||||
|
free(fpk->strtab);
|
||||||
|
free(fpk);
|
||||||
|
}
|
||||||
|
|
||||||
|
fpk_entry_t *fpk_find(struct fpk *fpk, const char *path)
|
||||||
|
{
|
||||||
|
uint64_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < fpk->entry_count; i++) {
|
||||||
|
if (strcmp(fpk->entries[i].path, path) == 0)
|
||||||
|
return &fpk->entries[i];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *fpk_read(struct fpk *fpk, const fpk_entry_t *entry, size_t *out_size)
|
||||||
|
{
|
||||||
|
void *buf;
|
||||||
|
|
||||||
|
buf = malloc((size_t)entry->data_size);
|
||||||
|
if (buf == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (fseek(fpk->file, (long)entry->data_offset, SEEK_SET) != 0 ||
|
||||||
|
fread(buf, 1, (size_t)entry->data_size, fpk->file) !=
|
||||||
|
entry->data_size) {
|
||||||
|
free(buf);
|
||||||
|
buf = NULL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_size = (size_t)entry->data_size;
|
||||||
|
out:
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fpk_entry_count(const struct fpk *fpk)
|
||||||
|
{
|
||||||
|
return fpk->entry_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fpk_entry_t *fpk_entry_at(const struct fpk *fpk, uint64_t index)
|
||||||
|
{
|
||||||
|
if (index >= fpk->entry_count)
|
||||||
|
return NULL;
|
||||||
|
return &fpk->entries[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FPK_WRITER_INITIAL_CAPACITY 16
|
||||||
|
|
||||||
|
struct fpk_writer *fpk_writer_open(const char *path)
|
||||||
|
{
|
||||||
|
struct fpk_writer *w;
|
||||||
|
unsigned char placeholder[FPK_HEADER_SIZE];
|
||||||
|
|
||||||
|
w = malloc(sizeof(*w));
|
||||||
|
if (w == NULL)
|
||||||
|
goto out;
|
||||||
|
w->entries = NULL;
|
||||||
|
w->count = 0;
|
||||||
|
w->capacity = 0;
|
||||||
|
|
||||||
|
w->file = fopen(path, "wb");
|
||||||
|
if (w->file == NULL)
|
||||||
|
goto fail_free;
|
||||||
|
|
||||||
|
memset(placeholder, 0, sizeof(placeholder));
|
||||||
|
if (fwrite(placeholder, 1, FPK_HEADER_SIZE, w->file) !=
|
||||||
|
FPK_HEADER_SIZE)
|
||||||
|
goto fail_close;
|
||||||
|
w->cursor = FPK_HEADER_SIZE;
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
fail_close:
|
||||||
|
fclose(w->file);
|
||||||
|
fail_free:
|
||||||
|
free(w);
|
||||||
|
w = NULL;
|
||||||
|
out:
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fpk_writer_add(struct fpk_writer *w, const char *path, const void *data,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
struct fpk_writer_entry *entry;
|
||||||
|
char *path_copy = NULL;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (w->count == w->capacity) {
|
||||||
|
uint64_t new_capacity = w->capacity == 0
|
||||||
|
? FPK_WRITER_INITIAL_CAPACITY
|
||||||
|
: w->capacity * 2;
|
||||||
|
struct fpk_writer_entry *new_entries = realloc(
|
||||||
|
w->entries, (size_t)new_capacity * sizeof(*new_entries));
|
||||||
|
|
||||||
|
if (new_entries == NULL) {
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
w->entries = new_entries;
|
||||||
|
w->capacity = new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
path_copy = dup_path(path);
|
||||||
|
if (path_copy == NULL) {
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 0 && fwrite(data, 1, size, w->file) != size) {
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = &w->entries[w->count];
|
||||||
|
entry->path = path_copy;
|
||||||
|
entry->data_offset = w->cursor;
|
||||||
|
entry->data_size = size;
|
||||||
|
entry->method = FPK_METHOD_STORE;
|
||||||
|
|
||||||
|
w->cursor += size;
|
||||||
|
w->count++;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (ret != 0)
|
||||||
|
free(path_copy);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fpk_writer_finalize(struct fpk_writer *w)
|
||||||
|
{
|
||||||
|
uint64_t strtab_offset, dir_offset, path_offset;
|
||||||
|
unsigned char header[FPK_HEADER_SIZE];
|
||||||
|
unsigned char rec[FPK_DIR_ENTRY_SIZE];
|
||||||
|
uint64_t i;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
strtab_offset = w->cursor;
|
||||||
|
if (fseek(w->file, (long)strtab_offset, SEEK_SET) != 0) {
|
||||||
|
ret = -1;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
path_offset = 0;
|
||||||
|
for (i = 0; i < w->count && ret == 0; i++) {
|
||||||
|
size_t len = strlen(w->entries[i].path) + 1;
|
||||||
|
|
||||||
|
if (fwrite(w->entries[i].path, 1, len, w->file) != len)
|
||||||
|
ret = -1;
|
||||||
|
else
|
||||||
|
path_offset += len;
|
||||||
|
}
|
||||||
|
if (ret != 0)
|
||||||
|
goto out;
|
||||||
|
dir_offset = strtab_offset + path_offset;
|
||||||
|
|
||||||
|
path_offset = 0;
|
||||||
|
for (i = 0; i < w->count && ret == 0; i++) {
|
||||||
|
put_u64le(rec + FPK_DIR_ENTRY_FIELD_PATH_OFFSET, path_offset);
|
||||||
|
put_u64le(rec + FPK_DIR_ENTRY_FIELD_DATA_OFFSET,
|
||||||
|
w->entries[i].data_offset);
|
||||||
|
put_u64le(rec + FPK_DIR_ENTRY_FIELD_DATA_SIZE,
|
||||||
|
w->entries[i].data_size);
|
||||||
|
rec[FPK_DIR_ENTRY_FIELD_METHOD] = w->entries[i].method;
|
||||||
|
|
||||||
|
if (fwrite(rec, 1, FPK_DIR_ENTRY_SIZE, w->file) !=
|
||||||
|
FPK_DIR_ENTRY_SIZE)
|
||||||
|
ret = -1;
|
||||||
|
else
|
||||||
|
path_offset += strlen(w->entries[i].path) + 1;
|
||||||
|
}
|
||||||
|
if (ret != 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
memcpy(header, FPK_MAGIC, FPK_MAGIC_SIZE);
|
||||||
|
put_u64le(header + FPK_HEADER_FIELD_ENTRY_COUNT, w->count);
|
||||||
|
put_u64le(header + FPK_HEADER_FIELD_DIR_OFFSET, dir_offset);
|
||||||
|
put_u64le(header + FPK_HEADER_FIELD_STRTAB_OFFSET, strtab_offset);
|
||||||
|
|
||||||
|
if (fseek(w->file, 0, SEEK_SET) != 0 ||
|
||||||
|
fwrite(header, 1, FPK_HEADER_SIZE, w->file) != FPK_HEADER_SIZE ||
|
||||||
|
fflush(w->file) != 0)
|
||||||
|
ret = -1;
|
||||||
|
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fpk_writer_close(struct fpk_writer *w)
|
||||||
|
{
|
||||||
|
uint64_t i;
|
||||||
|
|
||||||
|
if (w == NULL)
|
||||||
|
return;
|
||||||
|
if (w->file != NULL)
|
||||||
|
fclose(w->file);
|
||||||
|
for (i = 0; i < w->count; i++)
|
||||||
|
free(w->entries[i].path);
|
||||||
|
free(w->entries);
|
||||||
|
free(w);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CC ?= cc
|
||||||
|
CFLAGS = -Wall -Wpedantic -I../include -std=c89
|
||||||
|
LDFLAGS = -L../bin/debug -lfpk
|
||||||
|
|
||||||
|
.PHONY: all test clean
|
||||||
|
|
||||||
|
all: test
|
||||||
|
|
||||||
|
test_program: test.c
|
||||||
|
${CC} ${CFLAGS} -o ${.TARGET} test.c ${LDFLAGS}
|
||||||
|
|
||||||
|
test: test_program
|
||||||
|
@./test_program
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f test_program
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
CC := cc
|
||||||
|
CFLAGS = -Wall -Wpedantic -I../include -std=c89
|
||||||
|
LDFLAGS = -L../bin/debug -lfpk
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
SHELL := cmd.exe
|
||||||
|
.SHELLFLAGS := /c
|
||||||
|
EXE_EXT = .exe
|
||||||
|
else
|
||||||
|
EXE_EXT =
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: all test clean
|
||||||
|
|
||||||
|
all: test
|
||||||
|
|
||||||
|
test_program$(EXE_EXT): test.c
|
||||||
|
$(CC) $(CFLAGS) -o $@ test.c $(LDFLAGS)
|
||||||
|
|
||||||
|
test: test_program$(EXE_EXT)
|
||||||
|
@./test_program$(EXE_EXT)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
@del /Q test_program.exe 2>nul || true
|
||||||
|
else
|
||||||
|
@rm -f test_program
|
||||||
|
endif
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#include <fpk.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define CHECK(cond, msg) \
|
||||||
|
do { \
|
||||||
|
if (!(cond)) { \
|
||||||
|
fprintf(stderr, "FAIL: %s\n", msg); \
|
||||||
|
return EXIT_FAILURE; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static const char *PAK_PATH = "roundtrip.fpk";
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
struct fpk_writer *w;
|
||||||
|
struct fpk *r;
|
||||||
|
fpk_entry_t *entry;
|
||||||
|
void *data;
|
||||||
|
size_t out_size;
|
||||||
|
|
||||||
|
static const char hello[] = "hello, fpk";
|
||||||
|
static const char world[] = "world";
|
||||||
|
|
||||||
|
w = fpk_writer_open(PAK_PATH);
|
||||||
|
CHECK(w != NULL, "fpk_writer_open failed");
|
||||||
|
CHECK(fpk_writer_add(w, "a/hello.txt", hello, sizeof(hello)) == 0,
|
||||||
|
"fpk_writer_add(hello) failed");
|
||||||
|
CHECK(fpk_writer_add(w, "b/world.txt", world, sizeof(world)) == 0,
|
||||||
|
"fpk_writer_add(world) failed");
|
||||||
|
CHECK(fpk_writer_add(w, "c/empty.bin", NULL, 0) == 0,
|
||||||
|
"fpk_writer_add(empty) failed");
|
||||||
|
CHECK(fpk_writer_finalize(w) == 0, "fpk_writer_finalize failed");
|
||||||
|
fpk_writer_close(w);
|
||||||
|
|
||||||
|
r = fpk_open(PAK_PATH);
|
||||||
|
CHECK(r != NULL, "fpk_open failed");
|
||||||
|
CHECK(fpk_entry_count(r) == 3, "unexpected entry_count");
|
||||||
|
|
||||||
|
entry = fpk_find(r, "a/hello.txt");
|
||||||
|
CHECK(entry != NULL, "fpk_find(hello) failed");
|
||||||
|
CHECK(entry->data_size == sizeof(hello), "hello size mismatch");
|
||||||
|
data = fpk_read(r, entry, &out_size);
|
||||||
|
CHECK(data != NULL, "fpk_read(hello) failed");
|
||||||
|
CHECK(out_size == sizeof(hello), "hello read size mismatch");
|
||||||
|
CHECK(memcmp(data, hello, sizeof(hello)) == 0, "hello content mismatch");
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
entry = fpk_find(r, "b/world.txt");
|
||||||
|
CHECK(entry != NULL, "fpk_find(world) failed");
|
||||||
|
data = fpk_read(r, entry, &out_size);
|
||||||
|
CHECK(data != NULL, "fpk_read(world) failed");
|
||||||
|
CHECK(out_size == sizeof(world), "world read size mismatch");
|
||||||
|
CHECK(memcmp(data, world, sizeof(world)) == 0, "world content mismatch");
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
entry = fpk_find(r, "c/empty.bin");
|
||||||
|
CHECK(entry != NULL, "fpk_find(empty) failed");
|
||||||
|
CHECK(entry->data_size == 0, "empty size mismatch");
|
||||||
|
|
||||||
|
CHECK(fpk_find(r, "does/not/exist") == NULL,
|
||||||
|
"fpk_find(missing) should return NULL");
|
||||||
|
|
||||||
|
fpk_close(r);
|
||||||
|
remove(PAK_PATH);
|
||||||
|
|
||||||
|
printf("PASS\n");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
+284
@@ -0,0 +1,284 @@
|
|||||||
|
#include "fpk.h"
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
+263
@@ -0,0 +1,263 @@
|
|||||||
|
#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
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user