diff --git a/GNUmakefile b/GNUmakefile index e7e93c9..ec2a42f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -5,11 +5,11 @@ MKDIR ?= mkdir -p EXTENSION ?= out CFLAGS ?= -Wall -Wpedantic -Wextra -g -Og -CXXFLAGS ?= -Wall -Wpedantic -Wextra -g -Og +CXXFLAGS ?= -std=c++20 -Wall -Wpedantic -Wextra -g -Og LDFLAGS ?= NAME := suwi -libs := m portaudio SDL3 +libs := m z portaudio SDL3 includes := src/ cflags := $(addprefix -I,${includes}) ${CFLAGS} cxxflags := $(addprefix -I,${includes}) ${CXXFLAGS} diff --git a/src/ass.h b/src/ass.h new file mode 100644 index 0000000..e4aaec7 --- /dev/null +++ b/src/ass.h @@ -0,0 +1,25 @@ +#pragma once + +#if defined(__cplusplus) +extern "C" { +#define restrict +#endif + +#include + +enum ass_status { + ASS_OK, // success + ASS_BAD, // bad file + ASS_USER, // user callback returned non-zero + ASS_TRUNC, // truncated file + ASS_LAZY, // my lazy programming would mis-parse the file + ASS_MEM, // malloc failure + ASS_UNSUPPORTED, // unsupported format +}; + +enum ass_status assread(FILE *restrict file, int (*cb)(char const *restrict filename, size_t length, void const *restrict data, size_t size, void *restrict ctx), void *restrict ctx); +enum ass_status asswrite(FILE *restrict file, void (*cb)(char *restrict *, void *restrict *, size_t *restrict, void *restrict ctx), void *restrict ctx); + +#if defined(__cplusplus) +} +#endif diff --git a/src/assread.c b/src/assread.c new file mode 100644 index 0000000..34fd359 --- /dev/null +++ b/src/assread.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include "ass.h" + +static const char signature[10] = "\xdd\xdd\x0a" "Assets"; + +enum { + M_ZLIB, +}; + +#define ZBUF_SIZE (32 * 1024) + +enum ass_status assread(FILE *restrict file, int (*cb)(char const *restrict filename, size_t length, void const *restrict data, size_t size, void *restrict ctx), void *restrict ctx) { + unsigned char zbuf[ZBUF_SIZE]; + unsigned char *buf = NULL; + size_t read; + size_t bufsize = 0; + + if ((read = fread(zbuf, 1, ZBUF_SIZE, file)) < 11) { + return ASS_BAD; + } + if (memcmp(zbuf, signature, 10) != 0) { + return ASS_BAD; + } + if (zbuf[10] != M_ZLIB) { + return ASS_UNSUPPORTED; + } + + z_stream stream = { + .zalloc = Z_NULL, + .zfree = Z_NULL, + .opaque = Z_NULL, + .avail_in = read - 11, + .next_in = zbuf + 11, + }; + + enum ass_status status = ASS_OK; + int ret = inflateInit(&stream); + switch (ret) { + case Z_OK: + break; + case Z_MEM_ERROR: + return ASS_MEM; + case Z_VERSION_ERROR: + return ASS_UNSUPPORTED; + default: + return ASS_BAD; + } + buf = malloc(bufsize = 4096); + if (buf == NULL) { + status = ASS_MEM; + goto done; + } + stream.avail_out = 1; + stream.next_out = buf; + ret = inflate(&stream, Z_SYNC_FLUSH); + if (ret != Z_OK) { + status = ASS_BAD; + goto done; + } + if (stream.avail_in == 0) { + stream.avail_in = fread(zbuf, 1, ZBUF_SIZE, file); + stream.next_in = zbuf; + } + size_t outsize = buf[0]; + do { + unsigned outsizebytes = 0; + for (unsigned m = 0x80; outsize & m; m = m >> 1) { + outsizebytes++; + outsize &= ~m; + } + if (outsizebytes) { + stream.avail_out = outsizebytes; + stream.next_out = buf; + while (stream.avail_out) { + ret = inflate(&stream, Z_SYNC_FLUSH); + switch (ret) { + case Z_OK: + break; + case Z_STREAM_END: + status = ASS_TRUNC; + goto done; + default: + status = ASS_BAD; + goto done; + } + if (stream.avail_in == 0) { + stream.avail_in = fread(zbuf, 1, ZBUF_SIZE, file); + stream.next_in = zbuf; + } + } + for (unsigned i = 0; i < outsizebytes; i++) { + outsize = (outsize << 8) | buf[i]; + } + } + stream.avail_out = outsize + 1; + if (stream.avail_out != outsize + 1) { + status = ASS_LAZY; + goto done; + } + if (bufsize < outsize + 1) { + bufsize = outsize + 1; + void *tmp = realloc(buf, bufsize); + if (tmp == NULL) { + status = ASS_MEM; + goto done; + } + buf = tmp; + } + stream.next_out = buf; + while (stream.avail_out > 1) { + ret = inflate(&stream, Z_SYNC_FLUSH); + switch (ret) { + case Z_OK: + break; + case Z_STREAM_END: + if (stream.avail_out > 1) { + status = ASS_TRUNC; + goto done; + } + break; + default: + status = ASS_BAD; + goto done; + } + if (stream.avail_in == 0) { + stream.avail_in = fread(zbuf, 1, ZBUF_SIZE, file); + stream.next_in = zbuf; + } + } + size_t length = strnlen((char *) buf, outsize); + if (length == 0 || length == outsize) { + status = ASS_BAD; + goto done; + } + if (cb((char *) buf, length, buf + length + 1, outsize - length - 1, ctx)) { + status = ASS_USER; + goto done; + } + outsize = buf[outsize]; + } while (ret != Z_STREAM_END); + done: + inflateEnd(&stream); + free(buf); + return status; +} diff --git a/src/asswrite.c b/src/asswrite.c new file mode 100644 index 0000000..dbf242a --- /dev/null +++ b/src/asswrite.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include "ass.h" + +static const char signature[10] = "\xdd\xdd\x0a" "Assets"; + +enum { + M_ZLIB, +}; + +#define ZBUF_SIZE (32 * 1024) + +enum ass_status asswrite(FILE *restrict file, void (*cb)(char *restrict *, void *restrict *, size_t *restrict, void *restrict ctx), void *restrict ctx) { + int wrote_any_files_at_all = 0; + unsigned char zbuf[ZBUF_SIZE]; + fwrite(signature, 10, 1, file); + fputc(M_ZLIB, file); + z_stream stream = { + .zalloc = Z_NULL, + .zfree = Z_NULL, + .opaque = Z_NULL, + .avail_in = 0, + .next_in = NULL, + .avail_out = ZBUF_SIZE, + .next_out = zbuf, + }; + int ret = deflateInit(&stream, Z_BEST_COMPRESSION); + switch (ret) { + case Z_OK: + break; + case Z_MEM_ERROR: + return ASS_MEM; + case Z_VERSION_ERROR: + return ASS_UNSUPPORTED; + default: + return ASS_BAD; + } + char *filename = NULL; + void *filedata = NULL; + size_t filesize = 0; + enum ass_status status = ASS_OK; + while (1) { + cb(&filename, &filedata, &filesize, ctx); + if ((filename == NULL || filename[0] == '\0') && filedata == NULL) { + break; + } else if ((filename == NULL || filename[0] == '\0') || filedata == NULL) { + status = ASS_USER; + goto done; + } + if (filesize == 0) { + continue; + } + wrote_any_files_at_all = 1; + size_t length = strlen(filename); + unsigned char buf[9]; + unsigned bytes = 0; + size_t total = length + 1 + filesize; + if (total <= length || total <= filesize) { + status = ASS_TRUNC; + goto done; + } + for (bytes = 0; bytes < sizeof (size_t); bytes++) { + size_t byte = total >> (bytes * 8); + if ((byte & (0x7f >> bytes)) == byte) { + break; + } + buf[8 - bytes] = total >> (bytes * 8); + } + buf[8 - bytes] = (total >> (bytes * 8)) | (0xff00 >> bytes); + + stream.avail_in = bytes + 1; + stream.next_in = buf + 8 - bytes; + do { + if (stream.avail_out == 0) { + fwrite(zbuf, 1, ZBUF_SIZE, file); + stream.next_out = zbuf; + stream.avail_out = ZBUF_SIZE; + } + ret = deflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK) { + status = ASS_BAD; + goto done; + } + } while (stream.avail_out == 0); + + stream.avail_in = length + 1; + if (stream.avail_in != length + 1) { + status = ASS_LAZY; + goto done; + } + stream.next_in = (void *) filename; + do { + if (stream.avail_out == 0) { + fwrite(zbuf, 1, ZBUF_SIZE, file); + stream.next_out = zbuf; + stream.avail_out = ZBUF_SIZE; + } + ret = deflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK) { + status = ASS_BAD; + goto done; + } + } while (stream.avail_out == 0); + + stream.avail_in = filesize; + if (stream.avail_in != filesize) { + status = ASS_LAZY; + goto done; + } + stream.next_in = filedata; + do { + if (stream.avail_out == 0) { + fwrite(zbuf, 1, ZBUF_SIZE, file); + stream.next_out = zbuf; + stream.avail_out = ZBUF_SIZE; + } + ret = deflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK) { + status = ASS_BAD; + goto done; + } + } while (stream.avail_out == 0); + } + do { + if (stream.avail_out == 0) { + fwrite(zbuf, 1, ZBUF_SIZE, file); + stream.next_out = zbuf; + stream.avail_out = ZBUF_SIZE; + } + ret = deflate(&stream, Z_FINISH); + if (ret != Z_OK && ret != Z_STREAM_END) { + status = ASS_BAD; + goto done; + } + } while (stream.avail_out == 0); + fwrite(zbuf, 1, ZBUF_SIZE - stream.avail_out, file); + done: + deflateEnd(&stream); + cb(&filename, &filedata, NULL, ctx); + if (status == ASS_OK && wrote_any_files_at_all == 0) { + return ASS_TRUNC; + } + return status; +} diff --git a/src/main.c++ b/src/main.c++ index e234d0a..d5b4404 100644 --- a/src/main.c++ +++ b/src/main.c++ @@ -3,19 +3,43 @@ #define SDL_MAIN_USE_CALLBACKS 1 #include +#include +#include + #include #include +#include "ass.h" + #define WINDOW_WIDTH 128 #define WINDOW_HEIGHT 128 +#if 0 +// something something string_view faster but actually cryptic error messages so w/e future me problem +#include +struct string_hash { + using is_transparent = void; + [[nodiscard]] size_t operator()(const char *txt) const { + return std::hash{}(txt); + } + [[nodiscard]] size_t operator()(std::string_view txt) const { + return std::hash{}(txt); + } + [[nodiscard]] size_t operator()(const std::string &txt) const { + return std::hash{}(txt); + } +}; + std::unordered_map> programs, textures; +#endif + struct state { SDL_Window *window; SDL_GLContext context; float color; + std::unordered_map programs, textures; }; -SDL_AppResult SDL_AppInit(void **appstate, int, char **) { +SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { clock_t begin = clock(); auto state = new (struct state); @@ -24,8 +48,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int, char **) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "60"); state->window = SDL_CreateWindow("suwi", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL /*| SDL_WINDOW_RESIZABLE*/ | SDL_WINDOW_TRANSPARENT); + SDL_SetWindowMinimumSize(state->window, WINDOW_WIDTH, WINDOW_HEIGHT); state->context = SDL_GL_CreateContext(state->window); @@ -36,6 +62,42 @@ SDL_AppResult SDL_AppInit(void **appstate, int, char **) { } fprintf(stderr, "info: GLES%u.%u\n", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version)); + FILE *file; + if (argc < 2) { + std::string path = argv[0]; + auto s = path.rfind('.'); + if (s != path.npos) { + path.erase(s); + } + path.append(".assets"); + file = fopen(path.c_str(), "rb"); + if (file == NULL) { + perror(path.c_str()); + return SDL_APP_FAILURE; + } + } else { + file = fopen(argv[1], "rb"); + if (file == NULL) { + perror(argv[1]); + return SDL_APP_FAILURE; + } + } + if (assread(file, [](char const *name, size_t length, void const *data, size_t size, /* struct state *state */ void *ctx) -> int { + auto &state = *(struct state *) ctx; + std::string filename = std::string(name, length); + (void) name; + (void) length; + (void) data; + (void) size; + if (filename.ends_with(".glsl")) { + state.programs[filename]; + } + state.textures[filename]; + return 0; + }, state)) { + return SDL_APP_FAILURE; + } + glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); SDL_ShowWindow(state->window); @@ -85,11 +147,11 @@ SDL_AppResult SDL_AppEvent(void *, SDL_Event *evt) { } SDL_AppResult SDL_AppIterate(void *appstate) { - auto state = (struct state *) appstate; + auto &state = *(struct state *) appstate; glClearColor(0.5, 0.5, 0.5, 0.0); glClear(GL_COLOR_BUFFER_BIT); - SDL_GL_SwapWindow(state->window); + SDL_GL_SwapWindow(state.window); return SDL_APP_CONTINUE; }