From 0ed91795a2db720e688fd2daefd22f7e9c754c2f Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Tue, 28 Apr 2026 07:45:45 +0200 Subject: Engage! --- tools/hexdump.c | 57 +++++++ tools/packer.c | 497 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 554 insertions(+) create mode 100644 tools/hexdump.c create mode 100644 tools/packer.c (limited to 'tools') diff --git a/tools/hexdump.c b/tools/hexdump.c new file mode 100644 index 0000000..988424a --- /dev/null +++ b/tools/hexdump.c @@ -0,0 +1,57 @@ +#include +#include + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + const char *filename = argv[1]; + const char *array_name = argv[2]; + + FILE *f = fopen(filename, "rb"); + if (!f) { + perror("fopen"); + return 1; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + rewind(f); + + unsigned char *buffer = malloc(size); + if (!buffer) { + perror("malloc"); + fclose(f); + return 1; + } + + if (fread(buffer, 1, size, f) != (size_t)size) { + perror("fread"); + free(buffer); + fclose(f); + return 1; + } + + // Generate include guard + printf("#ifndef %s_H\n", array_name); + printf("#define %s_H\n\n", array_name); + + printf("unsigned char %s[] = {\n", array_name); + for (long i = 0; i < size; i++) { + if (i % 12 == 0) printf("\t"); + printf("0x%02x", buffer[i]); + if (i != size - 1) printf(", "); + if ((i + 1) % 12 == 0) printf("\n"); + } + if (size % 12 != 0) printf("\n"); + printf("};\n"); + printf("unsigned int %s_len = %ld;\n", array_name, size); + + printf("\n#endif // %s_H\n", array_name); + + free(buffer); + fclose(f); + return 0; +} 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 [folder2] ...\n", prog); + printf(" %s -l, --list \n", prog); + printf(" %s -t, --tree \n", prog); + printf(" %s -i, --info \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; +} -- cgit v1.2.3