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
14static 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
31typedef struct {
32 char path[256];
33 long size;
34 void* data;
35} PendingFile;
36
37static PendingFile g_pending[MAX_FILES];
38static int g_count = 0;
39
40static char* g_ignore_patterns[MAX_FILES];
41static int g_ignore_count = 0;
42
43void 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
68void 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
75int 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
113void 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
154void 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
197void 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
246int 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
271void 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
291void 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
323typedef struct TreeNode {
324 char* name;
325 struct TreeNode* children;
326 struct TreeNode* next;
327 uint64_t size;
328 int is_file;
329} TreeNode;
330
331TreeNode* 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
339void 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
375void 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
400void 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
411void 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
431void 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
440int 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}