1#ifndef VFS_H
  2#define VFS_H
  3
  4#include <stddef.h>
  5#include <stdint.h>
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9
 10typedef struct {
 11	char path[256];
 12	uint64_t offset;
 13	uint64_t size;
 14} VfsEntry;
 15
 16typedef struct {
 17	char magic[4];
 18	uint32_t num_files;
 19} VfsHeader;
 20
 21typedef struct {
 22	FILE* f;
 23	uint64_t start;
 24	uint64_t size;
 25	uint64_t pos;
 26	int is_pak;
 27} VfsFile;
 28
 29void vfs_init(const char* pak_path);
 30void vfs_shutdown(void);
 31
 32// Basic API
 33void* vfs_read(const char* path, size_t* out_size);
 34void vfs_free(void* data);
 35
 36// Extended API for VFS-like behavior
 37VfsFile* vfs_open(const char* path);
 38size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file);
 39int vfs_fseek(VfsFile* file, int64_t offset, int whence);
 40int64_t vfs_ftell(VfsFile* file);
 41void vfs_fclose(VfsFile* file);
 42
 43#ifdef VFS_IMPLEMENTATION
 44
 45#include <errno.h>
 46
 47// Note: These must be defined in the implementation file or all.h
 48// LOG_INFO, LOG_ERROR, LOG_WARN, log_message
 49// If you don't have them, you can define fallbacks here.
 50
 51static VfsEntry* g_entries = NULL;
 52static uint32_t g_num_entries = 0;
 53static char g_pak_path[512] = {0};
 54static int g_disk_mode = 0;
 55
 56void vfs_init(const char* pak_path) {
 57	const char* debug_env = getenv("DEBUG");
 58	if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) {
 59		g_disk_mode = 1;
 60		TraceLog(LOG_INFO, "VFS: Operating in Disk Mode (DEBUG enabled)");
 61		return;
 62	}
 63
 64	FILE* f = fopen(pak_path, "rb");
 65	if (!f) {
 66		TraceLog(LOG_ERROR, "VFS: Failed to open pak file: %s (Error: %s)", pak_path, strerror(errno));
 67		TraceLog(LOG_WARNING, "VFS: Falling back to Disk Mode");
 68		g_disk_mode = 1;
 69		return;
 70	}
 71
 72	VfsHeader header;
 73	if (fread(&header, sizeof(VfsHeader), 1, f) != 1) {
 74		TraceLog(LOG_ERROR, "VFS: Failed to read header from %s", pak_path);
 75		fclose(f);
 76		g_disk_mode = 1;
 77		return;
 78	}
 79
 80	if (memcmp(header.magic, "DRP1", 4) != 0) {
 81		TraceLog(LOG_ERROR, "VFS: Invalid magic in %s", pak_path);
 82		fclose(f);
 83		g_disk_mode = 1;
 84		return;
 85	}
 86
 87	g_num_entries = header.num_files;
 88	g_entries = (VfsEntry*)malloc(sizeof(VfsEntry) * g_num_entries);
 89	if (fread(g_entries, sizeof(VfsEntry), g_num_entries, f) != g_num_entries) {
 90		TraceLog(LOG_ERROR, "VFS: Failed to read index table from %s", pak_path);
 91		free(g_entries);
 92		g_entries = NULL;
 93		fclose(f);
 94		g_disk_mode = 1;
 95		return;
 96	}
 97
 98	fclose(f);
 99	strncpy(g_pak_path, pak_path, sizeof(g_pak_path) - 1);
100	TraceLog(LOG_INFO, "VFS: Loaded %u files from %s", g_num_entries, pak_path);
101}
102
103void vfs_shutdown(void) {
104	if (g_entries) {
105		free(g_entries);
106		g_entries = NULL;
107	}
108}
109
110VfsFile* vfs_open(const char* path) {
111	if (g_disk_mode) {
112		FILE* f = fopen(path, "rb");
113		if (!f) return NULL;
114		VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile));
115		vf->f = f;
116		fseek(f, 0, SEEK_END);
117		vf->size = ftell(f);
118		fseek(f, 0, SEEK_SET);
119		vf->start = 0;
120		vf->pos = 0;
121		vf->is_pak = 0;
122		return vf;
123	}
124
125	for (uint32_t i = 0; i < g_num_entries; i++) {
126		if (strcmp(g_entries[i].path, path) == 0) {
127			FILE* f = fopen(g_pak_path, "rb");
128			if (!f) return NULL;
129			VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile));
130			vf->f = f;
131			vf->start = g_entries[i].offset;
132			vf->size = g_entries[i].size;
133			vf->pos = 0;
134			vf->is_pak = 1;
135			fseek(f, (long)vf->start, SEEK_SET);
136			return vf;
137		}
138	}
139
140	TraceLog(LOG_WARNING, "VFS: File not found: %s", path);
141	return NULL;
142}
143
144size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file) {
145	size_t total_to_read = size * nmemb;
146	if (file->pos + total_to_read > file->size) {
147		total_to_read = (size_t)(file->size - file->pos);
148	}
149
150	if (total_to_read <= 0) return 0;
151
152	size_t read_bytes = fread(ptr, 1, total_to_read, file->f);
153	file->pos += read_bytes;
154	return read_bytes / size;
155}
156
157int vfs_fseek(VfsFile* file, int64_t offset, int whence) {
158	int64_t new_pos;
159	switch (whence) {
160		case SEEK_SET: new_pos = offset; break;
161		case SEEK_CUR: new_pos = (int64_t)file->pos + offset; break;
162		case SEEK_END: new_pos = (int64_t)file->size + offset; break;
163		default: return -1;
164	}
165
166	if (new_pos < 0) new_pos = 0;
167	if (new_pos > (int64_t)file->size) new_pos = (int64_t)file->size;
168
169	file->pos = (uint64_t)new_pos;
170	return fseek(file->f, (long)(file->start + file->pos), SEEK_SET);
171}
172
173int64_t vfs_ftell(VfsFile* file) {
174	return (int64_t)file->pos;
175}
176
177void vfs_fclose(VfsFile* file) {
178	if (file->f) fclose(file->f);
179	free(file);
180}
181
182void* vfs_read(const char* path, size_t* out_size) {
183	VfsFile* f = vfs_open(path);
184	if (!f) return NULL;
185
186	void* data = malloc((size_t)f->size + 1);
187	if (!data) {
188		vfs_fclose(f);
189		return NULL;
190	}
191
192	if (vfs_fread(data, 1, (size_t)f->size, f) != f->size) {
193		free(data);
194		vfs_fclose(f);
195		return NULL;
196	}
197
198	((char*)data)[f->size] = '\0';
199
200	if (out_size) *out_size = (size_t)f->size;
201	vfs_fclose(f);
202	return data;
203}
204
205void vfs_free(void* data) {
206	free(data);
207}
208
209#endif // VFS_IMPLEMENTATION
210
211#endif // VFS_H