#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; }