VFS and extra tooling from other project

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-11 20:42:16 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-11 20:42:16 +0200
Commit 0a559e5abf962a20e1218c6d18123d3afd9946f1 (patch)
-rw-r--r-- libraries/vfs.h 216
-rw-r--r-- main.c 2
-rw-r--r-- libraries/nonstd.h 0
-rw-r--r-- tools/hexdump.c 57
-rw-r--r-- tools/packer.c 497
5 files changed, 771 insertions, 1 deletions
diff --git a/libraries/vfs.h b/libraries/vfs.h
  
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
  
  
10
typedef struct {
  
11
	char path[256];
  
12
	uint64_t offset;
  
13
	uint64_t size;
  
14
} VfsEntry;
  
15
  
  
16
typedef struct {
  
17
	char magic[4];
  
18
	uint32_t num_files;
  
19
} VfsHeader;
  
20
  
  
21
typedef struct {
  
22
	FILE* f;
  
23
	uint64_t start;
  
24
	uint64_t size;
  
25
	uint64_t pos;
  
26
	int is_pak;
  
27
} VfsFile;
  
28
  
  
29
void vfs_init(const char* pak_path);
  
30
void vfs_shutdown(void);
  
31
  
  
32
// Basic API
  
33
void* vfs_read(const char* path, size_t* out_size);
  
34
void vfs_free(void* data);
  
35
  
  
36
// Extended API for VFS-like behavior
  
37
VfsFile* vfs_open(const char* path);
  
38
size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file);
  
39
int vfs_fseek(VfsFile* file, int64_t offset, int whence);
  
40
int64_t vfs_ftell(VfsFile* file);
  
41
void vfs_fclose(VfsFile* file);
  
42
  
  
43
#ifdef VFS_IMPLEMENTATION
  
44
  
  
45
#include <errno.h>
  
46
  
  
47
// Note: Standard printf is used for logging.
  
48
  
  
49
static VfsEntry* g_entries = NULL;
  
50
static uint32_t g_num_entries = 0;
  
51
static char g_pak_path[512] = {0};
  
52
static int g_disk_mode = 0;
  
53
  
  
54
void vfs_init(const char* pak_path) {
  
55
	const char* debug_env = getenv("DEBUG");
  
56
	if (debug_env && (strcmp(debug_env, "1") == 0 || strcmp(debug_env, "true") == 0)) {
  
57
		g_disk_mode = 1;
  
58
		printf("VFS INFO: Operating in Disk Mode (DEBUG enabled)\n");
  
59
		fflush(stdout);
  
60
		return;
  
61
	}
  
62
  
  
63
	FILE* f = fopen(pak_path, "rb");
  
64
	if (!f) {
  
65
		printf("VFS ERROR: Failed to open pak file: %s (Error: %s)\n", pak_path, strerror(errno));
  
66
		printf("VFS WARNING: Falling back to Disk Mode\n");
  
67
		fflush(stdout);
  
68
		g_disk_mode = 1;
  
69
		return;
  
70
	}
  
71
  
  
72
	VfsHeader header;
  
73
	if (fread(&header, sizeof(VfsHeader), 1, f) != 1) {
  
74
		printf("VFS ERROR: Failed to read header from %s\n", pak_path);
  
75
		fflush(stdout);
  
76
		fclose(f);
  
77
		g_disk_mode = 1;
  
78
		return;
  
79
	}
  
80
  
  
81
	if (memcmp(header.magic, "DRP1", 4) != 0) {
  
82
		printf("VFS ERROR: Invalid magic in %s\n", pak_path);
  
83
		fflush(stdout);
  
84
		fclose(f);
  
85
		g_disk_mode = 1;
  
86
		return;
  
87
	}
  
88
  
  
89
	g_num_entries = header.num_files;
  
90
	g_entries = (VfsEntry*)malloc(sizeof(VfsEntry) * g_num_entries);
  
91
	if (fread(g_entries, sizeof(VfsEntry), g_num_entries, f) != g_num_entries) {
  
92
		printf("VFS ERROR: Failed to read index table from %s\n", pak_path);
  
93
		fflush(stdout);
  
94
		free(g_entries);
  
95
		g_entries = NULL;
  
96
		fclose(f);
  
97
		g_disk_mode = 1;
  
98
		return;
  
99
	}
  
100
  
  
101
	fclose(f);
  
102
	strncpy(g_pak_path, pak_path, sizeof(g_pak_path) - 1);
  
103
	printf("VFS INFO: Loaded %u files from %s\n", g_num_entries, pak_path);
  
104
	fflush(stdout);
  
105
}
  
