+ "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);
+ }