summaryrefslogtreecommitdiff
path: root/libraries
diff options
context:
space:
mode:
Diffstat (limited to 'libraries')
-rw-r--r--libraries/nonstd.h839
-rw-r--r--libraries/vfs.h209
2 files changed, 1048 insertions, 0 deletions
diff --git a/libraries/nonstd.h b/libraries/nonstd.h
new file mode 100644
index 0000000..531834d
--- /dev/null
+++ b/libraries/nonstd.h
@@ -0,0 +1,839 @@
+// nonstd.h
+// A collection of useful functions and macros.
+// This library is licensed under the BSD 2-Clause License.
+//
+// This file provides both the interface and the implementation.
+// To instantiate the implementation,
+// #define NONSTD_IMPLEMENTATION
+// before including this file.
+
+#ifdef NONSTD_IMPLEMENTATION
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#endif
+
+#ifndef NONSTD_H
+#define NONSTD_H
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef NONSTD_DEF
+#ifdef NONSTD_STATIC
+#define NONSTD_DEF static
+#else
+#define NONSTD_DEF extern
+#endif
+#endif
+
+typedef int8_t i8;
+typedef uint8_t u8;
+typedef int16_t i16;
+typedef uint16_t u16;
+typedef int32_t i32;
+typedef uint32_t u32;
+typedef int64_t i64;
+typedef uint64_t u64;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef intptr_t isize;
+typedef uintptr_t usize;
+typedef char c8;
+
+#define countof(a) (sizeof(a) / sizeof((a)[0]))
+
+#define static_foreach(type, var, array) \
+ for (size_t _i_##var = 0, _n_##var = countof(array); \
+ _i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var)
+
+#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n)))
+#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n)))
+#define FREE(ptr) \
+ do { \
+ free(ptr); \
+ ptr = NULL; \
+ } while (0)
+
+NONSTD_DEF void *safe_malloc(size_t item_size, size_t count);
+NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count);
+
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x))))
+
+// From https://github.com/tsoding/nob.h/blob/e2c9a46f01d052ab740140e74453665dc3334832/nob.h#L205-L206.
+#define UNUSED(value) (void)(value)
+#define TODO(message) \
+ do { \
+ fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \
+ abort(); \
+ } while (0)
+#define UNREACHABLE(message) \
+ do { \
+ fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \
+ abort(); \
+ } while (0)
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg)
+#else
+#define STATIC_ASSERT(expr, msg) \
+ typedef char static_assertion_##msg[(expr) ? 1 : -1]
+#endif
+
+// String view - read-only, non-owning reference to a string
+typedef struct {
+ const char *data;
+ size_t length;
+} stringv;
+
+NONSTD_DEF stringv sv_from_cstr(const char *s);
+NONSTD_DEF stringv sv_from_parts(const char *data, size_t length);
+NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end);
+NONSTD_DEF int sv_equals(stringv a, stringv b);
+NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix);
+NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix);
+
+// String builder - owning, mutable, dynamically growing string buffer
+typedef struct {
+ char *data;
+ size_t length;
+ size_t capacity;
+} stringb;
+
+NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap);
+NONSTD_DEF void sb_free(stringb *sb);
+NONSTD_DEF void sb_ensure(stringb *sb, size_t additional);
+NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s);
+NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv);
+NONSTD_DEF void sb_append_char(stringb *sb, char c);
+NONSTD_DEF stringv sb_as_sv(const stringb *sb);
+
+// Slice - generic non-owning view into an array
+// Usage: SLICE_DEF(int); slice(int) view = ...;
+#define SLICE_DEF(T) \
+ typedef struct { \
+ T *data; \
+ size_t length; \
+ } slice_##T
+
+#define slice(T) slice_##T
+
+#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)})
+
+#define array_as_slice(T, arr) \
+ ((slice(T)){.data = (arr).data, .length = (arr).length})
+
+// Dynamic array - generic type-safe growable array using macros
+// Usage: array(int) numbers; array_init(numbers);
+#define array(T) \
+ struct { \
+ T *data; \
+ size_t length; \
+ size_t capacity; \
+ }
+
+#define array_init(arr) \
+ do { \
+ (arr).capacity = 0; \
+ (arr).data = NULL; \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_init_cap(arr, initial_cap) \
+ do { \
+ (arr).capacity = (initial_cap) ? (initial_cap) : 16; \
+ (arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_free(arr) \
+ do { \
+ FREE((arr).data); \
+ (arr).length = 0; \
+ (arr).capacity = 0; \
+ } while (0)
+
+#define array_ensure(arr, additional) \
+ do { \
+ size_t _needed = (arr).length + (additional); \
+ if (_needed > (arr).capacity) { \
+ size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \
+ while (_new_cap < _needed) { \
+ if (_new_cap > SIZE_MAX / 2) { \
+ _new_cap = SIZE_MAX; \
+ break; \
+ } \
+ _new_cap *= 2; \
+ } \
+ if (_new_cap < _needed) { /* Overflow or OOM */ \
+ break; \
+ } \
+ void *_new_data = \
+ safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \
+ if (_new_data) { \
+ (arr).data = _new_data; \
+ (arr).capacity = _new_cap; \
+ } \
+ } \
+ } while (0)
+
+#define array_push(arr, value) \
+ do { \
+ array_ensure((arr), 1); \
+ if ((arr).length < (arr).capacity) { \
+ (arr).data[(arr).length++] = (value); \
+ } \
+ } while (0)
+
+#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0)
+
+#define array_get(arr, index) ((arr).data[index])
+
+#define array_set(arr, index, value) \
+ do { \
+ if ((index) < (arr).length) { \
+ (arr).data[index] = (value); \
+ } \
+ } while (0)
+
+#define array_insert(arr, index, value) \
+ do { \
+ if ((index) <= (arr).length) { \
+ array_ensure((arr), 1); \
+ if ((arr).length < (arr).capacity) { \
+ for (size_t _i = (arr).length; _i > (index); --_i) { \
+ (arr).data[_i] = (arr).data[_i - 1]; \
+ } \
+ (arr).data[index] = (value); \
+ (arr).length++; \
+ } \
+ } \
+ } while (0)
+
+#define array_remove(arr, index) \
+ do { \
+ if ((index) < (arr).length) { \
+ for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \
+ (arr).data[_i] = (arr).data[_i + 1]; \
+ } \
+ (arr).length--; \
+ } \
+ } while (0)
+
+#define array_clear(arr) \
+ do { \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_reserve(arr, new_capacity) \
+ do { \
+ if ((new_capacity) > (arr).capacity) { \
+ void *_new_data = \
+ safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \
+ if (_new_data) { \
+ (arr).data = _new_data; \
+ (arr).capacity = (new_capacity); \
+ } \
+ } \
+ } while (0)
+
+#define array_foreach(arr, var) \
+ for (size_t _i_##var = 0; \
+ _i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \
+ ++_i_##var)
+
+#define array_foreach_idx(arr, var, index) \
+ for (size_t index = 0; \
+ index < (arr).length && ((var) = (arr).data[index], 1); ++index)
+
+// Arena - block-based memory allocator
+typedef struct {
+ char *ptr;
+ char *end;
+ array(char *) blocks;
+} Arena;
+
+#define ARENA_DEFAULT_BLOCK_SIZE (4096)
+
+NONSTD_DEF Arena arena_make(void);
+NONSTD_DEF void arena_grow(Arena *a, size_t min_size);
+NONSTD_DEF void *arena_alloc(Arena *a, size_t size);
+NONSTD_DEF void arena_free(Arena *a);
+
+// Image - simple RGB image structure
+typedef struct {
+ u8 r, g, b;
+} Color;
+
+typedef struct {
+ u32 width;
+ u32 height;
+ Color *pixels;
+} Canvas;
+
+#define COLOR_RGB(r, g, b) ((Color){(u8)(r), (u8)(g), (u8)(b)})
+#define COLOR_HEX(hex) ((Color){(u8)(((hex) >> 16) & 0xFF), (u8)(((hex) >> 8) & 0xFF), (u8)((hex) & 0xFF)})
+
+#define COLOR_BLACK COLOR_RGB(0, 0, 0)
+#define COLOR_WHITE COLOR_RGB(255, 255, 255)
+#define COLOR_RED COLOR_RGB(255, 0, 0)
+#define COLOR_GREEN COLOR_RGB(0, 255, 0)
+#define COLOR_BLUE COLOR_RGB(0, 0, 255)
+#define COLOR_YELLOW COLOR_RGB(255, 255, 0)
+#define COLOR_MAGENTA COLOR_RGB(255, 0, 255)
+#define COLOR_CYAN COLOR_RGB(0, 255, 255)
+
+NONSTD_DEF Canvas ppm_init(u32 width, u32 height);
+NONSTD_DEF void ppm_free(Canvas *img);
+NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color);
+NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y);
+NONSTD_DEF int ppm_save(const Canvas *img, const char *filename);
+NONSTD_DEF Canvas ppm_read(const char *filename);
+NONSTD_DEF void ppm_fill(Canvas *canvas, Color color);
+NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color);
+NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color);
+NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 x, i32 y, i32 r, Color color);
+NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color);
+
+// File I/O helpers
+NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
+NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
+NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
+NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
+NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
+
+// Logging
+typedef enum {
+ LOG_ERROR,
+ LOG_WARN,
+ LOG_INFO,
+ LOG_DEBUG,
+} LogLevel;
+
+NONSTD_DEF void set_log_level(LogLevel level);
+NONSTD_DEF LogLevel get_log_level_from_env(void);
+NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
+
+#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
+#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
+#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
+#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
+
+#define COLOR_RESET "\033[0m"
+#define COLOR_INFO "\033[32m"
+#define COLOR_DEBUG "\033[36m"
+#define COLOR_WARNING "\033[33m"
+#define COLOR_ERROR "\033[31m"
+
+#ifdef NONSTD_IMPLEMENTATION
+
+NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
+ if (count != 0 && item_size > SIZE_MAX / count) {
+ return NULL;
+ }
+ return malloc(item_size * count);
+}
+
+NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) {
+ if (count != 0 && item_size > SIZE_MAX / count) {
+ return NULL;
+ }
+ return realloc(ptr, item_size * count);
+}
+
+// String View Implementation
+
+NONSTD_DEF stringv sv_from_cstr(const char *s) {
+ return (stringv){.data = s, .length = s ? strlen(s) : 0};
+}
+
+NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) {
+ return (stringv){.data = data, .length = length};
+}
+
+NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) {
+ if (start > sv.length) {
+ start = sv.length;
+ }
+ if (end > sv.length) {
+ end = sv.length;
+ }
+ if (start > end) {
+ start = end;
+ }
+ return (stringv){.data = sv.data + start, .length = end - start};
+}
+
+NONSTD_DEF int sv_equals(stringv a, stringv b) {
+ return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0);
+}
+
+NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) {
+ return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0;
+}
+
+NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) {
+ return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0;
+}
+
+// String Builder Implementation
+
+NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) {
+ sb->capacity = initial_cap ? initial_cap : 16;
+ sb->data = ALLOC(char, sb->capacity);
+ sb->length = 0;
+ if (sb->data) {
+ sb->data[0] = '\0';
+ }
+}
+
+NONSTD_DEF void sb_free(stringb *sb) {
+ FREE(sb->data);
+ sb->length = 0;
+ sb->capacity = 0;
+}
+
+NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) {
+ size_t needed = sb->length + additional + 1;
+ size_t new_cap = sb->capacity;
+
+ if (needed > new_cap) {
+ while (new_cap < needed) {
+ if (new_cap > SIZE_MAX / 2) {
+ new_cap = SIZE_MAX;
+ break;
+ }
+ new_cap *= 2;
+ }
+ if (new_cap < needed)
+ return; // Overflow
+
+ char *new_data = safe_realloc(sb->data, sizeof(char), new_cap);
+ if (new_data) {
+ sb->data = new_data;
+ sb->capacity = new_cap;
+ }
+ }
+}
+
+NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) {
+ if (!s) {
+ return;
+ }
+ size_t slength = strlen(s);
+ sb_ensure(sb, slength);
+ if (sb->length + slength + 1 <= sb->capacity) {
+ memcpy(sb->data + sb->length, s, slength);
+ sb->length += slength;
+ sb->data[sb->length] = '\0';
+ }
+}
+
+NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) {
+ if (!sv.data || sv.length == 0) {
+ return;
+ }
+ sb_ensure(sb, sv.length);
+ if (sb->length + sv.length + 1 <= sb->capacity) {
+ memcpy(sb->data + sb->length, sv.data, sv.length);
+ sb->length += sv.length;
+ sb->data[sb->length] = '\0';
+ }
+}
+
+NONSTD_DEF void sb_append_char(stringb *sb, char c) {
+ sb_ensure(sb, 1);
+ if (sb->length + 2 <= sb->capacity) {
+ sb->data[sb->length++] = c;
+ sb->data[sb->length] = '\0';
+ }
+}
+
+NONSTD_DEF stringv sb_as_sv(const stringb *sb) {
+ return (stringv){.data = sb->data, .length = sb->length};
+}
+
+NONSTD_DEF Arena arena_make(void) {
+ Arena a = {0};
+ array_init(a.blocks);
+ return a;
+}
+
+// Arena Implementation
+
+NONSTD_DEF void arena_grow(Arena *a, size_t min_size) {
+ size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size);
+ char *block = ALLOC(char, size);
+ a->ptr = block;
+ a->end = block + size;
+ array_push(a->blocks, block);
+}
+
+NONSTD_DEF void *arena_alloc(Arena *a, size_t size) {
+ // Align to 8 bytes basically
+ size_t align = sizeof(void *);
+ uintptr_t current = (uintptr_t)a->ptr;
+ uintptr_t aligned = (current + align - 1) & ~(align - 1);
+ uintptr_t end = (uintptr_t)a->end;
+
+ // Check for overflow (aligned wrapped around) or out of bounds (aligned >= end)
+ // or not enough space ((end - aligned) < size)
+ if (aligned < current || aligned >= end || (end - aligned) < size) {
+ arena_grow(a, size);
+ current = (uintptr_t)a->ptr;
+ aligned = (current + align - 1) & ~(align - 1);
+ end = (uintptr_t)a->end;
+ }
+
+ // Double check after grow (in case grow failed or size is just too huge)
+ if (aligned < current || aligned >= end || (end - aligned) < size) {
+ return NULL;
+ }
+
+ a->ptr = (char *)(aligned + size);
+ return (void *)aligned;
+}
+
+NONSTD_DEF void arena_free(Arena *a) {
+ char *block;
+ array_foreach(a->blocks, block) { FREE(block); }
+ array_free(a->blocks);
+ a->ptr = NULL;
+ a->end = NULL;
+}
+
+// File I/O Implementation
+
+NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) {
+ FILE *f = fopen(filepath, "rb");
+ if (!f) {
+ return NULL;
+ }
+
+ fseek(f, 0, SEEK_END);
+ long length = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (length < 0) {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t size = (size_t)length;
+ char *buffer = ALLOC(char, size + 1);
+ if (!buffer) {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t read = fread(buffer, 1, size, f);
+ fclose(f);
+
+ if (read != size) {
+ FREE(buffer);
+ return NULL;
+ }
+
+ buffer[size] = '\0';
+ if (out_size) {
+ *out_size = size;
+ }
+
+ return buffer;
+}
+
+NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) {
+ FILE *f = fopen(filepath, "wb");
+ if (!f) {
+ return 0;
+ }
+
+ size_t written = fwrite(data, 1, size, f);
+ fclose(f);
+
+ return written == size;
+}
+
+NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
+ size_t size = 0;
+ char *data = read_entire_file(filepath, &size);
+ stringb sb = {0};
+ if (data) {
+ sb.data = data;
+ sb.length = size;
+ sb.capacity = size + 1;
+ }
+ return sb;
+}
+
+NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) {
+ return write_entire_file(filepath, sv.data, sv.length);
+}
+
+NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
+ return write_entire_file(filepath, sb->data, sb->length);
+}
+
+// Logging Implementation
+
+static LogLevel max_level = LOG_INFO;
+
+static const char *level_strings[] = {
+ "ERROR",
+ "WARN",
+ "INFO",
+ "DEBUG",
+};
+
+static const char *level_colors[] = {
+ COLOR_ERROR,
+ COLOR_WARNING,
+ COLOR_INFO,
+ COLOR_DEBUG,
+};
+
+NONSTD_DEF void set_log_level(LogLevel level) {
+ max_level = level;
+}
+
+NONSTD_DEF LogLevel get_log_level_from_env(void) {
+ const char *env = getenv("LOG_LEVEL");
+ if (env) {
+ int level = atoi(env);
+ if (level >= 0 && level <= 3) {
+ return (LogLevel)level;
+ }
+ }
+
+ return max_level;
+}
+
+NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
+ if (max_level < level)
+ return;
+
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ struct tm *tm_info = localtime(&tv.tv_sec);
+
+ char time_str[24];
+ strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
+
+ const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
+ const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
+
+ const char *log_format = "%s[%s.%03d] [%-5s] ";
+ fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stream, format, args);
+ va_end(args);
+
+ fprintf(stream, "%s\n", reset);
+ fflush(stream);
+}
+
+// PPM Image Implementation
+
+NONSTD_DEF Canvas ppm_init(u32 width, u32 height) {
+ Canvas img = {0};
+ img.width = width;
+ img.height = height;
+ img.pixels = ALLOC(Color, width * height);
+ if (img.pixels) {
+ memset(img.pixels, 0, sizeof(Color) * width * height);
+ }
+ return img;
+}
+
+NONSTD_DEF void ppm_free(Canvas *img) {
+ if (img->pixels) {
+ FREE(img->pixels);
+ }
+ img->width = 0;
+ img->height = 0;
+}
+
+NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color) {
+ if (x < img->width && y < img->height) {
+ img->pixels[y * img->width + x] = color;
+ }
+}
+
+NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y) {
+ if (x < img->width && y < img->height) {
+ return img->pixels[y * img->width + x];
+ }
+ return (Color){0, 0, 0};
+}
+
+NONSTD_DEF int ppm_save(const Canvas *img, const char *filename) {
+ FILE *f = fopen(filename, "w");
+ if (!f) {
+ return 0;
+ }
+
+ fprintf(f, "P3\n%u %u\n255\n", img->width, img->height);
+ for (u32 y = 0; y < img->height; ++y) {
+ for (u32 x = 0; x < img->width; ++x) {
+ Color c = ppm_get_pixel(img, x, y);
+ fprintf(f, "%d %d %d ", c.r, c.g, c.b);
+ }
+ fprintf(f, "\n");
+ }
+
+ fclose(f);
+ return 1;
+}
+
+NONSTD_DEF Canvas ppm_read(const char *filename) {
+ Canvas img = {0};
+ FILE *f = fopen(filename, "r");
+ if (!f) {
+ return img;
+ }
+
+ char magic[3];
+ if (fscanf(f, "%2s", magic) != 1 || strcmp(magic, "P3") != 0) {
+ fclose(f);
+ return img;
+ }
+
+ u32 w, h, max_val;
+ if (fscanf(f, "%u %u %u", &w, &h, &max_val) != 3) {
+ fclose(f);
+ return img;
+ }
+
+ img = ppm_init(w, h);
+ if (!img.pixels) {
+ fclose(f);
+ return img;
+ }
+
+ for (u32 i = 0; i < w * h; ++i) {
+ int r, g, b;
+ if (fscanf(f, "%d %d %d", &r, &g, &b) != 3) {
+ ppm_free(&img);
+ fclose(f);
+ return (Canvas){0};
+ }
+ img.pixels[i] = (Color){(u8)r, (u8)g, (u8)b};
+ }
+
+ fclose(f);
+ return img;
+}
+
+NONSTD_DEF void ppm_fill(Canvas *canvas, Color color) {
+ for (u32 i = 0; i < canvas->width * canvas->height; ++i) {
+ canvas->pixels[i] = color;
+ }
+}
+
+NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color) {
+ if (w == 0 || h == 0) {
+ return;
+ }
+ for (u32 i = x; i < x + w; ++i) {
+ ppm_set_pixel(canvas, i, y, color);
+ ppm_set_pixel(canvas, i, y + h - 1, color);
+ }
+ for (u32 j = y; j < y + h; ++j) {
+ ppm_set_pixel(canvas, x, j, color);
+ ppm_set_pixel(canvas, x + w - 1, j, color);
+ }
+}
+
+NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color) {
+ i32 dx = abs(x1 - x0);
+ i32 dy = -abs(y1 - y0);
+ i32 sx = x0 < x1 ? 1 : -1;
+ i32 sy = y0 < y1 ? 1 : -1;
+ i32 err = dx + dy;
+
+ while (1) {
+ ppm_set_pixel(canvas, (u32)x0, (u32)y0, color);
+ if (x0 == x1 && y0 == y1) {
+ break;
+ }
+
+ i32 e2 = 2 * err;
+ if (e2 >= dy) {
+ err += dy;
+ x0 += sx;
+ }
+ if (e2 <= dx) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+}
+
+NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 xm, i32 ym, i32 r, Color color) {
+ i32 x = -r, y = 0, err = 2 - 2 * r;
+ do {
+ ppm_set_pixel(canvas, (u32)(xm - x), (u32)(ym + y), color);
+ ppm_set_pixel(canvas, (u32)(xm - y), (u32)(ym - x), color);
+ ppm_set_pixel(canvas, (u32)(xm + x), (u32)(ym - y), color);
+ ppm_set_pixel(canvas, (u32)(xm + y), (u32)(ym + x), color);
+ r = err;
+ if (r <= y) {
+ err += ++y * 2 + 1;
+ }
+ if (r > x || err > y) {
+ err += ++x * 2 + 1;
+ }
+ } while (x < 0);
+}
+
+NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color) {
+ ppm_draw_line(canvas, x0, y0, x1, y1, color);
+ ppm_draw_line(canvas, x1, y1, x2, y2, color);
+ ppm_draw_line(canvas, x2, y2, x0, y0, color);
+}
+
+#endif // NONSTD_IMPLEMENTATION
+
+#endif // NONSTD_H
+
+/*
+BSD 2-Clause License
+
+Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/libraries/vfs.h b/libraries/vfs.h
new file mode 100644
index 0000000..6a243cc
--- /dev/null
+++ b/libraries/vfs.h
@@ -0,0 +1,209 @@
+#ifndef VFS_H
+#define VFS_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+ char path[256];
+ uint64_t offset;
+ uint64_t size;
+} VfsEntry;
+
+typedef struct {
+ char magic[4];
+ uint32_t num_files;
+} VfsHeader;
+
+typedef struct {
+ FILE* f;
+ uint64_t start;
+ uint64_t size;
+ uint64_t pos;
+ int is_pak;
+} VfsFile;
+
+void vfs_init(const char* pak_path);
+void vfs_shutdown(void);
+
+// Basic API
+void* vfs_read(const char* path, size_t* out_size);
+void vfs_free(void* data);
+
+// Extended API for VFS-like behavior
+VfsFile* vfs_open(const char* path);
+size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file);
+int vfs_fseek(VfsFile* file, int64_t offset, int whence);
+int64_t vfs_ftell(VfsFile* file);
+void vfs_fclose(VfsFile* file);
+
+#ifdef VFS_IMPLEMENTATION
+
+#include <errno.h>
+
+// Note: These must be defined in the implementation file or all.h
+// LOG_INFO, LOG_ERROR, LOG_WARN, log_message
+// If you don't have them, you can define fallbacks here.
+
+static VfsEntry* g_entries = NULL;
+static uint32_t g_num_entries = 0;
+static char g_pak_path[512] = {0};
+static int g_disk_mode = 0;
+
+void vfs_init(const char* pak_path) {
+ const char* debug_env = getenv("DEBUG");
+ if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) {
+ g_disk_mode = 1;
+ log_message(stdout, LOG_INFO, "VFS: Operating in Disk Mode (DEBUG enabled)");
+ return;
+ }
+
+ FILE* f = fopen(pak_path, "rb");
+ if (!f) {
+ log_message(stderr, LOG_ERROR, "VFS: Failed to open pak file: %s (Error: %s)", pak_path, strerror(errno));
+ log_message(stderr, LOG_WARN, "VFS: Falling back to Disk Mode");
+ g_disk_mode = 1;
+ return;
+ }
+
+ VfsHeader header;
+ if (fread(&header, sizeof(VfsHeader), 1, f) != 1) {
+ log_message(stderr, LOG_ERROR, "VFS: Failed to read header from %s", pak_path);
+ fclose(f);
+ g_disk_mode = 1;
+ return;
+ }
+
+ if (memcmp(header.magic, "DRP1", 4) != 0) {
+ log_message(stderr, LOG_ERROR, "VFS: Invalid magic in %s", pak_path);
+ fclose(f);
+ g_disk_mode = 1;
+ return;
+ }
+
+ g_num_entries = header.num_files;
+ g_entries = (VfsEntry*)malloc(sizeof(VfsEntry) * g_num_entries);
+ if (fread(g_entries, sizeof(VfsEntry), g_num_entries, f) != g_num_entries) {
+ log_message(stderr, LOG_ERROR, "VFS: Failed to read index table from %s", pak_path);
+ free(g_entries);
+ g_entries = NULL;
+ fclose(f);
+ g_disk_mode = 1;
+ return;
+ }
+
+ fclose(f);
+ strncpy(g_pak_path, pak_path, sizeof(g_pak_path) - 1);
+ log_message(stdout, LOG_INFO, "VFS: Loaded %u files from %s", g_num_entries, pak_path);
+}
+
+void vfs_shutdown(void) {
+ if (g_entries) {
+ free(g_entries);
+ g_entries = NULL;
+ }
+}
+
+VfsFile* vfs_open(const char* path) {
+ if (g_disk_mode) {
+ FILE* f = fopen(path, "rb");
+ if (!f) return NULL;
+ VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile));
+ vf->f = f;
+ fseek(f, 0, SEEK_END);
+ vf->size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ vf->start = 0;
+ vf->pos = 0;
+ vf->is_pak = 0;
+ return vf;
+ }
+
+ for (uint32_t i = 0; i < g_num_entries; i++) {
+ if (strcmp(g_entries[i].path, path) == 0) {
+ FILE* f = fopen(g_pak_path, "rb");
+ if (!f) return NULL;
+ VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile));
+ vf->f = f;
+ vf->start = g_entries[i].offset;
+ vf->size = g_entries[i].size;
+ vf->pos = 0;
+ vf->is_pak = 1;
+ fseek(f, (long)vf->start, SEEK_SET);
+ return vf;
+ }
+ }
+
+ log_message(stderr, LOG_WARN, "VFS: File not found: %s", path);
+ return NULL;
+}
+
+size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file) {
+ size_t total_to_read = size * nmemb;
+ if (file->pos + total_to_read > file->size) {
+ total_to_read = (size_t)(file->size - file->pos);
+ }
+
+ if (total_to_read <= 0) return 0;
+
+ size_t read_bytes = fread(ptr, 1, total_to_read, file->f);
+ file->pos += read_bytes;
+ return read_bytes / size;
+}
+
+int vfs_fseek(VfsFile* file, int64_t offset, int whence) {
+ int64_t new_pos;
+ switch (whence) {
+ case SEEK_SET: new_pos = offset; break;
+ case SEEK_CUR: new_pos = (int64_t)file->pos + offset; break;
+ case SEEK_END: new_pos = (int64_t)file->size + offset; break;
+ default: return -1;
+ }
+
+ if (new_pos < 0) new_pos = 0;
+ if (new_pos > (int64_t)file->size) new_pos = (int64_t)file->size;
+
+ file->pos = (uint64_t)new_pos;
+ return fseek(file->f, (long)(file->start + file->pos), SEEK_SET);
+}
+
+int64_t vfs_ftell(VfsFile* file) {
+ return (int64_t)file->pos;
+}
+
+void vfs_fclose(VfsFile* file) {
+ if (file->f) fclose(file->f);
+ free(file);
+}
+
+void* vfs_read(const char* path, size_t* out_size) {
+ VfsFile* f = vfs_open(path);
+ if (!f) return NULL;
+
+ void* data = malloc((size_t)f->size);
+ if (!data) {
+ vfs_fclose(f);
+ return NULL;
+ }
+
+ if (vfs_fread(data, 1, (size_t)f->size, f) != f->size) {
+ free(data);
+ vfs_fclose(f);
+ return NULL;
+ }
+
+ if (out_size) *out_size = (size_t)f->size;
+ vfs_fclose(f);
+ return data;
+}
+
+void vfs_free(void* data) {
+ free(data);
+}
+
+#endif // VFS_IMPLEMENTATION
+
+#endif // VFS_H