106
  
  
107
void vfs_shutdown(void) {
  
108
	if (g_entries) {
  
109
		free(g_entries);
  
110
		g_entries = NULL;
  
111
	}
  
112
}
  
113
  
  
114
VfsFile* vfs_open(const char* path) {
  
115
	if (g_disk_mode) {
  
116
		FILE* f = fopen(path, "rb");
  
117
		if (!f) return NULL;
  
118
		VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile));
  
119
		vf->f = f;
  
120
		fseek(f, 0, SEEK_END);
  
121
		vf->size = ftell(f);
  
122
		fseek(f, 0, SEEK_SET);
  
123
		vf->start = 0;
  
124
		vf->pos = 0;
  
125
		vf->is_pak = 0;
  
126
		return vf;
  
127
	}
  
128
  
  
129
	for (uint32_t i = 0; i < g_num_entries; i++) {
  
130
		if (strcmp(g_entries[i].path, path) == 0) {
  
131
			FILE* f = fopen(g_pak_path, "rb");
  
132
			if (!f) return NULL;
  
133
			VfsFile* vf = (VfsFile*)malloc(sizeof(VfsFile));
  
134
			vf->f = f;
  
135
			vf->start = g_entries[i].offset;
  
136
			vf->size = g_entries[i].size;
  
137
			vf->pos = 0;
  
138
			vf->is_pak = 1;
  
139
			fseek(f, (long)vf->start, SEEK_SET);
  
140
			return vf;
  
141
		}
  
142
	}
  
143
  
  
144
	printf("VFS WARNING: File not found: %s\n", path);
  
145
	fflush(stdout);
  
146
	return NULL;
  
147
}
  
148
  
  
149
size_t vfs_fread(void* ptr, size_t size, size_t nmemb, VfsFile* file) {
  
150
	size_t total_to_read = size * nmemb;
  
151
	if (file->pos + total_to_read > file->size) {
  
152
		total_to_read = (size_t)(file->size - file->pos);
  
153
	}
  
154
  
  
155
	if (total_to_read <= 0) return 0;
  
156
  
  
157
	size_t read_bytes = fread(ptr, 1, total_to_read, file->f);
  
158
	file->pos += read_bytes;
  
159
	return read_bytes / size;
  
160
}
  
161
  
  
162
int vfs_fseek(VfsFile* file, int64_t offset, int whence) {
  
163
	int64_t new_pos;
  
164
	switch (whence) {
  
165
		case SEEK_SET: new_pos = offset; break;
  
166
		case SEEK_CUR: new_pos = (int64_t)file->pos + offset; break;
  
167
		case SEEK_END: new_pos = (int64_t)file->size + offset; break;
  
168
		default: return -1;
  
169
	}
  
170
  
  
171
	if (new_pos < 0) new_pos = 0;
  
172
	if (new_pos > (int64_t)file->size) new_pos = (int64_t)file->size;
  
173
  
  
174
	file->pos = (uint64_t)new_pos;
  
175
	return fseek(file->f, (long)(file->start + file->pos), SEEK_SET);
  
176
}
  
177
  
  
178
int64_t vfs_ftell(VfsFile* file) {
  
179
	return (int64_t)file->pos;
  
180
}
  
