diff --git a/tools/borpak/source/borpak.c b/tools/borpak/source/borpak.c index d9e78b732..c43b36b42 100644 --- a/tools/borpak/source/borpak.c +++ b/tools/borpak/source/borpak.c @@ -1,6 +1,7 @@ /* Copyright 2006 Luigi Auriemma Copyright 2009-2011 Bryan Cain + Copyright 2026 Damon Caskey (dcurrent) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,6 +41,7 @@ #include #include #include +#include #ifdef WIN32 #include @@ -58,40 +60,67 @@ #endif #define FNAMEMAX 1024 -#define PAK_UINT_MAX UINT32_MAX #define PACK_MAGIC "PACK" #define PACK_MAGIC_LEN 4 #define PACK_HEADER_SIZE 8 -#define PACK_ENTRY_BASE_SIZE 12 -#define PAK64_MAGIC "PAK64" -#define PAK64_MAGIC_LEN 5 -#define PAK64_HEADER_SIZE 9 -#define PAK64_ENTRY_BASE_SIZE 20 +#define PAK32_ENTRY_BASE_SIZE 12U +#define PAK64_ENTRY_BASE_SIZE 20U +#define PAK32_FOOTER_SIZE 4U +#define PAK64_FOOTER_SIZE 8U + +/* +* PAK32 is the compatibility format for older +* engines, so the packer keeps its completed +* archives under the signed 32-bit file-position +* boundary those builds commonly rely on. +* +* The data section stops at 2,000,000,000 bytes, +* leaving the remaining archive budget for every +* file table record plus the final 32-bit footer. +* +* Technically PAK32 format can support up to 4GB +* now, and so can latest engine, but there's no +* use case. Any engine builds that can safely read +* >2GB PAK32 archives also support PAK64 format. +*/ +#define PAK32_ARCHIVE_LIMIT ((pak_size_t)INT_MAX) +#define PAK32_DATA_LIMIT ((pak_size_t)2000000000ULL) +#define PAK32_TABLE_CUSHION (PAK32_ARCHIVE_LIMIT - PAK32_DATA_LIMIT) typedef uint32_t pak_u32; typedef uint64_t pak_offset_t; typedef uint64_t pak_size_t; +/* +* PAK format enumeration. The packer uses this +* to differentiate between the 32-bit and 64-bit +* PAK formats. +*/ typedef enum { - PAK_FORMAT_PACK, - PAK_FORMAT_PAK64 + PAK_FORMAT_PAK32 = 0U, + PAK_FORMAT_PAK64 = 1U } pak_format_t; void file_write(const void *buffer, size_t size, FILE *file_object); -int pack_file(FILE *pak_file_object, char *file_path, pak_format_t format); +int pack_file(FILE *pak_file_object, const char *source_file_path, const char *archive_file_path, pak_format_t format); void extract_file(FILE *pak_file_object, char *file_path, pak_offset_t data_offset, pak_size_t data_size); pak_u32 read_le_uint(FILE *pak_file_object, int bits); uint64_t read_le_u64(FILE *pak_file_object); void write_le_uint(FILE *pak_file_object, pak_u32 num, int bits); void write_le_u64(FILE *pak_file_object, uint64_t value); -int pack_directory(FILE *pak_file_object, char *directory_path, pak_format_t format); +int pack_directory(FILE *pak_file_object, const char *source_directory_path, const char *archive_directory_path, pak_format_t format); void write_err(void); void std_err(void); pak_offset_t pak_tell(FILE *pak_file_object); +pak_size_t pak_file_size(FILE *file_object); void pak_seek_relative(FILE *pak_file_object, int64_t offset, int origin); void pak_seek_to_offset(FILE *pak_file_object, pak_offset_t offset); +void validate_pak32_budget(const char *file_path, pak_offset_t data_offset, pak_size_t data_size, pak_u32 record_size); +void copy_archive_path(char *destination, size_t destination_size, const char *source); +void copy_archive_root_from_input_directory(char *destination, size_t destination_size, const char *input_directory); +void print_format_details(pak_format_t format); int is_unsafe_path(const char *name); const char *format_name(pak_format_t format); @@ -99,22 +128,60 @@ const char *format_name(pak_format_t format); void winpause(void); #endif +/* +* One file table record kept in memory until +* the archive footer table is written. +*/ typedef struct { - pak_u32 record_size; + pak_u32 record_size; pak_offset_t data_offset; pak_size_t data_size; char *name; } pak_entry_t; -struct pak_entry_node_t { - pak_entry_t entry; - struct pak_entry_node_t *next; -}; +/* +* Linked list node for deferred table records. +* File data is written first, then this list is +* walked to write the final archive table. +*/ +typedef struct pak_entry_node_t { + pak_entry_t entry; + struct pak_entry_node_t *next; +} pak_entry_node_t; -struct pak_entry_node_t *pak_entries = NULL; +static pak_entry_node_t *pak_entries = NULL; +static pak_size_t pak_table_size = 0; const char *format_name(pak_format_t format) { - return format == PAK_FORMAT_PAK64 ? "PAK64" : "PACK"; + return format == PAK_FORMAT_PAK64 ? "PACK v1 PAK64 (64-bit)" : "PACK v0 PAK32 (32-bit)"; +} + +/* +* Limits for PAK32 format in gigabytes for +* user-friendly display. +*/ +const double pak32_data_limit_gb = (double)PAK32_DATA_LIMIT / 1000000000.0; +const double pak32_table_cushion_gb = (double)PAK32_TABLE_CUSHION / 1000000000.0; +const double pak32_archive_limit_gb = (double)PAK32_ARCHIVE_LIMIT / 1000000000.0; + +/* +* Caskey, Damon V. +* 2026-06-10 +* +* Prints details about the selected PAK format. +*/ +void print_format_details(pak_format_t format) { + printf("- format: %s\n", format_name(format)); + + if(format == PAK_FORMAT_PAK32) { + printf("- PAK32 data limit: %.2f GB.\n", pak32_data_limit_gb); + printf("- PAK32 table/footer cushion: %.2f GB\n", pak32_table_cushion_gb); + printf("- PAK32 archive limit: %.2f GB\n", pak32_archive_limit_gb); + printf("- Use PAK64 for larger archives or new-engine-only builds.\n"); + } else { + printf("- PAK64 format id: %u\n", (unsigned int)PAK_FORMAT_PAK64); + printf("- PAK64 offsets/sizes: 64-bit\n"); + } } /* @@ -134,7 +201,8 @@ void file_write(const void *buffer, size_t size, FILE *file_object) { * Caskey, Damon V. * 2026-06-10 * -* Returns the current file position as a 64-bit archive offset. +* Returns the current file position as a 64-bit +* archive offset. */ pak_offset_t pak_tell(FILE *pak_file_object) { #ifdef WIN32 @@ -158,12 +226,28 @@ pak_offset_t pak_tell(FILE *pak_file_object) { #endif } +/* +* Returns the size of an already-open file without +* changing the final read/write position seen by callers. +*/ +pak_size_t pak_file_size(FILE *file_object) { + pak_offset_t current_position; + pak_offset_t file_size; + + current_position = pak_tell(file_object); + pak_seek_relative(file_object, 0, SEEK_END); + file_size = pak_tell(file_object); + pak_seek_to_offset(file_object, current_position); + + return (pak_size_t)file_size; +} + /* * Seeks using the platform file API. * * The offset is signed because SEEK_END needs * negative movement when reading archive footers, -* such as the final 4-byte PACK footer or 8-byte +* such as the final 4-byte PAK32 footer or 8-byte * PAK64 footer. */ void pak_seek_relative(FILE *pak_file_object, int64_t offset, int origin) { @@ -194,6 +278,138 @@ void pak_seek_to_offset(FILE *pak_file_object, pak_offset_t offset) { pak_seek_relative(pak_file_object, (int64_t)offset, SEEK_SET); } +/* +* Caskey, Damon V. +* 2026-06-10 +* +* Checks whether adding one more file would exceed +* the PACK v0 PAK32 budget. The data section must +* stop at PAK32_DATA_LIMIT, leaving PAK32_TABLE_CUSHION +* bytes for every file table record and the final 32-bit +* footer while keeping the completed archive under INT_MAX. +*/ +void validate_pak32_budget(const char *file_path, pak_offset_t data_offset, pak_size_t data_size, pak_u32 record_size) { + pak_size_t table_budget_without_footer; + + if(data_offset > PAK32_DATA_LIMIT || data_size > PAK32_DATA_LIMIT - data_offset) { + printf("\nError: PACK v0 PAK32 data limit would be exceeded by %s\n", file_path); + printf("PACK v0 PAK32 data limit is %.2f GB.\n", pak32_data_limit_gb); + printf("Use -f pak64 to create a PACK v1 PAK64 archive.\n"); + exit(1); + } + + table_budget_without_footer = PAK32_TABLE_CUSHION - PAK32_FOOTER_SIZE; + + if(pak_table_size > table_budget_without_footer || + (pak_size_t)record_size > table_budget_without_footer - pak_table_size) { + printf("\nError: PACK v0 PAK32 file table cushion would be exceeded by %s\n", file_path); + printf("Reserved file table cushion is %.2f GB including the final footer.\n", + pak32_table_cushion_gb); + printf("Use -f pak64 to create a PACK v1 PAK64 archive.\n"); + exit(1); + } +} + + +/* +* Caskey, Damon V. +* 2026-06-10 +* +* Copies a path into the archive table using +* legacy OpenBOR pack naming: forward slashes +* become backslashes, and a leading "./" is +* ignored. Source paths and archive paths are +* handled separately so absolute -d paths do not +* leak host-specific prefixes such as /home/user/project +* into the pack table. +*/ +void copy_archive_path(char *destination, size_t destination_size, const char *source) { + size_t source_index = 0; + size_t destination_index = 0; + + if(destination_size == 0) { + return; + } + + while(source[source_index] == '.' && + (source[source_index + 1] == '/' || source[source_index + 1] == '\\')) { + source_index += 2; + } + + while(source[source_index] && destination_index < destination_size - 1) { + destination[destination_index] = source[source_index] == '/' ? '\\' : source[source_index]; + source_index++; + destination_index++; + } + + destination[destination_index] = '\0'; +} + +/* +* Caskey, Damon V. +* 2026-06-10 +* +* Derives the archive root from the build +* input directory. Relative "data" and absolute +* "/.../data" now both produce archive names +* rooted at "data", which keeps PAK32 output +* compatible with legacy engines that ask for +* data\\... paths. A build from "." keeps the +* old behavior of storing the current directory's +* contents without a synthetic root prefix. +*/ +void copy_archive_root_from_input_directory(char *destination, size_t destination_size, const char *input_directory) { + const char *component_end; + const char *component_start; + size_t component_length; + char component[FNAMEMAX]; + + if(destination_size == 0) { + return; + } + destination[0] = '\0'; + + if(!input_directory || !input_directory[0]) { + return; + } + + component_end = input_directory + strlen(input_directory); + while(component_end > input_directory && + (component_end[-1] == '/' || component_end[-1] == '\\')) { + component_end--; + } + + if(component_end == input_directory) { + return; + } + + component_start = component_end; + while(component_start > input_directory && + component_start[-1] != '/' && component_start[-1] != '\\') { + component_start--; + } + + component_length = (size_t)(component_end - component_start); + if(component_length == 0 || + (component_length == 1 && component_start[0] == '.')) { + return; + } + + if(component_length == 2 && component_start[0] == '.' && component_start[1] == '.') { + printf("\nError: input directory would create an unsafe archive root: %s\n", input_directory); + exit(1); + } + + if(component_length >= sizeof(component)) { + printf("\nError: archive root path too long: %s\n", input_directory); + exit(1); + } + + memcpy(component, component_start, component_length); + component[component_length] = '\0'; + copy_archive_path(destination, destination_size, component); +} + /* * Caskey, Damon V. * 2026-06-10 @@ -289,14 +505,14 @@ int main(int argc, char *argv[]) { size_t name_length; int list = 0; int build = 0; - int pak64 = 0; - pak_u32 packver = 0; + pak_u32 pack_format_id = 0; size_t file_count = 0; int fgetc_result = 0; - pak_format_t format = PAK_FORMAT_PACK; + pak_format_t format = PAK_FORMAT_PAK32; - unsigned char magic[PAK64_MAGIC_LEN]; + unsigned char magic[PACK_MAGIC_LEN]; char curdir[512]; + char archive_root[FNAMEMAX]; char *input_dir = NULL; char *pattern = NULL; char *pak_filename; @@ -321,7 +537,7 @@ int main(int argc, char *argv[]) { "web: https://www.chronocrash.com\n" "\n" "Updated to v0.4 by Damon Caskey (dcurrent)\n" - "web: https://www.chronocrash.com\n" + "web: https://www.chronocrash.com, https://www.caskeys.com/dc\n" "\n", stdout); if(argc < 2) { @@ -330,11 +546,12 @@ int main(int argc, char *argv[]) { "\n" "-d DIR files folder, default is the current\n" "-b build a PAK from the files folder, default is extraction\n" - "-6 build a PAK64 archive with 64-bit offsets and sizes\n" - "--pak64 same as -6\n" + "-f FORMAT specify the format for building the archive\n" + "\t\t pak32 - V0 32-bit (default). Max data size: %.2f GB.\n" + "\t\t pak64 - V1 64-bit. Requires OpenBOR 4x or later. Max data size: 9 exabytes. \n" "-l list files without extracting\n" "-p PAT extract only the files which contain PAT in their name\n" - "\n", argv[0]); + "\n", argv[0], pak32_data_limit_gb); exit(1); } @@ -345,8 +562,24 @@ int main(int argc, char *argv[]) { break; } - if(!strcmp(argv[i], "--pak64")) { - pak64 = 1; + if(!strcmp(argv[i], "-f") || !strcmp(argv[i], "--format")) { + const char *format_argument; + + if(i + 1 >= argc) { + printf("\nError: missing argument for -format option\n\n"); + exit(1); + } + + format_argument = argv[++i]; + if(!strcmp(format_argument, "pak32")) { + format = PAK_FORMAT_PAK32; + } else if(!strcmp(format_argument, "pak64")) { + format = PAK_FORMAT_PAK64; + } else { + printf("\nError: invalid format specified for -format option\n\n"); + exit(1); + } + continue; } @@ -363,9 +596,7 @@ int main(int argc, char *argv[]) { case 'b': build = 1; break; - case '6': - - pak64 = 1; break; + case 'l': list = 1; break; @@ -397,7 +628,8 @@ int main(int argc, char *argv[]) { } if(build) { - format = pak64 ? PAK_FORMAT_PAK64 : PAK_FORMAT_PACK; + pack_format_id = (pak_u32)format; + pak_table_size = 0; if(!input_dir) { printf("\n" @@ -406,9 +638,12 @@ int main(int argc, char *argv[]) { exit(1); } + copy_archive_root_from_input_directory(archive_root, sizeof(archive_root), input_dir); + printf("- create file: %s\n", pak_filename); - printf("- format: %s\n", format_name(format)); - printf("- directory: %s\n\n", input_dir); + print_format_details(format); + printf("- directory: %s\n", input_dir); + printf("- archive root: %s\n\n", archive_root[0] ? archive_root : "(directory contents)"); pak_file_object = fopen(pak_filename, "rb"); if(pak_file_object) { fclose(pak_file_object); @@ -425,12 +660,8 @@ int main(int argc, char *argv[]) { std_err(); } - if(format == PAK_FORMAT_PAK64) { - file_write(PAK64_MAGIC, PAK64_MAGIC_LEN, pak_file_object); - } else { - file_write(PACK_MAGIC, PACK_MAGIC_LEN, pak_file_object); - } - write_le_uint(pak_file_object, packver, 32); + file_write(PACK_MAGIC, PACK_MAGIC_LEN, pak_file_object); + write_le_uint(pak_file_object, pack_format_id, 32); /* * Print header based on format. @@ -445,15 +676,16 @@ int main(int argc, char *argv[]) { "--------------------------------\n"); } - if(pack_directory(pak_file_object, input_dir, format) < 0) { + if(pack_directory(pak_file_object, input_dir, archive_root, format) < 0) { printf("\nError: an error occurred during the directory scanning\n"); goto quit; } table_offset = pak_tell(pak_file_object); - if(format == PAK_FORMAT_PACK && table_offset > PAK_UINT_MAX) { - printf("\nError: archive exceeds PACK 32-bit offset limit.\n"); - printf("Use -6 or --pak64 to create a PAK64 archive.\n"); + if(format == PAK_FORMAT_PAK32 && table_offset > PAK32_DATA_LIMIT) { + printf("\nError: PACK v0 PAK32 data area has exceeded the limit.\n"); + printf("PACK v0 PAK32 data limit is %.2f GB.\n", pak32_data_limit_gb); + printf("Use -f pak64 to create a PACK v1 PAK64 archive.\n"); exit(1); } @@ -474,7 +706,7 @@ int main(int argc, char *argv[]) { write_le_uint(pak_file_object, entry_node->entry.record_size, 32); write_le_uint(pak_file_object, (pak_u32)entry_node->entry.data_offset, 32); write_le_uint(pak_file_object, (pak_u32)entry_node->entry.data_size, 32); - file_write(entry_node->entry.name, entry_node->entry.record_size - PACK_ENTRY_BASE_SIZE, pak_file_object); + file_write(entry_node->entry.name, entry_node->entry.record_size - PAK32_ENTRY_BASE_SIZE, pak_file_object); } entry_node_free = entry_node; @@ -490,15 +722,15 @@ int main(int argc, char *argv[]) { pak_offset_t footer_offset; /* - * The legacy PACK footer is a final 32-bit table offset. - * Make sure writing it will not push the archive beyond the - * 32-bit PACK size boundary. + * The PAK32 PACK footer is a final 32-bit table offset. The + * build-time data limit and table cushion should keep this safe; + * keep a final guard here in case a future code path bypasses them. */ footer_offset = pak_tell(pak_file_object); - if(footer_offset > PAK_UINT_MAX - 4) { - printf("\nError: archive exceeds PACK 32-bit size limit.\n"); - printf("Use -6 or --pak64 to create a PAK64 archive.\n"); + if(footer_offset > PAK32_ARCHIVE_LIMIT - PAK32_FOOTER_SIZE) { + printf("\nError: PACK v0 PAK32 archive exceeds the 32-bit size limit.\n"); + printf("Use -f pak64 to create a PACK v1 PAK64 archive.\n"); exit(1); } @@ -522,27 +754,31 @@ int main(int argc, char *argv[]) { } } - if(fread(magic, 1, PAK64_MAGIC_LEN, pak_file_object) != PAK64_MAGIC_LEN) { + if(fread(magic, 1, PACK_MAGIC_LEN, pak_file_object) != PACK_MAGIC_LEN) { + goto quit; + } + + if(memcmp(magic, PACK_MAGIC, PACK_MAGIC_LEN)) { + printf("\nError: unsupported pack format\n"); goto quit; } - if(!memcmp(magic, PAK64_MAGIC, PAK64_MAGIC_LEN)) { + pack_format_id = read_le_uint(pak_file_object, 32); + header_size = PACK_HEADER_SIZE; + + if(pack_format_id == (pak_u32)PAK_FORMAT_PAK32) { + format = PAK_FORMAT_PAK32; + entry_base_size = PAK32_ENTRY_BASE_SIZE; + } else if(pack_format_id == (pak_u32)PAK_FORMAT_PAK64) { format = PAK_FORMAT_PAK64; - header_size = PAK64_HEADER_SIZE; entry_base_size = PAK64_ENTRY_BASE_SIZE; - packver = read_le_uint(pak_file_object, 32); - } else if(!memcmp(magic, PACK_MAGIC, PACK_MAGIC_LEN)) { - format = PAK_FORMAT_PACK; - header_size = PACK_HEADER_SIZE; - entry_base_size = PACK_ENTRY_BASE_SIZE; - pak_seek_relative(pak_file_object, PACK_MAGIC_LEN, SEEK_SET); - packver = read_le_uint(pak_file_object, 32); } else { - printf("\nError: unsupported pack format\n"); + printf("\nError: unsupported PACK format id %u\n", (unsigned int)pack_format_id); goto quit; } printf("- format: %s\n", format_name(format)); + printf("- format id: %u\n", (unsigned int)pack_format_id); if(format == PAK_FORMAT_PAK64) { pak_seek_relative(pak_file_object, -8, SEEK_END); @@ -651,7 +887,7 @@ int main(int argc, char *argv[]) { return 0; } -int pack_file(FILE *pak_file_object, char *file_path, pak_format_t format) { +int pack_file(FILE *pak_file_object, const char *source_file_path, const char *archive_file_path, pak_format_t format) { struct pak_entry_node_t *entry_node; FILE *source_file_object; @@ -659,48 +895,44 @@ int pack_file(FILE *pak_file_object, char *file_path, pak_format_t format) { size_t name_length; pak_offset_t data_offset; pak_size_t data_size; + pak_size_t source_file_size; pak_u32 entry_base_size; + pak_u32 record_size; unsigned char copy_buffer[8192]; - char *path_cursor; + char archive_name[FNAMEMAX]; - // printf(" %s\r", file_path); - source_file_object = fopen(file_path, "rb"); + copy_archive_path(archive_name, sizeof(archive_name), archive_file_path); + if(!archive_name[0]) { + printf("\nError: empty archive path for %s\n", source_file_path); + exit(1); + } + + // printf(" %s\r", archive_name); + source_file_object = fopen(source_file_path, "rb"); if(!source_file_object){ std_err(); } - /* - * Skip leading "./" if present. - */ - if(file_path[0] == '.' && (file_path[1] == '/' || file_path[1] == '\\')) { - file_path += 2; - } + entry_base_size = format == PAK_FORMAT_PAK64 ? PAK64_ENTRY_BASE_SIZE : PAK32_ENTRY_BASE_SIZE; + name_length = strlen(archive_name) + 1; - for(path_cursor = file_path; *path_cursor; path_cursor++) { // WIN mode - if(*path_cursor == '/') { - *path_cursor = '\\'; - } /*else { - *path_cursor = toupper(*path_cursor); - } */ + if(name_length > (size_t)UINT32_MAX - entry_base_size) { + printf("\nError: file name too long for %s format: %s\n", format_name(format), archive_name); + exit(1); } + record_size = (pak_u32)(entry_base_size + name_length); data_offset = pak_tell(pak_file_object); - if(format == PAK_FORMAT_PACK && data_offset > PAK_UINT_MAX) { - printf("\nError: archive exceeds PACK 32-bit offset limit.\n"); - printf("Use -6 or --pak64 to create a PAK64 archive.\n"); - exit(1); + source_file_size = pak_file_size(source_file_object); + + if(format == PAK_FORMAT_PAK32) { + validate_pak32_budget(archive_name, data_offset, source_file_size, record_size); } data_size = 0; while((bytes_read = fread(copy_buffer, 1, sizeof(copy_buffer), source_file_object)) != 0) { - if(format == PAK_FORMAT_PACK && data_size > PAK_UINT_MAX - bytes_read) { - printf("\nError: file too large for PACK: %s\n", file_path); - printf("Use -6 or --pak64 to create a PAK64 archive.\n"); - exit(1); - } - file_write(copy_buffer, bytes_read, pak_file_object); data_size += bytes_read; } @@ -711,11 +943,16 @@ int pack_file(FILE *pak_file_object, char *file_path, pak_format_t format) { fclose(source_file_object); + if(data_size != source_file_size) { + printf("\nError: file changed while packing: %s\n", source_file_path); + exit(1); + } + for(entry_node = pak_entries; entry_node && entry_node->next; entry_node = entry_node->next); if(entry_node) { entry_node->next = malloc(sizeof(struct pak_entry_node_t)); - + if(!entry_node->next) { std_err(); } @@ -723,7 +960,7 @@ int pack_file(FILE *pak_file_object, char *file_path, pak_format_t format) { } else { pak_entries = malloc(sizeof(struct pak_entry_node_t)); - + if(!pak_entries) { std_err(); } @@ -731,34 +968,28 @@ int pack_file(FILE *pak_file_object, char *file_path, pak_format_t format) { } entry_node->next = NULL; - entry_base_size = format == PAK_FORMAT_PAK64 ? PAK64_ENTRY_BASE_SIZE : PACK_ENTRY_BASE_SIZE; - name_length = strlen(file_path) + 1; - - if(name_length > PAK_UINT_MAX - entry_base_size) { - printf("\nError: file name too long for %s format: %s\n", format_name(format), file_path); - exit(1); - } - - entry_node->entry.record_size = (pak_u32)(entry_base_size + name_length); + entry_node->entry.record_size = record_size; entry_node->entry.data_offset = data_offset; entry_node->entry.data_size = data_size; entry_node->entry.name = malloc(name_length); - + if(!entry_node->entry.name) { std_err(); } - memcpy(entry_node->entry.name, file_path, name_length); + memcpy(entry_node->entry.name, archive_name, name_length); + pak_table_size += record_size; if(format == PAK_FORMAT_PAK64) { - printf(" %016" PRIx64 " %20" PRIu64 " %s\n", (uint64_t)data_offset, (uint64_t)data_size, file_path); + printf(" %016" PRIx64 " %20" PRIu64 " %s\n", (uint64_t)data_offset, (uint64_t)data_size, archive_name); } else { - printf(" %08x %10" PRIu64 " %s\n", (unsigned int)data_offset, (uint64_t)data_size, file_path); + printf(" %08x %10" PRIu64 " %s\n", (unsigned int)data_offset, (uint64_t)data_size, archive_name); } return(0); } + void extract_file(FILE *pak_file_object, char *file_path, pak_offset_t data_offset, pak_size_t data_size) { FILE *output_file_object; size_t chunk_size; @@ -865,8 +1096,9 @@ void write_le_u64(FILE *pak_file_object, uint64_t value) { file_write(tmp, sizeof(tmp), pak_file_object); } -int pack_directory(FILE *pak_file_object, char *directory_path, pak_format_t format) { - char child_path[FNAMEMAX]; +int pack_directory(FILE *pak_file_object, const char *source_directory_path, const char *archive_directory_path, pak_format_t format) { + char child_source_path[FNAMEMAX]; + char child_archive_path[FNAMEMAX]; struct stat path_status; struct dirent **namelist; @@ -874,15 +1106,15 @@ int pack_directory(FILE *pak_file_object, char *directory_path, pak_format_t for i; int written; - n = scandir(directory_path, &namelist, NULL, alphasort); + n = scandir(source_directory_path, &namelist, NULL, alphasort); if(n < 0) { - if(stat(directory_path, &path_status) < 0) { - printf("**** %s", directory_path); + if(stat(source_directory_path, &path_status) < 0) { + printf("**** %s", source_directory_path); std_err(); } - if(pack_file(pak_file_object, directory_path, format) < 0) { + if(pack_file(pak_file_object, source_directory_path, archive_directory_path, format) < 0) { return(-1); } @@ -899,27 +1131,36 @@ int pack_directory(FILE *pak_file_object, char *directory_path, pak_format_t for } /* - * Construct the full path for the current entry. - * Check for path length overflow. + * Construct the source path and the archive path separately. This + * keeps absolute host paths out of the pack table while still reading + * files from the exact input directory the user supplied. */ - + written = snprintf(child_source_path, sizeof(child_source_path), "%s/%s", source_directory_path, namelist[i]->d_name); + if(written < 0 || (size_t)written >= sizeof(child_source_path)) { + printf("\nError: source path too long: %s/%s\n", source_directory_path, namelist[i]->d_name); + goto quit; + } - written = snprintf(child_path, sizeof(child_path), "%s/%s", directory_path, namelist[i]->d_name); - if(written < 0 || (size_t)written >= sizeof(child_path)) { - printf("\nError: path too long: %s/%s\n", directory_path, namelist[i]->d_name); + if(archive_directory_path && archive_directory_path[0]) { + written = snprintf(child_archive_path, sizeof(child_archive_path), "%s/%s", archive_directory_path, namelist[i]->d_name); + } else { + written = snprintf(child_archive_path, sizeof(child_archive_path), "%s", namelist[i]->d_name); + } + if(written < 0 || (size_t)written >= sizeof(child_archive_path)) { + printf("\nError: archive path too long: %s/%s\n", archive_directory_path ? archive_directory_path : "", namelist[i]->d_name); goto quit; } - if(stat(child_path, &path_status) < 0) { - printf("**** %s", child_path); + if(stat(child_source_path, &path_status) < 0) { + printf("**** %s", child_source_path); std_err(); } if(S_ISDIR(path_status.st_mode)) { - if(pack_directory(pak_file_object, child_path, format) < 0) { + if(pack_directory(pak_file_object, child_source_path, child_archive_path, format) < 0) { goto quit; } } else { - if(pack_file(pak_file_object, child_path, format) < 0) { + if(pack_file(pak_file_object, child_source_path, child_archive_path, format) < 0) { goto quit; } } @@ -937,6 +1178,7 @@ int pack_directory(FILE *pak_file_object, char *directory_path, pak_format_t for return(-1); } + void write_err(void) { printf("\nError: write error; probably out of disk space\n\n"); exit(1);