]> FriiDump Source - friidump.git/blobdiff - src/friidump.c
FriiDump 0.5.3.16: finalize release identity and documentation
[friidump.git] / src / friidump.c
index e30b553f8be6f56c8b5d645e8e887d20567f5cdb..f42b744efc2b0d251e9e941a4abd30cfc3022bfa 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "misc.h"
 #include <stdio.h>
+#include <signal.h>
 #include <stdlib.h>
 #include <time.h>
 #include <string.h>
 #include "dumper.h"
 #include "unscrambler.h"
 #include "xbox_ref/xbox_ref_log.h"
+#include "xbox_ref_bridge.h"
 #include "redump_dat.h"
+#include "native_report.h"
+
+#ifndef WIN32
+#include "linux_rawio.h"
+#endif
 
 #ifdef WIN32
 #include <sys/stat.h>
@@ -61,10 +68,15 @@ typedef struct {
        char disc_type[64];
        char game_id[64];
        char title[256];
+       char region[128];
        char output[512];
        u_int32_t command;
        int method;
        u_int32_t sectors;
+       u_int32_t source_sectors;
+       u_int32_t expected_output_sectors;
+       bool have_source_sectors;
+       bool have_expected_output_sectors;
        u_int32_t fail_sector;
        u_int32_t hlds_type;
        u_int32_t cache_base;
@@ -77,6 +89,11 @@ 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 friidump_native_report g_native_report;
+static bool g_native_report_dump_started = false;
+static bool g_native_report_resumed = false;
+static volatile sig_atomic_t g_native_cancel_requested = 0;
 
 static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
        if (!dst || dst_size == 0)
@@ -108,6 +125,235 @@ static void friidump_format_hms(double seconds, char *buf, size_t buf_size) {
 }
 
 
+static bool friidump_capture_seed_diagnostic (disc *d) {
+       disc_seed_diagnostic diag;
+       char cdb_text[3 * 12 + 1];
+       char note[768];
+       int i;
+       int used;
+
+       memset (&diag, 0, sizeof (diag));
+       if (!disc_get_seed_diagnostic (d, &diag))
+               return false;
+
+       cdb_text[0] = '\0';
+       used = 0;
+       for (i = 0; i < diag.cdb_length && i < 12; i++) {
+               int wrote = snprintf (cdb_text + used, sizeof (cdb_text) - (size_t) used,
+                       "%s%02X", i ? " " : "", diag.cdb[i]);
+               if (wrote < 0 || wrote >= (int) (sizeof (cdb_text) - (size_t) used))
+                       break;
+               used += wrote;
+       }
+
+       fprintf (stderr,
+               "\nSeed diagnostic final failure:\n"
+               "  Block.............: %u/20\n"
+               "  Sectors...........: %u..%u\n"
+               "  Retry.............: %d\n"
+               "  Stage.............: %s\n"
+               "  Detail............: %s\n"
+               "  Transport result..: %d\n"
+               "  OS error..........: %d\n"
+               "  SCSI status.......: 0x%02X\n"
+               "  Sense.............: %02X/%02X/%02X\n"
+               "  CDB...............: %s\n",
+               diag.seed_block_index + 1,
+               diag.sector_start, diag.sector_end,
+               diag.retry,
+               diag.stage[0] ? diag.stage : "unknown",
+               diag.detail[0] ? diag.detail : "none",
+               diag.transport_result,
+               diag.os_error,
+               diag.scsi_status & 0xff,
+               diag.sense_key & 0xff,
+               diag.asc & 0xff,
+               diag.ascq & 0xff,
+               cdb_text[0] ? cdb_text : "none");
+
+       snprintf (note, sizeof (note),
+               "Seed diagnostic: block %u/20, sectors %u..%u, retry %d, stage %s, detail %s, transport_result %d, os_error %d, scsi_status 0x%02X, sense %02X/%02X/%02X, CDB [%s].",
+               diag.seed_block_index + 1,
+               diag.sector_start, diag.sector_end,
+               diag.retry,
+               diag.stage[0] ? diag.stage : "unknown",
+               diag.detail[0] ? diag.detail : "none",
+               diag.transport_result,
+               diag.os_error,
+               diag.scsi_status & 0xff,
+               diag.sense_key & 0xff,
+               diag.asc & 0xff,
+               diag.ascq & 0xff,
+               cdb_text[0] ? cdb_text : "none");
+       if (friidump_native_report_is_enabled (&g_native_report))
+               friidump_native_report_add_note (&g_native_report, note);
+       return true;
+}
+
+#ifndef WIN32
+static bool friidump_linux_vendor_rawio_required(
+    disc *d,
+    bool xbox_output_requested,
+    bool drive_supported,
+    bool probe_requested) {
+    int method;
+
+    if (!d)
+        return false;
+
+    if (probe_requested)
+        return true;
+
+    if (xbox_output_requested)
+        return disc_is_xbox_unlock_drive(d);
+
+    method = (int) disc_get_method(d);
+    return drive_supported && method >= 0 && method <= 9;
+}
+
+static void friidump_linux_record_rawio_failure(
+    disc *d,
+    bool xbox_output_requested,
+    const char *note) {
+    bool stop_ok;
+
+    if (friidump_native_report_is_enabled(&g_native_report)) {
+        friidump_native_report_set_seed(
+            &g_native_report,
+            false,
+            xbox_output_requested ? "not_applicable" : "not_attempted",
+            false,
+            0.0);
+        friidump_native_report_set_dump(
+            &g_native_report,
+            false,
+            "not_attempted",
+            false,
+            0,
+            false,
+            0,
+            false,
+            0.0,
+            NULL,
+            false,
+            0,
+            NULL);
+        friidump_native_report_set_outcome(&g_native_report, "other", "fail");
+        friidump_native_report_add_note(&g_native_report, note);
+    }
+
+    stop_ok = disc_stop_unit(d, false);
+    fprintf(stderr,
+        "Issuing STOP UNIT / spin-down after Linux raw-I/O preflight failure... %s\n",
+        stop_ok ? "OK" : "Failed");
+
+    if (g_validation_summary.active) {
+        g_validation_summary.stop_attempted = true;
+        g_validation_summary.stop_ok = stop_ok;
+    }
+
+    if (friidump_native_report_is_enabled(&g_native_report)) {
+        friidump_native_report_add_note(
+            &g_native_report,
+            stop_ok
+                ? "STOP UNIT after Linux raw-I/O preflight failure succeeded."
+                : "STOP UNIT after Linux raw-I/O preflight failure failed.");
+    }
+}
+
+static bool friidump_linux_rawio_preflight(
+    disc *d,
+    bool xbox_output_requested,
+    bool drive_supported,
+    bool probe_requested) {
+    friidump_linux_rawio_state state;
+    unsigned long long effective_mask = 0;
+    char executable_path[1024];
+    char note[1536];
+    const char *display_path;
+
+    if (!friidump_linux_vendor_rawio_required(
+            d, xbox_output_requested, drive_supported, probe_requested))
+        return true;
+
+    executable_path[0] = '\0';
+    if (!friidump_linux_executable_path(
+            executable_path, sizeof(executable_path))) {
+        const char *fallback_dir = g_executable_dir[0] ? g_executable_dir : ".";
+        const char *suffix = "friidump";
+        size_t used = strlen(fallback_dir);
+        size_t suffix_len = strlen(suffix);
+
+        if (used >= sizeof(executable_path))
+            used = sizeof(executable_path) - 1;
+        memcpy(executable_path, fallback_dir, used);
+        executable_path[used] = '\0';
+
+        if (used > 0 && executable_path[used - 1] != '/' &&
+                used + 1 < sizeof(executable_path)) {
+            executable_path[used++] = '/';
+            executable_path[used] = '\0';
+        }
+
+        if (suffix_len > sizeof(executable_path) - used - 1)
+            suffix_len = sizeof(executable_path) - used - 1;
+        memcpy(executable_path + used, suffix, suffix_len);
+        executable_path[used + suffix_len] = '\0';
+    }
+    display_path = executable_path;
+
+    if (geteuid() == 0) {
+        fprintf(stderr,
+            "\nERROR: FriiDump refuses this Linux vendor-command path while running as root.\n"
+            "  Run FriiDump as your normal user and grant only CAP_SYS_RAWIO to the\n"
+            "  exact validated executable. Do not use sudo to run the whole program.\n"
+            "  Exact executable: %s\n"
+            "  Install: sudo setcap cap_sys_rawio=ep '%s'\n"
+            "  Verify:  getcap '%s'\n"
+            "  Rebuilding or replacing the executable clears file capabilities.\n\n",
+            display_path, display_path, display_path);
+        snprintf(
+            note,
+            sizeof(note),
+            "Linux raw-I/O preflight failed: FriiDump was running as root. The supported least-privilege configuration is a normal user process with cap_sys_rawio=ep on the exact validated executable (%s).",
+            display_path);
+        friidump_linux_record_rawio_failure(d, xbox_output_requested, note);
+        return false;
+    }
+
+    state = friidump_linux_rawio_effective(&effective_mask);
+    if (state == FRIIDUMP_LINUX_RAWIO_PRESENT) {
+        fprintf(stderr,
+            "Linux raw-I/O preflight: PASS (CAP_SYS_RAWIO effective; CapEff=0x%016llX).\n",
+            effective_mask);
+        return true;
+    }
+
+    fprintf(stderr,
+        "\nERROR: Linux vendor-command authorization is unavailable.\n"
+        "  This operation uses vendor-specific SCSI commands and requires effective\n"
+        "  CAP_SYS_RAWIO. Device access through the cdrom group is necessary but is\n"
+        "  not sufficient for commands such as HLDS 0xE7 memory reads.\n"
+        "  Exact executable: %s\n"
+        "  Install: sudo setcap cap_sys_rawio=ep '%s'\n"
+        "  Verify:  getcap '%s'\n"
+        "  Expected: %s cap_sys_rawio=ep\n"
+        "  Do not run the entire FriiDump process as root. Rebuilding or replacing\n"
+        "  the executable clears file capabilities, so reapply and verify afterward.\n\n",
+        display_path, display_path, display_path, display_path);
+
+    snprintf(
+        note,
+        sizeof(note),
+        "Linux raw-I/O preflight failed: effective CAP_SYS_RAWIO was %s for executable %s (CapEff=0x%016llX). Apply cap_sys_rawio=ep to the exact validated executable; do not run FriiDump as root. Rebuilds clear file capabilities.",
+        state == FRIIDUMP_LINUX_RAWIO_MISSING ? "missing" : "unavailable/undetectable",
+        display_path,
+        effective_mask);
+    friidump_linux_record_rawio_failure(d, xbox_output_requested, note);
+    return false;
+}
+#endif
+
 static void friidump_summary_begin(disc *d) {
        const char *target;
        if (!d || disc_get_hlds_e7_type(d) == 0)
@@ -126,15 +372,39 @@ static void friidump_summary_begin(disc *d) {
 }
 
 static void friidump_print_validation_summary(double duration, bool have_duration) {
-       double mib_total, mib_per_hour;
+       double mib_completed, mib_per_hour;
+       bool have_completed_measurement;
        const char *seed_status;
+       const char *dump_status;
        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;
+       dump_status = "NOT ATTEMPTED";
+       if (g_validation_summary.dump_attempted) {
+               if (g_validation_summary.dump_ok)
+                       dump_status = "OK";
+               else if (g_native_cancel_requested && g_native_report.have_byte_count &&
+                        g_native_report.byte_count > 0)
+                       dump_status = "CANCELLED (PARTIAL)";
+               else if (g_native_cancel_requested)
+                       dump_status = "CANCELLED";
+               else if (g_native_report.have_byte_count && g_native_report.byte_count > 0)
+                       dump_status = "FAILED (PARTIAL)";
+               else
+                       dump_status = "FAILED";
+       }
+       have_completed_measurement =
+               g_native_report.dump_attempted &&
+               g_native_report.have_byte_count &&
+               !g_native_report_resumed;
+       mib_completed = have_completed_measurement
+               ? (double) g_native_report.byte_count / 1024.0 / 1024.0
+               : 0.0;
+       mib_per_hour = (have_completed_measurement && have_duration && duration > 0.0)
+               ? (mib_completed / duration * 3600.0)
+               : 0.0;
        fprintf(stderr,
                "\nHLDS 0xE7 validation summary:\n"
                "  Model.............: %s\n"
@@ -144,35 +414,61 @@ static void friidump_print_validation_summary(double duration, bool have_duratio
                "  Memory windows....: %u\n"
                "  Disc type.........: %s\n"
                "  Game/Media ID.....: %s\n"
-               "  Title.............: %s\n"
-               "  Output............: %s\n"
-               "  Seed read.........: %s\n"
-               "  Dump status.......: %s\n"
-               "  STOP UNIT.........: %s\n",
+               "  Title.............: %s\n",
                g_validation_summary.model[0] ? g_validation_summary.model : "(unknown)",
                g_validation_summary.profile ? g_validation_summary.profile : "(unknown)",
                g_validation_summary.command, g_validation_summary.method,
                g_validation_summary.cache_base, g_validation_summary.mem_blocks,
                g_validation_summary.disc_type[0] ? g_validation_summary.disc_type : "(unknown)",
                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.title[0] ? g_validation_summary.title : "(unknown)");
+       if (g_validation_summary.region[0])
+               fprintf(stderr, "  Region............: %s\n", g_validation_summary.region);
+       fprintf(stderr,
+               "  Output............: %s\n"
+               "  Seed read.........: %s\n"
+               "  Dump status.......: %s\n"
+               "  STOP UNIT.........: %s\n",
                g_validation_summary.output[0] ? g_validation_summary.output : "(none)",
                seed_status,
-               g_validation_summary.dump_attempted ? (g_validation_summary.dump_ok ? "OK" : "FAILED") : "NOT ATTEMPTED",
+               dump_status,
                g_validation_summary.stop_attempted ? (g_validation_summary.stop_ok ? "OK" : "FAILED") : "NOT ATTEMPTED");
        if (g_validation_summary.have_seed_duration) {
                char seed_buf[32];
                friidump_format_hms(g_validation_summary.seed_duration, seed_buf, sizeof(seed_buf));
                fprintf(stderr, "  Seed elapsed......: %s (%.2f seconds)\n", seed_buf, g_validation_summary.seed_duration);
        }
-       if (g_validation_summary.dump_attempted && !g_validation_summary.dump_ok && g_validation_summary.fail_sector != 0xFFFFFFFFU)
-               fprintf(stderr, "  Failure sector....: %u..%u\n", g_validation_summary.fail_sector, g_validation_summary.fail_sector + 15);
-       if (g_validation_summary.sectors)
-               fprintf(stderr, "  Sectors...........: %u\n", g_validation_summary.sectors);
+       if (g_validation_summary.dump_attempted && !g_validation_summary.dump_ok &&
+           g_validation_summary.fail_sector != 0xFFFFFFFFU) {
+               if (g_native_cancel_requested)
+                       fprintf(stderr, "  Cancelled at......: sector %u\n", g_validation_summary.fail_sector);
+               else
+                       fprintf(stderr, "  Failure sector....: %u..%u\n", g_validation_summary.fail_sector, g_validation_summary.fail_sector + 15);
+       }
+       if (g_validation_summary.have_source_sectors)
+               fprintf(stderr, "  Source sectors....: %u\n", g_validation_summary.source_sectors);
+       if (g_validation_summary.have_expected_output_sectors)
+               fprintf(stderr, "  Expected output...: %u sectors\n", g_validation_summary.expected_output_sectors);
+       else if (g_validation_summary.sectors)
+               fprintf(stderr, "  Expected sectors..: %u\n", g_validation_summary.sectors);
+       if (g_native_report.have_sector_count)
+               fprintf(stderr, g_native_report_resumed
+                       ? "  Output sectors....: %llu (includes prior resumed data)\n"
+                       : "  Completed sectors.: %llu\n",
+                       (unsigned long long) g_native_report.sector_count);
+       if (g_native_report.have_byte_count)
+               fprintf(stderr, g_native_report_resumed
+                       ? "  Output bytes......: %llu (%.2f MiB; includes prior resumed data)\n"
+                       : "  Completed bytes...: %llu (%.2f MiB)\n",
+                       (unsigned long long) g_native_report.byte_count,
+                       (double) g_native_report.byte_count / 1024.0 / 1024.0);
        if (have_duration)
                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, "  Observed average..: %.2f MiB/h over %.2f MiB completed output\n",
+                       mib_per_hour, mib_completed);
+       else if (g_native_report_resumed)
+               fprintf(stderr, "  Observed average..: N/A (resumed output; measurement scope unknown)\n");
        if (g_validation_summary.dump_ok) {
                if (g_redump_attempted) {
                        fprintf(stderr, "  Redump verify.....: %s\n", redump_verify_overall_string(&g_redump_result));
@@ -195,7 +491,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_VERSION "0.5.3.10"
+#define PACKAGE_VERSION "0.5.3.16-pf1"
 
 
 #ifdef WIN32
@@ -245,6 +541,13 @@ struct {
        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;
+       char *native_report_json;
+       char *native_report_dir;
+       char *firmware_modified_note;
 } options;
 
 
@@ -267,6 +570,8 @@ static char friidump_drive_letter_from_device(const char *device) {
 
 
 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;
@@ -486,6 +791,59 @@ 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:
@@ -673,7 +1031,7 @@ static void friidump_verify_redump_values(disc_type type_id,
     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; documented synthetic reconstruction ranges may prevent an exact match."
+        ? "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) {
@@ -699,8 +1057,11 @@ static void friidump_verify_redump_values(disc_type type_id,
         return;
     }
 
-    dat_dir = (options.redump_dat_dir && options.redump_dat_dir[0]) ? options.redump_dat_dir : "redump_dat";
-    snprintf(dat_path, sizeof(dat_path), "%s/%s", dat_dir, basename);
+    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);
 
@@ -766,6 +1127,16 @@ static void friidump_verify_redump_iso(dumper *dmp, disc_type type_id) {
                                   "FriiDump finalized ISO multihash");
 }
 
+static bool friidump_uses_windows_xbox_reference_path(disc *d, disc_type type_id) {
+#ifdef WIN32
+    return type_id == DISC_TYPE_XBOX && d && disc_is_xbox_challenge_drive(d);
+#else
+    (void) d;
+    (void) type_id;
+    return false;
+#endif
+}
+
 static void friidump_verify_redump_xbox_reference(dumper *dmp) {
     xbox_ref_dump_result result;
 
@@ -811,7 +1182,490 @@ static void friidump_verify_redump_xbox_reference(dumper *dmp) {
                                   result.md5,
                                   result.sha1,
                                   result.sha256,
-                                  "GDR-8050L reference finalized full-file hashes");
+                                  "Xbox finalized full-file hashes");
+}
+
+
+static void friidump_native_signal_handler(int signal_number) {
+    (void) signal_number;
+    g_native_cancel_requested = 1;
+}
+
+static bool friidump_native_cancel_callback(void *unused) {
+    (void) unused;
+    return g_native_cancel_requested != 0;
+}
+
+static const char *friidump_native_platform(disc_type type_id) {
+    switch (type_id) {
+        case DISC_TYPE_GAMECUBE:
+            return "gamecube";
+        case DISC_TYPE_WII:
+        case DISC_TYPE_WII_DL:
+            return "wii";
+        case DISC_TYPE_XBOX:
+            return "xbox";
+        case DISC_TYPE_DVD:
+            return "dvd";
+        default:
+            return "unknown";
+    }
+}
+
+static bool friidump_native_report_operation_supported(void) {
+    return options.device &&
+           !options.raw_in &&
+           !options.allmethods &&
+           !options.stop_unit &&
+           !options.hlds_e7_scan &&
+           !options.hlds_e7_subcmd_sweep &&
+           !options.hlds_e7_memrange_sweep &&
+           !options.xgd1_layout_probe &&
+           !options.xgd1_raw_id_probe &&
+           !(options.raw_out && options.iso_requested) &&
+           (options.autodump || options.raw_out ||
+            options.iso_requested || options.xiso_requested);
+}
+
+static bool friidump_native_report_option_requested(void) {
+    return options.native_report_json ||
+           options.native_report_dir ||
+           options.firmware_modified_note;
+}
+
+static void friidump_native_configure(void) {
+    if (!friidump_native_report_operation_supported())
+        return;
+
+    friidump_native_report_enable_default(&g_native_report);
+
+    if (options.native_report_json)
+        friidump_native_report_enable_path(
+            &g_native_report, options.native_report_json);
+    if (options.native_report_dir)
+        friidump_native_report_enable_dir(
+            &g_native_report, options.native_report_dir);
+
+#ifdef FRIIDUMP_BUILD_COMMIT
+    friidump_native_report_set_build_commit(&g_native_report, FRIIDUMP_BUILD_COMMIT);
+#endif
+
+    if (options.firmware_modified_note)
+        friidump_native_report_set_firmware_modified(
+            &g_native_report, true, options.firmware_modified_note);
+
+    if (signal(SIGINT, friidump_native_signal_handler) == SIG_ERR) {
+        fprintf(stderr, "WARNING: could not install the native-report Ctrl+C handler.\n");
+    }
+}
+
+static void friidump_native_set_drive(disc *d) {
+    if (!friidump_native_report_is_enabled(&g_native_report) || !d)
+        return;
+
+    friidump_native_report_set_drive(
+        &g_native_report,
+        disc_get_drive_vendor(d),
+        disc_get_drive_product_id(d),
+        disc_get_drive_firmware_revision(d),
+        "ATAPI",
+        disc_get_device(d));
+}
+
+static void friidump_native_set_profile(disc *d, bool xbox_output_requested) {
+    (void) xbox_output_requested;
+
+    if (!friidump_native_report_is_enabled(&g_native_report) || !d)
+        return;
+
+    friidump_native_report_set_profile(
+        &g_native_report,
+        disc_get_hlds_e7_support_tier(d),
+        disc_get_hlds_e7_static_cdb_base(d),
+        disc_get_hlds_e7_static_gate(d));
+}
+
+static void friidump_native_set_preflight_failure(int media_rc,
+                                                   int sense_key,
+                                                   int asc,
+                                                   int ascq) {
+    char note[256];
+
+    if (!friidump_native_report_is_enabled(&g_native_report))
+        return;
+
+    friidump_native_report_set_seed(
+        &g_native_report, false, "not_attempted", false, 0.0);
+    friidump_native_report_set_dump(
+        &g_native_report, false, "not_attempted",
+        false, 0, false, 0, false, 0.0,
+        NULL, false, 0, NULL);
+    friidump_native_report_set_reference(
+        &g_native_report, NULL, "not_applicable", NULL);
+
+    if (media_rc == 0) {
+        friidump_native_report_set_outcome(
+            &g_native_report, "diagnostic_no_media", "not_applicable");
+        snprintf(note, sizeof(note),
+                 "Media preflight found no readable disc (sense %02X/%02X/%02X).",
+                 sense_key & 0xff, asc & 0xff, ascq & 0xff);
+    } else {
+        friidump_native_report_set_outcome(
+            &g_native_report, "other", "fail");
+        snprintf(note, sizeof(note),
+                 "Drive or media did not become ready (sense %02X/%02X/%02X).",
+                 sense_key & 0xff, asc & 0xff, ascq & 0xff);
+    }
+    friidump_native_report_add_note(&g_native_report, note);
+}
+
+static void friidump_native_set_media(disc_type type_id,
+                                      const char *title,
+                                      const char *region,
+                                      const char *disc_id) {
+    if (!friidump_native_report_is_enabled(&g_native_report))
+        return;
+    friidump_native_report_set_media(
+        &g_native_report,
+        friidump_native_platform(type_id),
+        title,
+        region,
+        disc_id);
+}
+
+static void friidump_native_add_measurement_scope(const char *scope) {
+    char note[128];
+    if (!friidump_native_report_is_enabled(&g_native_report) || !scope)
+        return;
+    snprintf(note, sizeof(note), "Measurement scope: %s.", scope);
+    friidump_native_report_add_note(&g_native_report, note);
+}
+
+static void friidump_native_capture_output(dumper *dmp,
+                                           disc_type type_id,
+                                           bool xiso_mode,
+                                           bool success,
+                                           bool cancelled,
+                                           u_int32_t failure_sector,
+                                           u_int32_t expected_sectors) {
+    xbox_ref_dump_result xbox_result;
+    bool have_xbox_result;
+    const char *path;
+    const char *crc32;
+    const char *md5;
+    const char *sha1;
+    const char *sha256;
+    uint64_t bytes;
+    uint64_t sectors;
+    uint64_t sector_size;
+    bool partial;
+    const char *failure_stage;
+
+    if (!friidump_native_report_is_enabled(&g_native_report) || !dmp)
+        return;
+
+    g_native_report_dump_started = true;
+    g_native_report_resumed = dumper_get_start_sector(dmp) > 0;
+
+    xbox_ref_dump_result_init(&xbox_result);
+    have_xbox_result = dumper_get_xbox_reference_result(dmp, &xbox_result);
+    path = NULL;
+    crc32 = md5 = sha1 = sha256 = NULL;
+    bytes = 0;
+    sectors = 0;
+    sector_size = 2048;
+
+    if (have_xbox_result) {
+        if (xbox_result.cancelled)
+            cancelled = true;
+        path = xbox_result.output_path[0] ? xbox_result.output_path : NULL;
+        bytes = xbox_result.output_size;
+        sectors = xbox_result.output_sectors;
+        if (g_validation_summary.active) {
+            if (xbox_result.output_path[0])
+                friidump_summary_copy(
+                    g_validation_summary.output,
+                    sizeof(g_validation_summary.output),
+                    xbox_result.output_path);
+            if (xbox_result.title[0])
+                friidump_summary_copy(
+                    g_validation_summary.title,
+                    sizeof(g_validation_summary.title),
+                    xbox_result.title);
+            if (xbox_result.media_id[0])
+                friidump_summary_copy(
+                    g_validation_summary.game_id,
+                    sizeof(g_validation_summary.game_id),
+                    xbox_result.media_id);
+            if (xbox_result.region[0])
+                friidump_summary_copy(
+                    g_validation_summary.region,
+                    sizeof(g_validation_summary.region),
+                    xbox_result.region);
+            if (expected_sectors > 0) {
+                g_validation_summary.source_sectors = expected_sectors;
+                g_validation_summary.have_source_sectors = true;
+            }
+            if (xbox_result.output_sectors > 0) {
+                g_validation_summary.expected_output_sectors =
+                    xbox_result.output_sectors;
+                g_validation_summary.have_expected_output_sectors = true;
+            }
+            g_validation_summary.seed_applicable = false;
+            g_validation_summary.have_seed_duration = false;
+        }
+        if (xbox_result.hashes_complete) {
+            crc32 = xbox_result.crc32;
+            md5 = xbox_result.md5;
+            sha1 = xbox_result.sha1;
+            sha256 = xbox_result.sha256;
+        }
+        if (xbox_result.title[0] || xbox_result.media_id[0] || xbox_result.region[0]) {
+            friidump_native_report_set_media(
+                &g_native_report,
+                "xbox",
+                xbox_result.title[0] ? xbox_result.title :
+                    (g_native_report.have_title ? g_native_report.title : NULL),
+                xbox_result.region[0] ? xbox_result.region : NULL,
+                xbox_result.media_id[0] ? xbox_result.media_id :
+                    (g_native_report.have_disc_id ? g_native_report.disc_id : NULL));
+        }
+        if (xbox_result.have_game_region) {
+            char region_note[96];
+            snprintf(region_note, sizeof(region_note),
+                     "Xbox XBE GameRegion mask: 0x%08x.",
+                     xbox_result.game_region);
+            friidump_native_report_add_note(&g_native_report, region_note);
+        }
+        if (xbox_result.elapsed_seconds > 0.0) {
+            g_native_report.have_dump_duration = true;
+            g_native_report.dump_duration_seconds = xbox_result.elapsed_seconds;
+        }
+    } else if (xiso_mode) {
+        path = (options.xiso_out && options.xiso_out[0]) ? options.xiso_out : NULL;
+        if (path)
+            bytes = friidump_file_size(path);
+        sectors = bytes / 2048;
+        if (!options.no_hashing) {
+            crc32 = dumper_get_xiso_crc32(dmp);
+            md5 = dumper_get_xiso_md5(dmp);
+            sha1 = dumper_get_xiso_sha1(dmp);
+            sha256 = dumper_get_xiso_sha2(dmp);
+        }
+    } else if (options.iso_out && options.iso_out[0]) {
+        path = options.iso_out;
+        bytes = friidump_file_size(path);
+        sectors = bytes / 2048;
+        if (!options.no_hashing) {
+            crc32 = dumper_get_iso_crc32(dmp);
+            md5 = dumper_get_iso_md5(dmp);
+            sha1 = dumper_get_iso_sha1(dmp);
+            sha256 = dumper_get_iso_sha2(dmp);
+        }
+    } else if (options.raw_out && options.raw_out[0]) {
+        path = options.raw_out;
+        sector_size = 2064;
+        bytes = friidump_file_size(path);
+        sectors = bytes / sector_size;
+        if (!options.no_hashing) {
+            crc32 = dumper_get_raw_crc32(dmp);
+            md5 = dumper_get_raw_md5(dmp);
+            sha1 = dumper_get_raw_sha1(dmp);
+            sha256 = dumper_get_raw_sha2(dmp);
+        }
+    }
+
+    if (path && (!success || bytes == 0)) {
+        uint64_t observed_bytes = friidump_file_size(path);
+        if (observed_bytes > 0 || !success)
+            bytes = observed_bytes;
+        if (!success)
+            sectors = bytes / sector_size;
+    }
+
+    if (success && sectors == 0 && type_id != DISC_TYPE_XBOX) {
+        sectors = expected_sectors;
+        bytes = sectors * sector_size;
+    }
+
+    if (g_native_report_resumed) {
+        /* FriiDump's streaming hashes cover only bytes produced by this
+         * invocation when a file is resumed, not the complete output file. */
+        crc32 = md5 = sha1 = sha256 = NULL;
+    }
+
+    partial = !success && (bytes > 0 ||
+        (failure_sector != 0 && failure_sector != 0xFFFFFFFFU));
+    failure_stage = NULL;
+    if (!success) {
+        if (cancelled)
+            failure_stage = "user_cancelled";
+        else
+            failure_stage = (failure_sector == 0xFFFFFFFFU)
+                ? "xbox_reference_dump"
+                : "raw_sector_read_or_output_write";
+    }
+
+    friidump_native_report_set_dump(
+        &g_native_report,
+        true,
+        success ? "pass" : (partial ? "partial" : "fail"),
+        success || sectors > 0,
+        sectors,
+        success || bytes > 0,
+        bytes,
+        g_native_report.have_dump_duration,
+        g_native_report.dump_duration_seconds,
+        failure_stage,
+        !success && failure_sector != 0xFFFFFFFFU,
+        failure_sector,
+        path);
+
+    friidump_native_report_set_hashes(
+        &g_native_report, crc32, md5, sha1, sha256);
+
+    if (path) {
+        friidump_native_report_add_artifact(
+            &g_native_report,
+            "dump_output",
+            path,
+            true,
+            bytes,
+            sha256);
+    }
+
+    if (success)
+        friidump_native_report_set_outcome(
+            &g_native_report, "full_dump", "pass");
+    else if (partial)
+        friidump_native_report_set_outcome(
+            &g_native_report, "partial_dump", "partial");
+    else if (cancelled)
+        friidump_native_report_set_outcome(
+            &g_native_report, "other", "fail");
+    else
+        friidump_native_report_set_outcome(
+            &g_native_report, "full_dump", "fail");
+
+    if (cancelled)
+        friidump_native_report_add_note(
+            &g_native_report,
+            "The invocation was cancelled by the user; only completed output bytes are represented.");
+
+    if (g_native_report_resumed) {
+        friidump_native_add_measurement_scope("unknown");
+        friidump_native_report_add_note(
+            &g_native_report,
+            "Resumed output: duration is not used for throughput because the output byte count includes data from an earlier invocation.");
+        friidump_native_report_add_note(
+            &g_native_report,
+            "Resumed output: streaming hashes were suppressed because they do not describe the complete output file.");
+    } else if (!success && partial) {
+        friidump_native_add_measurement_scope("partial_progress");
+    } else if (type_id == DISC_TYPE_XBOX || xiso_mode) {
+        friidump_native_add_measurement_scope("assembled_output");
+        friidump_native_report_add_note(
+            &g_native_report,
+            "Xbox output throughput describes the produced logical or assembled output and is not directly comparable to a full optical-payload read.");
+    } else if (success) {
+        friidump_native_add_measurement_scope("full_optical_payload");
+    } else {
+        friidump_native_add_measurement_scope("unknown");
+    }
+
+    if (options.no_hashing)
+        friidump_native_report_add_note(
+            &g_native_report,
+            "Output hashing was disabled for this invocation.");
+}
+
+static bool friidump_elapsed_from_stats(const progstats *stats, double *duration) {
+    double value;
+    suseconds_t usecs;
+
+    if (!stats || !duration || stats->start_time.tv_sec == 0 ||
+        stats->end_time.tv_sec == 0)
+        return false;
+
+    value = (double) (stats->end_time.tv_sec - stats->start_time.tv_sec);
+    if (stats->end_time.tv_usec >= stats->start_time.tv_usec) {
+        usecs = stats->end_time.tv_usec - stats->start_time.tv_usec;
+    } else {
+        value -= 1.0;
+        usecs = USECS_PER_SEC + stats->end_time.tv_usec - stats->start_time.tv_usec;
+    }
+    value += (double) usecs / (double) USECS_PER_SEC;
+    if (value < 0.0)
+        return false;
+    *duration = value;
+    return true;
+}
+
+static void friidump_native_finalize_reference(void) {
+    if (!friidump_native_report_is_enabled(&g_native_report))
+        return;
+
+    if (strcmp(g_native_report.run_result, "not_applicable") == 0) {
+        friidump_native_report_set_reference(
+            &g_native_report, NULL, "not_applicable", NULL);
+    } else if (g_redump_attempted) {
+        if (g_redump_result.status == REDUMP_VERIFY_MATCH) {
+            friidump_native_report_set_reference(
+                &g_native_report,
+                "redump",
+                "match",
+                g_redump_result.rom_name[0]
+                    ? g_redump_result.rom_name
+                    : g_redump_result.game_name);
+        } else if (g_redump_result.status == REDUMP_VERIFY_NO_MATCH) {
+            friidump_native_report_set_reference(
+                &g_native_report, "redump", "mismatch", NULL);
+        } else {
+            friidump_native_report_set_reference(
+                &g_native_report, "redump", "not_checked", NULL);
+        }
+    } else if (options.xiso_requested) {
+        friidump_native_report_set_reference(
+            &g_native_report, NULL, "not_applicable", NULL);
+    } else {
+        friidump_native_report_set_reference(
+            &g_native_report, NULL, "not_checked", NULL);
+    }
+}
+
+static void friidump_native_publish(double duration, bool have_duration) {
+    char path[FRIIDUMP_REPORT_PATH_MAX];
+    char error_text[256];
+    const char *name_basis;
+
+    if (!friidump_native_report_is_enabled(&g_native_report))
+        return;
+
+    name_basis = xbox_ref_log_path();
+    if (!name_basis)
+        name_basis = friidump_requested_output_target();
+    if (!name_basis)
+        name_basis = "friidump.log";
+    friidump_native_report_set_name_basis(&g_native_report, name_basis);
+
+    if (g_native_report_dump_started && !g_native_report_resumed &&
+        !g_native_report.have_dump_duration && have_duration) {
+        g_native_report.have_dump_duration = true;
+        g_native_report.dump_duration_seconds = duration;
+    }
+
+    friidump_native_finalize_reference();
+
+    if (friidump_native_report_write(
+            &g_native_report,
+            path,
+            sizeof(path),
+            error_text,
+            sizeof(error_text))) {
+        fprintf(stderr, "Native report.......: %s (atomic write)\n", path);
+    } else {
+        fprintf(stderr, "WARNING: native report was not written: %s\n", error_text);
+    }
 }
 
 void help (void) {
@@ -901,9 +1755,20 @@ void help (void) {
                "     --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: redump_dat)\n"
+               "                              (default: bundled executable data, installed share data, then current directory)\n"
+               "                              Environment override: FRIIDUMP_REDUMP_DAT_DIR\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"
+               "                              Native .friidump.json reports are created by default\n"
+               "                              beside the final log using its filename\n"
+               "     --report-json <file>     Override the complete native report pathname\n"
+               "     --report-dir <directory> Override only the report destination directory\n"
+               "                              (may be combined with --report-json)\n"
+               "     --firmware-modified <note> Mark firmware modified; omission assumes stock\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"
@@ -914,6 +1779,14 @@ void help (void) {
                " -f, --donottflush             Do not call fflush() after every fwrite()\n"
 #endif
        );
+#ifndef WIN32
+       fprintf (stderr,
+               "\nLinux raw-I/O requirement:\n"
+               "  GC/Wii memory-dump methods and Xbox vendor-unlock paths require effective\n"
+               "  CAP_SYS_RAWIO. Apply cap_sys_rawio=ep to the exact validated executable;\n"
+               "  do not run FriiDump as root. Rebuilds and replacements clear capabilities.\n"
+               "  See docs/LINUX.md and validation/friidump-linux-rawio-capability.sh.\n");
+#endif
 
        return;
 }
@@ -962,6 +1835,11 @@ bool optparse (int argc, char **argv) {
                {"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},
+               {"report-json", 1, 0, 1011},
+               {"report-dir", 1, 0, 1012},
+               {"firmware-modified", 1, 0, 1013},
 #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'},
@@ -1010,6 +1888,13 @@ bool optparse (int argc, char **argv) {
        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;
+       options.native_report_json = NULL;
+       options.native_report_dir = NULL;
+       options.firmware_modified_note = NULL;
 
        do {
 #ifdef DEBUG
@@ -1165,6 +2050,25 @@ bool optparse (int argc, char **argv) {
                        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 1011:
+                               my_strdup (options.native_report_json, optarg);
+                               break;
+                       case 1012:
+                               my_strdup (options.native_report_dir, optarg);
+                               break;
+                       case 1013:
+                               my_strdup (options.firmware_modified_note, optarg);
+                               break;
                        case -1:
                                break;
                        default:
@@ -1179,6 +2083,9 @@ bool optparse (int argc, char **argv) {
                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) {
@@ -1192,6 +2099,35 @@ 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");
+       } 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 if ((options.native_report_json && !options.native_report_json[0]) ||
+                  (options.native_report_dir && !options.native_report_dir[0])) {
+               fprintf (stderr, "Native report file and directory arguments must not be empty.\n");
+       } else if ((options.native_report_json && strlen(options.native_report_json) >= FRIIDUMP_REPORT_PATH_MAX) ||
+                  (options.native_report_dir && strlen(options.native_report_dir) >= FRIIDUMP_REPORT_PATH_MAX)) {
+               fprintf (stderr, "Native report path is too long.\n");
+       } else if (options.firmware_modified_note &&
+                  !options.firmware_modified_note[0]) {
+               fprintf (stderr, "--firmware-modified requires a non-empty explanation.\n");
+       } else if (options.firmware_modified_note &&
+                  strlen(options.firmware_modified_note) > 1000) {
+               fprintf (stderr, "--firmware-modified note exceeds the 1000-byte report limit.\n");
+       } else if (friidump_native_report_option_requested() &&
+                  !friidump_native_report_operation_supported()) {
+               fprintf (stderr, "Native compatibility report options require one normal device dump invocation with exactly one primary output and cannot be combined with conversion, all-methods, stop-only, or research probe modes.\n");
+       } else if (options.device &&
+                  !options.allmethods && !options.stop_unit &&
+                  !options.hlds_e7_scan && !options.hlds_e7_subcmd_sweep &&
+                  !options.hlds_e7_memrange_sweep && !options.xgd1_layout_probe &&
+                  !options.xgd1_raw_id_probe &&
+                  options.raw_out && options.iso_requested) {
+               fprintf (stderr, "Native compatibility reports are created by default and require exactly one primary output; do not combine -r and -i.\n");
        } else {
                /* Specified options seem to make sense */
                out = true;
@@ -1212,8 +2148,10 @@ int dologic (disc *d, progstats *stats) {
        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;
+       out = false;
+       g_native_cancel_requested = 0;
        
        
 
@@ -1233,6 +2171,8 @@ int dologic (disc *d, progstats *stats) {
                                );
 
                                        if (xbox_output_requested && !disc_is_xbox_unlock_drive (d)) {
+                                               if (friidump_native_report_is_enabled(&g_native_report))
+                                                       friidump_native_report_add_note(&g_native_report, "The selected drive does not expose a supported Xbox unlock profile; dumping was not attempted.");
                                                fprintf (stderr,
                                                        "Xbox/XGD output is limited to the supported Xbox unlock profiles "
                                                        "currently wired into this branch: GDR-8050L, GDR-3120L, "
@@ -1249,8 +2189,11 @@ int dologic (disc *d, progstats *stats) {
 
                                        init_range(d, options.sec_disc, options.sec_mem);
 
-                                       if (!(disc_set_read_method (d, options.dump_method)))
-                                               exit (2);
+                                       if (!(disc_set_read_method (d, options.dump_method))) {
+                                               if (friidump_native_report_is_enabled(&g_native_report))
+                                                       friidump_native_report_add_note(&g_native_report, "FriiDump could not select the requested read method; dumping was not attempted.");
+                                               return false;
+                                       }
 
                                        if (xbox_output_requested && disc_get_method(d) == 10) {
                                                fprintf (stderr, "Command............: Xbox direct MMC/SCSI path\n");
@@ -1283,6 +2226,17 @@ int dologic (disc *d, progstats *stats) {
                                        }
                                        options.dump_method=disc_get_method(d);
                                        friidump_summary_begin(d);
+                                       friidump_native_set_profile(d, xbox_output_requested);
+#ifndef WIN32
+                                       if (!friidump_linux_rawio_preflight(
+                                                       d,
+                                                       xbox_output_requested,
+                                                       drive_supported,
+                                                       options.hlds_e7_scan ||
+                                                       options.hlds_e7_subcmd_sweep ||
+                                                       options.hlds_e7_memrange_sweep))
+                                               return false;
+#endif
                                if ((options.dump_method==0) 
                                || (options.dump_method==1) || (options.dump_method==2) || (options.dump_method==3)
                                || (options.dump_method==4) || (options.dump_method==5) || (options.dump_method==6)
@@ -1314,17 +2268,97 @@ int dologic (disc *d, progstats *stats) {
                                                        "  This GC/Wii 0xE7 path assumes a cross-flashed or modified GDR-8050L firmware with 0xE7 memdump support added.\n"
                                                        "  Stock GDR-8050L firmware is still supported for Xbox ripping, but it is not expected to dump GC/Wii discs through this path.\n");
                                        }
+#ifdef WIN32
                                        fprintf (stderr,
                                                "\nWindows AutoPlay warning:\n"
                                                "  HLDS 0xE7 GC/Wii seed reads are sensitive to Windows polling.\n"
                                                "  Disable AutoPlay for this drive/media and close File Explorer or any \"insert a disc\" dialogs before dumping.\n"
                                                "  FriiDump will try to lock the volume, but AutoPlay can still interfere before or during seed retrieval.\n");
                                        fprintf (stderr, "Applying Windows volume lock guard for HLDS 0xE7 seed reads... ");
+#else
+                                       fprintf (stderr,
+                                               "\nLinux media-polling warning:\n"
+                                               "  HLDS 0xE7 GC/Wii seed reads can be disturbed by desktop automount and media polling.\n"
+                                               "  Unmount the disc and close file managers or media players before dumping.\n");
+#endif
                                        disc_refresh_volume (d);
-                                       if (disc_lock_volume (d) < 0)
-                                               fprintf (stderr, "Warning: failed; disable AutoPlay, close File Explorer/AutoPlay dialogs for this drive, then rerun.\n");
+                                       {
+                                               int volume_lock_result = disc_lock_volume (d);
+#ifdef WIN32
+                                               if (volume_lock_result < 0)
+                                                       fprintf (stderr, "Warning: failed; disable AutoPlay, close File Explorer/AutoPlay dialogs for this drive, then rerun.\n");
+                                               else
+                                                       fprintf (stderr, "OK\n");
+#else
+                                               if (volume_lock_result > 0)
+                                                       fprintf (stderr, "Linux exclusive optical-device lock: unavailable; continuing without one.\n");
+                                               else if (volume_lock_result < 0)
+                                                       fprintf (stderr, "Linux exclusive optical-device lock: failed; continuing without one.\n");
+                                               else
+                                                       fprintf (stderr, "Linux exclusive optical-device lock: acquired.\n");
+#endif
+                                       }
+                               }
+
+                               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
-                                               fprintf (stderr, "OK\n");
+                                               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) {
@@ -1353,6 +2387,8 @@ int dologic (disc *d, progstats *stats) {
                                        fprintf (stderr, "\nChecking for ready media before disc seed retrieval... ");
                                        media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
                                        if (media_rc <= 0) {
+                                               friidump_native_set_preflight_failure(
+                                                       media_rc, media_sense_key, media_asc, media_ascq);
                                                if (media_rc == 0)
                                                        fprintf (stderr, "No readable disc present (sense %02X/%02X/%02X). Insert a disc, wait for spin-up, and retry.\n", media_sense_key, media_asc, media_ascq);
                                                else
@@ -1361,7 +2397,7 @@ int dologic (disc *d, progstats *stats) {
                                        }
                                        fprintf (stderr, "OK\n");
 
-                                       time_t seed_start, seed_end;
+                                       struct timeval seed_start, seed_end;
                                        double seed_elapsed;
                                        char seed_elapsed_buf[32];
 
@@ -1370,10 +2406,12 @@ int dologic (disc *d, progstats *stats) {
                                        else
                                                fprintf (stderr, "\nRetrieving disc seeds, this might take a while... ");
 
-                                       seed_start = time(NULL);
+                                       gettimeofday(&seed_start, NULL);
                                        if (!disc_init (d, options.disctype, options.sectors_no)) {
-                                               seed_end = time(NULL);
-                                               seed_elapsed = difftime(seed_end, seed_start);
+                                               gettimeofday(&seed_end, NULL);
+                                               seed_elapsed = (double)(seed_end.tv_sec - seed_start.tv_sec) +
+                                                       (double)(seed_end.tv_usec - seed_start.tv_usec) / (double)USECS_PER_SEC;
+                                               if (seed_elapsed < 0.0) seed_elapsed = 0.0;
                                                friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
                                                if (!xbox_output_requested)
                                                        fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
@@ -1381,11 +2419,42 @@ int dologic (disc *d, progstats *stats) {
                                                        g_validation_summary.have_seed_duration = true;
                                                        g_validation_summary.seed_duration = seed_elapsed;
                                                }
+                                               if (friidump_native_report_is_enabled(&g_native_report)) {
+                                                       if (g_native_cancel_requested) {
+                                                               friidump_native_report_set_seed(&g_native_report, true, "fail", true, seed_elapsed);
+                                                               friidump_native_report_set_outcome(&g_native_report, "other", "fail");
+                                                               friidump_native_report_add_note(&g_native_report, "The invocation was cancelled by the user during media initialization or seed retrieval.");
+                                                       } else if (xbox_output_requested) {
+                                                               friidump_native_report_set_seed(&g_native_report, false, "not_applicable", false, 0.0);
+                                                               friidump_native_report_set_outcome(&g_native_report, "other", "fail");
+                                                               friidump_native_report_add_note(&g_native_report, "Xbox/media initialization failed before dumping.");
+                                                       } else {
+                                                               friidump_native_report_set_seed(&g_native_report, true, "fail", true, seed_elapsed);
+                                                               friidump_native_report_set_outcome(&g_native_report, "seed_only", "fail");
+                                                               friidump_native_report_add_note(&g_native_report, "Disc initialization or seed retrieval failed.");
+                                                       }
+                                                       friidump_native_report_set_dump(&g_native_report, false, "not_attempted", false, 0, false, 0, false, 0.0, NULL, false, 0, NULL);
+                                               }
+                                               if (!xbox_output_requested) {
+                                                       bool stop_ok;
+                                                       friidump_capture_seed_diagnostic (d);
+                                                       stop_ok = disc_stop_unit (d, false);
+                                                       fprintf (stderr, "\nIssuing STOP UNIT / spin-down after seed failure... %s\n", stop_ok ? "OK" : "Failed");
+                                                       if (g_validation_summary.active) {
+                                                               g_validation_summary.stop_attempted = true;
+                                                               g_validation_summary.stop_ok = stop_ok;
+                                                       }
+                                                       if (friidump_native_report_is_enabled(&g_native_report))
+                                                               friidump_native_report_add_note(&g_native_report,
+                                                                       stop_ok ? "STOP UNIT after seed failure succeeded." : "STOP UNIT after seed failure failed.");
+                                               }
                                                fprintf (stderr, "Failed\n");
                                                out = false;
                                        } else {
-                                               seed_end = time(NULL);
-                                               seed_elapsed = difftime(seed_end, seed_start);
+                                               gettimeofday(&seed_end, NULL);
+                                               seed_elapsed = (double)(seed_end.tv_sec - seed_start.tv_sec) +
+                                                       (double)(seed_end.tv_usec - seed_start.tv_usec) / (double)USECS_PER_SEC;
+                                               if (seed_elapsed < 0.0) seed_elapsed = 0.0;
                                                friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
                                                if (!xbox_output_requested)
                                                        fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
@@ -1407,6 +2476,20 @@ int dologic (disc *d, progstats *stats) {
                                                friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), title);
                                                g_validation_summary.sectors = disc_get_sectors_no(d);
                                        }
+                                       friidump_native_set_media(type_id, title,
+                                                       type_id == DISC_TYPE_XBOX ? NULL : region, game_id);
+                                       if (friidump_native_report_is_enabled(&g_native_report)) {
+                                               if (type_id == DISC_TYPE_GAMECUBE || type_id == DISC_TYPE_WII || type_id == DISC_TYPE_WII_DL)
+                                                       friidump_native_report_set_seed(&g_native_report, true, "pass", true, seed_elapsed);
+                                               else
+                                                       friidump_native_report_set_seed(&g_native_report, false, "not_applicable", false, 0.0);
+                                       }
+                                       if (g_native_cancel_requested) {
+                                               friidump_native_report_set_dump(&g_native_report, false, "not_attempted", false, 0, false, 0, false, 0.0, NULL, false, 0, NULL);
+                                               friidump_native_report_set_outcome(&g_native_report, "other", "fail");
+                                               friidump_native_report_add_note(&g_native_report, "The invocation was cancelled by the user before dumping began.");
+                                               return false;
+                                       }
                                        fprintf (stderr, 
                                                "\n"
                                                "Disc information:\n"
@@ -1468,6 +2551,8 @@ int dologic (disc *d, progstats *stats) {
                                                        fprintf (stderr, options.xiso_out && options.xiso_out[0] ? "Writing to file \"%s\" in Xbox XISO format\n\n" : "Writing Xbox XISO using XBE-derived filename (override with -X <file>)\n\n", (options.xiso_out && options.xiso_out[0]) ? options.xiso_out : "");
 
                                                        dmp = dumper_new (d);
+                                                       if (friidump_native_report_is_enabled(&g_native_report))
+                                                               dumper_set_cancel_callback(dmp, friidump_native_cancel_callback, NULL);
                                                        dumper_set_hashing (dmp, !options.no_hashing);
                                                        dumper_set_flushing (dmp, !options.no_flushing);
 
@@ -1484,9 +2569,11 @@ int dologic (disc *d, progstats *stats) {
                                                                dump_attempted = true;
                                                                if (g_validation_summary.active)
                                                                        g_validation_summary.dump_attempted = true;
-                                                               if (dumper_dump_xiso (dmp, &current_sector)) {
+                                                               out = dumper_dump_xiso (dmp, &current_sector);
+                                                               gettimeofday(&(stats -> end_time), NULL);
+                                                               if (out) {
                                                                        fprintf (stderr, "Xbox XISO dump completed successfully!\n");
-                                                                       if (!options.no_hashing && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d)))
+                                                                       if (!options.no_hashing && !friidump_uses_windows_xbox_reference_path(d, type_id))
                                                                                fprintf (stderr,
                                                                                "XISO image hashes:\n"
                                                                                "CRC32...: %s\n"
@@ -1500,7 +2587,10 @@ int dologic (disc *d, progstats *stats) {
                                                                if (g_validation_summary.active)
                                                                        g_validation_summary.dump_ok = true;
                                                                } else {
-                                                                       fprintf (stderr, "\nXbox XISO dump failed at output sector: %u\n", current_sector);
+                                                                       if (dumper_was_cancelled(dmp))
+                                                                               fprintf (stderr, "\nXbox XISO dump cancelled at output sector: %u\n", current_sector);
+                                                                       else
+                                                                               fprintf (stderr, "\nXbox XISO dump failed at output sector: %u\n", current_sector);
                                                                        out = false;
                                                                        if (g_validation_summary.active) {
                                                                                g_validation_summary.dump_ok = false;
@@ -1509,6 +2599,12 @@ int dologic (disc *d, progstats *stats) {
                                                                }
                                                        }
 
+                                                       if (dump_attempted)
+                                                               friidump_native_capture_output(dmp, type_id, true, out, dumper_was_cancelled(dmp), current_sector, disc_get_sectors_no(d));
+                                                       else if (friidump_native_report_is_enabled(&g_native_report)) {
+                                                               friidump_native_report_set_outcome(&g_native_report, "other", "fail");
+                                                               friidump_native_report_add_note(&g_native_report, "Xbox XISO output could not be prepared; dumping was not attempted.");
+                                                       }
                                                        dmp = dumper_destroy (dmp);
                                                }
                                                } else if (options.raw_out || options.iso_requested) {
@@ -1525,6 +2621,8 @@ int dologic (disc *d, progstats *stats) {
                                                        fprintf (stderr, "\n");
 
                                                dmp = dumper_new (d);
+                                               if (friidump_native_report_is_enabled(&g_native_report))
+                                                       dumper_set_cancel_callback(dmp, friidump_native_cancel_callback, NULL);
 
                                                dumper_set_hashing (dmp, !options.no_hashing);
                                                dumper_set_flushing (dmp, !options.no_flushing);
@@ -1547,7 +2645,9 @@ int dologic (disc *d, progstats *stats) {
                                                        dump_attempted = true;
                                                        if (g_validation_summary.active)
                                                                g_validation_summary.dump_attempted = true;
-                                                       if (dumper_dump (dmp, &current_sector)) {
+                                                       out = dumper_dump (dmp, &current_sector);
+                                                       gettimeofday(&(stats -> end_time), NULL);
+                                                       if (out) {
                                                                fprintf (stderr, "Dump completed successfully!\n");
                                                                if (!options.no_hashing && options.raw_out)
                                                                        fprintf (stderr,
@@ -1561,7 +2661,7 @@ int dologic (disc *d, progstats *stats) {
                                                                                dumper_get_raw_crc32 (dmp), /*dumper_get_raw_md4 (dmp),*/ dumper_get_raw_md5 (dmp),
                                                                                dumper_get_raw_sha1 (dmp), dumper_get_raw_sha2 (dmp)/*, dumper_get_raw_ed2k (dmp)*/
                                                                        );
-                                                               if (!options.no_hashing && options.iso_out && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d)))
+                                                               if (!options.no_hashing && options.iso_out && !friidump_uses_windows_xbox_reference_path(d, type_id))
                                                                        fprintf (stderr,
                                                                                "ISO image hashes:\n"
                                                                                "CRC32...: %s\n"
@@ -1587,7 +2687,9 @@ int dologic (disc *d, progstats *stats) {
                                                                        g_validation_summary.dump_ok = false;
                                                                        g_validation_summary.fail_sector = current_sector;
                                                                }
-                                                               if (current_sector == 0xFFFFFFFFU)
+                                                               if (dumper_was_cancelled(dmp))
+                                                                       fprintf (stderr, "\nDump cancelled at sector: %u\n", current_sector);
+                                                               else if (current_sector == 0xFFFFFFFFU)
                                                                        fprintf (stderr, "\nXbox reference dumper failed; see the Xbox-specific message above.\n");
                                                                else
                                                                        fprintf (stderr, "\nDump failed at sectors: %u..%u\n", current_sector, current_sector+15);
@@ -1596,6 +2698,12 @@ int dologic (disc *d, progstats *stats) {
                                                        }
                                                }
 
+                                               if (dump_attempted)
+                                                       friidump_native_capture_output(dmp, type_id, false, out, dumper_was_cancelled(dmp), current_sector, disc_get_sectors_no(d));
+                                               else if (friidump_native_report_is_enabled(&g_native_report)) {
+                                                       friidump_native_report_set_outcome(&g_native_report, "other", "fail");
+                                                       friidump_native_report_add_note(&g_native_report, "Dump output could not be prepared; dumping was not attempted.");
+                                               }
                                                dmp = dumper_destroy (dmp);
                                        } else {
                                                fprintf (stderr, "No output file for dumping specified, please take a look at the -i, -r, -X and -a options\n");
@@ -1644,7 +2752,7 @@ static int try_all_methods (progstats *stats) {
 #ifndef WIN32
                                fprintf (stderr,
                                        "Probably you do not have access to the DVD device. Ask the administrator\n"
-                                       "to add you to the proper group, or use 'sudo'.\n"
+                                       "to add you to the proper device-access group. Do not run FriiDump as root.\n"
                                );
 #endif
                                continue;
@@ -1671,7 +2779,7 @@ int main (int argc, char *argv[]) {
        disc *d;
        progstats stats;
        double duration;
-       suseconds_t us;
+       bool have_duration;
        int out, ret;
        unscrambler *u;
        unscrambler_progress_func pfunc;
@@ -1679,6 +2787,8 @@ int main (int argc, char *argv[]) {
 
        /* First of all... */
        drop_euid ();
+       friidump_native_report_init(&g_native_report, PACKAGE_VERSION);
+       friidump_init_executable_dir((argc > 0) ? argv[0] : NULL);
        xbox_ref_log_open_for_target(NULL, 0);
        
        welcome ();
@@ -1688,6 +2798,7 @@ int main (int argc, char *argv[]) {
        out = false;
        ret = EXIT_FAILURE;
        if (optparse (argc, argv)) {
+               friidump_native_configure();
                friidump_retarget_log_from_options();
                if (options.device) {
                        if (options.allmethods) {
@@ -1701,11 +2812,12 @@ int main (int argc, char *argv[]) {
 #ifndef WIN32
                                        fprintf (stderr,
                                                "Probably you do not have access to the DVD device. Ask the administrator\n"
-                                               "to add you to the proper group, or use 'sudo'.\n"
+                                               "to add you to the proper device-access group. Do not run FriiDump as root.\n"
                                        );
 #endif
                                } else {
                                        fprintf (stderr, "OK\n");
+                                       friidump_native_set_drive(d);
                                        out = dologic (d, &stats);
                                        d = disc_destroy (d);
                                }
@@ -1729,35 +2841,36 @@ int main (int argc, char *argv[]) {
                        MY_ASSERT (0);
                }
 
-               if (out) {
-                       duration = stats.end_time.tv_sec - stats.start_time.tv_sec;
-                       if (stats.end_time.tv_usec >= stats.start_time.tv_usec) {
-                               us = stats.end_time.tv_usec - stats.start_time.tv_usec;
-                       } else {
-                               if (duration > 0)
-                                       duration--;
-                               us = USECS_PER_SEC + stats.end_time.tv_usec - stats.start_time.tv_usec;
-                       }
-                       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);
+               duration = 0.0;
+               have_duration = friidump_elapsed_from_stats(&stats, &duration);
+               if (g_operation_duration_override_valid) {
+                       duration = g_operation_duration_override;
+                       have_duration = true;
+               }
 
+               if (out) {
+                       if (have_duration)
+                               fprintf (stderr, "Operation took %.2f seconds\n", duration);
+                       friidump_print_validation_summary(duration, have_duration);
                        ret = EXIT_SUCCESS;
                } else {
-                       friidump_print_validation_summary(0.0, false);
+                       friidump_print_validation_summary(duration, have_duration);
                        ret = EXIT_FAILURE;
                }
-               
+
+               friidump_native_publish(duration, have_duration);
+
                my_free (options.device);
                my_free (options.iso_out);
                my_free (options.xiso_out);
                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);
+               my_free (options.native_report_json);
+               my_free (options.native_report_dir);
+               my_free (options.firmware_modified_note);
        }
 
        if (xbox_ref_log_path())