181
  
  
182
void vfs_fclose(VfsFile* file) {
  
183
	if (file->f) fclose(file->f);
  
184
	free(file);
  
185
}
  
186
  
  
187
void* vfs_read(const char* path, size_t* out_size) {
  
188
	VfsFile* f = vfs_open(path);
  
189
	if (!f) return NULL;
  
190
  
  
191
	void* data = malloc((size_t)f->size + 1);
  
192
	if (!data) {
  
193
		vfs_fclose(f);
  
194
		return NULL;
  
195
	}
  
196
  
  
197
	if (vfs_fread(data, 1, (size_t)f->size, f) != f->size) {
  
198
		free(data);
  
199
		vfs_fclose(f);
  
200
		return NULL;
  
201
	}
  
202
  
  
203
	((char*)data)[f->size] = '\0';
  
204
  
  
205
	if (out_size) *out_size = (size_t)f->size;
  
206
	vfs_fclose(f);
  
207
	return data;
  
208
}
  
209
  
  
210
void vfs_free(void* data) {
  
211
	free(data);
  
212
}
  
213
  
  
214
#endif // VFS_IMPLEMENTATION
  
215
  
  
216
#endif // VFS_H
diff --git a/main.c b/main.c
...
12
#include <math.h>
12
#include <math.h>
13
  
13
  
14
#define NONSTD_IMPLEMENTATION
14
#define NONSTD_IMPLEMENTATION
15
#include "nonstd.h"
15
#include "libraries/nonstd.h"
16
  
16
  
17
Game game = {0};
17
Game game = {0};
18
  
18
  
...
diff --git a/libraries/nonstd.h b/libraries/nonstd.h
...
diff --git a/tools/hexdump.c b/tools/hexdump.c
  
1
#include <stdio.h>
  
2
#include <stdlib.h>
  
3
  
  
4
int main(int argc, char *argv[]) {
  
5
	if (argc < 3) {
  
6
		fprintf(stderr, "Usage: %s <input_file> <array_name>\n", argv[0]);
  
7
		return 1;
  
8
	}
  
9
  
  
10
	const char *filename = argv[1];
  
11
	const char *array_name = argv[2];
  
12
  
  
13
	FILE *f = fopen(filename, "rb");
  
14
	if (!f) {
  
15
		perror("fopen");
  
16
		return 1;
  
17
	}
  
18
  
  
19
	fseek(f, 0, SEEK_END);
  
20
	long size = ftell(f);
  
21
	rewind(f);
  
22
  
  
23
	unsigned char *buffer = malloc(size);
  
24
	if (!buffer) {
  
25
		perror("malloc");
  
26
		fclose(f);
  
27
		return 1;
  
28
	}
  
29
  
  
30
	if (fread(buffer, 1, size, f) != (size_t)size) {
  
31
		perror("fread");
  
32
		free(buffer);
  
33
		fclose(f);
  
34
		return 1;
  
35
	}
  
36
  
  
37
	// Generate include guard
  
38
	printf("#ifndef %s_H\n", array_name);
  
39
	printf("#define %s_H\n\n", array_name);
  
40
  
  
41
	printf("unsigned char %s[] = {\n", array_name);
  
42
	for (long i = 0; i < size; i++) {
  
43
		if (i % 12 == 0) printf("\t");
  
44
		printf("0x%02x", buffer[i]);
  
45
		if (i != size - 1) printf(", ");
  
46
		if ((i + 1) % 12 == 0) printf("\n");
  
47
	}
  
48
	if (size % 12 != 0) printf("\n");
  
49
	printf("};\n");
  
50
	printf("unsigned int %s_len = %ld;\n", array_name, size);
  
51
  
  
52
	printf("\n#endif // %s_H\n", array_name);
  
53
  
  
54
	free(buffer);
  
55
	fclose(f);
  
56
	return 0;
  
57
}
diff --git a/tools/packer.c b/tools/packer.c
  
1
#define _DEFAULT_SOURCE
  
2
#include <stdio.h>
  
