#include #include #include #include #include "ass.h" #if defined(__unix__) #include #if defined(_POSIX_VERSION) #include #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; } }