suwi/utl/assetsmk.c
2025-11-11 23:15:54 +01:00

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