3
#include <stdlib.h>
  
4
#include <string.h>
  
5
#include <stdint.h>
  
6
#include <dirent.h>
  
7
#include <sys/stat.h>
  
8
#include <getopt.h>
  
9
#include <fnmatch.h>
  
10
#include <ctype.h>
  
11
  
  
12
#include "../libraries/vfs.h"
  
13
  
  
14
static void format_size(char* buf, size_t buf_size, uint64_t size) {
  
15
	const char* units[] = {"B", "KB", "MB", "GB", "TB"};
  
16
	int i = 0;
  
17
	double d_size = (double)size;
  
18
	while (d_size >= 1024 && i < 4) {
  
19
		d_size /= 1024.0;
  
20
		i++;
  
21
	}
  
22
	if (i == 0) {
  
23
		snprintf(buf, buf_size, "%llu B", (unsigned long long)size);
  
24
	} else {
  
25
		snprintf(buf, buf_size, "%.2f %s", d_size, units[i]);
  
26
	}
  
27
}
  
28
  
  
29
#define MAX_FILES 4096
  
30
  
  
31
typedef struct {
  
32
	char path[256];
  
33
	long size;
  
34
	void* data;
  
35
} PendingFile;
  
36
  
  
37
static PendingFile g_pending[MAX_FILES];
  
38
static int g_count = 0;
  
39
  
  
40
static char* g_ignore_patterns[MAX_FILES];
  
41
static int g_ignore_count = 0;
  
42
  
  
43
void load_gitignore() {
  
44
	FILE* f = fopen(".gitignore", "r");
  
45
	if (!f) return;
  
46
  
  
47
	char line[1024];
  
48
	while (fgets(line, sizeof(line), f)) {
  
49
		// Strip whitespace and newline
  
50
		char* p = line;
  
51
		while (isspace(*p)) p++;
  
52
		char* end = p + strlen(p) - 1;
  
53
		while (end >= p && isspace(*end)) {
  
54
			*end = '\0';
  
55
			end--;
  
56
		}
  
57
  
  
58
		if (*p == '\0' || *p == '#') continue;
  
59
  
  
60
		if (g_ignore_count < MAX_FILES) {
  
61
			char* s = strdup(p);
  
62
			if (s) g_ignore_patterns[g_ignore_count++] = s;
  
63
		}
  
64
	}
  
65
	fclose(f);
  
66
}
  
67
  
  
68
void free_gitignore() {
  
69
	for (int i = 0; i < g_ignore_count; i++) {
  
70
		free(g_ignore_patterns[i]);
  
71
	}
  
72
	g_ignore_count = 0;
  
73
}
  
74
  
  
75
int should_ignore(const char* path, int is_dir) {
  
76
	// Normalize path for matching: remove leading "./" if present
  
77
	const char* p = path;
  
78
	if (strncmp(p, "./", 2) == 0) p += 2;
  
79
  
  
80
	for (int i = 0; i < g_ignore_count; i++) {
  
81
		const char* pattern = g_ignore_patterns[i];
  
82
		int pattern_is_dir_only = (pattern[strlen(pattern) - 1] == '/');
  
83
  
  
84
		char clean_pattern[1024];
  
85
		strncpy(clean_pattern, pattern, 1023);
  
86
		clean_pattern[1023] = '\0';
  
87
		if (pattern_is_dir_only) {
  
88
			clean_pattern[strlen(clean_pattern) - 1] = '\0';
  
89
		}
  
90
  
  
91
		// If pattern has a slash (not at the end), it's relative to root
  
92
		int has_slash = (strchr(clean_pattern, '/') != NULL);
  
93
  
  
94
		if (has_slash) {
  
95
			// Match against full path from root
  
96
			if (fnmatch(clean_pattern, p, FNM_PATHNAME) == 0) {
  
97
				if (!pattern_is_dir_only || is_dir) return 1;
  
98
			}
  
99
		} else {
  
100
			// Match against filename only
  
101
			const char* filename = strrchr(p, '/');
  
102
			if (filename) filename++;
  
103
			else filename = p;
  
104
  
  
105
			if (fnmatch(clean_pattern, filename, 0) == 0) {
  
106
				if (!pattern_is_dir_only || is_dir) return 1;
  
107
			}
  
108
		}
  
109
	}
  
110
	return 0;
  
111
}
  
