#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>
+#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
bool active;
bool dump_attempted;
bool seed_ok;
+ bool seed_applicable;
bool have_seed_duration;
double seed_duration;
bool dump_ok;
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;
} 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 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)
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 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)
}
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;
- 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;
+ seed_status = g_validation_summary.seed_applicable
+ ? (g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED")
+ : "N/A (Xbox reference auth path)";
+ 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"
" Cache base........: 0x%08x\n"
" Memory windows....: %u\n"
" Disc type.........: %s\n"
- " Game ID...........: %s\n"
- " Title.............: %s\n"
- " Output............: %s\n"
- " Seed read.........: %s\n"
- " Dump status.......: %s\n"
- " STOP UNIT.........: %s\n",
+ " Game/Media ID.....: %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)",
- g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED",
- g_validation_summary.dump_attempted ? (g_validation_summary.dump_ok ? "OK" : "FAILED") : "NOT ATTEMPTED",
+ seed_status,
+ 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);
- if (g_validation_summary.dump_ok)
- fprintf(stderr, " Hash compare......: compare printed hashes against known-good target for this disc\n");
+ 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));
+ 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");
+ }
+ }
}
#define PACKAGE_NAME "FriiDump"
/* Define to the version of this package. */
-#define PACKAGE_VERSION "0.5.3.5"
+#define PACKAGE_VERSION "0.5.3.16-pf1-candidate21"
#ifdef WIN32
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;
+ char *native_report_json;
+ char *native_report_dir;
+ char *firmware_modified_note;
} options;
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;
}
+
+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 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;
+
+ 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,
+ "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) {
/* 80 cols guide:
* |-------------------------------------------------------------------------------|
" --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: 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"
" -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;
}
{"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},
+ {"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'},
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;
+ options.native_report_json = NULL;
+ options.native_report_dir = NULL;
+ options.firmware_modified_note = NULL;
do {
#ifdef DEBUG
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 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:
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) {
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;
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;
);
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, "
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");
}
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)
" 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) {
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
}
fprintf (stderr, "OK\n");
- time_t seed_start, seed_end;
+ struct timeval seed_start, seed_end;
double seed_elapsed;
char seed_elapsed_buf[32];
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);
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);
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"
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);
dump_attempted = true;
if (g_validation_summary.active)
g_validation_summary.dump_attempted = true;
- if (dumper_dump_xiso (dmp, ¤t_sector)) {
+ out = dumper_dump_xiso (dmp, ¤t_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"
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;
}
}
+ 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) {
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);
dump_attempted = true;
if (g_validation_summary.active)
g_validation_summary.dump_attempted = true;
- if (dumper_dump (dmp, ¤t_sector)) {
+ out = dumper_dump (dmp, ¤t_sector);
+ gettimeofday(&(stats -> end_time), NULL);
+ if (out) {
fprintf (stderr, "Dump completed successfully!\n");
if (!options.no_hashing && options.raw_out)
fprintf (stderr,
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"
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;
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);
}
}
+ 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");
#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;
disc *d;
progstats stats;
double duration;
- suseconds_t us;
+ bool have_duration;
int out, ret;
unscrambler *u;
unscrambler_progress_func pfunc;
/* 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 ();
out = false;
ret = EXIT_FAILURE;
if (optparse (argc, argv)) {
+ friidump_native_configure();
friidump_retarget_log_from_options();
if (options.device) {
if (options.allmethods) {
#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);
}
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;
- 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())