369 lines
8.7 KiB
C
369 lines
8.7 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include "ass.h"
|
|
|
|
#if defined(__unix__)
|
|
#include <unistd.h>
|
|
#if defined(_POSIX_VERSION)
|
|
#include <fcntl.h>
|
|
#endif
|
|
#endif
|
|
|
|
struct blob {
|
|
void *data;
|
|
size_t size;
|
|
};
|
|
|
|
static void blob_free(struct blob *const blob);
|
|
|
|
// on error blob.data == NULL
|
|
static struct blob load_file(FILE *file);
|
|
|
|
struct ctx {
|
|
char **args;
|
|
FILE *assets;
|
|
FILE *names;
|
|
};
|
|
|
|
static char *read_one_line(FILE *file);
|
|
|
|
int verbose = 0, zero = 0;
|
|
|
|
void create_cb(char *restrict *filename, void *restrict *filedata, size_t *restrict filesize, void *restrict ctx_) {
|
|
struct ctx *ctx = ctx_;
|
|
free(*filename);
|
|
free(*filedata);
|
|
if (filesize == NULL) {
|
|
return;
|
|
}
|
|
if (*ctx->args == NULL && ctx->assets == NULL) {
|
|
*filename = NULL;
|
|
*filedata = NULL;
|
|
return;
|
|
}
|
|
if (*ctx->args != NULL) {
|
|
*filename = strdup(*ctx->args++);
|
|
FILE *file = fopen(*filename, "rb");
|
|
if (file == NULL) {
|
|
perror(*filename);
|
|
return;
|
|
}
|
|
struct blob data = load_file(file);
|
|
if (data.data == NULL) {
|
|
perror(*filename);
|
|
return;
|
|
}
|
|
*filedata = data.data;
|
|
*filesize = data.size;
|
|
} else {
|
|
*filename = read_one_line(ctx->assets);
|
|
if (*filename == NULL) {
|
|
*filedata = NULL;
|
|
if (ctx->names != NULL) {
|
|
char *n = read_one_line(ctx->names);
|
|
if (n != NULL) {
|
|
fputs("more names than paths\n", stderr);
|
|
free(n);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
FILE *file = fopen(*filename, "rb");
|
|
if (file == NULL) {
|
|
perror(*filename);
|
|
return;
|
|
}
|
|
struct blob data = load_file(file);
|
|
if (data.data == NULL) {
|
|
perror(*filename);
|
|
return;
|
|
}
|
|
if (ctx->names != NULL) {
|
|
char *name = read_one_line(ctx->names);
|
|
if (name == NULL) {
|
|
fputs("more paths than names\n", stderr);
|
|
ctx->names = NULL;
|
|
} else {
|
|
free(*filename);
|
|
*filename = name;
|
|
}
|
|
}
|
|
*filedata = data.data;
|
|
*filesize = data.size;
|
|
}
|
|
}
|
|
|
|
int extract_cb(char const *restrict filename, size_t length, void const *restrict data, size_t size, void *restrict ctx_) {
|
|
return 1; // todo
|
|
struct ctx *ctx = ctx_;
|
|
#if defined (_POSIX_VERSION)
|
|
open(*ctx->args++, O_WRONLY | O_EXCL | O_CREAT, 0666);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int list_cb(char const *restrict filename, size_t length, void const *restrict data, size_t size, void *restrict ctx_) {
|
|
struct ctx *ctx = ctx_;
|
|
if (*ctx->args != NULL || ctx->assets || ctx->names) {
|
|
fputs("--list does not take arguments\n", stderr);
|
|
return 1;
|
|
}
|
|
if (zero && verbose) {
|
|
fputs("--list doesnt support --verbose and --nul at the same time\n", stderr);
|
|
return 1;
|
|
} else if (zero) {
|
|
fwrite(filename, 1, length + 1, stdout);
|
|
} else if (verbose) {
|
|
fprintf(stdout, "%zu\t%s\n", size, filename);
|
|
} else {
|
|
fprintf(stdout, "%s\n", filename);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
struct option const options[] = {
|
|
{"help", 0, NULL, 'h'},
|
|
{"verbose", 0, NULL, 'v'},
|
|
{"nul", 0, NULL, '0'},
|
|
{"create", 0, NULL, 'c'},
|
|
{"extract", 0, NULL, 'x'},
|
|
{"list", 0, NULL, 'l'},
|
|
{"file", 1, NULL, 'f'},
|
|
{"files", 1, NULL, 'F'},
|
|
{"names", 1, NULL, 'N'},
|
|
{NULL, 0, NULL, 0 },
|
|
};
|
|
int opt, fail = 0;
|
|
enum {
|
|
A_NONE,
|
|
A_CREATE,
|
|
A_EXTRACT,
|
|
A_LIST,
|
|
} action = A_NONE;
|
|
char *filename = NULL;
|
|
FILE *assets = NULL, *names = NULL;
|
|
while ((opt = getopt_long(argc, argv, "hv0cxlf:F:N:", options, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'h':
|
|
fprintf(stderr, "usage: %s [-h | -c | -l | -x] [-0v] [-f ASSET_FILE] [-F PATHS] [-N NAMES]\n", argv[0]);
|
|
fputs("\n", stderr);
|
|
fputs("-h --help this message\n", stderr);
|
|
fputs("-c --create create an asset pack\n", stderr);
|
|
fputs("-l --list list files in an asset pack\n", stderr);
|
|
fputs("-x --extract extract files from an asset pack\n", stderr);
|
|
fputs("\n", stderr);
|
|
fputs("-0 --nul entries are NUL-separated (for use with -l -F -N)\n", stderr);
|
|
fputs("-v --verbose\n", stderr);
|
|
fputs("\t(-c) print names of files put in the pack\n", stderr);
|
|
fputs("\t(-l) print sizes of files in the pack\n", stderr);
|
|
fputs("\t(-x) print files extracted from the pack\n", stderr);
|
|
fputs("\n", stderr);
|
|
fputs("-f --file=FILE file to use as the asset pack\n", stderr);
|
|
fputs("-F --files=FILE file containing the list of files to put in/extract from the asset pack\n", stderr);
|
|
fputs("-N --names=FILE file containing the list of names to use for the files (-c only)\n", stderr);
|
|
fputs("\n", stderr);
|
|
fputs("-F and -N may both be \"-\", if stdin contains the paths and names interleaved\n", stderr);
|
|
return 0;
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
case '0':
|
|
zero = 1;
|
|
break;
|
|
case 'c':
|
|
action = A_CREATE;
|
|
break;
|
|
case 'x':
|
|
action = A_EXTRACT;
|
|
break;
|
|
case 'l':
|
|
action = A_LIST;
|
|
break;
|
|
case 'f':
|
|
filename = optarg;
|
|
break;
|
|
case 'F':
|
|
if (strcmp(optarg, "-")) {
|
|
if ((assets = fopen(optarg, "rb")) == NULL) {
|
|
perror(optarg);
|
|
fail = 1;
|
|
}
|
|
} else {
|
|
assets = stdin;
|
|
}
|
|
break;
|
|
case 'N':
|
|
if (strcmp(optarg, "-")) {
|
|
if ((names = fopen(optarg, "rb")) == NULL) {
|
|
perror(optarg);
|
|
fail = 1;
|
|
}
|
|
} else {
|
|
names = stdin;
|
|
}
|
|
break;
|
|
default:
|
|
fail = 1;
|
|
}
|
|
}
|
|
FILE *file;
|
|
if (action == A_CREATE) {
|
|
if (filename == NULL || strcmp(filename, "-") == 0) {
|
|
file = stdout;
|
|
} else {
|
|
file = fopen(filename, "wb");
|
|
}
|
|
} else {
|
|
if (filename == NULL || strcmp(filename, "-") == 0) {
|
|
file = stdin;
|
|
} else {
|
|
file = fopen(filename, "rb");
|
|
}
|
|
}
|
|
if (file == NULL) {
|
|
perror(filename);
|
|
fail = 1;
|
|
}
|
|
if (file == stdin && (assets == stdin || names == stdin)) {
|
|
fputs("not enough stdins, try `-f'\n", stderr);
|
|
fail = 1;
|
|
}
|
|
if (assets == NULL && names != NULL) {
|
|
fputs("-N is useless without -F\n", stderr);
|
|
fail = 1;
|
|
}
|
|
if (optind != argc && assets != NULL) {
|
|
fputs("mixing -F and arguments is not supported\n", stderr);
|
|
fail = 1;
|
|
}
|
|
if (fail) {
|
|
return 1;
|
|
}
|
|
struct ctx ctx = {
|
|
.args = argv + optind,
|
|
.assets = assets,
|
|
.names = names,
|
|
};
|
|
int ret;
|
|
switch (action) {
|
|
case A_CREATE:
|
|
ret = asswrite(file, create_cb, &ctx);
|
|
break;
|
|
case A_EXTRACT:
|
|
#if !defined(_POSIX_VERSION)
|
|
if (optind == argc && assets == NULL) {
|
|
fputs("cannot use --extract without specifying any files\n(as a safety measure, specifying no files would prevent overwriting existing files, but this safety measure is not available in this version of the program, please use --list)\n", stderr);
|
|
return 1;
|
|
}
|
|
#endif
|
|
ret = assread(file, extract_cb, &ctx);
|
|
break;
|
|
case A_LIST:
|
|
ret = assread(file, list_cb, &ctx);
|
|
break;
|
|
default:
|
|
return 1;
|
|
}
|
|
switch (ret) {
|
|
case ASS_OK:
|
|
return 0;
|
|
case ASS_BAD:
|
|
fputs("not an asset pack, or its corrupt\n", stderr);
|
|
return ASS_BAD;
|
|
case ASS_USER:
|
|
return ASS_USER; // the callback printed its own error message, probably
|
|
case ASS_TRUNC:
|
|
fputs("truncated asset pack\n", stderr);
|
|
return ASS_TRUNC;
|
|
case ASS_MEM:
|
|
fputs("failed to allocate memory\n", stderr);
|
|
return ASS_MEM;
|
|
case ASS_LAZY:
|
|
fputs("some kind of failure which i could theoretically handle, but it would make the code more complicated than i want it to be, has occured\n", stderr);
|
|
return ASS_LAZY;
|
|
case ASS_UNSUPPORTED:
|
|
fputs("unsupported version\n", stderr);
|
|
return ASS_UNSUPPORTED;
|
|
default:
|
|
fputs("unknown error\n", stderr);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static void blob_free(struct blob *const blob) {
|
|
free(blob->data);
|
|
blob->data = NULL;
|
|
blob->size = 0;
|
|
}
|
|
|
|
static struct blob load_file(FILE *file) {
|
|
const size_t START_SIZE = 8192;
|
|
char *data = malloc(START_SIZE);
|
|
size_t allocated = START_SIZE;
|
|
size_t used = 0;
|
|
while (1) {
|
|
size_t read = fread(data + used, 1, allocated - used, file);
|
|
if (read != allocated - used) {
|
|
used += read;
|
|
break;
|
|
}
|
|
used += read;
|
|
allocated *= 2;
|
|
void *const newdata = realloc(data, allocated);
|
|
if (newdata == NULL) {
|
|
goto realloc_error;
|
|
}
|
|
data = newdata;
|
|
}
|
|
if (used == 0) {
|
|
//free(data);
|
|
fclose(file);
|
|
return (struct blob) {.data = data, .size = 0};
|
|
}
|
|
void *const newdata = realloc(data, used);
|
|
if (newdata == NULL) {
|
|
goto realloc_error;
|
|
}
|
|
fclose(file);
|
|
return (struct blob) {.data = newdata, .size = used};
|
|
|
|
realloc_error:
|
|
free(data);
|
|
fclose(file);
|
|
return (struct blob) {.data = NULL};
|
|
}
|
|
|
|
static char *read_one_line(FILE *file) {
|
|
size_t allocated = 32;
|
|
size_t used = 0;
|
|
char *buf = malloc(allocated);
|
|
while (1) {
|
|
int c = fgetc(file);
|
|
if ((c == EOF && used == 0) || (c == '\0' && zero == 0)) {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
if (used >= allocated) {
|
|
allocated *= 2;
|
|
if (allocated == 0) {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
void *tmp = realloc(buf, allocated);
|
|
if (tmp == NULL) {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
buf = tmp;
|
|
}
|
|
if (c == (zero? '\0': '\n') || c == EOF) {
|
|
buf[used] = '\0';
|
|
return buf;
|
|
}
|
|
buf[used++] = c;
|
|
}
|
|
}
|