112
  
  
113
void add_file(const char* path) {
  
114
	if (g_count >= MAX_FILES) {
  
115
		fprintf(stderr, "Too many files!\n");
  
116
		return;
  
117
	}
  
118
  
  
119
	FILE* f = fopen(path, "rb");
  
120
	if (!f) {
  
121
		fprintf(stderr, "Could not open %s\n", path);
  
122
		return;
  
123
	}
  
124
  
  
125
	fseek(f, 0, SEEK_END);
  
126
	long size = ftell(f);
  
127
	fseek(f, 0, SEEK_SET);
  
128
  
  
129
	void* data = malloc(size);
  
130
	if (!data) {
  
131
		fprintf(stderr, "Memory allocation failed for %s\n", path);
  
132
		fclose(f);
  
133
		return;
  
134
	}
  
135
	if (fread(data, 1, size, f) != (size_t)size) {
  
136
		fprintf(stderr, "Failed to read %s\n", path);
  
137
		free(data);
  
138
		fclose(f);
  
139
		return;
  
140
	}
  
141
	fclose(f);
  
142
  
  
143
	strncpy(	g_pending[g_count].path, path, 255);
  
144
	g_pending[g_count].size = size;
  
145
	g_pending[g_count].data = data;
  
146
	g_count++;
  
147
  
  
148
	char size_str[32];
  
149
	format_size(size_str, sizeof(size_str), (uint64_t)size);
  
150
	printf("Added: %s (%s)\n", path, size_str);
  
151
}
  
152
  
  
153
  
  
154
void traverse(const char* dir_path) {
  
155
	if (should_ignore(dir_path, 1)) return;
  
156
  
  
157
	char clean_dir[1024];
  
158
	strncpy(clean_dir, dir_path, 1023);
  
159
	clean_dir[1023] = '\0';
  
160
	size_t len = strlen(clean_dir);
  
161
	if (len > 1 && clean_dir[len - 1] == '/') {
  
162
		clean_dir[len - 1] = '\0';
  
163
	}
  
164
  
  
165
	DIR* dir = opendir(clean_dir);
  
166
	if (!dir) {
  
167
		struct stat st;
  
168
		if (stat(clean_dir, &st) == 0 && !S_ISDIR(st.st_mode)) {
  
169
			add_file(clean_dir);
  
170
		}
  
171
		return;
  
172
	}
  
173
  
  
174
	struct dirent* entry;
  
175
	while ((entry = readdir(dir)) != NULL) {
  
176
		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
  
177
			continue;
  
178
  
  
179
		char path[2048];
  
180
		snprintf(path, sizeof(path), "%s/%s", clean_dir, entry->d_name);
  
181
  
  
182
		struct stat st;
  
183
		if (stat(path, &st) == 0) {
  
184
			int is_dir = S_ISDIR(st.st_mode);
  
185
			if (should_ignore(path, is_dir)) continue;
  
186
  
  
187
			if (is_dir) {
  
188
				traverse(path);
  
189
			} else {
  
190
				add_file(path);
  
191
			}
  
192
		}
  
193
	}
  
194
	closedir(dir);
  
195
}
  
