diff options
Diffstat (limited to 'kon')
| -rw-r--r-- | kon/konsave.c | 389 | ||||
| -rw-r--r-- | kon/konsave.h | 99 |
2 files changed, 488 insertions, 0 deletions
diff --git a/kon/konsave.c b/kon/konsave.c new file mode 100644 index 0000000..e351e5a --- /dev/null +++ b/kon/konsave.c @@ -0,0 +1,389 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konsave.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "konsave.h" + +#include <stdio.h> +#include <string.h> + +/*** checksum ***/ + +static uint32_t crc32(const uint8_t *p, size_t n) +{ + uint32_t crc = 0xFFFFFFFFu; + size_t i; + int bit; + + for (i = 0; i < n; i++) { + crc ^= p[i]; + for (bit = 0; bit < 8; bit++) crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u))); + } + return ~crc; +} + +/*** writing ***/ + +static void putAt(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)(v & 0xFF); + p[1] = (uint8_t)((v >> 8) & 0xFF); + p[2] = (uint8_t)((v >> 16) & 0xFF); + p[3] = (uint8_t)((v >> 24) & 0xFF); +} + +static uint32_t getAt(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static int room(kon_saveWriter_t *w, size_t n) +{ + if (w->error) return 0; + if (!w->buf || n > w->cap - w->len) { + w->error = 1; + return 0; + } + return 1; +} + +static void closeChunk(kon_saveWriter_t *w) +{ + if (!w->open || w->error) return; + if (w->len - w->chunk - 8 > 0xFFFFFFFFu) { + w->error = 1; + return; + } + putAt(w->buf + w->chunk + 4, (uint32_t)(w->len - w->chunk - 8)); + w->open = 0; +} + +void kon_saveBegin(kon_saveWriter_t *w, void *buf, size_t cap, uint32_t version) +{ + kon_saveWriter_t zero = {0}; + + *w = zero; + w->buf = buf; + w->cap = buf ? cap : 0; + if (!room(w, 8)) return; + memcpy(w->buf, "KONS", 4); + putAt(w->buf + 4, version); + w->len = 8; +} + +void kon_saveChunk(kon_saveWriter_t *w, const char *tag) +{ + closeChunk(w); + if (!tag || strlen(tag) != 4 || !room(w, 8)) { + w->error = 1; + return; + } + w->chunk = w->len; + memcpy(w->buf + w->len, tag, 4); + putAt(w->buf + w->len + 4, 0); + w->len += 8; + w->open = 1; +} + +void kon_savePutBytes(kon_saveWriter_t *w, const void *data, size_t n) +{ + if (!w->open) { + w->error = 1; + return; + } + if (!room(w, n)) return; + if (n) memcpy(w->buf + w->len, data, n); + w->len += n; +} + +void kon_savePutU8(kon_saveWriter_t *w, unsigned v) +{ + uint8_t b = (uint8_t)(v & 0xFF); + + kon_savePutBytes(w, &b, 1); +} + +void kon_savePutU16(kon_saveWriter_t *w, unsigned v) +{ + uint8_t b[2]; + + b[0] = (uint8_t)(v & 0xFF); + b[1] = (uint8_t)((v >> 8) & 0xFF); + kon_savePutBytes(w, b, 2); +} + +void kon_savePutU32(kon_saveWriter_t *w, uint32_t v) +{ + uint8_t b[4]; + + putAt(b, v); + kon_savePutBytes(w, b, 4); +} + +void kon_savePutI32(kon_saveWriter_t *w, int32_t v) +{ + kon_savePutU32(w, (uint32_t)v); +} + +void kon_savePutF32(kon_saveWriter_t *w, float v) +{ + uint32_t bits; + + memcpy(&bits, &v, sizeof bits); + kon_savePutU32(w, bits); +} + +void kon_savePutString(kon_saveWriter_t *w, const char *s) +{ + size_t n = s ? strlen(s) : 0; + + if (n > 65535u) n = 65535u; + kon_savePutU16(w, (unsigned)n); + kon_savePutBytes(w, s, n); +} + +void kon_savePutSwitches(kon_saveWriter_t *w, const uint8_t *switches, int count) +{ + int i; + + if (count < 0 || !switches) count = 0; + kon_savePutU32(w, (uint32_t)count); + for (i = 0; i < count; i++) kon_savePutU8(w, switches[i] ? 1 : 0); +} + +void kon_savePutVars(kon_saveWriter_t *w, const int32_t *vars, int count) +{ + int i; + + if (count < 0 || !vars) count = 0; + kon_savePutU32(w, (uint32_t)count); + for (i = 0; i < count; i++) kon_savePutI32(w, vars[i]); +} + +size_t kon_saveEnd(kon_saveWriter_t *w) +{ + closeChunk(w); + if (!room(w, 4)) return 0; + putAt(w->buf + w->len, crc32(w->buf, w->len)); + w->len += 4; + return w->len; +} + +/*** reading ***/ + +int kon_saveOpen(kon_saveReader_t *r, const void *buf, size_t len) +{ + const uint8_t *p = buf; + size_t at; + + memset(r, 0, sizeof *r); + if (!p || len < 12 || memcmp(p, "KONS", 4) != 0) return 0; + if (crc32(p, len - 4) != getAt(p + len - 4)) return 0; + at = 8; + while (at < len - 4) { + size_t size; + + if (len - 4 - at < 8) return 0; + size = getAt(p + at + 4); + if (size > len - 4 - at - 8) return 0; + at += 8 + size; + } + r->buf = p; + r->len = len; + r->version = getAt(p + 4); + return 1; +} + +int kon_saveFind(const kon_saveReader_t *r, const char *tag, kon_saveCursor_t *c) +{ + size_t at = 8; + + c->p = c->end = NULL; + c->error = 0; + if (!r->buf || !tag || strlen(tag) != 4) { + c->error = 1; + return 0; + } + while (at + 8 <= r->len - 4) { + size_t size = getAt(r->buf + at + 4); + + if (memcmp(r->buf + at, tag, 4) == 0) { + c->p = r->buf + at + 8; + c->end = c->p + size; + return 1; + } + at += 8 + size; + } + c->error = 1; + return 0; +} + +void kon_saveGetBytes(kon_saveCursor_t *c, void *out, size_t n) +{ + if (c->error || !c->p || (size_t)(c->end - c->p) < n) { + c->error = 1; + if (out) memset(out, 0, n); + return; + } + if (n) memcpy(out, c->p, n); + c->p += n; +} + +unsigned kon_saveGetU8(kon_saveCursor_t *c) +{ + uint8_t b = 0; + + kon_saveGetBytes(c, &b, 1); + return b; +} + +unsigned kon_saveGetU16(kon_saveCursor_t *c) +{ + uint8_t b[2]; + + kon_saveGetBytes(c, b, 2); + return (unsigned)b[0] | ((unsigned)b[1] << 8); +} + +uint32_t kon_saveGetU32(kon_saveCursor_t *c) +{ + uint8_t b[4]; + + kon_saveGetBytes(c, b, 4); + return getAt(b); +} + +int32_t kon_saveGetI32(kon_saveCursor_t *c) +{ + uint32_t v = kon_saveGetU32(c); + + return v > 0x7FFFFFFFu ? -(int32_t)(0xFFFFFFFFu - v) - 1 : (int32_t)v; +} + +float kon_saveGetF32(kon_saveCursor_t *c) +{ + uint32_t bits = kon_saveGetU32(c); + float v; + + memcpy(&v, &bits, sizeof v); + return v; +} + +void kon_saveGetString(kon_saveCursor_t *c, char *out, size_t size) +{ + size_t n = kon_saveGetU16(c); + + if (c->error || (size_t)(c->end - c->p) < n) { + c->error = 1; + if (out && size) out[0] = '\0'; + return; + } + if (out && size) { + size_t keep = n < size - 1 ? n : size - 1; + + memcpy(out, c->p, keep); + out[keep] = '\0'; + } + c->p += n; +} + +int kon_saveGetSwitches(kon_saveCursor_t *c, uint8_t *switches, int count) +{ + uint32_t saved = kon_saveGetU32(c); + uint32_t i; + + for (i = 0; i < saved; i++) { + unsigned v = kon_saveGetU8(c); + + if (c->error) break; + if (switches && i < (uint32_t)count) switches[i] = v ? 1 : 0; + } + for (i = saved; switches && i < (uint32_t)count; i++) switches[i] = 0; + return c->error ? 0 : (saved > 0x7FFFFFFFu ? 0x7FFFFFFF : (int)saved); +} + +int kon_saveGetVars(kon_saveCursor_t *c, int32_t *vars, int count) +{ + uint32_t saved = kon_saveGetU32(c); + uint32_t i; + + for (i = 0; i < saved; i++) { + int32_t v = kon_saveGetI32(c); + + if (c->error) break; + if (vars && i < (uint32_t)count) vars[i] = v; + } + for (i = saved; vars && i < (uint32_t)count; i++) vars[i] = 0; + return c->error ? 0 : (saved > 0x7FFFFFFFu ? 0x7FFFFFFF : (int)saved); +} + +/*** files ***/ + +int kon_saveWriteFile(const char *path, const void *buf, size_t len) +{ + char temp[1024]; + FILE *f; + size_t n; + + if (!path || (!buf && len)) return 0; + n = strlen(path); + if (n + 5 > sizeof temp) return 0; + memcpy(temp, path, n); + memcpy(temp + n, ".tmp", 5); + f = fopen(temp, "wb"); + if (!f) return 0; + if ((len && fwrite(buf, 1, len, f) != len) || fflush(f) != 0) { + fclose(f); + remove(temp); + return 0; + } + if (fclose(f) != 0) { + remove(temp); + return 0; + } + if (rename(temp, path) != 0) { + remove(path); + if (rename(temp, path) != 0) { + remove(temp); + return 0; + } + } + return 1; +} + +long kon_saveReadFile(const char *path, void *buf, size_t cap) +{ + FILE *f; + size_t n; + int extra; + + if (!path || (!buf && cap)) return -1; + f = fopen(path, "rb"); + if (!f) return -1; + n = cap ? fread(buf, 1, cap, f) : 0; + extra = fgetc(f); + fclose(f); + if (extra != EOF || n > 0x7FFFFFFFu) return -1; + return (long)n; +} diff --git a/kon/konsave.h b/kon/konsave.h new file mode 100644 index 0000000..45e45c1 --- /dev/null +++ b/kon/konsave.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konsave.h + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef KONSAVE_H +#define KONSAVE_H + +#include <stddef.h> +#include <stdint.h> + +/*** definition ***/ + +/* a save is a list of tagged chunks, so each part of a game writes and reads its own and a menu can read just +the little META chunk of every slot. whole thing is checked with a crc, a damaged or cut short file is refused. numbers +are little endian whatever the machine. it all happens in a buffer you own, the file functions are at the end */ + +typedef struct kon_saveWriter { + uint8_t *buf; + size_t cap, len, chunk; + int error, open; +} kon_saveWriter_t; + +typedef struct kon_saveReader { + const uint8_t *buf; + size_t len; + uint32_t version; /* the number you gave kon_saveBegin, to change old saves into new ones */ +} kon_saveReader_t; + +typedef struct kon_saveCursor { + const uint8_t *p, *end; + int error; /* reading past the end gives 0s and sets this */ +} kon_saveCursor_t; + +/*** writing ***/ + +void kon_saveBegin(kon_saveWriter_t *w, void *buf, size_t cap, uint32_t version); +void kon_saveChunk(kon_saveWriter_t *w, const char *tag); /* four characters, ends the chunk before it */ +void kon_savePutU8(kon_saveWriter_t *w, unsigned v); +void kon_savePutU16(kon_saveWriter_t *w, unsigned v); +void kon_savePutU32(kon_saveWriter_t *w, uint32_t v); +void kon_savePutI32(kon_saveWriter_t *w, int32_t v); +void kon_savePutF32(kon_saveWriter_t *w, float v); +void kon_savePutBytes(kon_saveWriter_t *w, const void *data, size_t n); +void kon_savePutString(kon_saveWriter_t *w, const char *s); /* up to 65535 bytes */ +void kon_savePutSwitches(kon_saveWriter_t *w, const uint8_t *switches, int count); +void kon_savePutVars(kon_saveWriter_t *w, const int32_t *vars, int count); + +/* how many bytes the save is, or 0 if it did not fit in the buffer */ +size_t kon_saveEnd(kon_saveWriter_t *w); + +/*** reading ***/ + +/* 0 if it is not a save, is damaged or is cut short */ +int kon_saveOpen(kon_saveReader_t *r, const void *buf, size_t len); +int kon_saveFind(const kon_saveReader_t *r, const char *tag, kon_saveCursor_t *c); + +unsigned kon_saveGetU8(kon_saveCursor_t *c); +unsigned kon_saveGetU16(kon_saveCursor_t *c); +uint32_t kon_saveGetU32(kon_saveCursor_t *c); +int32_t kon_saveGetI32(kon_saveCursor_t *c); +float kon_saveGetF32(kon_saveCursor_t *c); +void kon_saveGetBytes(kon_saveCursor_t *c, void *out, size_t n); +void kon_saveGetString(kon_saveCursor_t *c, char *out, size_t size); /* always ends with a 0, cut if it does not fit */ + +/* into what you have, if the save had more they are dropped and if fewer the rest are 0. return how many the save had */ +int kon_saveGetSwitches(kon_saveCursor_t *c, uint8_t *switches, int count); +int kon_saveGetVars(kon_saveCursor_t *c, int32_t *vars, int count); + +/*** files ***/ + +/* written beside the real name first and then moved over it, so a crash leaves the old save. on Windows the old one +is removed just before the move. 1 if it worked */ +int kon_saveWriteFile(const char *path, const void *buf, size_t len); + +/* how many bytes were read, or -1 if it could not be opened or does not fit in cap */ +long kon_saveReadFile(const char *path, void *buf, size_t cap); + +#endif /* KONSAVE_H */ |
