]> FriiDump Source - friidump.git/blobdiff - src/friidump.c
FriiDump 0.5.3.15: close XGD1 geometry and lead-in capture
[friidump.git] / src / friidump.c
index f8b529240c07eeff281a356b5c858081171f6f68..292f658039302c82d08f63de712963ddb6ac91b8 100644 (file)
 #include "dumper.h"
 #include "unscrambler.h"
 #include "xbox_ref/xbox_ref_log.h"
 #include "dumper.h"
 #include "unscrambler.h"
 #include "xbox_ref/xbox_ref_log.h"
+#include "xbox_ref_bridge.h"
+#include "redump_dat.h"
+
+#ifdef WIN32
+#include <sys/stat.h>
+#include <io.h>
+#include <windows.h>
+#else
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
 
 #define printf xbox_ref_printf
 #define fprintf xbox_ref_log_fprintf
 
 #define printf xbox_ref_printf
 #define fprintf xbox_ref_log_fprintf
@@ -41,6 +52,7 @@ typedef struct {
        bool active;
        bool dump_attempted;
        bool seed_ok;
        bool active;
        bool dump_attempted;
        bool seed_ok;
+       bool seed_applicable;
        bool have_seed_duration;
        double seed_duration;
        bool dump_ok;
        bool have_seed_duration;
        double seed_duration;
        bool dump_ok;
@@ -62,6 +74,11 @@ typedef struct {
 } friidump_validation_summary;
 
 static friidump_validation_summary g_validation_summary;
 } friidump_validation_summary;
 
 static friidump_validation_summary g_validation_summary;
+static redump_verify_result g_redump_result;
+static bool g_redump_attempted = false;
+static bool g_operation_duration_override_valid = false;
+static double g_operation_duration_override = 0.0;
+static char g_executable_dir[1024];
 
 static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
        if (!dst || dst_size == 0)
 
 static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
        if (!dst || dst_size == 0)