196
  
  
197
void cmd_pack(const char* output_name, int num_folders, char** folders) {
  
198
	load_gitignore();
  
199
  
  
200
	for (int i = 0; i < num_folders; i++) {
  
201
		traverse(folders[i]);
  
202
	}
  
203
  
  
204
	if (g_count == 0) {
  
205
		fprintf(stderr, "No files to pack.\n");
  
206
		free_gitignore();
  
207
		return;
  
208
	}
  
209
  
  
210
	FILE* out = fopen(output_name, "wb");
  
211
	if (!out) {
  
212
		fprintf(stderr, "Failed to open output file %s\n", output_name);
  
213
		free_gitignore();
  
214
		return;
  
215
	}
  
216
  
  
217
	VfsHeader header;
  
218
	memcpy(header.magic, "DRP1", 4);
  
219
	header.num_files = (uint32_t)g_count;
  
220
	fwrite(&header, sizeof(VfsHeader), 1, out);
  
221
  
  
222
	VfsEntry* entries = malloc(sizeof(VfsEntry) * g_count);
  
223
	uint64_t current_offset = sizeof(VfsHeader) + sizeof(VfsEntry) * g_count;
  
224
  
  
225
	for (int i = 0; i < g_count; i++) {
  
226
		strncpy(entries[i].path, g_pending[i].path, 255);
  
227
		entries[i].size = (uint64_t)g_pending[i].size;
  
228
		entries[i].offset = current_offset;
  
229
		current_offset += entries[i].size;
  
230
	}
  
231
  
  
232
	fwrite(entries, sizeof(VfsEntry), g_count, out);
  
233
  
  
234
	for (int i = 0; i < g_count; i++) {
  
235
		fwrite(g_pending[i].data, 1, g_pending[i].size, out);
  
236
		free(g_pending[i].data);
  
237
	}
  
238
  
  
239
	fclose(out);
  
240
	free(entries);
  
241
	free_gitignore();
  
242
  
  
243
	printf("Successfully packed %d files into %s\n", g_count, output_name);
  
244
}
  
245
  
  
246
int read_pak(const char* path, VfsHeader* header, VfsEntry** entries) {
  
247
	FILE* f = fopen(path, "rb");
  
248
	if (!f) return 0;
  
249
  
  
250
	if (fread(header, sizeof(VfsHeader), 1, f) != 1) {
  
251
		fclose(f);
  
252
		return 0;
  
253
	}
  
254
  
  
255
	if (memcmp(header->magic, "DRP1", 4) != 0) {
  
256
		fclose(f);
  
257
		return 0;
  
258
	}
  
259
  
  
260
	*entries = malloc(sizeof(VfsEntry) * header->num_files);
  
261
	if (fread(*entries, sizeof(VfsEntry), header->num_files, f) != header->num_files) {
  
262
		free(*entries);
  
263
		fclose(f);
  
264
		return 0;
  
265
	}
  
266
  
  
267
	fclose(f);
  
268
	return 1;
  
269
}
  
270
  
  
271
void cmd_list(const char* path) {
  
272
	VfsHeader header;
  
273
	VfsEntry* entries = NULL;
  
274
	if (!read_pak(path, &header, &entries)) {
  
275
		fprintf(stderr, "Error: Could not read pak file %s\n", path);
  
276
		return;
  
277
	}
  
278
  
  
279
	printf("Listing %u files in %s:\n", header.num_files, path);
  
280
	printf("%-48s %16s %12s\n", "Path", "Size", "Offset");
  
281
	printf("--------------------------------------------------------------------------------\n");
  
282
	for (uint32_t i = 0; i < header.num_files; i++) {
  
283
		char size_str[32];
  
284
		format_size(size_str, sizeof(size_str), entries[i].size);
  
285
		printf("%-48s %16s %12lu\n", entries[i].path, size_str, (unsigned long)entries[i].offset);
  
286
	}
  
287
  
  
288
	free(entries);
  
289
}
  
