summaryrefslogtreecommitdiff
path: root/tools/packer.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-04-28 07:45:45 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-04-28 07:45:45 +0200
commit0ed91795a2db720e688fd2daefd22f7e9c754c2f (patch)
tree1856de605b3888132ad6917cbeb3ecf677bbda6d /tools/packer.c
downloadstalag-0ed91795a2db720e688fd2daefd22f7e9c754c2f.tar.gz
Engage!
Diffstat (limited to 'tools/packer.c')
-rw-r--r--tools/packer.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/tools/packer.c b/tools/packer.c
new file mode 100644
index 0000000..77652e8
--- /dev/null
+++ b/tools/packer.c
@@ -0,0 +1,497 @@
+#define _DEFAULT_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <fnmatch.h>
+#include <ctype.h>
+
+#include "../libraries/vfs.h"
+
+static void format_size(char* buf, size_t buf_size, uint64_t size) {
+ const char* units[] = {"B", "KB", "MB", "GB", "TB"};
+ int i = 0;
+ double d_size = (double)size;
+ while (d_size >= 1024 && i < 4) {
+ d_size /= 1024.0;
+ i++;
+ }
+ if (i == 0) {
+ snprintf(buf, buf_size, "%llu B", (unsigned long long)size);
+ } else {
+ snprintf(buf, buf_size, "%.2f %s", d_size, units[i]);
+ }
+}
+
+#define MAX_FILES 4096
+
+typedef struct {
+ char path[256];
+ long size;
+ void* data;
+} PendingFile;
+
+static PendingFile g_pending[MAX_FILES];
+static int g_count = 0;
+
+static char* g_ignore_patterns[MAX_FILES];
+static int g_ignore_count = 0;
+
+void load_gitignore() {
+ FILE* f = fopen(".gitignore", "r");
+ if (!f) return;
+
+ char line[1024];
+ while (fgets(line, sizeof(line), f)) {
+ // Strip whitespace and newline
+ char* p = line;
+ while (isspace(*p)) p++;
+ char* end = p + strlen(p) - 1;
+ while (end >= p && isspace(*end)) {
+ *end = '\0';
+ end--;
+ }
+
+ if (*p == '\0' || *p == '#') continue;
+
+ if (g_ignore_count < MAX_FILES) {
+ char* s = strdup(p);
+ if (s) g_ignore_patterns[g_ignore_count++] = s;
+ }
+ }
+ fclose(f);
+}
+
+void free_gitignore() {
+ for (int i = 0; i < g_ignore_count; i++) {
+ free(g_ignore_patterns[i]);
+ }
+ g_ignore_count = 0;
+}
+
+int should_ignore(const char* path, int is_dir) {
+ // Normalize path for matching: remove leading "./" if present
+ const char* p = path;
+ if (strncmp(p, "./", 2) == 0) p += 2;
+
+ for (int i = 0; i < g_ignore_count; i++) {
+ const char* pattern = g_ignore_patterns[i];
+ int pattern_is_dir_only = (pattern[strlen(pattern) - 1] == '/');
+
+ char clean_pattern[1024];
+ strncpy(clean_pattern, pattern, 1023);
+ clean_pattern[1023] = '\0';
+ if (pattern_is_dir_only) {
+ clean_pattern[strlen(clean_pattern) - 1] = '\0';
+ }
+
+ // If pattern has a slash (not at the end), it's relative to root
+ int has_slash = (strchr(clean_pattern, '/') != NULL);
+
+ if (has_slash) {
+ // Match against full path from root
+ if (fnmatch(clean_pattern, p, FNM_PATHNAME) == 0) {
+ if (!pattern_is_dir_only || is_dir) return 1;
+ }
+ } else {
+ // Match against filename only
+ const char* filename = strrchr(p, '/');
+ if (filename) filename++;
+ else filename = p;
+
+ if (fnmatch(clean_pattern, filename, 0) == 0) {
+ if (!pattern_is_dir_only || is_dir) return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+void add_file(const char* path) {
+ if (g_count >= MAX_FILES) {
+ fprintf(stderr, "Too many files!\n");
+ return;
+ }
+
+ FILE* f = fopen(path, "rb");
+ if (!f) {
+ fprintf(stderr, "Could not open %s\n", path);
+ return;
+ }
+
+ fseek(f, 0, SEEK_END);
+ long size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ void* data = malloc(size);
+ if (!data) {
+ fprintf(stderr, "Memory allocation failed for %s\n", path);
+ fclose(f);
+ return;
+ }
+ if (fread(data, 1, size, f) != (size_t)size) {
+ fprintf(stderr, "Failed to read %s\n", path);
+ free(data);
+ fclose(f);
+ return;
+ }
+ fclose(f);
+
+ strncpy( g_pending[g_count].path, path, 255);
+ g_pending[g_count].size = size;
+ g_pending[g_count].data = data;
+ g_count++;
+
+ char size_str[32];
+ format_size(size_str, sizeof(size_str), (uint64_t)size);
+ printf("Added: %s (%s)\n", path, size_str);
+}
+
+
+void traverse(const char* dir_path) {
+ if (should_ignore(dir_path, 1)) return;
+
+ char clean_dir[1024];
+ strncpy(clean_dir, dir_path, 1023);
+ clean_dir[1023] = '\0';
+ size_t len = strlen(clean_dir);
+ if (len > 1 && clean_dir[len - 1] == '/') {
+ clean_dir[len - 1] = '\0';
+ }
+
+ DIR* dir = opendir(clean_dir);
+ if (!dir) {
+ struct stat st;
+ if (stat(clean_dir, &st) == 0 && !S_ISDIR(st.st_mode)) {
+ add_file(clean_dir);
+ }
+ return;
+ }
+
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+ continue;
+
+ char path[2048];
+ snprintf(path, sizeof(path), "%s/%s", clean_dir, entry->d_name);
+
+ struct stat st;
+ if (stat(path, &st) == 0) {
+ int is_dir = S_ISDIR(st.st_mode);
+ if (should_ignore(path, is_dir)) continue;
+
+ if (is_dir) {
+ traverse(path);
+ } else {
+ add_file(path);
+ }
+ }
+ }
+ closedir(dir);
+}
+
+void cmd_pack(const char* output_name, int num_folders, char** folders) {
+ load_gitignore();
+
+ for (int i = 0; i < num_folders; i++) {
+ traverse(folders[i]);
+ }
+
+ if (g_count == 0) {
+ fprintf(stderr, "No files to pack.\n");
+ free_gitignore();
+ return;
+ }
+
+ FILE* out = fopen(output_name, "wb");
+ if (!out) {
+ fprintf(stderr, "Failed to open output file %s\n", output_name);
+ free_gitignore();
+ return;
+ }
+
+ VfsHeader header;
+ memcpy(header.magic, "DRP1", 4);
+ header.num_files = (uint32_t)g_count;
+ fwrite(&header, sizeof(VfsHeader), 1, out);
+
+ VfsEntry* entries = malloc(sizeof(VfsEntry) * g_count);
+ uint64_t current_offset = sizeof(VfsHeader) + sizeof(VfsEntry) * g_count;
+
+ for (int i = 0; i < g_count; i++) {
+ strncpy(entries[i].path, g_pending[i].path, 255);
+ entries[i].size = (uint64_t)g_pending[i].size;
+ entries[i].offset = current_offset;
+ current_offset += entries[i].size;
+ }
+
+ fwrite(entries, sizeof(VfsEntry), g_count, out);
+
+ for (int i = 0; i < g_count; i++) {
+ fwrite(g_pending[i].data, 1, g_pending[i].size, out);
+ free(g_pending[i].data);
+ }
+
+ fclose(out);
+ free(entries);
+ free_gitignore();
+
+ printf("Successfully packed %d files into %s\n", g_count, output_name);
+}
+
+int read_pak(const char* path, VfsHeader* header, VfsEntry** entries) {
+ FILE* f = fopen(path, "rb");
+ if (!f) return 0;
+
+ if (fread(header, sizeof(VfsHeader), 1, f) != 1) {
+ fclose(f);
+ return 0;
+ }
+
+ if (memcmp(header->magic, "DRP1", 4) != 0) {
+ fclose(f);
+ return 0;
+ }
+
+ *entries = malloc(sizeof(VfsEntry) * header->num_files);
+ if (fread(*entries, sizeof(VfsEntry), header->num_files, f) != header->num_files) {
+ free(*entries);
+ fclose(f);
+ return 0;
+ }
+
+ fclose(f);
+ return 1;
+}
+
+void cmd_list(const char* path) {
+ VfsHeader header;
+ VfsEntry* entries = NULL;
+ if (!read_pak(path, &header, &entries)) {
+ fprintf(stderr, "Error: Could not read pak file %s\n", path);
+ return;
+ }
+
+ printf("Listing %u files in %s:\n", header.num_files, path);
+ printf("%-48s %16s %12s\n", "Path", "Size", "Offset");
+ printf("--------------------------------------------------------------------------------\n");
+ for (uint32_t i = 0; i < header.num_files; i++) {
+ char size_str[32];
+ format_size(size_str, sizeof(size_str), entries[i].size);
+ printf("%-48s %16s %12lu\n", entries[i].path, size_str, (unsigned long)entries[i].offset);
+ }
+
+ free(entries);
+}
+
+void cmd_info(const char* path) {
+ VfsHeader header;
+ VfsEntry* entries = NULL;
+ if (!read_pak(path, &header, &entries)) {
+ fprintf(stderr, "Error: Could not read pak file %s\n", path);
+ return;
+ }
+
+ uint64_t total_data_size = 0;
+ for (uint32_t i = 0; i < header.num_files; i++) {
+ total_data_size += entries[i].size;
+ }
+
+ struct stat st;
+ uint64_t file_size = 0;
+ if (stat(path, &st) == 0) file_size = st.st_size;
+
+ char data_size_str[32], archive_size_str[32];
+ format_size(data_size_str, sizeof(data_size_str), total_data_size);
+ format_size(archive_size_str, sizeof(archive_size_str), file_size);
+
+ printf("PAK Information: %s\n", path);
+ printf(" Magic: %.4s\n", header.magic);
+ printf(" File Count: %u\n", header.num_files);
+ printf(" Data Size: %lu bytes (%s)\n", (unsigned long)total_data_size, data_size_str);
+ printf(" Archive Size: %lu bytes (%s)\n", (unsigned long)file_size, archive_size_str);
+ printf(" Header Size: %lu bytes\n", (unsigned long)sizeof(VfsHeader));
+ printf(" Index Size: %lu bytes\n", (unsigned long)(sizeof(VfsEntry) * header.num_files));
+
+ free(entries);
+}
+
+typedef struct TreeNode {
+ char* name;
+ struct TreeNode* children;
+ struct TreeNode* next;
+ uint64_t size;
+ int is_file;
+} TreeNode;
+
+TreeNode* create_node(const char* name, int is_file, uint64_t size) {
+ TreeNode* n = calloc(1, sizeof(TreeNode));
+ n->name = strdup(name);
+ n->is_file = is_file;
+ n->size = size;
+ return n;
+}
+
+void add_to_tree(TreeNode* root, const char* path, uint64_t size) {
+ char temp[1024];
+ strncpy(temp, path, 1023);
+ temp[1023] = '\0';
+ char* saveptr;
+ char* token = strtok_r(temp, "/", &saveptr);
+ TreeNode* current = root;
+
+ while (token) {
+ char* next_token = strtok_r(NULL, "/", &saveptr);
+ int is_file = (next_token == NULL);
+
+ TreeNode* child = current->children;
+ TreeNode* prev = NULL;
+ while (child) {
+ if (strcmp(child->name, token) == 0) break;
+ prev = child;
+ child = child->next;
+ }
+
+ if (!child) {
+ child = create_node(token, is_file, is_file ? size : 0);
+ if (prev) prev->next = child;
+ else current->children = child;
+ } else if (!is_file) {
+ // Directory node already exists, continue traversal
+ } else {
+ child->size = size;
+ child->is_file = 1;
+ }
+
+ current = child;
+ token = next_token;
+ }
+}
+
+void print_tree(TreeNode* node, const char* prefix, int is_last) {
+ if (node->name[0] != '\0') {
+ printf("%s%s %s", prefix, is_last ? "└──" : "├──", node->name);
+ if (node->is_file) {
+ char size_str[32];
+ format_size(size_str, sizeof(size_str), node->size);
+ printf(" (%s)", size_str);
+ }
+ printf("\n");
+ }
+
+ char new_prefix[1024];
+ if (node->name[0] == '\0') {
+ new_prefix[0] = '\0';
+ } else {
+ snprintf(new_prefix, sizeof(new_prefix), "%s%s ", prefix, is_last ? " " : "│");
+ }
+
+ TreeNode* child = node->children;
+ while (child) {
+ print_tree(child, new_prefix, child->next == NULL);
+ child = child->next;
+ }
+}
+
+void free_tree(TreeNode* node) {
+ TreeNode* child = node->children;
+ while (child) {
+ TreeNode* next = child->next;
+ free_tree(child);
+ child = next;
+ }
+ free(node->name);
+ free(node);
+}
+
+void cmd_tree(const char* path) {
+ VfsHeader header;
+ VfsEntry* entries = NULL;
+ if (!read_pak(path, &header, &entries)) {
+ fprintf(stderr, "Error: Could not read pak file %s\n", path);
+ return;
+ }
+
+ TreeNode* root = create_node("", 0, 0);
+ for (uint32_t i = 0; i < header.num_files; i++) {
+ add_to_tree(root, entries[i].path, entries[i].size);
+ }
+
+ printf("Tree view of %s:\n", path);
+ print_tree(root, "", 1);
+
+ free_tree(root);
+ free(entries);
+}
+
+void print_usage(const char* prog) {
+ printf("Usage:\n");
+ printf(" %s -p, --pack <output.pak> <folder1> [folder2] ...\n", prog);
+ printf(" %s -l, --list <input.pak>\n", prog);
+ printf(" %s -t, --tree <input.pak>\n", prog);
+ printf(" %s -i, --info <input.pak>\n", prog);
+ printf(" %s -h, --help\n", prog);
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ static struct option long_options[] = {
+ {"pack", no_argument, 0, 'p'},
+ {"list", no_argument, 0, 'l'},
+ {"tree", no_argument, 0, 't'},
+ {"info", no_argument, 0, 'i'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ int mode = 0;
+ int opt;
+ while ((opt = getopt_long(argc, argv, "pltih", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 'p': mode = 'p'; break;
+ case 'l': mode = 'l'; break;
+ case 't': mode = 't'; break;
+ case 'i': mode = 'i'; break;
+ case 'h': print_usage(argv[0]); return 0;
+ default: print_usage(argv[0]); return 1;
+ }
+ }
+
+ if (mode == 0) {
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ if (optind >= argc) {
+ fprintf(stderr, "Error: Missing target file argument.\n");
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ const char* target = argv[optind];
+
+ switch (mode) {
+ case 'p':
+ cmd_pack(target, argc - optind - 1, argv + optind + 1);
+ break;
+ case 'l':
+ cmd_list(target);
+ break;
+ case 't':
+ cmd_tree(target);
+ break;
+ case 'i':
+ cmd_info(target);
+ break;
+ }
+
+ return 0;
+}