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