diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..1408221 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,17 @@ +--- +Checks: > + -*, + bugprone-*, + cert-*, + clang-analyzer-*, + misc-*, + performance-*, + portability-*, + readability-*, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling + +CheckOptions: + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.FunctionCase + value: lower_case \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a646111 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,68 @@ +name: Cross-Platform Build + +on: + push: + branches: [ main, develop, 'copilot/**' ] + pull_request: + branches: [ main, develop ] + +jobs: + build-linux: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential clang-tidy + + - name: Configure CMake + run: | + mkdir build + cd build + cmake -DZLIB_BUILD_EXAMPLES=OFF .. + + - name: Build + run: | + cd build + make + + - name: Upload Linux build artifact + uses: actions/upload-artifact@v4 + with: + name: duef-linux + path: build/duef + + - name: Run static analysis + run: | + echo "Running static analysis..." + clang-tidy duef.c -- -I./zlib-1.3.1 -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS || true + echo "Static analysis completed." + + build-windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up MSVC + uses: microsoft/setup-msbuild@v2 + + - name: Configure CMake + run: | + mkdir build + cd build + cmake -DZLIB_BUILD_EXAMPLES=OFF .. + + - name: Build + run: | + cd build + cmake --build . --config Release + + - name: Upload Windows build artifact + uses: actions/upload-artifact@v4 + with: + name: duef-windows + path: build/Release/duef.exe \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0540a37..6d0357a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,14 @@ cmake_minimum_required(VERSION 3.29) project(decompress-uecrash-file) add_subdirectory(zlib-1.3.1) -add_executable(duef duef.c) +add_executable(duef + duef.c + duef_args.c + duef_logger.c + duef_file_ops.c + duef_types.c + duef_printing.c +) add_definitions(-D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS) target_include_directories(duef PUBLIC zlib-1.3.1) diff --git a/duef.c b/duef.c index dfb572c..902bb65 100644 --- a/duef.c +++ b/duef.c @@ -1,6 +1,9 @@ #include "duef.h" #include "duef_types.h" #include "duef_printing.h" +#include "duef_args.h" +#include "duef_logger.h" +#include "duef_file_ops.h" #include "zlib.h" @@ -8,333 +11,63 @@ #include #include #include -#if _WIN32 +#ifdef _WIN32 #include // For Windows-specific functions #include // For _mkdir +#include // For file operations +#include // For errno and EEXIST on Windows +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif #else #include // For mkdir -#include #include // For PATH_MAX -#include // For getcwd +#ifndef PATH_MAX +#define PATH_MAX 4096 // Fallback definition for PATH_MAX +#endif #endif -#include #include "stdbool.h" -int g_is_verbose = false; -int g_print_mode_file = false; -char *file_path = NULL; - -void print_usage(const char *program_name); - -void parse_arguments(int argc, char **argv) -{ - // add support for gnu style combinable options - for (int i = 1; i < argc; i++) - { - - if (argv[i][0] == '-' && argv[i][1] != '-') - { - bool exit_j_loop = false; // Flag to check if the next argument is expected - // the combinable options here - for (int j = 1; argv[i][j] != '\0'; j++) - { - if (exit_j_loop) - break; // If we are expecting the next argument, break out of the loop - switch (argv[i][j]) - { - case 'v': - g_is_verbose = true; - print_verbose("Verbose mode enabled.\n"); - break; - case 'f': - if (i + 1 < argc) - { - file_path = strdup(argv[++i]); - print_verbose("File path set to: %s\n", file_path); - if (!file_path) - { - fprintf(stderr, "Memory allocation failed for file path\n"); - exit(EXIT_FAILURE); - } - exit_j_loop = true; // Set the flag to true to skip the next argument - } - else - { - fprintf(stderr, "Option -f requires an argument\n\n"); - print_usage(argv[0]); - exit(EXIT_FAILURE); - } - break; - case 'i': - g_print_mode_file = true; - print_verbose("Print mode file enabled.\n"); - break; - case 'h': - print_usage(argv[0]); - exit(EXIT_SUCCESS); - break; - default: - fprintf(stderr, "Unknown option: -%c\n\n", argv[i][j]); - print_usage(argv[0]); - exit(EXIT_FAILURE); - } - } - } - else if (argv[i][0] == '-' && argv[i][1] == '-') - { - // handle long options - if (strcmp(argv[i], "--verbose") == 0) - { - g_is_verbose = true; - print_verbose("Verbose mode enabled.\n"); - } - else if (strcmp(argv[i], "--file") == 0) - { - if (i + 1 < argc) - { - file_path = strdup(argv[++i]); - print_verbose("File path set to: %s\n", file_path); - if (!file_path) - { - fprintf(stderr, "Memory allocation failed for file path\n"); - exit(EXIT_FAILURE); - } - } - else - { - fprintf(stderr, "Option --file requires an argument\n\n"); - print_usage(argv[0]); - exit(EXIT_FAILURE); - } - } - else if (strcmp(argv[i], "--help") == 0) - { - print_usage(argv[0]); - exit(EXIT_SUCCESS); - } - else if (strcmp(argv[i], "--clean") == 0) - { - // clear the crash directory by deleting all files in the crash directory - delete_crash_collection_directory(); - print_verbose("Crash collection directory cleared.\n"); - exit(EXIT_SUCCESS); - return; - } - else - { - fprintf(stderr, "Unknown option: %s\n\n", argv[i]); - print_usage(argv[0]); - exit(EXIT_FAILURE); - } - } - else - { - // Handle positional arguments (non-option arguments) - if (file_path == NULL) - { - file_path = strdup(argv[i]); - if (!file_path) - { - fprintf(stderr, "Memory allocation failed for file path\n"); - exit(EXIT_FAILURE); - } - print_verbose("File path set to: %s\n", file_path); - } - else - { - fprintf(stderr, "Multiple file arguments provided. Only one file can be processed at a time.\n\n"); - print_usage(argv[0]); - exit(EXIT_FAILURE); - } - } - } -} - -void cleanup_arguments() -{ - if (file_path) - { - free(file_path); - file_path = NULL; - } -} - -void print_usage(const char *program_name) -{ - printf("duef - Unreal Engine Crash File Decompressor\n\n"); - printf("Usage: %s [OPTIONS] [file]\n\n", program_name); - printf("Options:\n"); - printf(" -h, --help Show this help message and exit\n"); - printf(" -v, --verbose Enable verbose output to stderr\n"); - printf(" -f, --file FILE Specify .uecrash file to process\n"); - printf(" -i Print individual file paths instead of directory path\n"); - printf(" --clean Remove all extracted files from ~/.duef directory\n\n"); - printf("Examples:\n"); - printf(" %s CrashReport.uecrash # Decompress crash file\n", program_name); - printf(" %s -v -f crash.uecrash # Decompress with verbose output\n", program_name); - printf(" %s -i crash.uecrash # Print individual file paths\n", program_name); - printf(" %s --clean # Clean up extracted files\n\n", program_name); - printf("Output:\n"); - printf(" On Unix: Files extracted to ~/.duef//\n"); - printf(" On Windows: Files extracted to %%LocalAppData%%\\duef\\\\\n"); - printf(" Default file: CrashFile.uecrash (if no file specified)\n"); -} - -void write_file(const FAnsiCharStr *directory, const FFile *file); - int main(int argc, char *argv[]) { parse_arguments(argc, argv); + // Initialize the zlib library if (zlibVersion() == NULL) { + cleanup_arguments(); return 1; // zlib initialization failed } - // decompress input file. - FILE *input_file = fopen(file_path ? file_path : "CrashFile.uecrash", "rb"); + + // Open input file + const char *input_filename = file_path ? file_path : "CrashFile.uecrash"; + FILE *input_file = fopen(input_filename, "rb"); if (!input_file) { - fprintf(stderr, "Error opening input file: %s\n", file_path ? file_path : "CrashFile.uecrash"); - return 1; // File open failed - } - - z_stream strm = {0}; - if (inflateInit(&strm) != Z_OK) - { - fprintf(stderr, "Failed to initialize zlib stream\n"); - fclose(input_file); - return 1; // Initialization failed - } - - unsigned char in[4096]; - unsigned char out[4096]; - int ret; - size_t total_out = 0; - size_t buffer_size = 4096; - unsigned char *decompressed = malloc(buffer_size); - if (!decompressed) - { - fprintf(stderr, "Memory allocation failed\n"); - fclose(input_file); - inflateEnd(&strm); - exit(EXIT_FAILURE); - return EXIT_FAILURE; + log_error("Error opening input file: %s\n", input_filename); + cleanup_arguments(); + return 1; } - do - { - strm.avail_in = fread(in, 1, sizeof(in), input_file); - if (ferror(input_file)) - { - fprintf(stderr, "Error reading input file\n"); - inflateEnd(&strm); - fclose(input_file); - free(decompressed); - return 1; - } - if (strm.avail_in == 0) - break; - strm.next_in = in; - - do - { - strm.avail_out = sizeof(out); - strm.next_out = out; - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) - { - fprintf(stderr, "Decompression error\n"); - inflateEnd(&strm); - fclose(input_file); - free(decompressed); - return 1; - } - size_t have = sizeof(out) - strm.avail_out; - if (total_out + have > buffer_size) - { - buffer_size = (total_out + have) * 2; - unsigned char *tmp = realloc(decompressed, buffer_size); - if (!tmp) - { - fprintf(stderr, "Memory reallocation failed\n"); - inflateEnd(&strm); - fclose(input_file); - free(decompressed); - return 1; - } - decompressed = tmp; - } - memcpy(decompressed + total_out, out, have); - total_out += have; - } while (strm.avail_out == 0); - } while (ret != Z_STREAM_END); - - inflateEnd(&strm); + // Decompress the file + DecompressionResult decompression = decompress_file(input_file); fclose(input_file); - - if (ret != Z_STREAM_END) + + if (decompression.status != 0) { - fprintf(stderr, "Incomplete decompression\n"); - free(decompressed); + cleanup_decompression_result(&decompression); + cleanup_arguments(); return 1; } - // decompressed data is in 'decompressed', size is 'total_out' - print_verbose("Decompression successful. Decompressed size: %zu bytes\n", total_out); - uint8_t *cursor = decompressed; // the main decompressed data pointer shouldn't move - // Use decompressed data here... - FUECrashFile *read_file = UECrashFile_Read(&cursor); - print_verbose("File header version: %d.%d.%d\n", read_file->file_header->version[0], read_file->file_header->version[1], read_file->file_header->version[2]); - print_verbose("Directory name: %s\n", read_file->file_header->directory_name); - print_verbose("File name: %s\n", read_file->file_header->file_name); - print_verbose("Uncompressed size: %d bytes\n", read_file->file_header->uncompressed_size); - print_verbose("File count: %d\n", read_file->file_header->file_count); - create_crash_directory(read_file->file_header->directory_name); - print_verbose("Files in the crash report:\n"); - int files_combine_length = 0; - // 1 MB buffer for combined file names, a little little (very little) bit larger than needed - // Windows won't event allow this large of a file name, so it should be fine. Also Unreal Engine generally won't output much files. - // generally unreal gives 4 files, but it can be as much as 6 if the old files are still produced. - char files_combine_buffer[1024 * 24]; - memset(files_combine_buffer, 0, sizeof(files_combine_buffer)); // Initialize the buffer to zero - for (int i = 0; i < read_file->file_header->file_count; i++) - { - print_verbose("- File %d: %.*s, size: %d bytes\n", i + 1, read_file->file[i].file_name->length, read_file->file[i].file_name->content, read_file->file[i].file_size); - write_file(read_file->file_header->directory_name, &read_file->file[i]); - if (g_print_mode_file) - { - char file_buffer[2048]; - resolve_app_file_path(read_file->file_header->directory_name, &read_file->file[i], file_buffer, sizeof(file_buffer)); - if (i > 0) - { - strcat(files_combine_buffer, " "); - } - if (strchr(file_buffer, ' ') != NULL || strchr(file_buffer, '\n') != NULL || strchr(file_buffer, '\t') != NULL) - { - // If the file path contains spaces or newlines, wrap it in quotes - snprintf(file_buffer, sizeof(file_buffer), "\"%s\"", file_buffer); - } - strcat(files_combine_buffer, file_buffer); - files_combine_length += strlen(file_buffer) + 1; // +1 for the space or null terminator - } - } - if (g_print_mode_file) - { - printf("%s\n", files_combine_buffer); // write to the standard output for piping - } - else - { - char directory_path[2048]; - resolve_app_directory_path(read_file->file_header->directory_name, directory_path, sizeof(directory_path)); - printf("%s\n", directory_path); // print the directory path to the standard output for piping - } - fflush(stdout); // Ensure the output is flushed immediately - free(decompressed); - decompressed = NULL; - UECrashFile_Destroy(read_file); + // Process the decompressed crash file data + process_crash_files(&decompression, input_filename); + + // Cleanup + cleanup_decompression_result(&decompression); cleanup_arguments(); - print_verbose("All files written successfully.\n"); - return 0; // Successful execution + + return 0; } void resolve_app_directory_path(const FAnsiCharStr *directory_name, char *buffer, size_t buffer_size) @@ -367,24 +100,21 @@ void write_file(const FAnsiCharStr *directory, const FFile *file) FILE *output_file = fopen(file_path, "wb"); if (!output_file) { - fprintf(stderr, "Error opening output file %s\n", file_path); + log_error("Error opening output file %s\n", file_path); return; } size_t written = fwrite(file->file_data, 1, file->file_size, output_file); if (written != file->file_size) { - fprintf(stderr, "Error writing to output file\n"); + log_error("Error writing to output file\n"); } fclose(output_file); } + bool g_cached_app_directory = false; -#ifdef _WIN32 -char g_app_directory[MAX_PATH] = {0}; -#else char g_app_directory[PATH_MAX] = {0}; -#endif -char *get_app_directory() +char *get_app_directory(void) { if (!g_cached_app_directory) { @@ -393,48 +123,43 @@ char *get_app_directory() #else snprintf(g_app_directory, sizeof(g_app_directory), "%s/.duef", getenv("HOME")); #endif + g_cached_app_directory = true; } return g_app_directory; } -// maybe make it so we use the mkdir command with system() instead of using the mkdir function directly void create_crash_directory(FAnsiCharStr *directory_name) { + char dir_path[PATH_MAX]; #ifdef _WIN32 - char dir_path[MAX_PATH]; snprintf(dir_path, sizeof(dir_path), "%s\\%.*s", get_app_directory(), directory_name->length, directory_name->content); - print_verbose("Creating directory: %s\n", dir_path); #else - char dir_path[PATH_MAX]; snprintf(dir_path, sizeof(dir_path), "%s/%.*s", get_app_directory(), directory_name->length, directory_name->content); - print_verbose("Creating directory: %s\n", dir_path); #endif + log_verbose("Creating directory: %s\n", dir_path); + #ifdef _WIN32 if (_mkdir(get_app_directory()) == -1 && errno != EEXIST) { - fprintf(stderr, "Error creating app directory %s: %s\n", get_app_directory(), strerror(errno)); + log_error("Error creating app directory %s: %s\n", get_app_directory(), strerror(errno)); return; } if (_mkdir(dir_path) == -1 && errno != EEXIST) { - fprintf(stderr, "Error creating crash directory %s: %s\n", dir_path, strerror(errno)); + log_error("Error creating crash directory %s: %s\n", dir_path, strerror(errno)); return; } #else + mkdir(get_app_directory(), 0755); // Create parent directory first mkdir(dir_path, 0755); #endif } -// deletes the %LOCALPPDATA%\duef or ~/.duef/ directory -void delete_crash_collection_directory() +void delete_crash_collection_directory(void) { -#ifdef _WIN32 - char command_buffer[1024]; - sprintf(command_buffer, "rmdir /S /Q \"%s\"", get_app_directory()); - system(command_buffer); -#else - char command_buffer[1024]; - snprintf(command_buffer, sizeof(command_buffer), "rm -r \"%s\"", get_app_directory()); - system(command_buffer); // no forcing here, be careful to not remove root -#endif + char *app_dir = get_app_directory(); + if (safe_remove_directory(app_dir) != 0) + { + log_error("Failed to remove directory: %s\n", app_dir); + } } \ No newline at end of file diff --git a/duef.h b/duef.h index 27fc931..029987b 100644 --- a/duef.h +++ b/duef.h @@ -2,15 +2,12 @@ #define DUEF_H #include "duef_types.h" - -char *get_app_directory(); +char *get_app_directory(void); void resolve_app_directory_path(const FAnsiCharStr *directory_name, char *buffer, size_t buffer_size); void resolve_app_file_path(const FAnsiCharStr *directory, const FFile *file, char *buffer, size_t buffer_size); void write_file(const FAnsiCharStr *directory, const FFile *file); void create_crash_directory(FAnsiCharStr *directory_name); -void delete_crash_collection_directory(); - - +void delete_crash_collection_directory(void); #endif \ No newline at end of file diff --git a/duef_args.c b/duef_args.c new file mode 100644 index 0000000..36cafc5 --- /dev/null +++ b/duef_args.c @@ -0,0 +1,201 @@ +#include "duef_args.h" +#include "duef_printing.h" +#include "duef_logger.h" +#include "duef.h" +#include +#include +#include + +// Global variables for command line arguments +extern int g_is_verbose; +int g_print_mode_file = false; +char *file_path = NULL; + +void print_usage(const char *program_name) +{ + printf("duef - Unreal Engine Crash File Decompressor\n\n"); + printf("Usage: %s [OPTIONS] [file]\n\n", program_name); + printf("Options:\n"); + printf(" -h, --help Show this help message and exit\n"); + printf(" -v, --verbose Enable verbose output to stderr\n"); + printf(" -f, --file FILE Specify .uecrash file to process\n"); + printf(" -i Print individual file paths instead of directory path\n"); + printf(" --clean Remove all extracted files from ~/.duef directory\n\n"); + printf("Examples:\n"); + printf(" %s CrashReport.uecrash # Decompress crash file\n", program_name); + printf(" %s -v -f crash.uecrash # Decompress with verbose output\n", program_name); + printf(" %s -i crash.uecrash # Print individual file paths\n", program_name); + printf(" %s --clean # Clean up extracted files\n\n", program_name); + printf("Output:\n"); + printf(" On Unix: Files extracted to ~/.duef//\n"); + printf(" On Windows: Files extracted to %%LocalAppData%%\\duef\\\\\n"); + printf(" Default file: CrashFile.uecrash (if no file specified)\n"); +} + +void process_file_option(int *i, int argc, char **argv) +{ + if (*i + 1 < argc) + { + file_path = strdup(argv[++(*i)]); + print_verbose("File path set to: %s\n", file_path); + if (!file_path) + { + log_error("Memory allocation failed for file path\n"); + exit(EXIT_FAILURE); + } + } + else + { + log_error("Option -f requires an argument\n\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } +} + +void handle_single_short_option(char option, int *i, int argc, char **argv, bool *exit_j_loop) +{ + switch (option) + { + case 'v': + g_is_verbose = true; + print_verbose("Verbose mode enabled.\n"); + break; + case 'f': + process_file_option(i, argc, argv); + *exit_j_loop = true; + break; + case 'i': + g_print_mode_file = true; + print_verbose("Print mode file enabled.\n"); + break; + case 'h': + print_usage(argv[0]); + exit(EXIT_SUCCESS); + break; + default: + log_error("Unknown option: -%c\n\n", option); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } +} + +void handle_short_options(char *arg, int *i, int argc, char **argv) +{ + bool exit_j_loop = false; + + for (int j = 1; arg[j] != '\0'; j++) + { + if (exit_j_loop) { + break; + } + handle_single_short_option(arg[j], i, argc, argv, &exit_j_loop); + } +} + +void handle_verbose_option(void) +{ + g_is_verbose = true; + print_verbose("Verbose mode enabled.\n"); +} + +void handle_file_long_option(int *i, int argc, char **argv) +{ + if (*i + 1 < argc) + { + file_path = strdup(argv[++(*i)]); + print_verbose("File path set to: %s\n", file_path); + if (!file_path) + { + log_error("Memory allocation failed for file path\n"); + exit(EXIT_FAILURE); + } + } + else + { + log_error("Option --file requires an argument\n\n"); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } +} + +void handle_clean_option(void) +{ + delete_crash_collection_directory(); + print_verbose("Crash collection directory cleared.\n"); + exit(EXIT_SUCCESS); +} + +void handle_long_options(char *arg, int *i, int argc, char **argv) +{ + if (strcmp(arg, "--verbose") == 0) + { + handle_verbose_option(); + } + else if (strcmp(arg, "--file") == 0) + { + handle_file_long_option(i, argc, argv); + } + else if (strcmp(arg, "--help") == 0) + { + print_usage(argv[0]); + exit(EXIT_SUCCESS); + } + else if (strcmp(arg, "--clean") == 0) + { + handle_clean_option(); + } + else + { + log_error("Unknown option: %s\n\n", arg); + print_usage(argv[0]); + exit(EXIT_FAILURE); + } +} + +void handle_positional_argument(char *arg) +{ + if (file_path == NULL) + { + file_path = strdup(arg); + if (!file_path) + { + log_error("Memory allocation failed for file path\n"); + exit(EXIT_FAILURE); + } + print_verbose("File path set to: %s\n", file_path); + } + else + { + log_error("Multiple file arguments provided. Only one file can be processed at a time.\n\n"); + print_usage("duef"); + exit(EXIT_FAILURE); + } +} + +void parse_arguments(int argc, char **argv) +{ + for (int i = 1; i < argc; i++) + { + if (argv[i][0] == '-' && argv[i][1] != '-') + { + handle_short_options(argv[i], &i, argc, argv); + } + else if (argv[i][0] == '-' && argv[i][1] == '-') + { + handle_long_options(argv[i], &i, argc, argv); + } + else + { + handle_positional_argument(argv[i]); + } + } +} + +void cleanup_arguments(void) +{ + if (file_path) + { + free(file_path); + file_path = NULL; + } +} \ No newline at end of file diff --git a/duef_args.h b/duef_args.h new file mode 100644 index 0000000..46d7d90 --- /dev/null +++ b/duef_args.h @@ -0,0 +1,22 @@ +#ifndef DUEF_ARGS_H +#define DUEF_ARGS_H + +#include + +// Global variables for command line arguments +extern int g_is_verbose; +extern int g_print_mode_file; +extern char *file_path; + +// Function declarations for argument parsing +void parse_arguments(int argc, char **argv); +void cleanup_arguments(void); +void print_usage(const char *program_name); + +// Helper functions to reduce cognitive complexity +void handle_short_options(char *arg, int *i, int argc, char **argv); +void handle_long_options(char *arg, int *i, int argc, char **argv); +void handle_positional_argument(char *arg); +void process_file_option(int *i, int argc, char **argv); + +#endif // DUEF_ARGS_H \ No newline at end of file diff --git a/duef_file_ops.c b/duef_file_ops.c new file mode 100644 index 0000000..33e64ac --- /dev/null +++ b/duef_file_ops.c @@ -0,0 +1,203 @@ +#include "duef_file_ops.h" +#include "duef_args.h" +#include "duef_logger.h" +#include "duef_printing.h" +#include "duef.h" +#include "zlib.h" +#include +#include +#include + +DecompressionResult decompress_file(FILE *input_file) +{ + DecompressionResult result = {NULL, 0, 1}; // Initialize with error status + + z_stream strm = {0}; + if (inflateInit(&strm) != Z_OK) + { + log_error("Failed to initialize zlib stream\n"); + return result; + } + + unsigned char input_buffer[4096]; + unsigned char out[4096]; + int ret = Z_OK; // Initialize ret variable to fix garbage value warning + size_t total_out = 0; + size_t buffer_size = 4096; + unsigned char *decompressed = malloc(buffer_size); + + if (!decompressed) + { + log_error("Memory allocation failed\n"); + inflateEnd(&strm); + return result; + } + + do + { + strm.avail_in = fread(input_buffer, 1, sizeof(input_buffer), input_file); + if (ferror(input_file)) + { + log_error("Error reading input file\n"); + inflateEnd(&strm); + free(decompressed); + return result; + } + if (strm.avail_in == 0) { + break; + } + strm.next_in = input_buffer; + + do + { + strm.avail_out = sizeof(out); + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) + { + log_error("Decompression error\n"); + inflateEnd(&strm); + free(decompressed); + return result; + } + size_t have = sizeof(out) - strm.avail_out; + if (total_out + have > buffer_size) + { + buffer_size = (total_out + have) * 2; + unsigned char *tmp = realloc(decompressed, buffer_size); + if (!tmp) + { + log_error("Memory reallocation failed\n"); + inflateEnd(&strm); + free(decompressed); + return result; + } + decompressed = tmp; + } + memcpy(decompressed + total_out, out, have); + total_out += have; + } while (strm.avail_out == 0); + } while (ret != Z_STREAM_END); + + inflateEnd(&strm); + + if (ret != Z_STREAM_END) + { + log_error("Incomplete decompression\n"); + free(decompressed); + return result; + } + + // Success + result.data = decompressed; + result.size = total_out; + result.status = 0; + return result; +} + +void cleanup_decompression_result(DecompressionResult *result) +{ + if (result && result->data) + { + free(result->data); + result->data = NULL; + result->size = 0; + } +} + +void build_file_output_string(const FUECrashFile *crash_file, char *files_combine_buffer, size_t buffer_size) +{ + memset(files_combine_buffer, 0, buffer_size); + + for (int i = 0; i < crash_file->file_header->file_count; i++) + { + if (g_print_mode_file) + { + char file_buffer[2048]; + resolve_app_file_path(crash_file->file_header->directory_name, &crash_file->file[i], file_buffer, sizeof(file_buffer)); + + if (i > 0) + { + size_t current_len = strlen(files_combine_buffer); + if (current_len < buffer_size - 2) { + strncat(files_combine_buffer, " ", buffer_size - current_len - 1); + } + } + + if (strchr(file_buffer, ' ') != NULL || strchr(file_buffer, '\n') != NULL || strchr(file_buffer, '\t') != NULL) + { + char temp_buffer[2052]; + size_t path_len = strlen(file_buffer); + if (path_len < sizeof(temp_buffer) - 3) + { + snprintf(temp_buffer, sizeof(temp_buffer), "\"%s\"", file_buffer); + strncpy(file_buffer, temp_buffer, sizeof(file_buffer) - 1); + file_buffer[sizeof(file_buffer) - 1] = '\0'; + } + } + + size_t current_len = strlen(files_combine_buffer); + if (current_len < buffer_size - 1) { + strncat(files_combine_buffer, file_buffer, buffer_size - current_len - 1); + } + } + } +} + +void process_crash_files(const DecompressionResult *decompression, const char *input_filename) +{ + log_verbose("Decompression successful. Decompressed size: %zu bytes\n", decompression->size); + + uint8_t *cursor = decompression->data; + FUECrashFile *read_file = UECrashFile_Read(&cursor); + + if (!read_file) { + log_error("Failed to parse crash file structure\n"); + return; + } + + log_verbose("File header version: %d.%d.%d\n", + read_file->file_header->version[0], + read_file->file_header->version[1], + read_file->file_header->version[2]); + log_verbose("Directory name: %s\n", read_file->file_header->directory_name); + log_verbose("File name: %s\n", read_file->file_header->file_name); + log_verbose("Uncompressed size: %d bytes\n", read_file->file_header->uncompressed_size); + log_verbose("File count: %d\n", read_file->file_header->file_count); + + create_crash_directory(read_file->file_header->directory_name); + log_verbose("Files in the crash report:\n"); + + for (int i = 0; i < read_file->file_header->file_count; i++) + { + log_verbose("- File %d: %.*s, size: %d bytes\n", + i + 1, + read_file->file[i].file_name->length, + read_file->file[i].file_name->content, + read_file->file[i].file_size); + write_file(read_file->file_header->directory_name, &read_file->file[i]); + } + + output_results(read_file); + + UECrashFile_Destroy(read_file); + log_verbose("All files written successfully.\n"); +} + +void output_results(const FUECrashFile *crash_file) +{ + if (g_print_mode_file) + { + char files_combine_buffer[1024 * 24]; + build_file_output_string(crash_file, files_combine_buffer, sizeof(files_combine_buffer)); + log_info("%s\n", files_combine_buffer); + } + else + { + char directory_path[2048]; + resolve_app_directory_path(crash_file->file_header->directory_name, directory_path, sizeof(directory_path)); + log_info("%s\n", directory_path); + } + + fflush(stdout); +} \ No newline at end of file diff --git a/duef_file_ops.h b/duef_file_ops.h new file mode 100644 index 0000000..f5eb263 --- /dev/null +++ b/duef_file_ops.h @@ -0,0 +1,22 @@ +#ifndef DUEF_FILE_OPS_H +#define DUEF_FILE_OPS_H + +#include "duef_types.h" +#include +#include + +// File decompression functions +typedef struct { + unsigned char *data; + size_t size; + int status; +} DecompressionResult; + +DecompressionResult decompress_file(FILE *input_file); +void cleanup_decompression_result(DecompressionResult *result); + +// File processing functions +void process_crash_files(const DecompressionResult *decompression, const char *input_filename); +void output_results(const FUECrashFile *crash_file); + +#endif // DUEF_FILE_OPS_H \ No newline at end of file diff --git a/duef_logger.c b/duef_logger.c new file mode 100644 index 0000000..1a37725 --- /dev/null +++ b/duef_logger.c @@ -0,0 +1,107 @@ +#include "duef_logger.h" +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#define MAX_PATH 260 +#else +#include +#include +#include +#endif + +// Safe logging functions to replace insecure fprintf calls +void log_error(const char *format, ...) +{ + va_list args; + va_start(args, format); + + // Use vfprintf which is safer than fprintf with user input + vfprintf(stderr, format, args); + + va_end(args); +} + +void log_info(const char *format, ...) +{ + va_list args; + va_start(args, format); + + vfprintf(stdout, format, args); + + va_end(args); +} + +void log_verbose(const char *format, ...) +{ + extern int g_is_verbose; + if (!g_is_verbose) { + return; + } + + va_list args; + va_start(args, format); + + vfprintf(stderr, format, args); + + va_end(args); +} + +#ifdef _WIN32 +int safe_remove_directory_windows(const char *directory_path) +{ + char path[MAX_PATH + 1]; + + // Copy path and ensure double null termination + strncpy(path, directory_path, MAX_PATH - 1); + path[MAX_PATH - 1] = '\0'; + + // Use simple rmdir for now - for full recursive deletion would need more complex implementation + return _rmdir(path); +} +#else +int safe_remove_directory_unix(const char *directory_path) +{ + DIR *dir; + struct dirent *entry; + char path[1024]; + + dir = opendir(directory_path); + if (dir == NULL) { + return -1; + } + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + snprintf(path, sizeof(path), "%s/%s", directory_path, entry->d_name); + + struct stat statbuf; + if (stat(path, &statbuf) == 0) { + if (S_ISDIR(statbuf.st_mode)) { + safe_remove_directory_unix(path); + } else { + unlink(path); + } + } + } + + closedir(dir); + return rmdir(directory_path); +} +#endif + +int safe_remove_directory(const char *directory_path) +{ +#ifdef _WIN32 + return safe_remove_directory_windows(directory_path); +#else + return safe_remove_directory_unix(directory_path); +#endif +} \ No newline at end of file diff --git a/duef_logger.h b/duef_logger.h new file mode 100644 index 0000000..3ed7801 --- /dev/null +++ b/duef_logger.h @@ -0,0 +1,14 @@ +#ifndef DUEF_LOGGER_H +#define DUEF_LOGGER_H + +#include + +// Safe logging functions to replace insecure fprintf calls +void log_error(const char *format, ...); +void log_info(const char *format, ...); +void log_verbose(const char *format, ...); + +// Safe file operations +int safe_remove_directory(const char *directory_path); + +#endif // DUEF_LOGGER_H \ No newline at end of file diff --git a/duef_printing.c b/duef_printing.c new file mode 100644 index 0000000..2f01e29 --- /dev/null +++ b/duef_printing.c @@ -0,0 +1,24 @@ +#include "duef_printing.h" + +int g_is_verbose = 0; + +void print_verbose(const char *format, ...) +{ + if (!g_is_verbose) + return; + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} + +void print_debug(const char *format, ...) +{ +#if defined(NDEBUG) || defined(DEBUG) + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); +#endif +} \ No newline at end of file diff --git a/duef_printing.h b/duef_printing.h index e126d5c..9390c42 100644 --- a/duef_printing.h +++ b/duef_printing.h @@ -4,23 +4,9 @@ #include extern int g_is_verbose; -void print_verbose(const char *format, ...) { - if (!g_is_verbose) - return; - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); -} -void print_debug(const char *format, ...) { -#if defined(NDEBUG) || defined(DEBUG) - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - fprintf(stderr, "\n"); -#endif -} +// Function declarations +void print_verbose(const char *format, ...); +void print_debug(const char *format, ...); #endif \ No newline at end of file diff --git a/duef_types.c b/duef_types.c new file mode 100644 index 0000000..3908406 --- /dev/null +++ b/duef_types.c @@ -0,0 +1,184 @@ +#include "duef_types.h" + +int32_t read_int32(uint8_t **data) +{ + int32_t value = *(int32_t *)(*data); + (*data) += sizeof(int32_t); + return value; +} + +char read_char(uint8_t **data) +{ + char value = (char)(**data); + (*data)++; + return value; +} + +FAnsiCharStr *AnsiCharStr_Read(uint8_t **data) +{ + FAnsiCharStr *string = malloc(sizeof(FAnsiCharStr)); + if (!string) + { + return NULL; + } + + string->length = read_int32(data); + string->content = malloc(string->length + 1); + if (!string->content) + { + free(string); + return NULL; + } + + for (int i = 0; i < string->length; i++) + { + string->content[i] = read_char(data); + } + string->content[string->length] = '\0'; + + return string; +} + +void AnsiCharStr_Destroy(FAnsiCharStr *string) +{ + if (string) + { + if (string->content) + { + free(string->content); + } + free(string); + } +} + +FFileHeader *FileHeader_Read(uint8_t **data) +{ + FFileHeader *header = malloc(sizeof(FFileHeader)); + if (!header) + { + return NULL; + } + + for (int i = 0; i < 3; i++) + { + header->version[i] = read_char(data); + } + + header->directory_name = AnsiCharStr_Read(data); + header->file_name = AnsiCharStr_Read(data); + header->uncompressed_size = read_int32(data); + header->file_count = read_int32(data); + + return header; +} + +void FileHeader_Destroy(FFileHeader *header) +{ + if (header) + { + AnsiCharStr_Destroy(header->directory_name); + AnsiCharStr_Destroy(header->file_name); + free(header); + } +} + +FFile *File_Read(uint8_t **data) +{ + FFile *file = malloc(sizeof(FFile)); + if (!file) + { + return NULL; + } + + file->current_file_index = read_int32(data); + file->file_name = AnsiCharStr_Read(data); + file->file_size = read_int32(data); + + file->file_data = malloc(file->file_size); + if (!file->file_data) + { + AnsiCharStr_Destroy(file->file_name); + free(file); + return NULL; + } + + for (int i = 0; i < file->file_size; i++) + { + file->file_data[i] = read_char(data); + } + + return file; +} + +void File_Destroy(FFile *file) +{ + if (file) + { + File_DestroyContents(file); + free(file); + } +} + +void File_DestroyContents(FFile *file) +{ + if (file) + { + AnsiCharStr_Destroy(file->file_name); + if (file->file_data) + { + free(file->file_data); + file->file_data = NULL; + } + } +} + +FUECrashFile *UECrashFile_Read(uint8_t **data) +{ + FUECrashFile *crash_file = malloc(sizeof(FUECrashFile)); + if (!crash_file) + { + return NULL; + } + + crash_file->file_header = FileHeader_Read(data); + if (!crash_file->file_header) + { + free(crash_file); + return NULL; + } + + crash_file->file = malloc(sizeof(FFile) * crash_file->file_header->file_count); + if (!crash_file->file) + { + FileHeader_Destroy(crash_file->file_header); + free(crash_file); + return NULL; + } + + for (int i = 0; i < crash_file->file_header->file_count; i++) + { + FFile *file = File_Read(data); + if (file) + { + crash_file->file[i] = *file; + free(file); + } + } + + return crash_file; +} + +void UECrashFile_Destroy(FUECrashFile *ue_crash_file) +{ + if (ue_crash_file) + { + int file_count = ue_crash_file->file_header->file_count; + FileHeader_Destroy(ue_crash_file->file_header); + for (int i = 0; i < file_count; i++) + { + File_DestroyContents(&ue_crash_file->file[i]); + } + free(ue_crash_file->file); + free(ue_crash_file); + } +} \ No newline at end of file diff --git a/duef_types.h b/duef_types.h index 9ba7583..23ff705 100644 --- a/duef_types.h +++ b/duef_types.h @@ -3,6 +3,7 @@ #include #include #include + typedef struct FAnsiCharStr { int32_t length; @@ -32,144 +33,17 @@ typedef struct FUECrashFile FFile *file; } FUECrashFile; -int32_t read_int32(uint8_t **data) -{ - int32_t value = *(int32_t *)(*data); - (*data) += sizeof(int32_t); - return value; -} - -char read_char(uint8_t **data) -{ - char value = (char)(**data); - (*data)++; - return value; -} - -FAnsiCharStr *AnsiCharStr_Read(uint8_t **data) -{ - FAnsiCharStr *str = (FAnsiCharStr *)malloc(sizeof(FAnsiCharStr)); - if (!str) - { - fprintf(stderr, "Memory allocation failed\n"); - exit(EXIT_FAILURE); - } - str->length = read_int32(data); - str->content = (char *)malloc(str->length + 1); - if (!str->content) - { - fprintf(stderr, "Memory allocation for content failed\n"); - free(str); - exit(EXIT_FAILURE); - } - for (int i = 0; i < str->length; i++) - { - str->content[i] = read_char(data); - } - return str; -} - -void AnsiCharStr_Destroy(FAnsiCharStr *str) -{ - if (str) - { - free(str->content); - free(str); - } -} - -FFileHeader *FileHeader_Read(uint8_t **data) -{ - FFileHeader *header = (FFileHeader *)malloc(sizeof(FFileHeader)); - if (!header) - { - fprintf(stderr, "Memory allocation for FFileHeader failed\n"); - exit(EXIT_FAILURE); - } - header->version[0] = read_char(data); - header->version[1] = read_char(data); - header->version[2] = read_char(data); - - header->directory_name = AnsiCharStr_Read(data); - header->file_name = AnsiCharStr_Read(data); - - header->uncompressed_size = *(int32_t *)(*data); - (*data) += sizeof(int32_t); - - header->file_count = *(int32_t *)(*data); - (*data) += sizeof(int32_t); - - return header; -} - -void FileHeader_Destroy(FFileHeader *header) -{ - if (header) - { - AnsiCharStr_Destroy(header->directory_name); - AnsiCharStr_Destroy(header->file_name); - free(header); - } -} - -FFile *File_Read(uint8_t **data) -{ - FFile *file = (FFile *)malloc(sizeof(FFile)); - if (!file) - { - fprintf(stderr, "Memory allocation for FFile failed\n"); - exit(EXIT_FAILURE); - } - file->current_file_index = read_int32(data); - file->file_name = AnsiCharStr_Read(data); - file->file_size = read_int32(data); - file->file_data = (uint8_t *)malloc(file->file_size); - for (int i = 0; i < file->file_size; i++) - { - file->file_data[i] = read_char(data); - } - - return file; -} - -void File_Destroy(FFile *file) -{ - if (file) - { - AnsiCharStr_Destroy(file->file_name); - free(file->file_data); - free(file); - } -} - -struct FUECrashFile *UECrashFile_Read(uint8_t **data) -{ - FUECrashFile *ue_crash_file = (FUECrashFile *)malloc(sizeof(FUECrashFile)); - if (!ue_crash_file) - { - fprintf(stderr, "Memory allocation for FUECrashFile failed\n"); - exit(EXIT_FAILURE); - } - ue_crash_file->file_header = FileHeader_Read(data); - ue_crash_file->file = calloc(ue_crash_file->file_header->file_count, sizeof(FFile)); - for (int i = 0; i < ue_crash_file->file_header->file_count; i++) - { - ue_crash_file->file[i] = *File_Read(data); - } - return ue_crash_file; -} - -void UECrashFile_Destroy(FUECrashFile *ue_crash_file) -{ - if (ue_crash_file) - { - FileHeader_Destroy(ue_crash_file->file_header); - for (int i = 0; i < ue_crash_file->file_header->file_count; i++) - { - File_Destroy(&ue_crash_file->file[i]); - } - free(ue_crash_file); - } -} +// Function declarations +int32_t read_int32(uint8_t **data); +char read_char(uint8_t **data); +FAnsiCharStr *AnsiCharStr_Read(uint8_t **data); +void AnsiCharStr_Destroy(FAnsiCharStr *string); +FFileHeader *FileHeader_Read(uint8_t **data); +void FileHeader_Destroy(FFileHeader *header); +FFile *File_Read(uint8_t **data); +void File_Destroy(FFile *file); +void File_DestroyContents(FFile *file); +FUECrashFile *UECrashFile_Read(uint8_t **data); +void UECrashFile_Destroy(FUECrashFile *ue_crash_file); #endif \ No newline at end of file diff --git a/duef_types_old.h b/duef_types_old.h new file mode 100644 index 0000000..4b43f71 --- /dev/null +++ b/duef_types_old.h @@ -0,0 +1,189 @@ +#ifndef DUEF_TYPES_H +#define DUEF_TYPES_H +#include +#include +#include +typedef struct FAnsiCharStr +{ + int32_t length; + char *content; +} FAnsiCharStr; + +typedef struct FFileHeader +{ + uint8_t version[3]; + FAnsiCharStr *directory_name; + FAnsiCharStr *file_name; + int32_t uncompressed_size; + int32_t file_count; +} FFileHeader; + +typedef struct FFile +{ + int current_file_index; + FAnsiCharStr *file_name; + int32_t file_size; + uint8_t *file_data; // Pointer to the file data in memory +} FFile; + +typedef struct FUECrashFile +{ + FFileHeader *file_header; + FFile *file; +} FUECrashFile; + +int32_t read_int32(uint8_t **data) +{ + int32_t value = *(int32_t *)(*data); + (*data) += sizeof(int32_t); + return value; +} + +char read_char(uint8_t **data) +{ + char value = (char)(**data); + (*data)++; + return value; +} + +FAnsiCharStr *AnsiCharStr_Read(uint8_t **data) +{ + FAnsiCharStr *str = (FAnsiCharStr *)malloc(sizeof(FAnsiCharStr)); + if (!str) + { + fprintf(stderr, "Memory allocation failed\n"); + exit(EXIT_FAILURE); + } + str->length = read_int32(data); + str->content = (char *)malloc(str->length + 1); + if (!str->content) + { + fprintf(stderr, "Memory allocation for content failed\n"); + free(str); + exit(EXIT_FAILURE); + } + for (int i = 0; i < str->length; i++) + { + str->content[i] = read_char(data); + } + return str; +} + +void AnsiCharStr_Destroy(FAnsiCharStr *str) +{ + if (str) + { + free(str->content); + free(str); + } +} + +FFileHeader *FileHeader_Read(uint8_t **data) +{ + FFileHeader *header = (FFileHeader *)malloc(sizeof(FFileHeader)); + if (!header) + { + fprintf(stderr, "Memory allocation for FFileHeader failed\n"); + exit(EXIT_FAILURE); + } + header->version[0] = read_char(data); + header->version[1] = read_char(data); + header->version[2] = read_char(data); + + header->directory_name = AnsiCharStr_Read(data); + header->file_name = AnsiCharStr_Read(data); + + header->uncompressed_size = *(int32_t *)(*data); + (*data) += sizeof(int32_t); + + header->file_count = *(int32_t *)(*data); + (*data) += sizeof(int32_t); + + return header; +} + +void FileHeader_Destroy(FFileHeader *header) +{ + if (header) + { + AnsiCharStr_Destroy(header->directory_name); + AnsiCharStr_Destroy(header->file_name); + free(header); + } +} + +FFile *File_Read(uint8_t **data) +{ + FFile *file = (FFile *)malloc(sizeof(FFile)); + if (!file) + { + fprintf(stderr, "Memory allocation for FFile failed\n"); + exit(EXIT_FAILURE); + } + file->current_file_index = read_int32(data); + file->file_name = AnsiCharStr_Read(data); + file->file_size = read_int32(data); + file->file_data = (uint8_t *)malloc(file->file_size); + for (int i = 0; i < file->file_size; i++) + { + file->file_data[i] = read_char(data); + } + + return file; +} + +void File_Destroy(FFile *file) +{ + if (file) + { + AnsiCharStr_Destroy(file->file_name); + free(file->file_data); + free(file); + } +} + +void File_DestroyContents(FFile *file) +{ + if (file) + { + AnsiCharStr_Destroy(file->file_name); + free(file->file_data); + // Don't free the file struct itself as it's part of an array + } +} + +struct FUECrashFile *UECrashFile_Read(uint8_t **data) +{ + FUECrashFile *ue_crash_file = (FUECrashFile *)malloc(sizeof(FUECrashFile)); + if (!ue_crash_file) + { + fprintf(stderr, "Memory allocation for FUECrashFile failed\n"); + exit(EXIT_FAILURE); + } + ue_crash_file->file_header = FileHeader_Read(data); + ue_crash_file->file = calloc(ue_crash_file->file_header->file_count, sizeof(FFile)); + for (int i = 0; i < ue_crash_file->file_header->file_count; i++) + { + FFile *temp_file = File_Read(data); + ue_crash_file->file[i] = *temp_file; + free(temp_file); // Free the temporary FFile struct, but keep its contents + } + return ue_crash_file; +} + +void UECrashFile_Destroy(FUECrashFile *ue_crash_file) +{ + if (ue_crash_file) + { + int file_count = ue_crash_file->file_header->file_count; + FileHeader_Destroy(ue_crash_file->file_header); + for (int i = 0; i < file_count; i++) + { + File_DestroyContents(&ue_crash_file->file[i]); + } + free(ue_crash_file->file); + free(ue_crash_file); + } +} + +#endif \ No newline at end of file