290
  
  
291
void cmd_info(const char* path) {
  
292
	VfsHeader header;
  
293
	VfsEntry* entries = NULL;
  
294
	if (!read_pak(path, &header, &entries)) {
  
295
		fprintf(stderr, "Error: Could not read pak file %s\n", path);
  
296
		return;
  
297
	}
  
298
  
  
299
	uint64_t total_data_size = 0;
  
300
	for (uint32_t i = 0; i < header.num_files; i++) {
  
301
		total_data_size += entries[i].size;
  
302
	}
  
303
  
  
304
	struct stat st;
  
305
	uint64_t file_size = 0;
  
306
	if (stat(path, &st) == 0) file_size = st.st_size;
  
307
  
  
308
	char data_size_str[32], archive_size_str[32];
  
309
	format_size(data_size_str, sizeof(data_size_str), total_data_size);
  
310
	format_size(archive_size_str, sizeof(archive_size_str), file_size);
  
311
  
  
312
	printf("PAK Information: %s\n", path);
  
313
	printf("  Magic:         %.4s\n", header.magic);
  
314
	printf("  File Count:    %u\n", header.num_files);
  
315
	printf("  Data Size:     %lu bytes (%s)\n", (unsigned long)total_data_size, data_size_str);
  
316
	printf("  Archive Size:  %lu bytes (%s)\n", (unsigned long)file_size, archive_size_str);
  
317
	printf("  Header Size:   %lu bytes\n", (unsigned long)sizeof(VfsHeader));
  
318
	printf("  Index Size:    %lu bytes\n", (unsigned long)(sizeof(VfsEntry) * header.num_files));
  
319
  
  
320
	free(entries);
  
321
}
  
322
  
  
323
typedef struct TreeNode {
  
324
	char* name;
  
325
	struct TreeNode* children;
  
326
	struct TreeNode* next;
  
327
	uint64_t size;
  
328
	int is_file;
  
329
} TreeNode;
  
330
  
  
331
TreeNode* create_node(const char* name, int is_file, uint64_t size) {
  
332
	TreeNode* n = calloc(1, sizeof(TreeNode));
  
333
	n->name = strdup(name);
  
334
	n->is_file = is_file;
  
335
	n->size = size;
  
336
	return n;
  
337
}
  
338
  
  
339
void add_to_tree(TreeNode* root, const char* path, uint64_t size) {
  
340
	char temp[1024];
  
341
	strncpy(temp, path, 1023);
  
342
	temp[1023] = '\0';
  
343
	char* saveptr;
  
344
	char* token = strtok_r(temp, "/", &saveptr);
  
345
	TreeNode* current = root;
  
346
  
  
347
	while (token) {
  
348
		char* next_token = strtok_r(NULL, "/", &saveptr);
  
349
		int is_file = (next_token == NULL);
  
350
  
  
351
		TreeNode* child = current->children;
  
352
		TreeNode* prev = NULL;
  
353
		while (child) {
  
354
			if (strcmp(child->name, token) == 0) break;
  
355
			prev = child;
  
356
			child = child->next;
  
357
		}
  
358
  
  
359
		if (!child) {
  
360
			child = create_node(token, is_file, is_file ? size : 0);
  
361
			if (prev) prev->next = child;
  
362
			else current->children = child;
  
363
		} else if (!is_file) {
  
364
			// Directory node already exists, continue traversal
  
365
		} else {
  
366
			child->size = size;
  
367
			child->is_file = 1;
  
368
		}
  
369
  
  
370
		current = child;
  
371
		token = next_token;
  
372
	}
  
373
}
  
374
  
  
375
void print_tree(TreeNode* node, const char* prefix, int is_last) {
  
376
	if (node->name[0] != '\0') {
  
377
		printf("%s%s %s", prefix, is_last ? "└──" : "├──", node->name);
  
378
		if (node->is_file) {
  
379
			char size_str[32];
  
380
			format_size(size_str, sizeof(size_str), node->size);
  
381
			printf(" (%s)", size_str);
  
382
		}
  
383
		printf("\n");
  
384
	}
  
385
  
  
386
	char new_prefix[1024];
  
387
	if (node->name[0] == '\0') {
  
388
		new_prefix[0] = '\0';
  
389
	} else {
  
390
		snprintf(new_prefix, sizeof(new_prefix), "%s%s   ", prefix, is_last ? " " : "│");
  
391
	}
  
392
  
  
393
	TreeNode* child = node->children;
  
394
	while (child) {
  
395
		print_tree(child, new_prefix, child->next == NULL);
  
396
		child = child->next;
  
397
	}
  
398
}
  
