X-Git-Url: https://git.jtryba.com/browse/friidump.git/blobdiff_plain/d44030dfa440b1967ec7dcd3ccfe46fb69934486..HEAD:/src/friidump.c diff --git a/src/friidump.c b/src/friidump.c index f8b5292..f42b744 100644 --- a/src/friidump.c +++ b/src/friidump.c @@ -21,6 +21,7 @@ #include "misc.h" #include +#include #include #include #include @@ -28,6 +29,22 @@ #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 +#include +#include +#else +#include +#include +#endif #define printf xbox_ref_printf #define fprintf xbox_ref_log_fprintf @@ -41,6 +58,7 @@ typedef struct { bool active; bool dump_attempted; bool seed_ok; + bool seed_applicable; bool have_seed_duration; double seed_duration; bool dump_ok; @@ -50,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; @@ -62,6 +85,15 @@ typedef struct { } 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) @@ -74,6 +106,9 @@ static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) { static void friidump_summary_reset(void) { memset(&g_validation_summary, 0, sizeof(g_validation_summary)); g_validation_summary.fail_sector = 0xFFFFFFFFU; + 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) { @@ -90,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) @@ -108,11 +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; - 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" @@ -121,38 +413,71 @@ static void friidump_print_validation_summary(double duration, bool have_duratio " 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"); + } + } } @@ -166,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.5" +#define PACKAGE_VERSION "0.5.3.16-pf1" #ifdef WIN32 @@ -213,6 +538,16 @@ struct { 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; @@ -235,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; @@ -453,6 +790,884 @@ void welcome (void) { } + +static void friidump_init_executable_dir(const char *argv0) { + char path[1024]; + size_t length = 0; + char *slash; + char *backslash; + char *separator; + + g_executable_dir[0] = '\0'; + path[0] = '\0'; + +#ifdef WIN32 + { + DWORD result = GetModuleFileNameA(NULL, path, (DWORD)sizeof(path)); + if (result > 0 && result < sizeof(path)) { + path[result] = '\0'; + length = (size_t)result; + } + } +#else +#if defined(__linux__) + { + ssize_t result = readlink("/proc/self/exe", path, sizeof(path) - 1); + if (result > 0 && (size_t)result < sizeof(path)) { + path[result] = '\0'; + length = (size_t)result; + } + } +#endif + if (length == 0 && argv0 && argv0[0] && + (strchr(argv0, '/') || strchr(argv0, '\\'))) { + char *resolved = realpath(argv0, path); + if (resolved) + length = strlen(path); + } +#endif + + if (length == 0) + return; + + slash = strrchr(path, '/'); + backslash = strrchr(path, '\\'); + separator = slash; + if (backslash && (!separator || backslash > separator)) + separator = backslash; + + if (!separator) + return; + + *separator = '\0'; + if (path[0]) + snprintf(g_executable_dir, sizeof(g_executable_dir), "%s", path); +} + +static const char *friidump_redump_dat_basename(disc_type type_id) { + switch (type_id) { + case DISC_TYPE_GAMECUBE: + return "Nintendo - GameCube.dat"; + case DISC_TYPE_WII: + case DISC_TYPE_WII_DL: + return "Nintendo - Wii.dat"; + case DISC_TYPE_XBOX: + return "Microsoft - Xbox.dat"; + default: + return NULL; + } +} + +static uint64_t friidump_file_size(const char *path) { + if (!path || !path[0]) + return 0; +#ifdef WIN32 + { + struct _stat64 st; + if (_stat64(path, &st) == 0) + return (uint64_t)st.st_size; + } +#else + { + struct stat st; + if (stat(path, &st) == 0) + return (uint64_t)st.st_size; + } +#endif + return 0; +} + +static bool friidump_sync_report_file(FILE *f) { + if (!f || fflush(f) != 0) + return false; +#ifdef WIN32 + return _commit(_fileno(f)) == 0; +#else + return fsync(fileno(f)) == 0; +#endif +} + +static bool friidump_atomic_replace(const char *temporary_path, const char *final_path) { + if (!temporary_path || !final_path) + return false; +#ifdef WIN32 + return MoveFileExA(temporary_path, final_path, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0; +#else + return rename(temporary_path, final_path) == 0; +#endif +} + +static void friidump_json_write_string_value(FILE *f, const char *prefix, + const char *value, const char *suffix) { + fputs(prefix, f); + friidump_json_string(f, value ? value : ""); + fputs(suffix, f); +} + +static void friidump_write_redump_report(const redump_verify_result *result, + const char *output_path, + uint64_t output_size, + const char *crc32, + const char *md5, + const char *sha1, + const char *sha256, + const char *hash_source, + const char *representation_note) { + FILE *f; + char *temporary_path; + char line[256]; + size_t temporary_path_size; + bool write_ok; + + if (!options.redump_report || !options.redump_report[0] || !result) + return; + + temporary_path_size = strlen(options.redump_report) + 5; + temporary_path = (char *)malloc(temporary_path_size); + if (!temporary_path) { + fprintf(stderr, "Redump report.......: memory allocation failed\n"); + return; + } + snprintf(temporary_path, temporary_path_size, "%s.tmp", options.redump_report); + remove(temporary_path); + + f = fopen(temporary_path, "wb"); + if (!f) { + fprintf(stderr, "Redump report.......: could not write temporary file %s\n", temporary_path); + free(temporary_path); + return; + } + + /* Use fputs/fputc rather than the logged fprintf wrapper. Report contents + * belong in the JSON artifact, not as a malformed partial mirror in the + * human-readable run log. */ + fputs("{\n", f); + fputs(" \"schema\": 2,\n", f); + friidump_json_write_string_value(f, " \"producer\": ", "friidump-" PACKAGE_VERSION, ",\n"); + friidump_json_write_string_value(f, " \"status\": ", redump_verify_status_string(result->status), ",\n"); + friidump_json_write_string_value(f, " \"overall\": ", redump_verify_overall_string(result), ",\n"); + friidump_json_write_string_value(f, " \"confidence\": ", redump_verify_confidence_string(result), ",\n"); + friidump_json_write_string_value(f, " \"output\": ", output_path ? output_path : "", ",\n"); + snprintf(line, sizeof(line), " \"output_size\": %llu,\n", (unsigned long long)output_size); + fputs(line, f); + friidump_json_write_string_value(f, " \"crc32\": ", crc32 ? crc32 : "", ",\n"); + friidump_json_write_string_value(f, " \"md5\": ", md5 ? md5 : "", ",\n"); + friidump_json_write_string_value(f, " \"sha1\": ", sha1 ? sha1 : "", ",\n"); + friidump_json_write_string_value(f, " \"sha256\": ", sha256 ? sha256 : "", ",\n"); + friidump_json_write_string_value(f, " \"hash_source\": ", hash_source ? hash_source : "", ",\n"); + friidump_json_write_string_value(f, " \"representation_note\": ", representation_note ? representation_note : "", ",\n"); + friidump_json_write_string_value(f, " \"dat_path\": ", result->dat_path, ",\n"); + snprintf(line, sizeof(line), " \"entries_scanned\": %lu,\n", result->entries_scanned); + fputs(line, f); + snprintf(line, sizeof(line), " \"exact_matches\": %lu,\n", result->exact_matches); + fputs(line, f); + friidump_json_write_string_value(f, " \"match_kind\": ", redump_verify_match_kind_string(result), ",\n"); + friidump_json_write_string_value(f, " \"game_name\": ", result->game_name, ",\n"); + friidump_json_write_string_value(f, " \"rom_name\": ", result->rom_name, ",\n"); + + fputs(" \"field_match_counts\": {\n", f); + snprintf(line, sizeof(line), " \"size\": %lu,\n", result->size_matches); fputs(line, f); + snprintf(line, sizeof(line), " \"crc32\": %lu,\n", result->crc32_matches); fputs(line, f); + snprintf(line, sizeof(line), " \"md5\": %lu,\n", result->md5_matches); fputs(line, f); + snprintf(line, sizeof(line), " \"sha1\": %lu\n", result->sha1_matches); fputs(line, f); + fputs(" },\n", f); + + fputs(" \"expected\": {\n", f); + friidump_json_write_string_value(f, " \"size\": ", result->expected_size, ",\n"); + friidump_json_write_string_value(f, " \"crc32\": ", result->expected_crc32, ",\n"); + friidump_json_write_string_value(f, " \"md5\": ", result->expected_md5, ",\n"); + friidump_json_write_string_value(f, " \"sha1\": ", result->expected_sha1, "\n"); + fputs(" },\n", f); + + fputs(" \"evidence\": {\n", f); + friidump_json_write_string_value(f, " \"size\": ", redump_field_status_string(result->size_status), ",\n"); + friidump_json_write_string_value(f, " \"crc32\": ", redump_field_status_string(result->crc32_status), ",\n"); + friidump_json_write_string_value(f, " \"md5\": ", redump_field_status_string(result->md5_status), ",\n"); + friidump_json_write_string_value(f, " \"sha1\": ", redump_field_status_string(result->sha1_status), "\n"); + fputs(" },\n", f); + friidump_json_write_string_value(f, " \"detail\": ", result->detail, "\n"); + fputs("}\n", f); + + write_ok = !ferror(f) && friidump_sync_report_file(f); + if (fclose(f) != 0) + write_ok = false; + + if (!write_ok) { + remove(temporary_path); + fprintf(stderr, "Redump report.......: write/flush failed; final report was not replaced\n"); + free(temporary_path); + return; + } + + if (!friidump_atomic_replace(temporary_path, options.redump_report)) { + fprintf(stderr, "Redump report.......: could not replace %s; complete temporary report retained at %s\n", + options.redump_report, temporary_path); + free(temporary_path); + return; + } + + fprintf(stderr, "Redump report.......: %s (atomic write)\n", options.redump_report); + free(temporary_path); +} + +static void friidump_print_redump_field(const char *label, redump_field_status status) { + fprintf(stderr, "%-20s %s\n", label, redump_field_status_string(status)); +} + +static void friidump_verify_redump_values(disc_type type_id, + const char *output_path, + uint64_t output_size, + const char *crc32, + const char *md5, + const char *sha1, + const char *sha256, + const char *hash_source) { + const char *basename; + const char *dat_dir; + const char *representation_note; + char dat_path[1024]; + + redump_verify_result_init(&g_redump_result); + g_redump_attempted = false; + representation_note = (type_id == DISC_TYPE_XBOX) + ? "XGD1 acquisition success and exact Redump hash identity are separate claims; unresolved zero-filled pregame/postgame content may prevent an exact match." + : ""; + + if (options.no_redump_verify) { + fprintf(stderr, "Redump verification: DISABLED (--no-redump-verify)\n"); + return; + } + if (options.no_hashing) { + fprintf(stderr, "Redump verification: SKIPPED (hashing disabled)\n"); + return; + } + if (!output_path || !output_path[0]) { + fprintf(stderr, "Redump verification: SKIPPED (final output path unavailable)\n"); + return; + } + if (!crc32 || !crc32[0] || !md5 || !md5[0] || !sha1 || !sha1[0]) { + fprintf(stderr, "Redump verification: SKIPPED (finalized CRC32/MD5/SHA-1 evidence incomplete)\n"); + return; + } + + basename = friidump_redump_dat_basename(type_id); + if (!basename) { + fprintf(stderr, "Redump verification: SKIPPED (no DAT mapping for this disc type)\n"); + return; + } + + dat_dir = (options.redump_dat_dir && options.redump_dat_dir[0]) + ? options.redump_dat_dir + : NULL; + redump_resolve_dat_path(dat_dir, g_executable_dir, basename, + dat_path, sizeof(dat_path)); + if (output_size == 0) + output_size = friidump_file_size(output_path); + + g_redump_attempted = true; + redump_verify_dat_file(dat_path, output_size, crc32, md5, sha1, &g_redump_result); + + fprintf(stderr, "\nRedump verification\n"); + fprintf(stderr, "------------------------------------------------------------\n"); + fprintf(stderr, "%-20s %s\n", "DAT", g_redump_result.dat_path); + fprintf(stderr, "%-20s %s\n", "Hash source", hash_source ? hash_source : "Finalized output hashes"); + fprintf(stderr, "%-20s %lu\n", "Entries scanned", g_redump_result.entries_scanned); + + if (g_redump_result.status == REDUMP_VERIFY_MATCH) { + fprintf(stderr, "%-20s %s\n", "Matched entry", g_redump_result.game_name); + fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name); + friidump_print_redump_field("Image size", g_redump_result.size_status); + friidump_print_redump_field("CRC32", g_redump_result.crc32_status); + friidump_print_redump_field("MD5", g_redump_result.md5_status); + friidump_print_redump_field("SHA-1", g_redump_result.sha1_status); + if (g_redump_result.exact_matches > 1) + fprintf(stderr, "%-20s %lu exact entries\n", "Duplicate matches", g_redump_result.exact_matches); + } else if (g_redump_result.candidate_available) { + fprintf(stderr, "%-20s %s\n", "Closest candidate", g_redump_result.game_name); + fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name); + friidump_print_redump_field("Image size", g_redump_result.size_status); + friidump_print_redump_field("CRC32", g_redump_result.crc32_status); + friidump_print_redump_field("MD5", g_redump_result.md5_status); + friidump_print_redump_field("SHA-1", g_redump_result.sha1_status); + } else { + fprintf(stderr, "%-20s %s\n", "Closest candidate", "None (no hash-correlated entry)"); + fprintf(stderr, "%-20s %lu entries\n", "Size matches", g_redump_result.size_matches); + fprintf(stderr, "%-20s %lu entries\n", "CRC32 matches", g_redump_result.crc32_matches); + fprintf(stderr, "%-20s %lu entries\n", "MD5 matches", g_redump_result.md5_matches); + fprintf(stderr, "%-20s %lu entries\n", "SHA-1 matches", g_redump_result.sha1_matches); + } + + fprintf(stderr, "%-20s %s\n", "Overall", redump_verify_overall_string(&g_redump_result)); + fprintf(stderr, "%-20s %s\n", "Confidence", redump_verify_confidence_string(&g_redump_result)); + if (g_redump_result.status != REDUMP_VERIFY_MATCH) + fprintf(stderr, "%-20s %s\n", "Detail", g_redump_result.detail); + if (type_id == DISC_TYPE_XBOX) + fprintf(stderr, "%-20s %s\n", "Representation note", representation_note); + + friidump_write_redump_report(&g_redump_result, output_path, output_size, + crc32, md5, sha1, sha256, hash_source, + representation_note); +} + +static void friidump_verify_redump_iso(dumper *dmp, disc_type type_id) { + const char *output_path; + + if (!dmp) + return; + + output_path = options.iso_out; + friidump_verify_redump_values(type_id, + output_path, + friidump_file_size(output_path), + dumper_get_iso_crc32(dmp), + dumper_get_iso_md5(dmp), + dumper_get_iso_sha1(dmp), + dumper_get_iso_sha2(dmp), + "FriiDump finalized ISO multihash"); +} + +static 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: * |-------------------------------------------------------------------------------| @@ -539,6 +1754,21 @@ void help (void) { " --scan-log JSON output path for --hlds-e7-scan\n" " --scan-dump-prefix Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n" " --hlds-profile-report Write selected HLDS profile/evidence JSON\n" + " --redump-dat-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 Write atomic Redump evidence JSON\n" + " --no-redump-verify Disable automatic post-dump DAT verification\n" + " --xgd1-layout-probe Read-only locked/unlocked XGD1 boundary probe;\n" + " writes atomic JSON and does not create an ISO\n" + " --xgd1-raw-id-probe 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 Override the complete native report pathname\n" + " --report-dir Override only the report destination directory\n" + " (may be combined with --report-json)\n" + " --firmware-modified 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" @@ -549,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; } @@ -594,6 +1832,14 @@ bool optparse (int argc, char **argv) { {"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'}, @@ -639,6 +1885,16 @@ bool optparse (int argc, char **argv) { options.hlds_e7_scan_log = NULL; options.hlds_e7_scan_dump_prefix = NULL; options.hlds_profile_report = NULL; + options.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 @@ -785,6 +2041,34 @@ bool optparse (int argc, char **argv) { 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: @@ -799,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) { @@ -812,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; @@ -832,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; @@ -853,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, " @@ -869,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"); @@ -903,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) @@ -934,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) { @@ -973,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 @@ -981,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]; @@ -990,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); @@ -1001,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); @@ -1027,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" @@ -1088,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 )\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); @@ -1104,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, ¤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" @@ -1120,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; @@ -1129,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) { @@ -1145,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); @@ -1167,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, ¤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, @@ -1181,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" @@ -1194,6 +2674,11 @@ int dologic (disc *d, progstats *stats) { 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; @@ -1202,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); @@ -1211,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"); @@ -1259,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; @@ -1286,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; @@ -1294,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 (); @@ -1303,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) { @@ -1316,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); } @@ -1344,33 +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; - 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())