summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkdx <kikoodx@paranoici.org>2023-12-28 04:07:13 +0100
committerkdx <kikoodx@paranoici.org>2023-12-28 04:07:13 +0100
commit65739fd6e94c291fb7d73fa6b133b7421dc7c776 (patch)
treec0b48b9a3368d06d92f42d17cb4bca64c05377b4
parent208ea0d5aea5b09bcbf0ac0a9a25808403057af3 (diff)
download007-65739fd6e94c291fb7d73fa6b133b7421dc7c776.tar.gz
fuck artgames
-rw-r--r--Tupfile4
-rw-r--r--compile_flags.txt1
-rw-r--r--inc/cell.h19
-rw-r--r--inc/config.h17
-rw-r--r--inc/world.h20
-rw-r--r--src/cell.c102
-rw-r--r--src/config.c95
-rw-r--r--src/main.c5
-rw-r--r--src/world.c128
-rw-r--r--vendors/_.c150
-rw-r--r--vendors/_.h52
-rw-r--r--vendors/ini.c277
-rw-r--r--vendors/ini.h20
13 files changed, 887 insertions, 3 deletions
diff --git a/Tupfile b/Tupfile
index d33ea47..6e2369c 100644
--- a/Tupfile
+++ b/Tupfile
@@ -1,9 +1,9 @@
CC = gcc
LD = gcc
SRC = src/*.c vendors/*.c
-CFLAGS = -std=c2x -Wall -Wextra -Wno-override-init -iquoteinc -includevendors/_.h -O3
+CFLAGS = -std=c2x -Wall -Wextra -Wno-override-init -iquoteinc -iquotevendors -includevendors/_.h -O3
LDFLAGS = -O3 -s
-LIBS = -lSDL2 -lSDL2_mixer
+LIBS = -lm -lSDL2 -lSDL2_mixer
NAME = 007
: foreach $(SRC) |> $(CC) -c -o %o $(CFLAGS) %f |> build/%B.o
diff --git a/compile_flags.txt b/compile_flags.txt
index 2225c3e..99f31f5 100644
--- a/compile_flags.txt
+++ b/compile_flags.txt
@@ -3,4 +3,5 @@
-Wextra
-Wno-initializer-overrides
-iquoteinc
+-iquotevendors
-includevendors/_.h
diff --git a/inc/cell.h b/inc/cell.h
new file mode 100644
index 0000000..eceb45c
--- /dev/null
+++ b/inc/cell.h
@@ -0,0 +1,19 @@
+#pragma once
+
+typedef struct Cell Cell;
+struct Cell {
+ int id;
+ int x;
+ int y;
+ int width;
+ int height;
+ int *data;
+ Cell *next;
+};
+
+extern float g_shake;
+
+Cell *cell_load(const char *pattern, int id, int x, int y);
+void cell_destroy(Cell *this, bool recurse);
+void cell_draw(Cell *this, int x, int y);
+int2 cell_find(Cell *this, int tile);
diff --git a/inc/config.h b/inc/config.h
new file mode 100644
index 0000000..e5fe194
--- /dev/null
+++ b/inc/config.h
@@ -0,0 +1,17 @@
+#pragma once
+
+typedef struct Config {
+ int tile_width;
+ int tile_height;
+ int cell_width;
+ int cell_height;
+ int world_width;
+ int world_height;
+ char *tileset_path;
+ char *world_path;
+} Config;
+
+extern Config cfg;
+
+void config_init(const char *path);
+void config_deinit(void);
diff --git a/inc/world.h b/inc/world.h
new file mode 100644
index 0000000..d4c56f0
--- /dev/null
+++ b/inc/world.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "cell.h"
+
+typedef struct World {
+ Cell *root_cell;
+ int width;
+ int height;
+ Cell **cells;
+ int x;
+ int y;
+ bool polarity;
+} World;
+
+extern World g_world;
+
+void world_init(const char *path, const char *cell_pattern);
+void world_deinit(void);
+void world_draw(void);
+int world_get(int x, int y);
+int2 world_find(int tile);
diff --git a/src/cell.c b/src/cell.c
new file mode 100644
index 0000000..b90d1ee
--- /dev/null
+++ b/src/cell.c
@@ -0,0 +1,102 @@
+float g_shake = 0.0f;
+
+#define expect(X) if (!(X)) { \
+ perr("expect failed: "STR(X)); \
+ fclose(fp); \
+ cell_destroy(this, false); \
+ return NULL; \
+}
+
+Cell *
+cell_load(const char *pattern, int id, int x, int y)
+{
+ assert(pattern != NULL);
+ assert(id >= 0);
+
+ char buf[64] = {0};
+ snprintf(buf, sizeof(buf) - 1, pattern, id);
+
+ FILE *fp = fopen(buf, "rb");
+ if (fp == NULL) {
+ perr("failed to open '%s': %s", buf, strerror(errno));
+ return NULL;
+ }
+
+ Cell *this = NULL;
+ int width = 0, height = 0;
+ fscanf(fp, "%d[^,\n]", &width);
+ fscanf(fp, "%*c");
+ fscanf(fp, "%d[^,\n]", &height);
+ fscanf(fp, "%*c");
+
+ expect(width > 0);
+ expect(height > 0);
+
+ this = alloc(sizeof(Cell));
+ expect(this != NULL);
+
+ this->id = id;
+ this->x = x;
+ this->y = y;
+ this->width = width;
+ this->height = height;
+ this->data = alloc(sizeof(this->data[0]) * this->width * this->height);
+ expect(this->data != NULL);
+
+ rfor (i, 0, this->width * this->height) {
+ fscanf(fp, "%d[^,\n]", &this->data[i]);
+ fscanf(fp, "%*c");
+ }
+
+ fclose(fp);
+ return this;
+}
+
+void
+cell_destroy(Cell *this, bool recurse)
+{
+ if (this == NULL)
+ return;
+ if (recurse)
+ cell_destroy(this->next, true);
+ if (this->data != NULL)
+ free(this->data);
+ free(this);
+}
+
+void
+cell_draw(Cell *this, int x, int y)
+{
+ const TZR_Uint tset = TZR_RES("res/tset.bmp");
+ if (tset == 0)
+ return;
+
+ const int tset_width = TZR_GetImageWidth(tset) / cfg.tile_width;
+ rfor (ty, 0, this->height) {
+ rfor (tx, 0, this->width) {
+ const int tile = this->data[tx + ty * this->width];
+ if (tile == 0)
+ continue;
+ int dy = y + ty * cfg.tile_height;
+ int dx = x + tx * cfg.tile_width;
+ const int ix = (tile-1) % tset_width * cfg.tile_width;
+ int iy = (tile-1) / tset_width * cfg.tile_height;
+ const int r = rand();
+ TZR_DrawImage(tset,
+ dx, dy,
+ ix, iy,
+ cfg.tile_width, cfg.tile_height,
+ .flip_x=r&1, .flip_y=(r&2)!=0);
+ }
+ }
+}
+
+int2
+cell_find(Cell *this, int tile)
+{
+ rfor (y, 0, this->height)
+ rfor (x, 0, this->width)
+ if (this->data[x + y * this->width] == tile)
+ return I2(x, y);
+ return I2R(-1);
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..9a673ee
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,95 @@
+#include "ini.h"
+
+Config cfg = {0};
+
+#define expect(X) if (!(X)) { \
+ perr("expect failed: "STR(X)); \
+ if (ini != NULL) ini_free(ini); \
+ config_deinit(); \
+}
+
+void
+config_init(const char *path)
+{
+ ini_t *ini = NULL;
+
+ ini = ini_load(path);
+ expect(ini != NULL);
+
+ const char *tileset = ini_get(ini, "tileset", "path");
+ if (tileset == NULL) {
+ pwrn("[tileset] path unspecified, using default");
+ tileset = "tileset.png";
+ }
+ cfg.tileset_path = alloc(strlen(tileset) + 1);
+ expect(cfg.tileset_path != NULL);
+ strcpy(cfg.tileset_path, tileset);
+
+ const char *world_path = ini_get(ini, "world", "path");
+ if (world_path == NULL) {
+ pwrn("[world] path unspecified, using default");
+ world_path = "world.csv";
+ }
+ cfg.world_path = alloc(strlen(world_path) + 1);
+ expect(cfg.world_path != NULL);
+ strcpy(cfg.world_path, world_path);
+
+ with (tile_width, ini_get(ini, "tile", "width")) {
+ cfg.tile_width = atoi(tile_width);
+ } else {
+ pwrn("[tile] width unspecified, using default");
+ cfg.tile_width = 16;
+ }
+ expect(cfg.tile_width > 0);
+
+ with (tile_height, ini_get(ini, "tile", "height")) {
+ cfg.tile_height = atoi(tile_height);
+ } else {
+ pwrn("[tile] height unspecified, using default");
+ cfg.tile_height = 16;
+ }
+ expect(cfg.tile_height > 0);
+
+ with (cell_width, ini_get(ini, "cell", "width")) {
+ cfg.cell_width = atoi(cell_width);
+ } else {
+ pwrn("[cell] width unspecified, using default");
+ cfg.cell_width = 25;
+ }
+ expect(cfg.cell_width > 0);
+
+ with (cell_height, ini_get(ini, "cell", "height")) {
+ cfg.cell_height = atoi(cell_height);
+ } else {
+ pwrn("[cell] height unspecified, using default");
+ cfg.cell_height = 14;
+ }
+ expect(cfg.cell_height > 0);
+
+ with (world_width, ini_get(ini, "world", "width")) {
+ cfg.world_width = atoi(world_width);
+ } else {
+ pwrn("[world] width unspecified, using default");
+ cfg.world_width = 16;
+ }
+ expect(cfg.world_width > 0);
+
+ with (world_height, ini_get(ini, "world", "height")) {
+ cfg.world_height = atoi(world_height);
+ } else {
+ pwrn("[world] height unspecified, using default");
+ cfg.world_height = 16;
+ }
+ expect(cfg.world_height > 0);
+
+ ini_free(ini);
+}
+
+void
+config_deinit(void)
+{
+ if (cfg.world_path != NULL)
+ free(cfg.world_path);
+ if (cfg.tileset_path != NULL)
+ free(cfg.tileset_path);
+}
diff --git a/src/main.c b/src/main.c
index 3298b03..de7b5ff 100644
--- a/src/main.c
+++ b/src/main.c
@@ -13,6 +13,10 @@ main([[maybe_unused]] int argc, [[maybe_unused]] char **argv)
{
srand(time(nullptr));
defer(wdeinit);
+ config_init("res/world/aancyk.ini");
+ defer(config_deinit);
+ world_init("res/world/world.csv", "res/world/%d.csv");
+ defer(world_deinit);
assert(TZR_Init(.interlace=true,
.width=640,
.height=640,
@@ -55,6 +59,7 @@ _main_loop([[maybe_unused]] void *udata)
.sx=1.0+randf()*.1,
.sy=1.0+randf()*.1);
}
+ world_draw();
//map_draw();
//player_draw();
} else {
diff --git a/src/world.c b/src/world.c
new file mode 100644
index 0000000..a24c7a6
--- /dev/null
+++ b/src/world.c
@@ -0,0 +1,128 @@
+World g_world = {0};
+
+#define expect(X) if (!(X)) { \
+ perr("expect failed: "STR(X)); \
+ if (fp != NULL) fclose(fp); \
+ world_deinit(); \
+ exit(EXIT_FAILURE); \
+}
+
+void
+world_init(const char *path, const char *cell_pattern)
+{
+ assert(path != NULL);
+ assert(cell_pattern != NULL);
+
+ FILE *fp = fopen(path, "rw");
+ expect(fp != NULL);
+
+ int cell_count = 0;
+ fscanf(fp, "%d[^,\n", &cell_count);
+ fscanf(fp, "%*c");
+ expect(cell_count > 0);
+
+ // skip x and y
+ fscanf(fp, "%*d[^,\n]");
+ fscanf(fp, "%*c");
+ fscanf(fp, "%*d[^,\n]");
+ fscanf(fp, "%*c");
+
+ int min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN;
+ rfor (i, 0, cell_count) {
+ int id = 0, x = 0, y = 0;
+ fscanf(fp, "%d[^,\n]", &id);
+ fscanf(fp, "%*c");
+ fscanf(fp, "%d[^,\n]", &x);
+ fscanf(fp, "%*c");
+ fscanf(fp, "%d[^,\n]", &y);
+ fscanf(fp, "%*c");
+
+ min_x = min(x, min_x);
+ min_y = min(x, min_y);
+ max_x = max(x, max_x);
+ max_y = max(x, max_y);
+
+ Cell *const cell = cell_load(cell_pattern, id, x, y);
+ expect(cell != NULL);
+ cell->next = g_world.root_cell;
+ g_world.root_cell = cell;
+ }
+ expect(g_world.root_cell != NULL);
+
+ fclose(fp);
+ fp = NULL;
+
+ // flatten the cell structure
+ g_world.width = max_x - min_x + 1;
+ g_world.height = max_y - min_y + 1;
+ g_world.cells = alloc(sizeof(Cell*) * g_world.width * g_world.height);
+ expect(g_world.cells != NULL);
+
+ foreach (cell, g_world.root_cell) {
+ const int cell_x = cell->x - min_x;
+ const int cell_y = cell->y - min_y;
+ g_world.cells[cell_x + cell_y * g_world.width] = cell;
+ }
+}
+
+void
+world_deinit(void)
+{
+ if (g_world.cells != NULL) {
+ free(g_world.cells);
+ g_world.cells = NULL;
+ }
+ cell_destroy(g_world.root_cell, true);
+}
+
+void
+world_draw(void)
+{
+ with (cell, g_world.cells[g_world.x + g_world.y * g_world.width])
+ cell_draw(cell, 0, 0);
+}
+
+int
+world_get(int x, int y)
+{
+ int wx = g_world.x;
+ int wy = g_world.y;
+
+ if (x < 0) {
+ x += cfg.tile_width * cfg.cell_width;
+ wx -= 1;
+ } else if (x >= cfg.tile_width * cfg.cell_width) {
+ x -= cfg.tile_width * cfg.cell_width;
+ wx += 1;
+ }
+ if (y < 0) {
+ y += cfg.tile_height * cfg.cell_height;
+ wy -= 1;
+ } else if (y >= cfg.tile_height * cfg.cell_height) {
+ y -= cfg.tile_height * cfg.cell_height;
+ wy += 1;
+ }
+
+ const int i = x / cfg.tile_width + (y / cfg.tile_height) * cfg.cell_width;
+ with (cell, g_world.cells[wx + wy * g_world.width])
+ return cell->data[i] ? (cell->data[i] - 1) : 0;
+ return 0; // oob tile
+}
+
+int2
+world_find(int tile)
+{
+ rfor (y, 0, g_world.height) {
+ rfor (x, 0, g_world.width) {
+ Cell *const cell = g_world.cells[x + y * g_world.width];
+ if (cell == NULL)
+ continue;
+ const int2 spawn = cell_find(cell, tile + 1);
+ if (spawn.x < 0)
+ continue;
+ return I2(x, y);
+ }
+ }
+
+ return I2(-1, -1);
+}
diff --git a/vendors/_.c b/vendors/_.c
index 9092bad..fc7ce07 100644
--- a/vendors/_.c
+++ b/vendors/_.c
@@ -1,4 +1,5 @@
#include "_.h"
+#include <math.h>
#undef free
#undef malloc
#undef calloc
@@ -77,3 +78,152 @@ _realloc(void *ptr, size_t size)
panic("fucked up realloc, have fun debugging");
__builtin_unreachable();
}
+
+// mathematical vectors
+
+float2
+tofloat2(int2 a)
+{
+ return F2(a.x, a.y);
+}
+
+int2
+toint2(float2 a)
+{
+ return I2(a.x, a.y);
+}
+
+float2
+float2_sub(float2 a, float2 b)
+{
+ return F2(a.x - b.x, a.y - b.y);
+}
+
+float2
+float2_add(float2 a, float2 b)
+{
+ return F2(a.x + b.x, a.y + b.y);
+}
+
+float2
+float2_mul(float2 a, float x)
+{
+ return F2(a.x * x, a.y * x);
+}
+
+float2
+float2_div(float2 a, float x)
+{
+ return F2(a.x / x, a.y / x);
+}
+
+float
+float2_length(float2 a)
+{
+ return sqrtf(a.x * a.x + a.y * a.y);
+}
+
+float2
+float2_normalize(float2 a)
+{
+ const auto len = float2_length(a);
+ if (len < 1.0)
+ return a;
+ return F2(a.x / len, a.y / len);
+}
+
+float
+float2_angle(float2 a)
+{
+ return atanf(a.y / a.x) / 3.125 / 2 + 0.5 * (a.x < 0);
+}
+
+int2
+float2_round(float2 a)
+{
+ return I2(roundf(a.x), roundf(a.y));
+}
+
+float2
+float2_clamp(float2 min, float2 a, float2 max)
+{
+ rfor (i, 0, 2) {
+ if (a.a[i] < min.a[i])
+ a.a[i] = min.a[i];
+ if (a.a[i] > max.a[i])
+ a.a[i] = max.a[i];
+ }
+ return a;
+}
+
+float2
+float2_swp(float2 a)
+{
+ return F2(a.y, a.x);
+}
+
+float2
+float2_lerp(float2 a, float2 b, float x)
+{
+ return float2_add(a, float2_mul(float2_sub(b, a), x));
+}
+
+int2
+int2_sub(int2 a, int2 b)
+{
+ return I2(a.x - b.x, a.y - b.y);
+}
+
+int2
+int2_add(int2 a, int2 b)
+{
+ return I2(a.x + b.x, a.y + b.y);
+}
+
+int2
+int2_mul(int2 a, float x)
+{
+ return I2(a.x * x, a.y * x);
+}
+
+int2
+int2_div(int2 a, float x)
+{
+ return I2(a.x / x, a.y / x);
+}
+
+static int
+___abs(int a)
+{
+ return (a < 0) ? (-a) : (a);
+}
+
+int2
+int2_abs(int2 a)
+{
+ return I2(___abs(a.x), ___abs(a.y));
+}
+
+int2
+int2_swp(int2 a)
+{
+ return I2(a.y, a.x);
+}
+
+int2
+int2_inv(int2 a)
+{
+ return I2(-a.x, -a.y);
+}
+
+int2
+int4_xy(int4 a)
+{
+ return I2(a.x, a.y);
+}
+
+int2
+int4_zw(int4 a)
+{
+ return I2(a.z, a.w);
+}
diff --git a/vendors/_.h b/vendors/_.h
index ef10927..544ff29 100644
--- a/vendors/_.h
+++ b/vendors/_.h
@@ -5,7 +5,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "TZR.h"
void wdeinit(void);
[[nodiscard]] void *alloc(size_t size);
@@ -53,3 +52,54 @@ void *_realloc(void *ptr, size_t size);
#define pwrn(...) pgeneric("93mWRN", __VA_ARGS__)
#define panic(...) perr(__VA_ARGS__), exit(EXIT_FAILURE)
#define assert(X) if (!(X)) panic("assert failed: "STR(X))
+
+// mathematical vectors
+
+#define UNPACK(V) (V).x, (V).y
+#define I2(X, Y) (int2){{(X), (Y)}}
+#define I2R(X) I2((X), (X))
+#define I2Z I2(0, 0)
+#define F2(X, Y) (float2){{(X), (Y)}}
+#define F2R(X) F2((X), (X))
+#define F2Z F2(0.0f, 0.0f)
+#define I4(X, Y, Z, W) (int4){{(X), (Y), (Z), (W)}}
+#define I4Z I4(0, 0, 0, 0)
+#define I21 I2R(1)
+#define F21 F2R(1)
+
+typedef union { struct { int x, y; }; int a[2]; } int2;
+typedef union { struct { float x, y; }; float a[2]; } float2;
+typedef union { struct { int x, y, z, w; }; int a[4]; } int4;
+
+float2 tofloat2(int2 a);
+int2 toint2(float2 a);
+
+float2 float2_add(float2 a, float2 b);
+float2 float2_sub(float2 a, float2 b);
+float2 float2_mul(float2 a, float x);
+float2 float2_div(float2 a, float x);
+float float2_length(float2 a);
+float2 float2_normalize(float2 a);
+float float2_angle(float2 a);
+int2 float2_round(float2 a);
+float2 float2_clamp(float2 min, float2 a, float2 max);
+float2 float2_swp(float2 a);
+float2 float2_lerp(float2 a, float2 b, float x);
+
+int2 int2_sub(int2 a, int2 b);
+int2 int2_add(int2 a, int2 b);
+int2 int2_mul(int2 a, float x);
+int2 int2_div(int2 a, float x);
+int2 int2_abs(int2 a);
+int2 int2_swp(int2 a);
+int2 int2_inv(int2 a);
+
+int2 int4_xy(int4 a);
+int2 int4_zw(int4 a);
+
+#define int2_log(L, A) log_##L("%d %d", (A).x, (A).y)
+#define float2_log(L, A) log_##L("%f %f", (A).x, (A).y)
+
+#include "TZR.h"
+#include "config.h"
+#include "world.h"
diff --git a/vendors/ini.c b/vendors/ini.c
new file mode 100644
index 0000000..d6f5b75
--- /dev/null
+++ b/vendors/ini.c
@@ -0,0 +1,277 @@
+/**
+ * Copyright (c) 2016 rxi
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "ini.h"
+
+struct ini_t {
+ char *data;
+ char *end;
+};
+
+
+/* Case insensitive string compare */
+static int strcmpci(const char *a, const char *b) {
+ for (;;) {
+ int d = tolower(*a) - tolower(*b);
+ if (d != 0 || !*a) {
+ return d;
+ }
+ a++, b++;
+ }
+}
+
+/* Returns the next string in the split data */
+static char* next(ini_t *ini, char *p) {
+ p += strlen(p);
+ while (p < ini->end && *p == '\0') {
+ p++;
+ }
+ return p;
+}
+
+static void trim_back(ini_t *ini, char *p) {
+ while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
+ *p-- = '\0';
+ }
+}
+
+static char* discard_line(ini_t *ini, char *p) {
+ while (p < ini->end && *p != '\n') {
+ *p++ = '\0';
+ }
+ return p;
+}
+
+
+static char *unescape_quoted_value(ini_t *ini, char *p) {
+ /* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
+ * as escape sequences are always larger than their resultant data */
+ char *q = p;
+ p++;
+ while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
+ if (*p == '\\') {
+ /* Handle escaped char */
+ p++;
+ switch (*p) {
+ default : *q = *p; break;
+ case 'r' : *q = '\r'; break;
+ case 'n' : *q = '\n'; break;
+ case 't' : *q = '\t'; break;
+ case '\r' :
+ case '\n' :
+ case '\0' : goto end;
+ }
+
+ } else {
+ /* Handle normal char */
+ *q = *p;
+ }
+ q++, p++;
+ }
+end:
+ return q;
+}
+
+
+/* Splits data in place into strings containing section-headers, keys and
+ * values using one or more '\0' as a delimiter. Unescapes quoted values */
+static void split_data(ini_t *ini) {
+ char *value_start, *line_start;
+ char *p = ini->data;
+
+ while (p < ini->end) {
+ switch (*p) {
+ case '\r':
+ case '\n':
+ case '\t':
+ case ' ':
+ *p = '\0';
+ /* Fall through */
+
+ case '\0':
+ p++;
+ break;
+
+ case '[':
+ p += strcspn(p, "]\n");
+ *p = '\0';
+ break;
+
+ case ';':
+ p = discard_line(ini, p);
+ break;
+
+ default:
+ line_start = p;
+ p += strcspn(p, "=\n");
+
+ /* Is line missing a '='? */
+ if (*p != '=') {
+ p = discard_line(ini, line_start);
+ break;
+ }
+ trim_back(ini, p - 1);
+
+ /* Replace '=' and whitespace after it with '\0' */
+ do {
+ *p++ = '\0';
+ } while (*p == ' ' || *p == '\r' || *p == '\t');
+
+ /* Is a value after '=' missing? */
+ if (*p == '\n' || *p == '\0') {
+ p = discard_line(ini, line_start);
+ break;
+ }
+
+ if (*p == '"') {
+ /* Handle quoted string value */
+ value_start = p;
+ p = unescape_quoted_value(ini, p);
+
+ /* Was the string empty? */
+ if (p == value_start) {
+ p = discard_line(ini, line_start);
+ break;
+ }
+
+ /* Discard the rest of the line after the string value */
+ p = discard_line(ini, p);
+
+ } else {
+ /* Handle normal value */
+ p += strcspn(p, "\n");
+ trim_back(ini, p - 1);
+ }
+ break;
+ }
+ }
+}
+
+
+
+ini_t* ini_load(const char *filename) {
+ ini_t *ini = NULL;
+ FILE *fp = NULL;
+ int n, sz;
+
+ /* Init ini struct */
+ ini = malloc(sizeof(*ini));
+ if (!ini) {
+ goto fail;
+ }
+ memset(ini, 0, sizeof(*ini));
+
+ /* Open file */
+ fp = fopen(filename, "rb");
+ if (!fp) {
+ goto fail;
+ }
+
+ /* Get file size */
+ fseek(fp, 0, SEEK_END);
+ sz = ftell(fp);
+ rewind(fp);
+
+ /* Load file content into memory, null terminate, init end var */
+ ini->data = malloc(sz + 1);
+ if (!ini->data) {
+ goto fail;
+ }
+ ini->data[sz] = '\0';
+ ini->end = ini->data + sz;
+ n = fread(ini->data, 1, sz, fp);
+ if (n != sz) {
+ goto fail;
+ }
+
+ /* Prepare data */
+ split_data(ini);
+
+ /* Clean up and return */
+ fclose(fp);
+ return ini;
+
+fail:
+ if (fp) fclose(fp);
+ if (ini) ini_free(ini);
+ return NULL;
+}
+
+
+void ini_free(ini_t *ini) {
+ if (ini->data) free(ini->data);
+ free(ini);
+}
+
+
+const char* ini_get(ini_t *ini, const char *section, const char *key) {
+ char *current_section = "";
+ char *val;
+ char *p = ini->data;
+
+ if (*p == '\0') {
+ p = next(ini, p);
+ }
+
+ while (p < ini->end) {
+ if (*p == '[') {
+ /* Handle section */
+ current_section = p + 1;
+
+ } else {
+ /* Handle key */
+ val = next(ini, p);
+ if (!section || !strcmpci(section, current_section)) {
+ if (!strcmpci(p, key)) {
+ return val;
+ }
+ }
+ p = val;
+ }
+
+ p = next(ini, p);
+ }
+
+ return NULL;
+}
+
+
+int ini_sget(
+ ini_t *ini, const char *section, const char *key,
+ const char *scanfmt, void *dst
+) {
+ const char *val = ini_get(ini, section, key);
+ if (!val) {
+ return 0;
+ }
+ if (scanfmt) {
+ sscanf(val, scanfmt, dst);
+ } else {
+ *((const char**) dst) = val;
+ }
+ return 1;
+}
diff --git a/vendors/ini.h b/vendors/ini.h
new file mode 100644
index 0000000..cd6af9f
--- /dev/null
+++ b/vendors/ini.h
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2016 rxi
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the MIT license. See `ini.c` for details.
+ */
+
+#ifndef INI_H
+#define INI_H
+
+#define INI_VERSION "0.1.1"
+
+typedef struct ini_t ini_t;
+
+ini_t* ini_load(const char *filename);
+void ini_free(ini_t *ini);
+const char* ini_get(ini_t *ini, const char *section, const char *key);
+int ini_sget(ini_t *ini, const char *section, const char *key, const char *scanfmt, void *dst);
+
+#endif