diff --git a/GNUmakefile b/GNUmakefile index 917a8b8..f61e1a4 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -26,9 +26,12 @@ deps := $(wildcard out/*.d) .PHONY: all res bin run clean all: res bin + bin: ${ns}/${NAME}.${EXTENSION} -run: ${ns}/${NAME}.${EXTENSION} - $< +res: out/${NAME}.assets + +run: ${ns}/${NAME}.${EXTENSION} out/${NAME}.assets + $^ clean: ${RM} -r ${ns}/ @@ -45,7 +48,7 @@ ${ns}/%.o: src/%.c++ | ${ns}/ %/: @${MKDIR} $@ -#include assets.mk +include assets.mk ifeq ($(filter clean mostlyclean,${MAKECMDGOALS}),) include ${deps} diff --git a/assets.mk b/assets.mk new file mode 100644 index 0000000..d31341b --- /dev/null +++ b/assets.mk @@ -0,0 +1,35 @@ +PRITE ?= libresprite + +glsl := $(wildcard src/*.glsl) +frag := $(wildcard src/*.frag) +#shaders := ${glsl:src/%.glsl=out/%.shader} ${frag:src/%.frag=out/%.shader} +shaders := $(patsubst src/%,out/%.shader,${glsl:.glsl=} ${frag:.frag=}) +pictures := $(patsubst src/%.ase,out/%.picture, $(wildcard src/*.ase)) + +out/suwi.assets: ${shaders} ${pictures} | out/assetsmk + printf '%s\0' $^ | sed -nz 'p;s:.*/::p' | $| -c0f out/suwi.assets -F - -N - + +out/%.shader: src/%.glsl utl/vert.awk utl/frag.awk + awk -f utl/vert.awk $< > $@ + printf '\0' >> $@ + awk -f utl/frag.awk $< >> $@ + printf '\0' >> $@ + +out/%.shader: src/%.frag utl/vert.awk utl/frag.awk src/default.glsl + awk -f utl/vert.awk src/default.glsl > $@ + printf '\0' >> $@ + cat $< >> $@ + printf '\0' >> $@ + +out/%.png: src/%.ase + ${PRITE} -b $< --save-as $@ + +out/%.picture: out/picmake out/%.png + $^ -o $@ + + +out/picmake: utl/picmake.c + ${CC} -o $@ $< -lpng + +out/assetsmk: utl/assetsmk.c src/assread.c src/asswrite.c + ${CC} -o $@ $^ -lz -I src/ diff --git a/src/assets.c++ b/src/assets.c++ index 8f82d20..904c9bd 100644 --- a/src/assets.c++ +++ b/src/assets.c++ @@ -200,6 +200,9 @@ int assets_load(FILE *file, char const *name) { struct pack_result res = guilotine(pictures.rect.data(), pictures.rect.size(), 512, 1); if (res.bins == 0) { + if (pictures.rect.size() == 0) { + fprintf(stderr, "error: %s: no textures?? lmao\n", name); + } return 1; } diff --git a/src/suwi.ase b/src/suwi.ase new file mode 100644 index 0000000..6117492 Binary files /dev/null and b/src/suwi.ase differ diff --git a/utl/assetsmk.c b/utl/assetsmk.c new file mode 100644 index 0000000..222c01d --- /dev/null +++ b/utl/assetsmk.c @@ -0,0 +1,368 @@ +#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; + } +} diff --git a/utl/frag.awk b/utl/frag.awk new file mode 100755 index 0000000..07dd643 --- /dev/null +++ b/utl/frag.awk @@ -0,0 +1,12 @@ +#!/usr/bin/env -S awk -f + +BEGIN {mode = "any"; lineno = 1;} + +{lineno++;} + +/@@/ {mode = "any"; $0 = ("#line " lineno);} +/%%/ {mode = "vert";} +/##/ {mode = "frag"; $0 = ("#line " lineno);} + +mode == "any" +mode == "frag" diff --git a/utl/picmake.c b/utl/picmake.c new file mode 100644 index 0000000..b08f28b --- /dev/null +++ b/utl/picmake.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include + +static char const pic_sig[8] = {'\xdd', '\xdd', 'p', 'i', 'c', '\n', '\0', 0}; +static char const help_text[] = "%s [-fh] [-o OUTPUT.dat] INPUT.png\n"; + +static void write_vlq(size_t value, FILE *file) { + unsigned char buf[9]; + size_t bytes; + for (bytes = 0; bytes < sizeof (size_t); bytes++) { + size_t byte = value >> (bytes * 8); + if ((byte & (0x7f >> bytes)) == byte) { + break; + } + buf[8 - bytes] = value >> (bytes * 8); + } + buf[8 - bytes] = (value >> (bytes * 8)) | (0xff00 >> bytes); + + fwrite(buf + 8 - bytes, 1, bytes + 1, file); +} + +int main(int argc, char **argv) { + struct option const options[] = { + {"force", 0, NULL, 'f'}, + {"help", 0, NULL, 'h'}, + {"output", 1, NULL, 'o'}, + {"verbose", 0, NULL, 'v'}, + {NULL, 0, NULL, 0 }, + }; + int opt, force = 0, verbose = 0; + char *outname = NULL; + while ((opt = getopt_long(argc, argv, "fho:v", options, NULL)) != -1) { + switch (opt) { + case 'f': + force++; + break; + case 'h': + fprintf(stderr, help_text, argv[0]); + fputs("-f --force allow outputting not-quite-successful conversions\n", stderr); + fputs("-h --help this message\n", stderr); + fputs("-o --output=FILE output converted image to FILE\n", stderr); + fputs("-v --verbose print diagnostics\n", stderr); + return EXIT_SUCCESS; + case 'o': + outname = optarg; + break; + case 'v': + verbose++; + break; + default: + return EXIT_FAILURE; + } + } + if (optind + 1 != argc) { + fprintf(stderr, help_text, argv[0]); + return EXIT_FAILURE; + } + + // half-assed libpng use, will be fixed in a project where proper libpng use is necessary + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp) NULL, NULL, NULL); + if (png_ptr == NULL) { + return EXIT_FAILURE; + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return EXIT_FAILURE; + } + FILE *fp = NULL; + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (fp != NULL) { + fclose(fp); + } + return EXIT_FAILURE; + } + fp = fopen(argv[optind], "rb"); + if (fp == NULL) { + perror(argv[optind]); + return EXIT_FAILURE; + } + png_init_io(png_ptr, fp); + + // we do NOT want gamma! + //png_set_gamma_fixed(png_ptr, PNG_GAMMA_LINEAR, PNG_GAMMA_LINEAR); + //png_set_alpha_mode(png_ptr, PNG_ALPHA_PNG, PNG_GAMMA_LINEAR); + + png_read_info(png_ptr, info_ptr); + + png_uint_32 width, height; + int bit_depth, color_type; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + } else if (bit_depth < 8) { + png_set_packing(png_ptr); + } else if (bit_depth > 8) { + png_set_scale_16(png_ptr); + } + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + } else { + png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER); + } + png_set_rgb_to_gray(png_ptr, 1, -1, -1); + //png_set_expand_gray_1_2_4_to_8(png_ptr); + png_read_update_info(png_ptr, info_ptr); + + png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); + png_bytepp row_pointers = malloc(sizeof (png_bytep) * height); + unsigned char *image = malloc(rowbytes * height); + for (unsigned i = 0; i < height; i++) { + row_pointers[i] = image + (height - i - 1) * rowbytes; + } + + png_read_image(png_ptr, row_pointers); + + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + //free(row_pointers); + + if (outname != NULL) { + FILE *out = fopen(outname, "wb"); + if (out == NULL) { + perror(outname); + return EXIT_FAILURE; + } + fwrite(pic_sig, 1, sizeof (pic_sig), out); + write_vlq(width - 1, out); + write_vlq(height - 1, out); + for (size_t i = 0; i < width * height; i++) { + if (image[i * 2 + 1] < 0x80) { + fputc(0x00, out); + } else if (image[i * 2 + 0] < 0x80) { + fputc(0x80, out); + } else { + fputc(0xff, out); + } + } + fclose(out); + } + + return EXIT_SUCCESS; +} diff --git a/utl/vert.awk b/utl/vert.awk new file mode 100755 index 0000000..620e51d --- /dev/null +++ b/utl/vert.awk @@ -0,0 +1,12 @@ +#!/usr/bin/env -S awk -f + +BEGIN {mode = "any"; lineno = 1;} + +{lineno++;} + +/@@/ {mode = "any"; $0 = ("#line " lineno);} +/%%/ {mode = "vert"; $0 = ("#line " lineno);} +/##/ {mode = "frag";} + +mode == "any" +mode == "vert"