@@ -74,6 +91,9 @@ static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
 static void friidump_summary_reset(void) {
        memset(&g_validation_summary, 0, sizeof(g_validation_summary));
        g_validation_summary.fail_sector = 0xFFFFFFFFU;
 static void friidump_summary_reset(void) {
        memset(&g_validation_summary, 0, sizeof(g_validation_summary));
        g_validation_summary.fail_sector = 0xFFFFFFFFU;
+       g_validation_summary.seed_applicable = true;
+       g_operation_duration_override_valid = false;
+       g_operation_duration_override = 0.0;
 }
 
 static void friidump_format_hms(double seconds, char *buf, size_t buf_size) {
 }
 
 static void friidump_format_hms(double seconds, char *buf, size_t buf_size) {
@@ -109,8 +129,12 @@ static void friidump_summary_begin(disc *d) {
 
 static void friidump_print_validation_summary(double duration, bool have_duration) {
        double mib_total, mib_per_hour;
 
 static void friidump_print_validation_summary(double duration, bool have_duration) {
        double mib_total, mib_per_hour;
+       const char *seed_status;
        if (!g_validation_summary.active)
                return;
        if (!g_validation_summary.active)
                return;
+       seed_status = g_validation_summary.seed_applicable
+               ? (g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED")
+               : "N/A (Xbox reference auth path)";
        mib_total = (double) g_validation_summary.sectors * 2048.0 / 1024.0 / 1024.0;
        mib_per_hour = (have_duration && duration > 0.0) ? (mib_total / duration * 3600.0) : 0.0;
        fprintf(stderr,
        mib_total = (double) g_validation_summary.sectors * 2048.0 / 1024.0 / 1024.0;
        mib_per_hour = (have_duration && duration > 0.0) ? (mib_total / duration * 3600.0) : 0.0;
        fprintf(stderr,
@@ -121,7 +145,7 @@ static void friidump_print_validation_summary(double duration, bool have_duratio
                "  Cache base........: 0x%08x\n"
                "  Memory windows....: %u\n"
                "  Disc type.........: %s\n"
                "  Cache base........: 0x%08x\n"
                "  Memory windows....: %u\n"
                "  Disc type.........: %s\n"
-               "  Game ID...........: %s\n"
+               "  Game/Media ID.....: %s\n"
                "  Title.............: %s\n"
                "  Output............: %s\n"
                "  Seed read.........: %s\n"
                "  Title.............: %s\n"
                "  Output............: %s\n"
                "  Seed read.........: %s\n"
@@ -135,7 +159,7 @@ static void friidump_print_validation_summary(double duration, bool have_duratio
                g_validation_summary.game_id[0] ? g_validation_summary.game_id : "(unknown)",
                g_validation_summary.title[0] ? g_validation_summary.title : "(unknown)",
                g_validation_summary.output[0] ? g_validation_summary.output : "(none)",
                g_validation_summary.game_id[0] ? g_validation_summary.game_id : "(unknown)",
                g_validation_summary.title[0] ? g_validation_summary.title : "(unknown)",
                g_validation_summary.output[0] ? g_validation_summary.output : "(none)",
-               g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED",
+               seed_status,
                g_validation_summary.dump_attempted ? (g_validation_summary.dump_ok ? "OK" : "FAILED") : "NOT ATTEMPTED",
                g_validation_summary.stop_attempted ? (g_validation_summary.stop_ok ? "OK" : "FAILED") : "NOT ATTEMPTED");
        if (g_validation_summary.have_seed_duration) {
                g_validation_summary.dump_attempted ? (g_validation_summary.dump_ok ? "OK" : "FAILED") : "NOT ATTEMPTED",
                g_validation_summary.stop_attempted ? (g_validation_summary.stop_ok ? "OK" : "FAILED") : "NOT ATTEMPTED");
        if (g_validation_summary.have_seed_duration) {
@@ -151,8 +175,15 @@ static void friidump_print_validation_summary(double duration, bool have_duratio
                fprintf(stderr, "  Duration..........: %.2f seconds\n", duration);
        if (have_duration && mib_per_hour > 0.0)
                fprintf(stderr, "  Observed average..: %.2f MiB/h over %.2f MiB ISO payload\n", mib_per_hour, mib_total);
                fprintf(stderr, "  Duration..........: %.2f seconds\n", duration);
        if (have_duration && mib_per_hour > 0.0)
                fprintf(stderr, "  Observed average..: %.2f MiB/h over %.2f MiB ISO payload\n", mib_per_hour, mib_total);
-       if (g_validation_summary.dump_ok)
-               fprintf(stderr, "  Hash compare......: compare printed hashes against known-good target for this disc\n");
+       if (g_validation_summary.dump_ok) {
+               if (g_redump_attempted) {
+                       fprintf(stderr, "  Redump verify.....: %s\n", redump_verify_overall_string(&g_redump_result));
+                       if (g_redump_result.status == REDUMP_VERIFY_MATCH)
+                               fprintf(stderr, "  Redump title......: %s\n", g_redump_result.game_name);
+               } else {
+                       fprintf(stderr, "  Redump verify.....: NOT RUN\n");
+               }
+       }
 }
 
 
 }
 
 
@@ -166,7 +197,7 @@ static void friidump_print_validation_summary(double duration, bool have_duratio
 #define PACKAGE_NAME "FriiDump"
 
 /* Define to the version of this package. */
 #define PACKAGE_NAME "FriiDump"
 
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "0.5.3.5"
+#define PACKAGE_VERSION "0.5.3.15"
 
 
 #ifdef WIN32
 
 
 #ifdef WIN32
@@ -213,6 +244,13 @@ struct {
        char *hlds_e7_scan_log;
        char *hlds_e7_scan_dump_prefix;
        char *hlds_profile_report;
        char *hlds_e7_scan_log;
        char *hlds_e7_scan_dump_prefix;
        char *hlds_profile_report;
+       char *redump_dat_dir;
+       char *redump_report;
+       bool no_redump_verify;
+       bool xgd1_layout_probe;
+       char *xgd1_layout_probe_report;
+       bool xgd1_raw_id_probe;
+       char *xgd1_raw_id_probe_report;
 } options;
 
 
 } options;
 
 
@@ -235,6 +273,8 @@ static char friidump_drive_letter_from_device(const char *device) {
 
 
 static const char *friidump_requested_output_target(void) {
 
 
 static const char *friidump_requested_output_target(void) {
+       if (options.xgd1_raw_id_probe_report && options.xgd1_raw_id_probe_report[0]) return options.xgd1_raw_id_probe_report;
+       if (options.xgd1_layout_probe_report && options.xgd1_layout_probe_report[0]) return options.xgd1_layout_probe_report;
        if (options.iso_out && options.iso_out[0]) return options.iso_out;
        if (options.xiso_out && options.xiso_out[0]) return options.xiso_out;
        if (options.raw_out && options.raw_out[0]) return options.raw_out;
        if (options.iso_out && options.iso_out[0]) return options.iso_out;
        if (options.xiso_out && options.xiso_out[0]) return options.xiso_out;
        if (options.raw_out && options.raw_out[0]) return options.raw_out;
@@ -453,6 +493,391 @@ void welcome (void) {
 }
 
 
 }
 
 
+
+static void friidump_init_executable_dir(const char *argv0) {
+    char path[1024];
+    size_t length = 0;
+    char *slash;
+    char *backslash;
+    char *separator;
+
+    g_executable_dir[0] = '\0';
+    path[0] = '\0';
+
+#ifdef WIN32
+    {
+        DWORD result = GetModuleFileNameA(NULL, path, (DWORD)sizeof(path));
+        if (result > 0 && result < sizeof(path)) {
+            path[result] = '\0';
+            length = (size_t)result;
+        }
+    }
+#else
+#if defined(__linux__)
+    {
+        ssize_t result = readlink("/proc/self/exe", path, sizeof(path) - 1);
+        if (result > 0 && (size_t)result < sizeof(path)) {
+            path[result] = '\0';
+            length = (size_t)result;
+        }
+    }
+#endif
+    if (length == 0 && argv0 && argv0[0] &&
+        (strchr(argv0, '/') || strchr(argv0, '\\'))) {
+        char *resolved = realpath(argv0, path);
+        if (resolved)
+            length = strlen(path);
+    }
+#endif
+
+    if (length == 0)
+        return;
+
+    slash = strrchr(path, '/');
+    backslash = strrchr(path, '\\');
+    separator = slash;
+    if (backslash && (!separator || backslash > separator))
+        separator = backslash;
+
+    if (!separator)
+        return;
+
+    *separator = '\0';
+    if (path[0])
+        snprintf(g_executable_dir, sizeof(g_executable_dir), "%s", path);
+}
+
+static const char *friidump_redump_dat_basename(disc_type type_id) {
+    switch (type_id) {
+        case DISC_TYPE_GAMECUBE:
+            return "Nintendo - GameCube.dat";
+        case DISC_TYPE_WII:
+        case DISC_TYPE_WII_DL:
+            return "Nintendo - Wii.dat";
+        case DISC_TYPE_XBOX:
+            return "Microsoft - Xbox.dat";
+        default:
+            return NULL;
+    }
+}
+
+static uint64_t friidump_file_size(const char *path) {
+    if (!path || !path[0])
+        return 0;
+#ifdef WIN32
+    {
+        struct _stat64 st;
+        if (_stat64(path, &st) == 0)
+            return (uint64_t)st.st_size;
+    }
+#else
+    {
+        struct stat st;
+        if (stat(path, &st) == 0)
+            return (uint64_t)st.st_size;
+    }
+#endif
+    return 0;
+}
+
+static bool friidump_sync_report_file(FILE *f) {
+    if (!f || fflush(f) != 0)
+        return false;
+#ifdef WIN32
+    return _commit(_fileno(f)) == 0;
+#else
+    return fsync(fileno(f)) == 0;
+#endif
+}
+
+static bool friidump_atomic_replace(const char *temporary_path, const char *final_path) {
+    if (!temporary_path || !final_path)
+        return false;
+#ifdef WIN32
+    return MoveFileExA(temporary_path, final_path,
+                       MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0;
+#else
+    return rename(temporary_path, final_path) == 0;
+#endif
+}
+
+static void friidump_json_write_string_value(FILE *f, const char *prefix,
+                                             const char *value, const char *suffix) {
+    fputs(prefix, f);
+    friidump_json_string(f, value ? value : "");
+    fputs(suffix, f);
+}
+
+static void friidump_write_redump_report(const redump_verify_result *result,
+                                         const char *output_path,
+                                         uint64_t output_size,
+                                         const char *crc32,
+                                         const char *md5,
+                                         const char *sha1,
+                                         const char *sha256,
+                                         const char *hash_source,
+                                         const char *representation_note) {
+    FILE *f;
+    char *temporary_path;
+    char line[256];
+    size_t temporary_path_size;
+    bool write_ok;
+
+    if (!options.redump_report || !options.redump_report[0] || !result)
+        return;
+
+    temporary_path_size = strlen(options.redump_report) + 5;
+    temporary_path = (char *)malloc(temporary_path_size);
+    if (!temporary_path) {
+        fprintf(stderr, "Redump report.......: memory allocation failed\n");
+        return;
+    }
+    snprintf(temporary_path, temporary_path_size, "%s.tmp", options.redump_report);
+    remove(temporary_path);
+
+    f = fopen(temporary_path, "wb");
+    if (!f) {
+        fprintf(stderr, "Redump report.......: could not write temporary file %s\n", temporary_path);
+        free(temporary_path);
+        return;
+    }
+
+    /* Use fputs/fputc rather than the logged fprintf wrapper. Report contents
+     * belong in the JSON artifact, not as a malformed partial mirror in the
+     * human-readable run log. */
+    fputs("{\n", f);
+    fputs("  \"schema\": 2,\n", f);
+    friidump_json_write_string_value(f, "  \"producer\": ", "friidump-" PACKAGE_VERSION, ",\n");
+    friidump_json_write_string_value(f, "  \"status\": ", redump_verify_status_string(result->status), ",\n");
+    friidump_json_write_string_value(f, "  \"overall\": ", redump_verify_overall_string(result), ",\n");
+    friidump_json_write_string_value(f, "  \"confidence\": ", redump_verify_confidence_string(result), ",\n");
+    friidump_json_write_string_value(f, "  \"output\": ", output_path ? output_path : "", ",\n");
+    snprintf(line, sizeof(line), "  \"output_size\": %llu,\n", (unsigned long long)output_size);
+    fputs(line, f);
+    friidump_json_write_string_value(f, "  \"crc32\": ", crc32 ? crc32 : "", ",\n");
+    friidump_json_write_string_value(f, "  \"md5\": ", md5 ? md5 : "", ",\n");
+    friidump_json_write_string_value(f, "  \"sha1\": ", sha1 ? sha1 : "", ",\n");
+    friidump_json_write_string_value(f, "  \"sha256\": ", sha256 ? sha256 : "", ",\n");
+    friidump_json_write_string_value(f, "  \"hash_source\": ", hash_source ? hash_source : "", ",\n");
+    friidump_json_write_string_value(f, "  \"representation_note\": ", representation_note ? representation_note : "", ",\n");
+    friidump_json_write_string_value(f, "  \"dat_path\": ", result->dat_path, ",\n");
+    snprintf(line, sizeof(line), "  \"entries_scanned\": %lu,\n", result->entries_scanned);
+    fputs(line, f);
+    snprintf(line, sizeof(line), "  \"exact_matches\": %lu,\n", result->exact_matches);
+    fputs(line, f);
+    friidump_json_write_string_value(f, "  \"match_kind\": ", redump_verify_match_kind_string(result), ",\n");
+    friidump_json_write_string_value(f, "  \"game_name\": ", result->game_name, ",\n");
+    friidump_json_write_string_value(f, "  \"rom_name\": ", result->rom_name, ",\n");
+
+    fputs("  \"field_match_counts\": {\n", f);
+    snprintf(line, sizeof(line), "    \"size\": %lu,\n", result->size_matches); fputs(line, f);
+    snprintf(line, sizeof(line), "    \"crc32\": %lu,\n", result->crc32_matches); fputs(line, f);
+    snprintf(line, sizeof(line), "    \"md5\": %lu,\n", result->md5_matches); fputs(line, f);
+    snprintf(line, sizeof(line), "    \"sha1\": %lu\n", result->sha1_matches); fputs(line, f);
+    fputs("  },\n", f);
+
+    fputs("  \"expected\": {\n", f);
+    friidump_json_write_string_value(f, "    \"size\": ", result->expected_size, ",\n");
+    friidump_json_write_string_value(f, "    \"crc32\": ", result->expected_crc32, ",\n");
+    friidump_json_write_string_value(f, "    \"md5\": ", result->expected_md5, ",\n");
+    friidump_json_write_string_value(f, "    \"sha1\": ", result->expected_sha1, "\n");
+    fputs("  },\n", f);
+
+    fputs("  \"evidence\": {\n", f);
+    friidump_json_write_string_value(f, "    \"size\": ", redump_field_status_string(result->size_status), ",\n");
+    friidump_json_write_string_value(f, "    \"crc32\": ", redump_field_status_string(result->crc32_status), ",\n");
+    friidump_json_write_string_value(f, "    \"md5\": ", redump_field_status_string(result->md5_status), ",\n");
+    friidump_json_write_string_value(f, "    \"sha1\": ", redump_field_status_string(result->sha1_status), "\n");
+    fputs("  },\n", f);
+    friidump_json_write_string_value(f, "  \"detail\": ", result->detail, "\n");
+    fputs("}\n", f);
+
+    write_ok = !ferror(f) && friidump_sync_report_file(f);
+    if (fclose(f) != 0)
+        write_ok = false;
+
+    if (!write_ok) {
+        remove(temporary_path);
+        fprintf(stderr, "Redump report.......: write/flush failed; final report was not replaced\n");
+        free(temporary_path);
+        return;
+    }
+
+    if (!friidump_atomic_replace(temporary_path, options.redump_report)) {
+        fprintf(stderr, "Redump report.......: could not replace %s; complete temporary report retained at %s\n",
+                options.redump_report, temporary_path);
+        free(temporary_path);
+        return;
+    }
+
+    fprintf(stderr, "Redump report.......: %s (atomic write)\n", options.redump_report);
+    free(temporary_path);
+}
+
+static void friidump_print_redump_field(const char *label, redump_field_status status) {
+    fprintf(stderr, "%-20s %s\n", label, redump_field_status_string(status));
+}
+
+static void friidump_verify_redump_values(disc_type type_id,
+                                            const char *output_path,
+                                            uint64_t output_size,
+                                            const char *crc32,
+                                            const char *md5,
+                                            const char *sha1,
+                                            const char *sha256,
+                                            const char *hash_source) {
+    const char *basename;
+    const char *dat_dir;
+    const char *representation_note;
+    char dat_path[1024];
+
+    redump_verify_result_init(&g_redump_result);
+    g_redump_attempted = false;
+    representation_note = (type_id == DISC_TYPE_XBOX)
+        ? "XGD1 acquisition success and exact Redump hash identity are separate claims; unresolved zero-filled pregame/postgame content may prevent an exact match."
+        : "";
+
+    if (options.no_redump_verify) {
+        fprintf(stderr, "Redump verification: DISABLED (--no-redump-verify)\n");
+        return;
+    }
+    if (options.no_hashing) {
+        fprintf(stderr, "Redump verification: SKIPPED (hashing disabled)\n");
+        return;
+    }
+    if (!output_path || !output_path[0]) {
+        fprintf(stderr, "Redump verification: SKIPPED (final output path unavailable)\n");
+        return;
+    }
+    if (!crc32 || !crc32[0] || !md5 || !md5[0] || !sha1 || !sha1[0]) {
+        fprintf(stderr, "Redump verification: SKIPPED (finalized CRC32/MD5/SHA-1 evidence incomplete)\n");
+        return;
+    }
+
+    basename = friidump_redump_dat_basename(type_id);
+    if (!basename) {
+        fprintf(stderr, "Redump verification: SKIPPED (no DAT mapping for this disc type)\n");
+        return;
+    }
+
+    dat_dir = (options.redump_dat_dir && options.redump_dat_dir[0])
+        ? options.redump_dat_dir
+        : NULL;
+    redump_resolve_dat_path(dat_dir, g_executable_dir, basename,
+                            dat_path, sizeof(dat_path));
+    if (output_size == 0)
+        output_size = friidump_file_size(output_path);
+
+    g_redump_attempted = true;
+    redump_verify_dat_file(dat_path, output_size, crc32, md5, sha1, &g_redump_result);
+
+    fprintf(stderr, "\nRedump verification\n");
+    fprintf(stderr, "------------------------------------------------------------\n");
+    fprintf(stderr, "%-20s %s\n", "DAT", g_redump_result.dat_path);
+    fprintf(stderr, "%-20s %s\n", "Hash source", hash_source ? hash_source : "Finalized output hashes");
+    fprintf(stderr, "%-20s %lu\n", "Entries scanned", g_redump_result.entries_scanned);
+
+    if (g_redump_result.status == REDUMP_VERIFY_MATCH) {
+        fprintf(stderr, "%-20s %s\n", "Matched entry", g_redump_result.game_name);
+        fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name);
+        friidump_print_redump_field("Image size", g_redump_result.size_status);
+        friidump_print_redump_field("CRC32", g_redump_result.crc32_status);
+        friidump_print_redump_field("MD5", g_redump_result.md5_status);
+        friidump_print_redump_field("SHA-1", g_redump_result.sha1_status);
+        if (g_redump_result.exact_matches > 1)
+            fprintf(stderr, "%-20s %lu exact entries\n", "Duplicate matches", g_redump_result.exact_matches);
+    } else if (g_redump_result.candidate_available) {
+        fprintf(stderr, "%-20s %s\n", "Closest candidate", g_redump_result.game_name);
+        fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name);
+        friidump_print_redump_field("Image size", g_redump_result.size_status);
+        friidump_print_redump_field("CRC32", g_redump_result.crc32_status);
+        friidump_print_redump_field("MD5", g_redump_result.md5_status);
+        friidump_print_redump_field("SHA-1", g_redump_result.sha1_status);
+    } else {
+        fprintf(stderr, "%-20s %s\n", "Closest candidate", "None (no hash-correlated entry)");
+        fprintf(stderr, "%-20s %lu entries\n", "Size matches", g_redump_result.size_matches);
+        fprintf(stderr, "%-20s %lu entries\n", "CRC32 matches", g_redump_result.crc32_matches);
+        fprintf(stderr, "%-20s %lu entries\n", "MD5 matches", g_redump_result.md5_matches);
+        fprintf(stderr, "%-20s %lu entries\n", "SHA-1 matches", g_redump_result.sha1_matches);
+    }
+
+    fprintf(stderr, "%-20s %s\n", "Overall", redump_verify_overall_string(&g_redump_result));
+    fprintf(stderr, "%-20s %s\n", "Confidence", redump_verify_confidence_string(&g_redump_result));
+    if (g_redump_result.status != REDUMP_VERIFY_MATCH)
+        fprintf(stderr, "%-20s %s\n", "Detail", g_redump_result.detail);
+    if (type_id == DISC_TYPE_XBOX)
+        fprintf(stderr, "%-20s %s\n", "Representation note", representation_note);
+
+    friidump_write_redump_report(&g_redump_result, output_path, output_size,
+                                 crc32, md5, sha1, sha256, hash_source,
+                                 representation_note);
+}
+
+static void friidump_verify_redump_iso(dumper *dmp, disc_type type_id) {
+    const char *output_path;
+
+    if (!dmp)
+        return;
+
+    output_path = options.iso_out;
+    friidump_verify_redump_values(type_id,
+                                  output_path,
+                                  friidump_file_size(output_path),
+                                  dumper_get_iso_crc32(dmp),
+                                  dumper_get_iso_md5(dmp),
+                                  dumper_get_iso_sha1(dmp),
+                                  dumper_get_iso_sha2(dmp),
+                                  "FriiDump finalized ISO multihash");
+}
+
+static void friidump_verify_redump_xbox_reference(dumper *dmp) {
+    xbox_ref_dump_result result;
+
+    xbox_ref_dump_result_init(&result);
+    if (!dumper_get_xbox_reference_result(dmp, &result)) {
+        fprintf(stderr, "Redump verification: SKIPPED (Xbox reference result handoff unavailable)\n");
+        return;
+    }
+    if (result.mode != '1') {
+        fprintf(stderr, "Redump verification: SKIPPED (Xbox XISO is not a full Redump disc image)\n");
+        return;
+    }
+    if (!result.dump_success) {
+        fprintf(stderr, "Redump verification: SKIPPED (Xbox reference dump did not complete)\n");
+        return;
+    }
+    if (!result.hashes_complete) {
+        fprintf(stderr, "Redump verification: SKIPPED (Xbox finalized full-file hashes incomplete)\n");
+        return;
+    }
+
+    if (g_validation_summary.active) {
+        if (result.output_path[0])
+            friidump_summary_copy(g_validation_summary.output, sizeof(g_validation_summary.output), result.output_path);
+        if (result.title[0])
+            friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), result.title);
+        if (result.media_id[0])
+            friidump_summary_copy(g_validation_summary.game_id, sizeof(g_validation_summary.game_id), result.media_id);
+        if (result.output_sectors)
+            g_validation_summary.sectors = result.output_sectors;
+        g_validation_summary.seed_applicable = false;
+        g_validation_summary.have_seed_duration = false;
+    }
+    if (result.elapsed_seconds > 0.0) {
+        g_operation_duration_override_valid = true;
+        g_operation_duration_override = result.elapsed_seconds;
+    }
+
+    friidump_verify_redump_values(DISC_TYPE_XBOX,
+                                  result.output_path,
+                                  result.output_size,
+                                  result.crc32,
+                                  result.md5,
+                                  result.sha1,
+                                  result.sha256,
+                                  "GDR-8050L reference finalized full-file hashes");
+}
+
 void help (void) {
        /* 80 cols guide:
         *      |-------------------------------------------------------------------------------|
 void help (void) {
        /* 80 cols guide:
         *      |-------------------------------------------------------------------------------|
@@ -539,6 +964,14 @@ void help (void) {
                "     --scan-log <file>         JSON output path for --hlds-e7-scan\n"
                "     --scan-dump-prefix <prefix>       Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n"
                "     --hlds-profile-report <file> Write selected HLDS profile/evidence JSON\n"
                "     --scan-log <file>         JSON output path for --hlds-e7-scan\n"
                "     --scan-dump-prefix <prefix>       Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n"
                "     --hlds-profile-report <file> Write selected HLDS profile/evidence JSON\n"
+               "     --redump-dat-dir <dir> Directory containing canonical Redump DAT files\n"
+               "                              (default: executable-relative redump_dat, then current directory)\n"
+               "     --redump-report <file>   Write atomic Redump evidence JSON\n"
+               "     --no-redump-verify       Disable automatic post-dump DAT verification\n"
+               "     --xgd1-layout-probe <file> Read-only locked/unlocked XGD1 boundary probe;\n"
+               "                              writes atomic JSON and does not create an ISO\n"
+               "     --xgd1-raw-id-probe <file> Modified-firmware cache-flushed, block-aligned raw-ID probe;\n"
+               "                              maps logical LBAs to decoded physical sector IDs\n"
                " -A, --allmethods              Try all known command/method combinations until\n"
                "                               one works. Reopens the drive for each command so\n"
                "                               command-specific vendor handlers are rebound.\n"
                " -A, --allmethods              Try all known command/method combinations until\n"
                "                               one works. Reopens the drive for each command so\n"
                "                               command-specific vendor handlers are rebound.\n"
@@ -594,6 +1027,11 @@ bool optparse (int argc, char **argv) {
                {"scan-log", 1, 0, 1001},
                {"scan-dump-prefix", 1, 0, 1002},
                {"hlds-profile-report", 1, 0, 1005},
                {"scan-log", 1, 0, 1001},
                {"scan-dump-prefix", 1, 0, 1002},
                {"hlds-profile-report", 1, 0, 1005},
+               {"redump-dat-dir", 1, 0, 1006},
+               {"redump-report", 1, 0, 1007},
+               {"no-redump-verify", 0, 0, 1008},
+               {"xgd1-layout-probe", 1, 0, 1009},
+               {"xgd1-raw-id-probe", 1, 0, 1010},
 #ifdef DEBUG
                /* We don't want newbies to generate and put into circulation bad dumps, so this options are disabled for releases */
                {"donottunscramble", 0, 0, 'n'},
 #ifdef DEBUG
                /* We don't want newbies to generate and put into circulation bad dumps, so this options are disabled for releases */
                {"donottunscramble", 0, 0, 'n'},
@@ -639,6 +1077,13 @@ bool optparse (int argc, char **argv) {
        options.hlds_e7_scan_log = NULL;
        options.hlds_e7_scan_dump_prefix = NULL;
        options.hlds_profile_report = NULL;
        options.hlds_e7_scan_log = NULL;
        options.hlds_e7_scan_dump_prefix = NULL;
        options.hlds_profile_report = NULL;
+       options.redump_dat_dir = NULL;
+       options.redump_report = NULL;
+       options.no_redump_verify = false;
+       options.xgd1_layout_probe = false;
+       options.xgd1_layout_probe_report = NULL;
+       options.xgd1_raw_id_probe = false;
+       options.xgd1_raw_id_probe_report = NULL;
 
        do {
 #ifdef DEBUG
 
        do {
 #ifdef DEBUG
@@ -785,6 +1230,25 @@ bool optparse (int argc, char **argv) {
                        case 1005:
                                my_strdup (options.hlds_profile_report, optarg);
                                break;
                        case 1005:
                                my_strdup (options.hlds_profile_report, optarg);
                                break;
+                       case 1006:
+                               my_strdup (options.redump_dat_dir, optarg);
+                               break;
+                       case 1007:
+                               my_strdup (options.redump_report, optarg);
+                               break;
+                       case 1008:
+                               options.no_redump_verify = true;
+                               break;
+                       case 1009:
+                               options.xgd1_layout_probe = true;
+                               my_strdup (options.xgd1_layout_probe_report, optarg);
+                               options.disctype = DISC_TYPE_XBOX;
+                               break;
+                       case 1010:
+                               options.xgd1_raw_id_probe = true;
+                               my_strdup (options.xgd1_raw_id_probe_report, optarg);
+                               options.disctype = DISC_TYPE_XBOX;
+                               break;
                        case -1:
                                break;
                        default:
                        case -1:
                                break;
                        default:
@@ -799,6 +1263,9 @@ bool optparse (int argc, char **argv) {
                fprintf (stderr, "WARNING: Extra parameters ignored\n");
        }
 
                fprintf (stderr, "WARNING: Extra parameters ignored\n");
        }
 
+       if (options.xgd1_layout_probe || options.xgd1_raw_id_probe)
+               options.disctype = DISC_TYPE_XBOX;
+
        /* Sanity checks... */
        out = false;
        if (!options.device && !options.raw_in) {
        /* Sanity checks... */
        out = false;
        if (!options.device && !options.raw_in) {
@@ -812,6 +1279,13 @@ bool optparse (int argc, char **argv) {
                fprintf (stderr, "The -r, -i and -X options cannot be used together with -a.\n");
        } else if (options.xiso_requested && (options.raw_out || options.iso_requested)) {
                fprintf (stderr, "The -X/--xiso option is a separate Xbox output mode and cannot be combined with -r or -i.\n");
                fprintf (stderr, "The -r, -i and -X options cannot be used together with -a.\n");
        } else if (options.xiso_requested && (options.raw_out || options.iso_requested)) {
                fprintf (stderr, "The -X/--xiso option is a separate Xbox output mode and cannot be combined with -r or -i.\n");
+       } else if ((options.xgd1_layout_probe || options.xgd1_raw_id_probe) &&
+                  (options.autodump || options.raw_in || options.raw_out ||
+                   options.iso_requested || options.xiso_requested ||
+                   options.allmethods || options.hlds_e7_scan ||
+                   options.hlds_e7_subcmd_sweep || options.hlds_e7_memrange_sweep ||
+                   (options.xgd1_layout_probe && options.xgd1_raw_id_probe))) {
+               fprintf (stderr, "XGD1 probe modes are mutually exclusive read-only diagnostics and cannot be combined with dump, conversion, all-methods, or HLDS 0xE7 probe options.\n");
        } else {
                /* Specified options seem to make sense */
                out = true;
        } else {
                /* Specified options seem to make sense */
                out = true;
@@ -832,7 +1306,7 @@ int dologic (disc *d, progstats *stats) {
        u_int32_t current_sector = 0;
        
        xbox_forced = (options.disctype == DISC_TYPE_XBOX);
        u_int32_t current_sector = 0;
        
        xbox_forced = (options.disctype == DISC_TYPE_XBOX);
-       xbox_output_requested = xbox_forced || options.xiso_requested;
+       xbox_output_requested = xbox_forced || options.xiso_requested || options.xgd1_layout_probe || options.xgd1_raw_id_probe;
        dump_attempted = false;
        
        
        dump_attempted = false;
        
        
@@ -947,6 +1421,67 @@ int dologic (disc *d, progstats *stats) {
                                                fprintf (stderr, "OK\n");
                                }
 
                                                fprintf (stderr, "OK\n");
                                }
 
+                               if (options.xgd1_layout_probe || options.xgd1_raw_id_probe) {
+#ifdef WIN32
+                                       int media_rc;
+                                       int media_sense_key;
+                                       int media_asc;
+                                       int media_ascq;
+                                       int probe_status;
+                                       bool stop_ok;
+                                       const char *probe_name;
+
+                                       probe_name = options.xgd1_raw_id_probe ? "XGD1 raw-sector ID" : "XGD1 logical-boundary";
+
+                                       if (!disc_is_xbox_challenge_drive (d)) {
+                                               fprintf (stderr,
+                                                        "%s probe currently supports only the GDR-8050L challenge-handshake profile.\n",
+                                                        probe_name);
+                                               return false;
+                                       }
+
+                                       if (options.xgd1_raw_id_probe &&
+                                           !(disc_get_hlds_e7_type (d) == 44 ||
+                                             disc_get_hlds_e7_type (d) == 45 ||
+                                             disc_get_hlds_e7_type (d) == 442 ||
+                                             disc_get_hlds_e7_type (d) == 443 ||
+                                             disc_get_hlds_e7_type (d) == 445)) {
+                                               fprintf (stderr,
+                                                        "--xgd1-raw-id-probe requires the modified GDR-8050L HIT 0xE7 memdump profile.\n");
+                                               return false;
+                                       }
+
+                                       fprintf (stderr, "\nChecking for ready Xbox media before %s probing... ", probe_name);
+                                       media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
+                                       if (media_rc <= 0) {
+                                               fprintf (stderr,
+                                                        "Failed (sense %02X/%02X/%02X). Insert the disc, wait for spin-up, close AutoPlay/File Explorer, and retry.\n",
+                                                        media_sense_key, media_asc, media_ascq);
+                                               return false;
+                                       }
+                                       fprintf (stderr, "OK\n");
+
+                                       gettimeofday (&(stats -> start_time), NULL);
+                                       if (options.xgd1_raw_id_probe)
+                                               probe_status = xbox_ref_xgd1_raw_id_probe_with_handle (
+                                                               disc_get_native_handle (d), disc_get_device (d), options.xgd1_raw_id_probe_report);
+                                       else
+                                               probe_status = xbox_ref_xgd1_layout_probe_with_handle (
+                                                               disc_get_native_handle (d), disc_get_device (d), options.xgd1_layout_probe_report);
+
+                                       fprintf (stderr, "Issuing STOP UNIT / spin-down after %s probe... ", probe_name);
+                                       stop_ok = disc_stop_unit (d, false);
+                                       fprintf (stderr, "%s\n", stop_ok ? "OK" : "Failed");
+                                       gettimeofday (&(stats -> end_time), NULL);
+                                       fprintf (stderr, "%s probe status: %s\n", probe_name, probe_status == 0 ? "OK" : "FAILED");
+                                       friidump_summary_reset();
+                                       return probe_status == 0;
+#else
+                                       fprintf (stderr, "XGD1 probe modes are available only in the Windows build.\n");
+                                       return false;
+#endif
+                               }
+
                                if (options.hlds_e7_scan) {
                                        out = disc_hlds_e7_scan (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
                                        fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 scan... ");
                                if (options.hlds_e7_scan) {
                                        out = disc_hlds_e7_scan (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
                                        fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 scan... ");
@@ -1194,6 +1729,11 @@ int dologic (disc *d, progstats *stats) {
                                                                                dumper_get_iso_sha1 (dmp), dumper_get_iso_sha2 (dmp)/*, dumper_get_iso_ed2k (dmp)*/
                                                                        );
 
                                                                                dumper_get_iso_sha1 (dmp), dumper_get_iso_sha2 (dmp)/*, dumper_get_iso_ed2k (dmp)*/
                                                                        );
 
+                                                               if (type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d) && options.iso_requested)
+                                                                       friidump_verify_redump_xbox_reference(dmp);
+                                                               else if (options.iso_out)
+                                                                       friidump_verify_redump_iso(dmp, type_id);
+
                                                                out = true;
                                                                if (g_validation_summary.active)
                                                                        g_validation_summary.dump_ok = true;
                                                                out = true;
                                                                if (g_validation_summary.active)
                                                                        g_validation_summary.dump_ok = true;
@@ -1294,6 +1834,7 @@ int main (int argc, char *argv[]) {
 
        /* First of all... */
        drop_euid ();
 
        /* First of all... */
        drop_euid ();
+       friidump_init_executable_dir((argc > 0) ? argv[0] : NULL);
        xbox_ref_log_open_for_target(NULL, 0);
        
        welcome ();
        xbox_ref_log_open_for_target(NULL, 0);
        
        welcome ();
@@ -1356,6 +1897,8 @@ int main (int argc, char *argv[]) {
                        duration += ((double) us / (double) USECS_PER_SEC);
                        if (duration < 0)
                                duration = 0;
                        duration += ((double) us / (double) USECS_PER_SEC);
                        if (duration < 0)
                                duration = 0;
+                       if (g_operation_duration_override_valid)
+                               duration = g_operation_duration_override;
                        fprintf (stderr, "Operation took %.2f seconds\n", duration);
                        friidump_print_validation_summary(duration, true);
 
                        fprintf (stderr, "Operation took %.2f seconds\n", duration);
                        friidump_print_validation_summary(duration, true);
 
@@ -1371,6 +1914,8 @@ int main (int argc, char *argv[]) {
                my_free (options.raw_out);
                my_free (options.raw_in);
                my_free (options.hlds_profile_report);
                my_free (options.raw_out);
                my_free (options.raw_in);
                my_free (options.hlds_profile_report);
+               my_free (options.xgd1_layout_probe_report);
+               my_free (options.xgd1_raw_id_probe_report);
        }
 
        if (xbox_ref_log_path())
        }
 
        if (xbox_ref_log_path())