399
  
  
400
void free_tree(TreeNode* node) {
  
401
	TreeNode* child = node->children;
  
402
	while (child) {
  
403
		TreeNode* next = child->next;
  
404
		free_tree(child);
  
405
		child = next;
  
406
	}
  
407
	free(node->name);
  
408
	free(node);
  
409
}
  
410
  
  
411
void cmd_tree(const char* path) {
  
412
	VfsHeader header;
  
413
	VfsEntry* entries = NULL;
  
414
	if (!read_pak(path, &header, &entries)) {
  
415
		fprintf(stderr, "Error: Could not read pak file %s\n", path);
  
416
		return;
  
417
	}
  
418
  
  
419
	TreeNode* root = create_node("", 0, 0);
  
420
	for (uint32_t i = 0; i < header.num_files; i++) {
  
421
		add_to_tree(root, entries[i].path, entries[i].size);
  
422
	}
  
423
  
  
424
	printf("Tree view of %s:\n", path);
  
425
	print_tree(root, "", 1);
  
426
  
  
427
	free_tree(root);
  
428
	free(entries);
  
429
}
  
430
  
  
431
void print_usage(const char* prog) {
  
432
	printf("Usage:\n");
  
433
	printf("  %s -p, --pack <output.pak> <folder1> [folder2] ...\n", prog);
  
434
	printf("  %s -l, --list <input.pak>\n", prog);
  
435
	printf("  %s -t, --tree <input.pak>\n", prog);
  
436
	printf("  %s -i, --info <input.pak>\n", prog);
  
437
	printf("  %s -h, --help\n", prog);
  
438
}
  
439
  
  
440
int main(int argc, char** argv) {
  
441
	if (argc < 2) {
  
442
		print_usage(argv[0]);
  
443
		return 1;
  
444
	}
  
445
  
  
446
	static struct option long_options[] = {
  
447
		{"pack", no_argument, 0, 'p'},
  
448
		{"list", no_argument, 0, 'l'},
  
449
		{"tree", no_argument, 0, 't'},
  
450
		{"info", no_argument, 0, 'i'},
  
451
		{"help", no_argument, 0, 'h'},
  
452
		{0, 0, 0, 0}
  
453
	};
  
454
  
  
455
	int mode = 0;
  
456
	int opt;
  
457
	while ((opt = getopt_long(argc, argv, "pltih", long_options, NULL)) != -1) {
  
458
		switch (opt) {
  
459
			case 'p': mode = 'p'; break;
  
460
			case 'l': mode = 'l'; break;
  
461
			case 't': mode = 't'; break;
  
462
			case 'i': mode = 'i'; break;
  
463
			case 'h': print_usage(argv[0]); return 0;
  
464
			default: print_usage(argv[0]); return 1;
  
465
		}
  
466
	}
  
467
  
  
468
	if (mode == 0) {
  
469
		print_usage(argv[0]);
  
470
		return 1;
  
471
	}
  
472
  
  
473
	if (optind >= argc) {
  
474
		fprintf(stderr, "Error: Missing target file argument.\n");
  
475
		print_usage(argv[0]);
  
476
		return 1;
  
477
	}
  
478
  
  
479
	const char* target = argv[optind];
  
480
  
  
481
	switch (mode) {
  
482
		case 'p':
  
483
			cmd_pack(target, argc - optind - 1, argv + optind + 1);
  
484
			break;
  
485
		case 'l':
  
486
			cmd_list(target);
  
487
			break;
  
488
		case 't':
  
489
			cmd_tree(target);
  
490
			break;
  
491
		case 'i':
  
492
			cmd_info(target);
  
493
			break;
  
494
	}
  
495
  
  
496
	return 0;
  
497
}