]> FriiDump Source - friidump.git/blob - src/friidump.c
FriiDump 0.5.3.16 candidate: add native reports and Linux Xbox support
[friidump.git] / src / friidump.c
1 /***************************************************************************
2  *   Copyright (C) 2007 by Arep                                            *
3  *   Support is provided through the forums at                             *
4  *   http://wii.console-tribe.com                                          *
5  *                                                                         *
6  *   This program is free software; you can redistribute it and/or modify  *
7  *   it under the terms of the GNU General Public License as published by  *
8  *   the Free Software Foundation; either version 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  *                                                                         *
11  *   This program is distributed in the hope that it will be useful,       *
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14  *   GNU General Public License for more details.                          *
15  *                                                                         *
16  *   You should have received a copy of the GNU General Public License     *
17  *   along with this program; if not, write to the                         *
18  *   Free Software Foundation, Inc.,                                       *
19  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
20  ***************************************************************************/
21
22 #include "misc.h"
23 #include <stdio.h>
24 #include <signal.h>
25 #include <stdlib.h>
26 #include <time.h>
27 #include <string.h>
28 #include "disc.h"
29 #include "dumper.h"
30 #include "unscrambler.h"
31 #include "xbox_ref/xbox_ref_log.h"
32 #include "xbox_ref_bridge.h"
33 #include "redump_dat.h"
34 #include "native_report.h"
35
36 #ifndef WIN32
37 #include "linux_rawio.h"
38 #endif
39
40 #ifdef WIN32
41 #include <sys/stat.h>
42 #include <io.h>
43 #include <windows.h>
44 #else
45 #include <sys/stat.h>
46 #include <unistd.h>
47 #endif
48
49 #define printf xbox_ref_printf
50 #define fprintf xbox_ref_log_fprintf
51
52 #define USECS_PER_SEC   1000000
53
54
55 static const char *friidump_requested_output_target(void);
56
57 typedef struct {
58         bool active;
59         bool dump_attempted;
60         bool seed_ok;
61         bool seed_applicable;
62         bool have_seed_duration;
63         double seed_duration;
64         bool dump_ok;
65         bool stop_attempted;
66         bool stop_ok;
67         char model[256];
68         char disc_type[64];
69         char game_id[64];
70         char title[256];
71         char region[128];
72         char output[512];
73         u_int32_t command;
74         int method;
75         u_int32_t sectors;
76         u_int32_t source_sectors;
77         u_int32_t expected_output_sectors;
78         bool have_source_sectors;
79         bool have_expected_output_sectors;
80         u_int32_t fail_sector;
81         u_int32_t hlds_type;
82         u_int32_t cache_base;
83         u_int32_t mem_blocks;
84         const char *profile;
85 } friidump_validation_summary;
86
87 static friidump_validation_summary g_validation_summary;
88 static redump_verify_result g_redump_result;
89 static bool g_redump_attempted = false;
90 static bool g_operation_duration_override_valid = false;
91 static double g_operation_duration_override = 0.0;
92 static char g_executable_dir[1024];
93 static friidump_native_report g_native_report;
94 static bool g_native_report_dump_started = false;
95 static bool g_native_report_resumed = false;
96 static volatile sig_atomic_t g_native_cancel_requested = 0;
97
98 static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
99         if (!dst || dst_size == 0)
100                 return;
101         if (!src)
102                 src = "";
103         snprintf(dst, dst_size, "%s", src);
104 }
105
106 static void friidump_summary_reset(void) {
107         memset(&g_validation_summary, 0, sizeof(g_validation_summary));
108         g_validation_summary.fail_sector = 0xFFFFFFFFU;
109         g_validation_summary.seed_applicable = true;
110         g_operation_duration_override_valid = false;
111         g_operation_duration_override = 0.0;
112 }
113
114 static void friidump_format_hms(double seconds, char *buf, size_t buf_size) {
115         long total, hours, minutes, secs;
116         if (!buf || buf_size == 0)
117                 return;
118         if (seconds < 0.0)
119                 seconds = 0.0;
120         total = (long) (seconds + 0.5);
121         hours = total / 3600;
122         minutes = (total / 60) % 60;
123         secs = total % 60;
124         snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hours, minutes, secs);
125 }
126
127
128 static bool friidump_capture_seed_diagnostic (disc *d) {
129         disc_seed_diagnostic diag;
130         char cdb_text[3 * 12 + 1];
131         char note[768];
132         int i;
133         int used;
134
135         memset (&diag, 0, sizeof (diag));
136         if (!disc_get_seed_diagnostic (d, &diag))
137                 return false;
138
139         cdb_text[0] = '\0';
140         used = 0;
141         for (i = 0; i < diag.cdb_length && i < 12; i++) {
142                 int wrote = snprintf (cdb_text + used, sizeof (cdb_text) - (size_t) used,
143                         "%s%02X", i ? " " : "", diag.cdb[i]);
144                 if (wrote < 0 || wrote >= (int) (sizeof (cdb_text) - (size_t) used))
145                         break;
146                 used += wrote;
147         }
148
149         fprintf (stderr,
150                 "\nSeed diagnostic final failure:\n"
151                 "  Block.............: %u/20\n"
152                 "  Sectors...........: %u..%u\n"
153                 "  Retry.............: %d\n"
154                 "  Stage.............: %s\n"
155                 "  Detail............: %s\n"
156                 "  Transport result..: %d\n"
157                 "  OS error..........: %d\n"
158                 "  SCSI status.......: 0x%02X\n"
159                 "  Sense.............: %02X/%02X/%02X\n"
160                 "  CDB...............: %s\n",
161                 diag.seed_block_index + 1,
162                 diag.sector_start, diag.sector_end,
163                 diag.retry,
164                 diag.stage[0] ? diag.stage : "unknown",
165                 diag.detail[0] ? diag.detail : "none",
166                 diag.transport_result,
167                 diag.os_error,
168                 diag.scsi_status & 0xff,
169                 diag.sense_key & 0xff,
170                 diag.asc & 0xff,
171                 diag.ascq & 0xff,
172                 cdb_text[0] ? cdb_text : "none");
173
174         snprintf (note, sizeof (note),
175                 "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].",
176                 diag.seed_block_index + 1,
177                 diag.sector_start, diag.sector_end,
178                 diag.retry,
179                 diag.stage[0] ? diag.stage : "unknown",
180                 diag.detail[0] ? diag.detail : "none",
181                 diag.transport_result,
182                 diag.os_error,
183                 diag.scsi_status & 0xff,
184                 diag.sense_key & 0xff,
185                 diag.asc & 0xff,
186                 diag.ascq & 0xff,
187                 cdb_text[0] ? cdb_text : "none");
188         if (friidump_native_report_is_enabled (&g_native_report))
189                 friidump_native_report_add_note (&g_native_report, note);
190         return true;
191 }
192
193 #ifndef WIN32
194 static bool friidump_linux_vendor_rawio_required(
195     disc *d,
196     bool xbox_output_requested,
197     bool drive_supported,
198     bool probe_requested) {
199     int method;
200
201     if (!d)
202         return false;
203
204     if (probe_requested)
205         return true;
206
207     if (xbox_output_requested)
208         return disc_is_xbox_unlock_drive(d);
209
210     method = (int) disc_get_method(d);
211     return drive_supported && method >= 0 && method <= 9;
212 }
213
214 static void friidump_linux_record_rawio_failure(
215     disc *d,
216     bool xbox_output_requested,
217     const char *note) {
218     bool stop_ok;
219
220     if (friidump_native_report_is_enabled(&g_native_report)) {
221         friidump_native_report_set_seed(
222             &g_native_report,
223             false,
224             xbox_output_requested ? "not_applicable" : "not_attempted",
225             false,
226             0.0);
227         friidump_native_report_set_dump(
228             &g_native_report,
229             false,
230             "not_attempted",
231             false,
232             0,
233             false,
234             0,
235             false,
236             0.0,
237             NULL,
238             false,
239             0,
240             NULL);
241         friidump_native_report_set_outcome(&g_native_report, "other", "fail");
242         friidump_native_report_add_note(&g_native_report, note);
243     }
244
245     stop_ok = disc_stop_unit(d, false);
246     fprintf(stderr,
247         "Issuing STOP UNIT / spin-down after Linux raw-I/O preflight failure... %s\n",
248         stop_ok ? "OK" : "Failed");
249
250     if (g_validation_summary.active) {
251         g_validation_summary.stop_attempted = true;
252         g_validation_summary.stop_ok = stop_ok;
253     }
254
255     if (friidump_native_report_is_enabled(&g_native_report)) {
256         friidump_native_report_add_note(
257             &g_native_report,
258             stop_ok
259                 ? "STOP UNIT after Linux raw-I/O preflight failure succeeded."
260                 : "STOP UNIT after Linux raw-I/O preflight failure failed.");
261     }
262 }
263
264 static bool friidump_linux_rawio_preflight(
265     disc *d,
266     bool xbox_output_requested,
267     bool drive_supported,
268     bool probe_requested) {
269     friidump_linux_rawio_state state;
270     unsigned long long effective_mask = 0;
271     char executable_path[1024];
272     char note[1536];
273     const char *display_path;
274
275     if (!friidump_linux_vendor_rawio_required(
276             d, xbox_output_requested, drive_supported, probe_requested))
277         return true;
278
279     executable_path[0] = '\0';
280     if (!friidump_linux_executable_path(
281             executable_path, sizeof(executable_path))) {
282         const char *fallback_dir = g_executable_dir[0] ? g_executable_dir : ".";
283         const char *suffix = "friidump";
284         size_t used = strlen(fallback_dir);
285         size_t suffix_len = strlen(suffix);
286
287         if (used >= sizeof(executable_path))
288             used = sizeof(executable_path) - 1;
289         memcpy(executable_path, fallback_dir, used);
290         executable_path[used] = '\0';
291
292         if (used > 0 && executable_path[used - 1] != '/' &&
293                 used + 1 < sizeof(executable_path)) {
294             executable_path[used++] = '/';
295             executable_path[used] = '\0';
296         }
297
298         if (suffix_len > sizeof(executable_path) - used - 1)
299             suffix_len = sizeof(executable_path) - used - 1;
300         memcpy(executable_path + used, suffix, suffix_len);
301         executable_path[used + suffix_len] = '\0';
302     }
303     display_path = executable_path;
304
305     if (geteuid() == 0) {
306         fprintf(stderr,
307             "\nERROR: FriiDump refuses this Linux vendor-command path while running as root.\n"
308             "  Run FriiDump as your normal user and grant only CAP_SYS_RAWIO to the\n"
309             "  exact validated executable. Do not use sudo to run the whole program.\n"
310             "  Exact executable: %s\n"
311             "  Install: sudo setcap cap_sys_rawio=ep '%s'\n"
312             "  Verify:  getcap '%s'\n"
313             "  Rebuilding or replacing the executable clears file capabilities.\n\n",
314             display_path, display_path, display_path);
315         snprintf(
316             note,
317             sizeof(note),
318             "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).",
319             display_path);
320         friidump_linux_record_rawio_failure(d, xbox_output_requested, note);
321         return false;
322     }
323
324     state = friidump_linux_rawio_effective(&effective_mask);
325     if (state == FRIIDUMP_LINUX_RAWIO_PRESENT) {
326         fprintf(stderr,
327             "Linux raw-I/O preflight: PASS (CAP_SYS_RAWIO effective; CapEff=0x%016llX).\n",
328             effective_mask);
329         return true;
330     }
331
332     fprintf(stderr,
333         "\nERROR: Linux vendor-command authorization is unavailable.\n"
334         "  This operation uses vendor-specific SCSI commands and requires effective\n"
335         "  CAP_SYS_RAWIO. Device access through the cdrom group is necessary but is\n"
336         "  not sufficient for commands such as HLDS 0xE7 memory reads.\n"
337         "  Exact executable: %s\n"
338         "  Install: sudo setcap cap_sys_rawio=ep '%s'\n"
339         "  Verify:  getcap '%s'\n"
340         "  Expected: %s cap_sys_rawio=ep\n"
341         "  Do not run the entire FriiDump process as root. Rebuilding or replacing\n"
342         "  the executable clears file capabilities, so reapply and verify afterward.\n\n",
343         display_path, display_path, display_path, display_path);
344
345     snprintf(
346         note,
347         sizeof(note),
348         "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.",
349         state == FRIIDUMP_LINUX_RAWIO_MISSING ? "missing" : "unavailable/undetectable",
350         display_path,
351         effective_mask);
352     friidump_linux_record_rawio_failure(d, xbox_output_requested, note);
353     return false;
354 }
355 #endif
356
357 static void friidump_summary_begin(disc *d) {
358         const char *target;
359         if (!d || disc_get_hlds_e7_type(d) == 0)
360                 return;
361         friidump_summary_reset();
362         g_validation_summary.active = true;
363         friidump_summary_copy(g_validation_summary.model, sizeof(g_validation_summary.model), disc_get_drive_model_string(d));
364         g_validation_summary.command = disc_get_command(d);
365         g_validation_summary.method = disc_get_method(d);
366         g_validation_summary.hlds_type = disc_get_hlds_e7_type(d);
367         g_validation_summary.profile = disc_get_hlds_e7_profile_name(d);
368         g_validation_summary.cache_base = disc_get_hlds_e7_cache_base(d);
369         g_validation_summary.mem_blocks = disc_get_hlds_e7_mem_blocks(d);
370         target = friidump_requested_output_target();
371         friidump_summary_copy(g_validation_summary.output, sizeof(g_validation_summary.output), target ? target : "(none)");
372 }
373
374 static void friidump_print_validation_summary(double duration, bool have_duration) {
375         double mib_completed, mib_per_hour;
376         bool have_completed_measurement;
377         const char *seed_status;
378         const char *dump_status;
379         if (!g_validation_summary.active)
380                 return;
381         seed_status = g_validation_summary.seed_applicable
382                 ? (g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED")
383                 : "N/A (Xbox reference auth path)";
384         dump_status = "NOT ATTEMPTED";
385         if (g_validation_summary.dump_attempted) {
386                 if (g_validation_summary.dump_ok)
387                         dump_status = "OK";
388                 else if (g_native_cancel_requested && g_native_report.have_byte_count &&
389                          g_native_report.byte_count > 0)
390                         dump_status = "CANCELLED (PARTIAL)";
391                 else if (g_native_cancel_requested)
392                         dump_status = "CANCELLED";
393                 else if (g_native_report.have_byte_count && g_native_report.byte_count > 0)
394                         dump_status = "FAILED (PARTIAL)";
395                 else
396                         dump_status = "FAILED";
397         }
398         have_completed_measurement =
399                 g_native_report.dump_attempted &&
400                 g_native_report.have_byte_count &&
401                 !g_native_report_resumed;
402         mib_completed = have_completed_measurement
403                 ? (double) g_native_report.byte_count / 1024.0 / 1024.0
404                 : 0.0;
405         mib_per_hour = (have_completed_measurement && have_duration && duration > 0.0)
406                 ? (mib_completed / duration * 3600.0)
407                 : 0.0;
408         fprintf(stderr,
409                 "\nHLDS 0xE7 validation summary:\n"
410                 "  Model.............: %s\n"
411                 "  Profile...........: %s\n"
412                 "  Command/method....: %u / %d\n"
413                 "  Cache base........: 0x%08x\n"
414                 "  Memory windows....: %u\n"
415                 "  Disc type.........: %s\n"
416                 "  Game/Media ID.....: %s\n"
417                 "  Title.............: %s\n",
418                 g_validation_summary.model[0] ? g_validation_summary.model : "(unknown)",
419                 g_validation_summary.profile ? g_validation_summary.profile : "(unknown)",
420                 g_validation_summary.command, g_validation_summary.method,
421                 g_validation_summary.cache_base, g_validation_summary.mem_blocks,
422                 g_validation_summary.disc_type[0] ? g_validation_summary.disc_type : "(unknown)",
423                 g_validation_summary.game_id[0] ? g_validation_summary.game_id : "(unknown)",
424                 g_validation_summary.title[0] ? g_validation_summary.title : "(unknown)");
425         if (g_validation_summary.region[0])
426                 fprintf(stderr, "  Region............: %s\n", g_validation_summary.region);
427         fprintf(stderr,
428                 "  Output............: %s\n"
429                 "  Seed read.........: %s\n"
430                 "  Dump status.......: %s\n"
431                 "  STOP UNIT.........: %s\n",
432                 g_validation_summary.output[0] ? g_validation_summary.output : "(none)",
433                 seed_status,
434                 dump_status,
435                 g_validation_summary.stop_attempted ? (g_validation_summary.stop_ok ? "OK" : "FAILED") : "NOT ATTEMPTED");
436         if (g_validation_summary.have_seed_duration) {
437                 char seed_buf[32];
438                 friidump_format_hms(g_validation_summary.seed_duration, seed_buf, sizeof(seed_buf));
439                 fprintf(stderr, "  Seed elapsed......: %s (%.2f seconds)\n", seed_buf, g_validation_summary.seed_duration);
440         }
441         if (g_validation_summary.dump_attempted && !g_validation_summary.dump_ok &&
442             g_validation_summary.fail_sector != 0xFFFFFFFFU) {
443                 if (g_native_cancel_requested)
444                         fprintf(stderr, "  Cancelled at......: sector %u\n", g_validation_summary.fail_sector);
445                 else
446                         fprintf(stderr, "  Failure sector....: %u..%u\n", g_validation_summary.fail_sector, g_validation_summary.fail_sector + 15);
447         }
448         if (g_validation_summary.have_source_sectors)
449                 fprintf(stderr, "  Source sectors....: %u\n", g_validation_summary.source_sectors);
450         if (g_validation_summary.have_expected_output_sectors)
451                 fprintf(stderr, "  Expected output...: %u sectors\n", g_validation_summary.expected_output_sectors);
452         else if (g_validation_summary.sectors)
453                 fprintf(stderr, "  Expected sectors..: %u\n", g_validation_summary.sectors);
454         if (g_native_report.have_sector_count)
455                 fprintf(stderr, g_native_report_resumed
456                         ? "  Output sectors....: %llu (includes prior resumed data)\n"
457                         : "  Completed sectors.: %llu\n",
458                         (unsigned long long) g_native_report.sector_count);
459         if (g_native_report.have_byte_count)
460                 fprintf(stderr, g_native_report_resumed
461                         ? "  Output bytes......: %llu (%.2f MiB; includes prior resumed data)\n"
462                         : "  Completed bytes...: %llu (%.2f MiB)\n",
463                         (unsigned long long) g_native_report.byte_count,
464                         (double) g_native_report.byte_count / 1024.0 / 1024.0);
465         if (have_duration)
466                 fprintf(stderr, "  Duration..........: %.2f seconds\n", duration);
467         if (have_duration && mib_per_hour > 0.0)
468                 fprintf(stderr, "  Observed average..: %.2f MiB/h over %.2f MiB completed output\n",
469                         mib_per_hour, mib_completed);
470         else if (g_native_report_resumed)
471                 fprintf(stderr, "  Observed average..: N/A (resumed output; measurement scope unknown)\n");
472         if (g_validation_summary.dump_ok) {
473                 if (g_redump_attempted) {
474                         fprintf(stderr, "  Redump verify.....: %s\n", redump_verify_overall_string(&g_redump_result));
475                         if (g_redump_result.status == REDUMP_VERIFY_MATCH)
476                                 fprintf(stderr, "  Redump title......: %s\n", g_redump_result.game_name);
477                 } else {
478                         fprintf(stderr, "  Redump verify.....: NOT RUN\n");
479                 }
480         }
481 }
482
483
484 /* Name of package */
485 #define PACKAGE "friidump"
486
487 /* Define to the address where bug reports for this package should be sent. */
488 #define PACKAGE_BUGREPORT "arep@no.net"
489
490 /* Define to the full name of this package. */
491 #define PACKAGE_NAME "FriiDump"
492
493 /* Define to the version of this package. */
494 #define PACKAGE_VERSION "0.5.3.16-pf1-candidate21"
495
496
497 #ifdef WIN32
498
499 #include "getopt-win32.h"
500
501
502 #else
503 #include <sys/time.h>
504 #include <getopt.h>
505 #endif
506
507
508 /* Struct for program options */
509 struct {
510         char *device;
511         bool autodump;
512         bool gui;
513         char *raw_in;
514         char *raw_out;
515         char *iso_out;
516         char *xiso_out;
517         bool iso_requested;
518         bool xiso_requested;
519         bool xbox_filename_override;
520         bool xbox_auto_filename;
521         bool resume;
522         int dump_method;
523         u_int32_t command;
524         u_int32_t start_sector;
525         u_int32_t sectors_no;
526         u_int32_t speed;
527         u_int32_t disctype;
528         u_int32_t sec_disc;
529         u_int32_t sec_mem;
530         bool no_hashing;
531         bool no_unscrambling;
532         bool no_flushing;
533         bool stop_unit;
534         bool allmethods;
535         bool hlds_e7_scan;
536         bool hlds_e7_subcmd_sweep;
537         bool hlds_e7_memrange_sweep;
538         char *hlds_e7_scan_log;
539         char *hlds_e7_scan_dump_prefix;
540         char *hlds_profile_report;
541         char *redump_dat_dir;
542         char *redump_report;
543         bool no_redump_verify;
544         bool xgd1_layout_probe;
545         char *xgd1_layout_probe_report;
546         bool xgd1_raw_id_probe;
547         char *xgd1_raw_id_probe_report;
548         char *native_report_json;
549         char *native_report_dir;
550         char *firmware_modified_note;
551 } options;
552
553
554 /* Struct for progress data */
555 typedef struct {
556         struct timeval start_time;
557         struct timeval end_time;
558         double mb_total;
559         double mb_total_real;
560         u_int32_t sectors_skipped;
561 } progstats;
562
563
564 static char friidump_drive_letter_from_device(const char *device) {
565         if (!device || !device[0]) return 0;
566         if (device[0] && device[1] == ':') return device[0];
567         if (device[0] == '\\' && device[1] == '\\' && device[2] == '.' && device[3] == '\\' && device[4] && device[5] == ':') return device[4];
568         return device[0];
569 }
570
571
572 static const char *friidump_requested_output_target(void) {
573         if (options.xgd1_raw_id_probe_report && options.xgd1_raw_id_probe_report[0]) return options.xgd1_raw_id_probe_report;
574         if (options.xgd1_layout_probe_report && options.xgd1_layout_probe_report[0]) return options.xgd1_layout_probe_report;
575         if (options.iso_out && options.iso_out[0]) return options.iso_out;
576         if (options.xiso_out && options.xiso_out[0]) return options.xiso_out;
577         if (options.raw_out && options.raw_out[0]) return options.raw_out;
578         return NULL;
579 }
580
581
582 static void friidump_json_string(FILE *f, const char *s) {
583         const unsigned char *p;
584         fputc('"', f);
585         if (s) {
586                 for (p = (const unsigned char *) s; *p; p++) {
587                         switch (*p) {
588                                 case '\\': fputs("\\\\", f); break;
589                                 case '"': fputs("\\\"", f); break;
590                                 case '\b': fputs("\\b", f); break;
591                                 case '\f': fputs("\\f", f); break;
592                                 case '\n': fputs("\\n", f); break;
593                                 case '\r': fputs("\\r", f); break;
594                                 case '\t': fputs("\\t", f); break;
595                                 default:
596                                         if (*p < 0x20) {
597                                                 char tmp[8];
598                                                 snprintf(tmp, sizeof(tmp), "\\u%04x", (unsigned int) *p);
599                                                 fputs(tmp, f);
600                                         } else {
601                                                 fputc(*p, f);
602                                         }
603                                         break;
604                         }
605                 }
606         }
607         fputc('"', f);
608 }
609
610 static void friidump_json_kv_string(FILE *f, const char *key, const char *value, bool comma) {
611         fputs("  ", f);
612         friidump_json_string(f, key);
613         fputs(": ", f);
614         friidump_json_string(f, value ? value : "");
615         fputs(comma ? ",\n" : "\n", f);
616 }
617
618 static void friidump_json_kv_u32_hex(FILE *f, const char *key, u_int32_t value, bool comma) {
619         char tmp[32];
620         snprintf(tmp, sizeof(tmp), "0x%08x", value);
621         friidump_json_kv_string(f, key, value ? tmp : "", comma);
622 }
623
624 static void friidump_json_kv_int(FILE *f, const char *key, int value, bool comma) {
625         char tmp[32];
626         fputs("  ", f);
627         friidump_json_string(f, key);
628         snprintf(tmp, sizeof(tmp), ": %d%s\n", value, comma ? "," : "");
629         fputs(tmp, f);
630 }
631
632 static void friidump_json_kv_bool(FILE *f, const char *key, bool value, bool comma) {
633         fputs("  ", f);
634         friidump_json_string(f, key);
635         fputs(value ? ": true" : ": false", f);
636         fputs(comma ? ",\n" : "\n", f);
637 }
638
639 static bool friidump_write_hlds_profile_report(disc *d, const char *path) {
640         FILE *f;
641         if (!d || !path || !path[0])
642                 return false;
643         f = fopen(path, "wb");
644         if (!f) {
645                 fprintf(stderr, "WARNING: could not write HLDS profile report: %s\n", path);
646                 return false;
647         }
648         fputs("{\n", f);
649         friidump_json_kv_string(f, "schema", "friidump_hlds_profile_report_v1", true);
650         friidump_json_kv_string(f, "drive_model", disc_get_drive_model_string(d), true);
651         friidump_json_kv_string(f, "profile_name", disc_get_hlds_e7_profile_name(d), true);
652         friidump_json_kv_string(f, "support_tier", disc_get_hlds_e7_support_tier(d), true);
653         friidump_json_kv_string(f, "family", disc_get_hlds_e7_family(d), true);
654         friidump_json_kv_string(f, "tokens", disc_get_hlds_e7_tokens(d), true);
655         friidump_json_kv_string(f, "stage5b_record_id", disc_get_hlds_e7_record_id(d), true);
656         friidump_json_kv_u32_hex(f, "static_e7_cdb_base", disc_get_hlds_e7_static_cdb_base(d), true);
657         friidump_json_kv_u32_hex(f, "static_e7_gate", disc_get_hlds_e7_static_gate(d), true);
658         friidump_json_kv_int(f, "runtime_e7_type", (int) disc_get_hlds_e7_type(d), true);
659         friidump_json_kv_u32_hex(f, "runtime_cache_base", disc_get_hlds_e7_cache_base(d), true);
660         friidump_json_kv_int(f, "runtime_mem_windows", (int) disc_get_hlds_e7_mem_blocks(d), true);
661         friidump_json_kv_int(f, "preferred_method", disc_get_hlds_e7_preferred_method(d), true);
662         friidump_json_kv_int(f, "selected_method", (int) disc_get_method(d), true);
663         friidump_json_kv_int(f, "selected_command", (int) disc_get_command(d), true);
664         friidump_json_kv_bool(f, "live_safe_from_static_only", false, true);
665         friidump_json_kv_string(f, "safety_note", "Static firmware addresses are evidence only; FriiDump does not use them as host-side commands and does not emit flash/write/update CDBs.", true);
666         friidump_json_kv_string(f, "notes", disc_get_hlds_e7_notes(d), false);
667         fputs("}\n", f);
668         fclose(f);
669         return true;
670 }
671
672
673 static void friidump_retarget_log_from_options(void) {
674         const char *target = friidump_requested_output_target();
675         if (target) {
676                 xbox_ref_log_retarget(target);
677         } else if (options.device && options.device[0]) {
678                 xbox_ref_log_open_for_target(NULL, friidump_drive_letter_from_device(options.device));
679         }
680 }
681
682
683 void progress_for_guis (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) {
684         int perc;
685         double elapsed, mb_done, mb_done_real, mb_hour, seconds_left;
686         struct timeval now;
687         time_t eta;
688         struct tm etatm;
689         char buf[50];
690         
691         if (start) {
692                 gettimeofday (&(stats -> start_time), NULL);
693                 stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024;
694                 stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024;
695                 stats -> sectors_skipped = sectors_done;
696         } else {
697                 perc = (int) (100.0 * sectors_done / total_sectors);
698                 gettimeofday (&now, NULL);
699                 elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec);
700                 mb_done = (double) sectors_done * 2064 / 1024 / 1024;
701                 mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024;
702                 mb_hour = mb_done_real / elapsed * 60 * 60;
703                 seconds_left = stats -> mb_total_real / mb_hour * 60 * 60;
704                 eta = (time_t) ((stats -> start_time).tv_sec + seconds_left);
705                 if (localtime_r (&eta, &etatm))
706                         strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm);
707                 else
708                         sprintf (buf, "N/A");
709
710                 /* This is the only thing we print to stdout, so that other programs can easily capture and parse our output */
711                 fprintf (stdout, "%d%%|%u/%u sectors|%.2lf/%.0lf MB|%.0lf/%.0lf seconds|%.2lf MB/h|%s\n",
712                          perc, sectors_done, total_sectors, mb_done, stats -> mb_total, elapsed, seconds_left, mb_hour, buf);
713                 fflush (stdout);
714         }
715
716         /* Save return time, in case this will be the last call */
717         gettimeofday (&(stats -> end_time), NULL);
718
719         return;
720 }
721
722
723 void progress (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) {
724         int perc, i;
725         double elapsed, mb_done, mb_done_real, mb_hour, seconds_left;
726         struct timeval now;
727         time_t eta;
728         struct tm etatm;
729         char buf[50];
730         
731         if (start) {
732                 gettimeofday (&(stats -> start_time), NULL);
733                 stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024;
734                 stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024;
735                 stats -> sectors_skipped = sectors_done;
736         } else {
737                 perc = (int) (100.0 * sectors_done / total_sectors);
738                 gettimeofday (&now, NULL);
739                 elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec);
740                 mb_done = (double) sectors_done * 2064 / 1024 / 1024;
741                 mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024;
742                 mb_hour = mb_done_real / elapsed * 60 * 60;
743                 seconds_left = stats -> mb_total_real / mb_hour * 60 * 60;
744                 eta = (time_t) ((stats -> start_time).tv_sec + seconds_left);
745                 if (localtime_r (&eta, &etatm))
746                         strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm);
747                 else
748                         sprintf (buf, "N/A");
749
750                 fprintf (stdout, "\r%3d%% ", perc);
751                 fprintf (stdout, "|");
752                 for (i = 0; i < 100 / 3; i++) {
753                         if (i == perc / 3)
754                                 fprintf (stdout, "*");
755                         else
756                                 fprintf (stdout, "-");
757                 }
758                 fprintf (stdout, "| ");
759                 fprintf (stdout, "%.2lf MB/h, ETA: %s", mb_hour, buf);
760                 fflush (stdout);
761         }
762
763         if (sectors_done == total_sectors)
764                 printf ("\n");
765
766         /* Save return time, in case this will be the last call */
767         gettimeofday (&(stats -> end_time), NULL);
768
769         return;
770 }
771
772
773
774 void welcome (void) {
775         /* Welcome text */
776         fprintf (stderr,
777                 "FriiDump " PACKAGE_VERSION " - Copyright (C) 2007 Arep\n"
778                 "This software comes with ABSOLUTELY NO WARRANTY.\n"
779                 "This is free software, and you are welcome to redistribute it\n"
780                 "under certain conditions; see COPYING for details.\n"
781                 "\n"
782                 "Official support forum: http://wii.console-tribe.com\n"
783                 "\n"
784                 "Forum for this UNOFFICIAL VERSION: http://forum.redump.org\n"
785                 "\n"
786                 );
787         fflush (stderr);
788
789         return;
790 }
791
792
793
794 static void friidump_init_executable_dir(const char *argv0) {
795     char path[1024];
796     size_t length = 0;
797     char *slash;
798     char *backslash;
799     char *separator;
800
801     g_executable_dir[0] = '\0';
802     path[0] = '\0';
803
804 #ifdef WIN32
805     {
806         DWORD result = GetModuleFileNameA(NULL, path, (DWORD)sizeof(path));
807         if (result > 0 && result < sizeof(path)) {
808             path[result] = '\0';
809             length = (size_t)result;
810         }
811     }
812 #else
813 #if defined(__linux__)
814     {
815         ssize_t result = readlink("/proc/self/exe", path, sizeof(path) - 1);
816         if (result > 0 && (size_t)result < sizeof(path)) {
817             path[result] = '\0';
818             length = (size_t)result;
819         }
820     }
821 #endif
822     if (length == 0 && argv0 && argv0[0] &&
823         (strchr(argv0, '/') || strchr(argv0, '\\'))) {
824         char *resolved = realpath(argv0, path);
825         if (resolved)
826             length = strlen(path);
827     }
828 #endif
829
830     if (length == 0)
831         return;
832
833     slash = strrchr(path, '/');
834     backslash = strrchr(path, '\\');
835     separator = slash;
836     if (backslash && (!separator || backslash > separator))
837         separator = backslash;
838
839     if (!separator)
840         return;
841
842     *separator = '\0';
843     if (path[0])
844         snprintf(g_executable_dir, sizeof(g_executable_dir), "%s", path);
845 }
846
847 static const char *friidump_redump_dat_basename(disc_type type_id) {
848     switch (type_id) {
849         case DISC_TYPE_GAMECUBE:
850             return "Nintendo - GameCube.dat";
851         case DISC_TYPE_WII:
852         case DISC_TYPE_WII_DL:
853             return "Nintendo - Wii.dat";
854         case DISC_TYPE_XBOX:
855             return "Microsoft - Xbox.dat";
856         default:
857             return NULL;
858     }
859 }
860
861 static uint64_t friidump_file_size(const char *path) {
862     if (!path || !path[0])
863         return 0;
864 #ifdef WIN32
865     {
866         struct _stat64 st;
867         if (_stat64(path, &st) == 0)
868             return (uint64_t)st.st_size;
869     }
870 #else
871     {
872         struct stat st;
873         if (stat(path, &st) == 0)
874             return (uint64_t)st.st_size;
875     }
876 #endif
877     return 0;
878 }
879
880 static bool friidump_sync_report_file(FILE *f) {
881     if (!f || fflush(f) != 0)
882         return false;
883 #ifdef WIN32
884     return _commit(_fileno(f)) == 0;
885 #else
886     return fsync(fileno(f)) == 0;
887 #endif
888 }
889
890 static bool friidump_atomic_replace(const char *temporary_path, const char *final_path) {
891     if (!temporary_path || !final_path)
892         return false;
893 #ifdef WIN32
894     return MoveFileExA(temporary_path, final_path,
895                        MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0;
896 #else
897     return rename(temporary_path, final_path) == 0;
898 #endif
899 }
900
901 static void friidump_json_write_string_value(FILE *f, const char *prefix,
902                                              const char *value, const char *suffix) {
903     fputs(prefix, f);
904     friidump_json_string(f, value ? value : "");
905     fputs(suffix, f);
906 }
907
908 static void friidump_write_redump_report(const redump_verify_result *result,
909                                          const char *output_path,
910                                          uint64_t output_size,
911                                          const char *crc32,
912                                          const char *md5,
913                                          const char *sha1,
914                                          const char *sha256,
915                                          const char *hash_source,
916                                          const char *representation_note) {
917     FILE *f;
918     char *temporary_path;
919     char line[256];
920     size_t temporary_path_size;
921     bool write_ok;
922
923     if (!options.redump_report || !options.redump_report[0] || !result)
924         return;
925
926     temporary_path_size = strlen(options.redump_report) + 5;
927     temporary_path = (char *)malloc(temporary_path_size);
928     if (!temporary_path) {
929         fprintf(stderr, "Redump report.......: memory allocation failed\n");
930         return;
931     }
932     snprintf(temporary_path, temporary_path_size, "%s.tmp", options.redump_report);
933     remove(temporary_path);
934
935     f = fopen(temporary_path, "wb");
936     if (!f) {
937         fprintf(stderr, "Redump report.......: could not write temporary file %s\n", temporary_path);
938         free(temporary_path);
939         return;
940     }
941
942     /* Use fputs/fputc rather than the logged fprintf wrapper. Report contents
943      * belong in the JSON artifact, not as a malformed partial mirror in the
944      * human-readable run log. */
945     fputs("{\n", f);
946     fputs("  \"schema\": 2,\n", f);
947     friidump_json_write_string_value(f, "  \"producer\": ", "friidump-" PACKAGE_VERSION, ",\n");
948     friidump_json_write_string_value(f, "  \"status\": ", redump_verify_status_string(result->status), ",\n");
949     friidump_json_write_string_value(f, "  \"overall\": ", redump_verify_overall_string(result), ",\n");
950     friidump_json_write_string_value(f, "  \"confidence\": ", redump_verify_confidence_string(result), ",\n");
951     friidump_json_write_string_value(f, "  \"output\": ", output_path ? output_path : "", ",\n");
952     snprintf(line, sizeof(line), "  \"output_size\": %llu,\n", (unsigned long long)output_size);
953     fputs(line, f);
954     friidump_json_write_string_value(f, "  \"crc32\": ", crc32 ? crc32 : "", ",\n");
955     friidump_json_write_string_value(f, "  \"md5\": ", md5 ? md5 : "", ",\n");
956     friidump_json_write_string_value(f, "  \"sha1\": ", sha1 ? sha1 : "", ",\n");
957     friidump_json_write_string_value(f, "  \"sha256\": ", sha256 ? sha256 : "", ",\n");
958     friidump_json_write_string_value(f, "  \"hash_source\": ", hash_source ? hash_source : "", ",\n");
959     friidump_json_write_string_value(f, "  \"representation_note\": ", representation_note ? representation_note : "", ",\n");
960     friidump_json_write_string_value(f, "  \"dat_path\": ", result->dat_path, ",\n");
961     snprintf(line, sizeof(line), "  \"entries_scanned\": %lu,\n", result->entries_scanned);
962     fputs(line, f);
963     snprintf(line, sizeof(line), "  \"exact_matches\": %lu,\n", result->exact_matches);
964     fputs(line, f);
965     friidump_json_write_string_value(f, "  \"match_kind\": ", redump_verify_match_kind_string(result), ",\n");
966     friidump_json_write_string_value(f, "  \"game_name\": ", result->game_name, ",\n");
967     friidump_json_write_string_value(f, "  \"rom_name\": ", result->rom_name, ",\n");
968
969     fputs("  \"field_match_counts\": {\n", f);
970     snprintf(line, sizeof(line), "    \"size\": %lu,\n", result->size_matches); fputs(line, f);
971     snprintf(line, sizeof(line), "    \"crc32\": %lu,\n", result->crc32_matches); fputs(line, f);
972     snprintf(line, sizeof(line), "    \"md5\": %lu,\n", result->md5_matches); fputs(line, f);
973     snprintf(line, sizeof(line), "    \"sha1\": %lu\n", result->sha1_matches); fputs(line, f);
974     fputs("  },\n", f);
975
976     fputs("  \"expected\": {\n", f);
977     friidump_json_write_string_value(f, "    \"size\": ", result->expected_size, ",\n");
978     friidump_json_write_string_value(f, "    \"crc32\": ", result->expected_crc32, ",\n");
979     friidump_json_write_string_value(f, "    \"md5\": ", result->expected_md5, ",\n");
980     friidump_json_write_string_value(f, "    \"sha1\": ", result->expected_sha1, "\n");
981     fputs("  },\n", f);
982
983     fputs("  \"evidence\": {\n", f);
984     friidump_json_write_string_value(f, "    \"size\": ", redump_field_status_string(result->size_status), ",\n");
985     friidump_json_write_string_value(f, "    \"crc32\": ", redump_field_status_string(result->crc32_status), ",\n");
986     friidump_json_write_string_value(f, "    \"md5\": ", redump_field_status_string(result->md5_status), ",\n");
987     friidump_json_write_string_value(f, "    \"sha1\": ", redump_field_status_string(result->sha1_status), "\n");
988     fputs("  },\n", f);
989     friidump_json_write_string_value(f, "  \"detail\": ", result->detail, "\n");
990     fputs("}\n", f);
991
992     write_ok = !ferror(f) && friidump_sync_report_file(f);
993     if (fclose(f) != 0)
994         write_ok = false;
995
996     if (!write_ok) {
997         remove(temporary_path);
998         fprintf(stderr, "Redump report.......: write/flush failed; final report was not replaced\n");
999         free(temporary_path);
1000         return;
1001     }
1002
1003     if (!friidump_atomic_replace(temporary_path, options.redump_report)) {
1004         fprintf(stderr, "Redump report.......: could not replace %s; complete temporary report retained at %s\n",
1005                 options.redump_report, temporary_path);
1006         free(temporary_path);
1007         return;
1008     }
1009
1010     fprintf(stderr, "Redump report.......: %s (atomic write)\n", options.redump_report);
1011     free(temporary_path);
1012 }
1013
1014 static void friidump_print_redump_field(const char *label, redump_field_status status) {
1015     fprintf(stderr, "%-20s %s\n", label, redump_field_status_string(status));
1016 }
1017
1018 static void friidump_verify_redump_values(disc_type type_id,
1019                                             const char *output_path,
1020                                             uint64_t output_size,
1021                                             const char *crc32,
1022                                             const char *md5,
1023                                             const char *sha1,
1024                                             const char *sha256,
1025                                             const char *hash_source) {
1026     const char *basename;
1027     const char *dat_dir;
1028     const char *representation_note;
1029     char dat_path[1024];
1030
1031     redump_verify_result_init(&g_redump_result);
1032     g_redump_attempted = false;
1033     representation_note = (type_id == DISC_TYPE_XBOX)
1034         ? "XGD1 acquisition success and exact Redump hash identity are separate claims; unresolved zero-filled pregame/postgame content may prevent an exact match."
1035         : "";
1036
1037     if (options.no_redump_verify) {
1038         fprintf(stderr, "Redump verification: DISABLED (--no-redump-verify)\n");
1039         return;
1040     }
1041     if (options.no_hashing) {
1042         fprintf(stderr, "Redump verification: SKIPPED (hashing disabled)\n");
1043         return;
1044     }
1045     if (!output_path || !output_path[0]) {
1046         fprintf(stderr, "Redump verification: SKIPPED (final output path unavailable)\n");
1047         return;
1048     }
1049     if (!crc32 || !crc32[0] || !md5 || !md5[0] || !sha1 || !sha1[0]) {
1050         fprintf(stderr, "Redump verification: SKIPPED (finalized CRC32/MD5/SHA-1 evidence incomplete)\n");
1051         return;
1052     }
1053
1054     basename = friidump_redump_dat_basename(type_id);
1055     if (!basename) {
1056         fprintf(stderr, "Redump verification: SKIPPED (no DAT mapping for this disc type)\n");
1057         return;
1058     }
1059
1060     dat_dir = (options.redump_dat_dir && options.redump_dat_dir[0])
1061         ? options.redump_dat_dir
1062         : NULL;
1063     redump_resolve_dat_path(dat_dir, g_executable_dir, basename,
1064                             dat_path, sizeof(dat_path));
1065     if (output_size == 0)
1066         output_size = friidump_file_size(output_path);
1067
1068     g_redump_attempted = true;
1069     redump_verify_dat_file(dat_path, output_size, crc32, md5, sha1, &g_redump_result);
1070
1071     fprintf(stderr, "\nRedump verification\n");
1072     fprintf(stderr, "------------------------------------------------------------\n");
1073     fprintf(stderr, "%-20s %s\n", "DAT", g_redump_result.dat_path);
1074     fprintf(stderr, "%-20s %s\n", "Hash source", hash_source ? hash_source : "Finalized output hashes");
1075     fprintf(stderr, "%-20s %lu\n", "Entries scanned", g_redump_result.entries_scanned);
1076
1077     if (g_redump_result.status == REDUMP_VERIFY_MATCH) {
1078         fprintf(stderr, "%-20s %s\n", "Matched entry", g_redump_result.game_name);
1079         fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name);
1080         friidump_print_redump_field("Image size", g_redump_result.size_status);
1081         friidump_print_redump_field("CRC32", g_redump_result.crc32_status);
1082         friidump_print_redump_field("MD5", g_redump_result.md5_status);
1083         friidump_print_redump_field("SHA-1", g_redump_result.sha1_status);
1084         if (g_redump_result.exact_matches > 1)
1085             fprintf(stderr, "%-20s %lu exact entries\n", "Duplicate matches", g_redump_result.exact_matches);
1086     } else if (g_redump_result.candidate_available) {
1087         fprintf(stderr, "%-20s %s\n", "Closest candidate", g_redump_result.game_name);
1088         fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name);
1089         friidump_print_redump_field("Image size", g_redump_result.size_status);
1090         friidump_print_redump_field("CRC32", g_redump_result.crc32_status);
1091         friidump_print_redump_field("MD5", g_redump_result.md5_status);
1092         friidump_print_redump_field("SHA-1", g_redump_result.sha1_status);
1093     } else {
1094         fprintf(stderr, "%-20s %s\n", "Closest candidate", "None (no hash-correlated entry)");
1095         fprintf(stderr, "%-20s %lu entries\n", "Size matches", g_redump_result.size_matches);
1096         fprintf(stderr, "%-20s %lu entries\n", "CRC32 matches", g_redump_result.crc32_matches);
1097         fprintf(stderr, "%-20s %lu entries\n", "MD5 matches", g_redump_result.md5_matches);
1098         fprintf(stderr, "%-20s %lu entries\n", "SHA-1 matches", g_redump_result.sha1_matches);
1099     }
1100
1101     fprintf(stderr, "%-20s %s\n", "Overall", redump_verify_overall_string(&g_redump_result));
1102     fprintf(stderr, "%-20s %s\n", "Confidence", redump_verify_confidence_string(&g_redump_result));
1103     if (g_redump_result.status != REDUMP_VERIFY_MATCH)
1104         fprintf(stderr, "%-20s %s\n", "Detail", g_redump_result.detail);
1105     if (type_id == DISC_TYPE_XBOX)
1106         fprintf(stderr, "%-20s %s\n", "Representation note", representation_note);
1107
1108     friidump_write_redump_report(&g_redump_result, output_path, output_size,
1109                                  crc32, md5, sha1, sha256, hash_source,
1110                                  representation_note);
1111 }
1112
1113 static void friidump_verify_redump_iso(dumper *dmp, disc_type type_id) {
1114     const char *output_path;
1115
1116     if (!dmp)
1117         return;
1118
1119     output_path = options.iso_out;
1120     friidump_verify_redump_values(type_id,
1121                                   output_path,
1122                                   friidump_file_size(output_path),
1123                                   dumper_get_iso_crc32(dmp),
1124                                   dumper_get_iso_md5(dmp),
1125                                   dumper_get_iso_sha1(dmp),
1126                                   dumper_get_iso_sha2(dmp),
1127                                   "FriiDump finalized ISO multihash");
1128 }
1129
1130 static bool friidump_uses_windows_xbox_reference_path(disc *d, disc_type type_id) {
1131 #ifdef WIN32
1132     return type_id == DISC_TYPE_XBOX && d && disc_is_xbox_challenge_drive(d);
1133 #else
1134     (void) d;
1135     (void) type_id;
1136     return false;
1137 #endif
1138 }
1139
1140 static void friidump_verify_redump_xbox_reference(dumper *dmp) {
1141     xbox_ref_dump_result result;
1142
1143     xbox_ref_dump_result_init(&result);
1144     if (!dumper_get_xbox_reference_result(dmp, &result)) {
1145         fprintf(stderr, "Redump verification: SKIPPED (Xbox reference result handoff unavailable)\n");
1146         return;
1147     }
1148     if (result.mode != '1') {
1149         fprintf(stderr, "Redump verification: SKIPPED (Xbox XISO is not a full Redump disc image)\n");
1150         return;
1151     }
1152     if (!result.dump_success) {
1153         fprintf(stderr, "Redump verification: SKIPPED (Xbox reference dump did not complete)\n");
1154         return;
1155     }
1156     if (!result.hashes_complete) {
1157         fprintf(stderr, "Redump verification: SKIPPED (Xbox finalized full-file hashes incomplete)\n");
1158         return;
1159     }
1160
1161     if (g_validation_summary.active) {
1162         if (result.output_path[0])
1163             friidump_summary_copy(g_validation_summary.output, sizeof(g_validation_summary.output), result.output_path);
1164         if (result.title[0])
1165             friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), result.title);
1166         if (result.media_id[0])
1167             friidump_summary_copy(g_validation_summary.game_id, sizeof(g_validation_summary.game_id), result.media_id);
1168         if (result.output_sectors)
1169             g_validation_summary.sectors = result.output_sectors;
1170         g_validation_summary.seed_applicable = false;
1171         g_validation_summary.have_seed_duration = false;
1172     }
1173     if (result.elapsed_seconds > 0.0) {
1174         g_operation_duration_override_valid = true;
1175         g_operation_duration_override = result.elapsed_seconds;
1176     }
1177
1178     friidump_verify_redump_values(DISC_TYPE_XBOX,
1179                                   result.output_path,
1180                                   result.output_size,
1181                                   result.crc32,
1182                                   result.md5,
1183                                   result.sha1,
1184                                   result.sha256,
1185                                   "Xbox finalized full-file hashes");
1186 }
1187
1188
1189 static void friidump_native_signal_handler(int signal_number) {
1190     (void) signal_number;
1191     g_native_cancel_requested = 1;
1192 }
1193
1194 static bool friidump_native_cancel_callback(void *unused) {
1195     (void) unused;
1196     return g_native_cancel_requested != 0;
1197 }
1198
1199 static const char *friidump_native_platform(disc_type type_id) {
1200     switch (type_id) {
1201         case DISC_TYPE_GAMECUBE:
1202             return "gamecube";
1203         case DISC_TYPE_WII:
1204         case DISC_TYPE_WII_DL:
1205             return "wii";
1206         case DISC_TYPE_XBOX:
1207             return "xbox";
1208         case DISC_TYPE_DVD:
1209             return "dvd";
1210         default:
1211             return "unknown";
1212     }
1213 }
1214
1215 static bool friidump_native_report_operation_supported(void) {
1216     return options.device &&
1217            !options.raw_in &&
1218            !options.allmethods &&
1219            !options.stop_unit &&
1220            !options.hlds_e7_scan &&
1221            !options.hlds_e7_subcmd_sweep &&
1222            !options.hlds_e7_memrange_sweep &&
1223            !options.xgd1_layout_probe &&
1224            !options.xgd1_raw_id_probe &&
1225            !(options.raw_out && options.iso_requested) &&
1226            (options.autodump || options.raw_out ||
1227             options.iso_requested || options.xiso_requested);
1228 }
1229
1230 static bool friidump_native_report_option_requested(void) {
1231     return options.native_report_json ||
1232            options.native_report_dir ||
1233            options.firmware_modified_note;
1234 }
1235
1236 static void friidump_native_configure(void) {
1237     if (!friidump_native_report_operation_supported())
1238         return;
1239
1240     friidump_native_report_enable_default(&g_native_report);
1241
1242     if (options.native_report_json)
1243         friidump_native_report_enable_path(
1244             &g_native_report, options.native_report_json);
1245     if (options.native_report_dir)
1246         friidump_native_report_enable_dir(
1247             &g_native_report, options.native_report_dir);
1248
1249 #ifdef FRIIDUMP_BUILD_COMMIT
1250     friidump_native_report_set_build_commit(&g_native_report, FRIIDUMP_BUILD_COMMIT);
1251 #endif
1252
1253     if (options.firmware_modified_note)
1254         friidump_native_report_set_firmware_modified(
1255             &g_native_report, true, options.firmware_modified_note);
1256
1257     if (signal(SIGINT, friidump_native_signal_handler) == SIG_ERR) {
1258         fprintf(stderr, "WARNING: could not install the native-report Ctrl+C handler.\n");
1259     }
1260 }
1261
1262 static void friidump_native_set_drive(disc *d) {
1263     if (!friidump_native_report_is_enabled(&g_native_report) || !d)
1264         return;
1265
1266     friidump_native_report_set_drive(
1267         &g_native_report,
1268         disc_get_drive_vendor(d),
1269         disc_get_drive_product_id(d),
1270         disc_get_drive_firmware_revision(d),
1271         "ATAPI",
1272         disc_get_device(d));
1273 }
1274
1275 static void friidump_native_set_profile(disc *d, bool xbox_output_requested) {
1276     (void) xbox_output_requested;
1277
1278     if (!friidump_native_report_is_enabled(&g_native_report) || !d)
1279         return;
1280
1281     friidump_native_report_set_profile(
1282         &g_native_report,
1283         disc_get_hlds_e7_support_tier(d),
1284         disc_get_hlds_e7_static_cdb_base(d),
1285         disc_get_hlds_e7_static_gate(d));
1286 }
1287
1288 static void friidump_native_set_preflight_failure(int media_rc,
1289                                                    int sense_key,
1290                                                    int asc,
1291                                                    int ascq) {
1292     char note[256];
1293
1294     if (!friidump_native_report_is_enabled(&g_native_report))
1295         return;
1296
1297     friidump_native_report_set_seed(
1298         &g_native_report, false, "not_attempted", false, 0.0);
1299     friidump_native_report_set_dump(
1300         &g_native_report, false, "not_attempted",
1301         false, 0, false, 0, false, 0.0,
1302         NULL, false, 0, NULL);
1303     friidump_native_report_set_reference(
1304         &g_native_report, NULL, "not_applicable", NULL);
1305
1306     if (media_rc == 0) {
1307         friidump_native_report_set_outcome(
1308             &g_native_report, "diagnostic_no_media", "not_applicable");
1309         snprintf(note, sizeof(note),
1310                  "Media preflight found no readable disc (sense %02X/%02X/%02X).",
1311                  sense_key & 0xff, asc & 0xff, ascq & 0xff);
1312     } else {
1313         friidump_native_report_set_outcome(
1314             &g_native_report, "other", "fail");
1315         snprintf(note, sizeof(note),
1316                  "Drive or media did not become ready (sense %02X/%02X/%02X).",
1317                  sense_key & 0xff, asc & 0xff, ascq & 0xff);
1318     }
1319     friidump_native_report_add_note(&g_native_report, note);
1320 }
1321
1322 static void friidump_native_set_media(disc_type type_id,
1323                                       const char *title,
1324                                       const char *region,
1325                                       const char *disc_id) {
1326     if (!friidump_native_report_is_enabled(&g_native_report))
1327         return;
1328     friidump_native_report_set_media(
1329         &g_native_report,
1330         friidump_native_platform(type_id),
1331         title,
1332         region,
1333         disc_id);
1334 }
1335
1336 static void friidump_native_add_measurement_scope(const char *scope) {
1337     char note[128];
1338     if (!friidump_native_report_is_enabled(&g_native_report) || !scope)
1339         return;
1340     snprintf(note, sizeof(note), "Measurement scope: %s.", scope);
1341     friidump_native_report_add_note(&g_native_report, note);
1342 }
1343
1344 static void friidump_native_capture_output(dumper *dmp,
1345                                            disc_type type_id,
1346                                            bool xiso_mode,
1347                                            bool success,
1348                                            bool cancelled,
1349                                            u_int32_t failure_sector,
1350                                            u_int32_t expected_sectors) {
1351     xbox_ref_dump_result xbox_result;
1352     bool have_xbox_result;
1353     const char *path;
1354     const char *crc32;
1355     const char *md5;
1356     const char *sha1;
1357     const char *sha256;
1358     uint64_t bytes;
1359     uint64_t sectors;
1360     uint64_t sector_size;
1361     bool partial;
1362     const char *failure_stage;
1363
1364     if (!friidump_native_report_is_enabled(&g_native_report) || !dmp)
1365         return;
1366
1367     g_native_report_dump_started = true;
1368     g_native_report_resumed = dumper_get_start_sector(dmp) > 0;
1369
1370     xbox_ref_dump_result_init(&xbox_result);
1371     have_xbox_result = dumper_get_xbox_reference_result(dmp, &xbox_result);
1372     path = NULL;
1373     crc32 = md5 = sha1 = sha256 = NULL;
1374     bytes = 0;
1375     sectors = 0;
1376     sector_size = 2048;
1377
1378     if (have_xbox_result) {
1379         if (xbox_result.cancelled)
1380             cancelled = true;
1381         path = xbox_result.output_path[0] ? xbox_result.output_path : NULL;
1382         bytes = xbox_result.output_size;
1383         sectors = xbox_result.output_sectors;
1384         if (g_validation_summary.active) {
1385             if (xbox_result.output_path[0])
1386                 friidump_summary_copy(
1387                     g_validation_summary.output,
1388                     sizeof(g_validation_summary.output),
1389                     xbox_result.output_path);
1390             if (xbox_result.title[0])
1391                 friidump_summary_copy(
1392                     g_validation_summary.title,
1393                     sizeof(g_validation_summary.title),
1394                     xbox_result.title);
1395             if (xbox_result.media_id[0])
1396                 friidump_summary_copy(
1397                     g_validation_summary.game_id,
1398                     sizeof(g_validation_summary.game_id),
1399                     xbox_result.media_id);
1400             if (xbox_result.region[0])
1401                 friidump_summary_copy(
1402                     g_validation_summary.region,
1403                     sizeof(g_validation_summary.region),
1404                     xbox_result.region);
1405             if (expected_sectors > 0) {
1406                 g_validation_summary.source_sectors = expected_sectors;
1407                 g_validation_summary.have_source_sectors = true;
1408             }
1409             if (xbox_result.output_sectors > 0) {
1410                 g_validation_summary.expected_output_sectors =
1411                     xbox_result.output_sectors;
1412                 g_validation_summary.have_expected_output_sectors = true;
1413             }
1414             g_validation_summary.seed_applicable = false;
1415             g_validation_summary.have_seed_duration = false;
1416         }
1417         if (xbox_result.hashes_complete) {
1418             crc32 = xbox_result.crc32;
1419             md5 = xbox_result.md5;
1420             sha1 = xbox_result.sha1;
1421             sha256 = xbox_result.sha256;
1422         }
1423         if (xbox_result.title[0] || xbox_result.media_id[0] || xbox_result.region[0]) {
1424             friidump_native_report_set_media(
1425                 &g_native_report,
1426                 "xbox",
1427                 xbox_result.title[0] ? xbox_result.title :
1428                     (g_native_report.have_title ? g_native_report.title : NULL),
1429                 xbox_result.region[0] ? xbox_result.region : NULL,
1430                 xbox_result.media_id[0] ? xbox_result.media_id :
1431                     (g_native_report.have_disc_id ? g_native_report.disc_id : NULL));
1432         }
1433         if (xbox_result.have_game_region) {
1434             char region_note[96];
1435             snprintf(region_note, sizeof(region_note),
1436                      "Xbox XBE GameRegion mask: 0x%08x.",
1437                      xbox_result.game_region);
1438             friidump_native_report_add_note(&g_native_report, region_note);
1439         }
1440         if (xbox_result.elapsed_seconds > 0.0) {
1441             g_native_report.have_dump_duration = true;
1442             g_native_report.dump_duration_seconds = xbox_result.elapsed_seconds;
1443         }
1444     } else if (xiso_mode) {
1445         path = (options.xiso_out && options.xiso_out[0]) ? options.xiso_out : NULL;
1446         if (path)
1447             bytes = friidump_file_size(path);
1448         sectors = bytes / 2048;
1449         if (!options.no_hashing) {
1450             crc32 = dumper_get_xiso_crc32(dmp);
1451             md5 = dumper_get_xiso_md5(dmp);
1452             sha1 = dumper_get_xiso_sha1(dmp);
1453             sha256 = dumper_get_xiso_sha2(dmp);
1454         }
1455     } else if (options.iso_out && options.iso_out[0]) {
1456         path = options.iso_out;
1457         bytes = friidump_file_size(path);
1458         sectors = bytes / 2048;
1459         if (!options.no_hashing) {
1460             crc32 = dumper_get_iso_crc32(dmp);
1461             md5 = dumper_get_iso_md5(dmp);
1462             sha1 = dumper_get_iso_sha1(dmp);
1463             sha256 = dumper_get_iso_sha2(dmp);
1464         }
1465     } else if (options.raw_out && options.raw_out[0]) {
1466         path = options.raw_out;
1467         sector_size = 2064;
1468         bytes = friidump_file_size(path);
1469         sectors = bytes / sector_size;
1470         if (!options.no_hashing) {
1471             crc32 = dumper_get_raw_crc32(dmp);
1472             md5 = dumper_get_raw_md5(dmp);
1473             sha1 = dumper_get_raw_sha1(dmp);
1474             sha256 = dumper_get_raw_sha2(dmp);
1475         }
1476     }
1477
1478     if (path && (!success || bytes == 0)) {
1479         uint64_t observed_bytes = friidump_file_size(path);
1480         if (observed_bytes > 0 || !success)
1481             bytes = observed_bytes;
1482         if (!success)
1483             sectors = bytes / sector_size;
1484     }
1485
1486     if (success && sectors == 0 && type_id != DISC_TYPE_XBOX) {
1487         sectors = expected_sectors;
1488         bytes = sectors * sector_size;
1489     }
1490
1491     if (g_native_report_resumed) {
1492         /* FriiDump's streaming hashes cover only bytes produced by this
1493          * invocation when a file is resumed, not the complete output file. */
1494         crc32 = md5 = sha1 = sha256 = NULL;
1495     }
1496
1497     partial = !success && (bytes > 0 ||
1498         (failure_sector != 0 && failure_sector != 0xFFFFFFFFU));
1499     failure_stage = NULL;
1500     if (!success) {
1501         if (cancelled)
1502             failure_stage = "user_cancelled";
1503         else
1504             failure_stage = (failure_sector == 0xFFFFFFFFU)
1505                 ? "xbox_reference_dump"
1506                 : "raw_sector_read_or_output_write";
1507     }
1508
1509     friidump_native_report_set_dump(
1510         &g_native_report,
1511         true,
1512         success ? "pass" : (partial ? "partial" : "fail"),
1513         success || sectors > 0,
1514         sectors,
1515         success || bytes > 0,
1516         bytes,
1517         g_native_report.have_dump_duration,
1518         g_native_report.dump_duration_seconds,
1519         failure_stage,
1520         !success && failure_sector != 0xFFFFFFFFU,
1521         failure_sector,
1522         path);
1523
1524     friidump_native_report_set_hashes(
1525         &g_native_report, crc32, md5, sha1, sha256);
1526
1527     if (path) {
1528         friidump_native_report_add_artifact(
1529             &g_native_report,
1530             "dump_output",
1531             path,
1532             true,
1533             bytes,
1534             sha256);
1535     }
1536
1537     if (success)
1538         friidump_native_report_set_outcome(
1539             &g_native_report, "full_dump", "pass");
1540     else if (partial)
1541         friidump_native_report_set_outcome(
1542             &g_native_report, "partial_dump", "partial");
1543     else if (cancelled)
1544         friidump_native_report_set_outcome(
1545             &g_native_report, "other", "fail");
1546     else
1547         friidump_native_report_set_outcome(
1548             &g_native_report, "full_dump", "fail");
1549
1550     if (cancelled)
1551         friidump_native_report_add_note(
1552             &g_native_report,
1553             "The invocation was cancelled by the user; only completed output bytes are represented.");
1554
1555     if (g_native_report_resumed) {
1556         friidump_native_add_measurement_scope("unknown");
1557         friidump_native_report_add_note(
1558             &g_native_report,
1559             "Resumed output: duration is not used for throughput because the output byte count includes data from an earlier invocation.");
1560         friidump_native_report_add_note(
1561             &g_native_report,
1562             "Resumed output: streaming hashes were suppressed because they do not describe the complete output file.");
1563     } else if (!success && partial) {
1564         friidump_native_add_measurement_scope("partial_progress");
1565     } else if (type_id == DISC_TYPE_XBOX || xiso_mode) {
1566         friidump_native_add_measurement_scope("assembled_output");
1567         friidump_native_report_add_note(
1568             &g_native_report,
1569             "Xbox output throughput describes the produced logical or assembled output and is not directly comparable to a full optical-payload read.");
1570     } else if (success) {
1571         friidump_native_add_measurement_scope("full_optical_payload");
1572     } else {
1573         friidump_native_add_measurement_scope("unknown");
1574     }
1575
1576     if (options.no_hashing)
1577         friidump_native_report_add_note(
1578             &g_native_report,
1579             "Output hashing was disabled for this invocation.");
1580 }
1581
1582 static bool friidump_elapsed_from_stats(const progstats *stats, double *duration) {
1583     double value;
1584     suseconds_t usecs;
1585
1586     if (!stats || !duration || stats->start_time.tv_sec == 0 ||
1587         stats->end_time.tv_sec == 0)
1588         return false;
1589
1590     value = (double) (stats->end_time.tv_sec - stats->start_time.tv_sec);
1591     if (stats->end_time.tv_usec >= stats->start_time.tv_usec) {
1592         usecs = stats->end_time.tv_usec - stats->start_time.tv_usec;
1593     } else {
1594         value -= 1.0;
1595         usecs = USECS_PER_SEC + stats->end_time.tv_usec - stats->start_time.tv_usec;
1596     }
1597     value += (double) usecs / (double) USECS_PER_SEC;
1598     if (value < 0.0)
1599         return false;
1600     *duration = value;
1601     return true;
1602 }
1603
1604 static void friidump_native_finalize_reference(void) {
1605     if (!friidump_native_report_is_enabled(&g_native_report))
1606         return;
1607
1608     if (strcmp(g_native_report.run_result, "not_applicable") == 0) {
1609         friidump_native_report_set_reference(
1610             &g_native_report, NULL, "not_applicable", NULL);
1611     } else if (g_redump_attempted) {
1612         if (g_redump_result.status == REDUMP_VERIFY_MATCH) {
1613             friidump_native_report_set_reference(
1614                 &g_native_report,
1615                 "redump",
1616                 "match",
1617                 g_redump_result.rom_name[0]
1618                     ? g_redump_result.rom_name
1619                     : g_redump_result.game_name);
1620         } else if (g_redump_result.status == REDUMP_VERIFY_NO_MATCH) {
1621             friidump_native_report_set_reference(
1622                 &g_native_report, "redump", "mismatch", NULL);
1623         } else {
1624             friidump_native_report_set_reference(
1625                 &g_native_report, "redump", "not_checked", NULL);
1626         }
1627     } else if (options.xiso_requested) {
1628         friidump_native_report_set_reference(
1629             &g_native_report, NULL, "not_applicable", NULL);
1630     } else {
1631         friidump_native_report_set_reference(
1632             &g_native_report, NULL, "not_checked", NULL);
1633     }
1634 }
1635
1636 static void friidump_native_publish(double duration, bool have_duration) {
1637     char path[FRIIDUMP_REPORT_PATH_MAX];
1638     char error_text[256];
1639     const char *name_basis;
1640
1641     if (!friidump_native_report_is_enabled(&g_native_report))
1642         return;
1643
1644     name_basis = xbox_ref_log_path();
1645     if (!name_basis)
1646         name_basis = friidump_requested_output_target();
1647     if (!name_basis)
1648         name_basis = "friidump.log";
1649     friidump_native_report_set_name_basis(&g_native_report, name_basis);
1650
1651     if (g_native_report_dump_started && !g_native_report_resumed &&
1652         !g_native_report.have_dump_duration && have_duration) {
1653         g_native_report.have_dump_duration = true;
1654         g_native_report.dump_duration_seconds = duration;
1655     }
1656
1657     friidump_native_finalize_reference();
1658
1659     if (friidump_native_report_write(
1660             &g_native_report,
1661             path,
1662             sizeof(path),
1663             error_text,
1664             sizeof(error_text))) {
1665         fprintf(stderr, "Native report.......: %s (atomic write)\n", path);
1666     } else {
1667         fprintf(stderr, "WARNING: native report was not written: %s\n", error_text);
1668     }
1669 }
1670
1671 void help (void) {
1672         /* 80 cols guide:
1673          *      |-------------------------------------------------------------------------------|
1674          */
1675         fprintf (stderr, "\n"
1676                 "Available command line options:\n"
1677                 "\n"
1678                 " -h, --help                    Show this help\n"
1679                 " -a, --autodump                        Dump the disc to an ISO file with an\n"
1680                 "                               automatically-generated name, resuming the dump\n"
1681                 "                               if possible\n"
1682                 " -g, --gui                     Use more verbose output that can be easily\n"
1683                 "                               parsed by a GUI frontend\n"
1684                 " -d, --device <device>         Dump disc from device <device>\n"
1685                 " -p, --stop                    Instruct device to stop disc rotation\n"
1686                 " -D, --dvd                     Force standard DVD-ROM mode for any drive\n"
1687                 "                               (equivalent to -T 3; uses FriiDump original\n"
1688                 "                               DVD/raw/ISO paths, not Xbox mode)\n"
1689                 " -c, --command <nr>            Force memory dump command:\n"
1690                 "                               0 - vanilla 2064\n"
1691                 "                               1 - vanilla 2384\n"
1692                 "                               2 - Hitachi\n"
1693                 "                               3 - Lite-On\n"
1694                 "                               4 - Renesas\n"
1695                 " -x, --speed <x>               Set streaming speed (1, 24, 32, 64, etc.,\n"
1696                 "                               where 1 = 150 KiB/s and so on)\n"
1697                 " -T, --type <nr>               Force disc type:\n"
1698                 "                               0 - GameCube\n"
1699                 "                               1 - Wii\n"
1700                 "                               2 - Wii_DL\n"
1701                 "                               3 - DVD\n"
1702                 "                               4 - Xbox/XGD 2048-byte-sector mode\n"
1703                 "                                   Native profiles: GDR-8050L and GDR-3120L.\n"
1704                 "                                   Other drives keep normal FriiDump behavior\n"
1705                 "                                   unless Xbox mode is explicitly forced.\n"
1706                 " -S, --size <sectors>          Force disc size\n"
1707                 " -r, --raw <file>              Output to file <file> in raw format (2064-byte\n"
1708                 "                               sectors)\n"
1709                 " -i, --iso[=<file>]            Output to file <file> in ISO format (2048-byte\n"
1710                 "                               sectors). For Xbox/GDR-8050L, omitting <file>\n"
1711                 "                               derives Title[MediaID].iso from the XBE/DMI;\n"
1712                 "                               providing <file> is an explicit override. For\n"
1713                 "                               Xbox/XGD this reconstructs the redump-style\n"
1714                 "                               XGD1 layout and writes .pfi.bin, .dmi.bin, and\n"
1715                 "                               .redump.json metadata when possible\n"
1716                 " -X, --xiso[=<file>]   Output Xbox/XGD game partition as XISO (.xiso).\n"
1717                 "                               For Xbox/GDR-8050L, omitting <file> derives\n"
1718                 "                               Title[MediaID].xiso from the XBE/DMI; providing\n"
1719                 "                               <file> is an explicit override. Attempts to read\n"
1720                 "                               the 32-sector game lead-in from drive-readable\n"
1721                 "                               sectors and zero-fills only unreadable sectors.\n"
1722                 " -u, --unscramble <file>       Convert (unscramble) raw image contained in\n"
1723                 "                               <file> to ISO format\n"
1724                 " -H, --nohash                  Do not compute CRC32/MD5/SHA-1/SHA-256 hashes\n"
1725                 "                               for generated files\n"
1726                 " -s, --resume                  Resume partial dump\n"
1727                 "                               -  General  -----------------------------------\n"
1728                 " -0, --method0[=<req>,<exp>]   Use dumping method 0 (Optional argument\n"
1729                 "                               specifies how many sectors to request from disc\n"
1730                 "                               and read from cache at a time. Values should be\n"
1731                 "                               separated with a comma. Default 16,16)\n"
1732                 "                               -  Non-Streaming  -----------------------------\n"
1733                 " -1, --method1[=<req>,<exp>]   Use dumping method 1 (Default 16,16)\n"
1734                 " -2, --method2[=<req>,<exp>]   Use dumping method 2 (Default 16,16)\n"
1735                 " -3, --method3[=<req>,<exp>]   Use dumping method 3 (Default 16,16)\n"
1736                 "                               -  Streaming  ---------------------------------\n"
1737                 " -4, --method4[=<req>,<exp>]   Use dumping method 4 (Default 27,27)\n"
1738                 " -5, --method5[=<req>,<exp>]   Use dumping method 5 (Default 27,27)\n"
1739                 " -6, --method6[=<req>,<exp>]   Use dumping method 6 (Default 27,27)\n"
1740                 "                               -  Hitachi  -----------------------------------\n"
1741                 " -7, --method7                 Use dumping method 7 (Read and dump 5 blocks\n"
1742                 "                               at a time, using streaming read)\n"
1743                 " -8, --method8                 Use dumping method 8 (Read and dump 5 blocks\n"
1744                 "                               at a time, using streaming read, using DMA)\n"
1745                 " -9, --method9                 Use dumping method 9 (Read and dump 5 blocks\n"
1746                 "                               at a time, using streaming read, using DMA and\n"
1747                 "                               some speed tricks)\n"
1748                 "     --hlds-e7-scan            Probe HLDS HIT 0xE7 cache/memdump bases only;\n"
1749                 "                               writes JSON and does not crack seeds or dump data\n"
1750                 "     --hlds-e7-subcmd-sweep    Probe HIT 0xE7 subcommands/address candidates only;\n"
1751                 "                               writes JSON and does not crack seeds or dump data\n"
1752                 "     --hlds-e7-memrange-sweep  Sweep wider HIT 0xE7 subcmd 0x01 address ranges;\n"
1753                 "                               writes JSON and does not crack seeds or dump data\n"
1754                 "     --scan-log <file>         JSON output path for --hlds-e7-scan\n"
1755                 "     --scan-dump-prefix <prefix>       Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n"
1756                 "     --hlds-profile-report <file> Write selected HLDS profile/evidence JSON\n"
1757                 "     --redump-dat-dir <dir> Directory containing canonical Redump DAT files\n"
1758                 "                              (default: bundled executable data, installed share data, then current directory)\n"
1759                 "                              Environment override: FRIIDUMP_REDUMP_DAT_DIR\n"
1760                 "     --redump-report <file>   Write atomic Redump evidence JSON\n"
1761                 "     --no-redump-verify       Disable automatic post-dump DAT verification\n"
1762                 "     --xgd1-layout-probe <file> Read-only locked/unlocked XGD1 boundary probe;\n"
1763                 "                              writes atomic JSON and does not create an ISO\n"
1764                 "     --xgd1-raw-id-probe <file> Modified-firmware cache-flushed, block-aligned raw-ID probe;\n"
1765                 "                              maps logical LBAs to decoded physical sector IDs\n"
1766                 "                              Native .friidump.json reports are created by default\n"
1767                 "                              beside the final log using its filename\n"
1768                 "     --report-json <file>     Override the complete native report pathname\n"
1769                 "     --report-dir <directory> Override only the report destination directory\n"
1770                 "                              (may be combined with --report-json)\n"
1771                 "     --firmware-modified <note> Mark firmware modified; omission assumes stock\n"
1772                 " -A, --allmethods              Try all known command/method combinations until\n"
1773                 "                               one works. Reopens the drive for each command so\n"
1774                 "                               command-specific vendor handlers are rebound.\n"
1775 #ifdef DEBUG
1776                 " -n, --donottunscramble                Do not try unscrambling to check EDC. Only\n"
1777                 "                               useful for testing the raw performance of the\n"
1778                 "                               different methods\n"
1779                 " -f, --donottflush             Do not call fflush() after every fwrite()\n"
1780 #endif
1781         );
1782 #ifndef WIN32
1783         fprintf (stderr,
1784                 "\nLinux raw-I/O requirement:\n"
1785                 "  GC/Wii memory-dump methods and Xbox vendor-unlock paths require effective\n"
1786                 "  CAP_SYS_RAWIO. Apply cap_sys_rawio=ep to the exact validated executable;\n"
1787                 "  do not run FriiDump as root. Rebuilds and replacements clear capabilities.\n"
1788                 "  See docs/LINUX.md and validation/friidump-linux-rawio-capability.sh.\n");
1789 #endif
1790
1791         return;
1792 }
1793
1794
1795 bool optparse (int argc, char **argv) {
1796         bool out;
1797         char *result = NULL;
1798         int c;
1799         int option_index = 0;
1800         static struct option long_options[] = {
1801                 {"help", 0, 0, 'h'},    //0 - no_argument
1802                 {"autodump", 0, 0, 'a'},
1803                 {"gui", 0, 0, 'g'},
1804                 {"device", 1, 0, 'd'},  //1 - required_argument
1805                 {"raw", 1, 0, 'r'},
1806                 {"iso", 2, 0, 'i'},
1807                 {"xiso", 2, 0, 'X'},
1808                 {"unscramble", 1, 0, 'u'},
1809                 {"nohash", 0, 0, 'H'},
1810                 {"resume", 0, 0, 's'},
1811                 {"method0", 2, 0, '0'}, //2 - optional_argument
1812                 {"method1", 2, 0, '1'},
1813                 {"method2", 2, 0, '2'},
1814                 {"method3", 2, 0, '3'},
1815                 {"method4", 2, 0, '4'},
1816                 {"method5", 2, 0, '5'},
1817                 {"method6", 2, 0, '6'},
1818                 {"method7", 0, 0, '7'},
1819                 {"method8", 0, 0, '8'},
1820                 {"method9", 0, 0, '9'},
1821                 {"stop", 0, 0, 'p'},
1822                 {"dvd", 0, 0, 'D'},
1823                 {"command", 1, 0, 'c'},
1824                 {"startsector", 1, 0, 't'},
1825                 {"size", 1, 0, 'S'},
1826                 {"speed", 1, 0, 'x'},
1827                 {"type", 1, 0, 'T'},
1828                 {"allmethods", 0, 0, 'A'},
1829                 {"hlds-e7-scan", 0, 0, 1000},
1830                 {"hlds-e7-subcmd-sweep", 0, 0, 1003},
1831                 {"hlds-e7-memrange-sweep", 0, 0, 1004},
1832                 {"scan-log", 1, 0, 1001},
1833                 {"scan-dump-prefix", 1, 0, 1002},
1834                 {"hlds-profile-report", 1, 0, 1005},
1835                 {"redump-dat-dir", 1, 0, 1006},
1836                 {"redump-report", 1, 0, 1007},
1837                 {"no-redump-verify", 0, 0, 1008},
1838                 {"xgd1-layout-probe", 1, 0, 1009},
1839                 {"xgd1-raw-id-probe", 1, 0, 1010},
1840                 {"report-json", 1, 0, 1011},
1841                 {"report-dir", 1, 0, 1012},
1842                 {"firmware-modified", 1, 0, 1013},
1843 #ifdef DEBUG
1844                 /* We don't want newbies to generate and put into circulation bad dumps, so this options are disabled for releases */
1845                 {"donottunscramble", 0, 0, 'n'},
1846                 {"donottflush", 0, 0, 'f'},
1847 #endif
1848                 {0, 0, 0, 0}
1849         };
1850
1851         if (argc == 1) {
1852                 help ();
1853                 exit (1);
1854         }
1855         
1856         /* Init options to default values */
1857         options.device = NULL;
1858         options.autodump = false;
1859         options.gui = false;
1860         options.raw_in = NULL;
1861         options.raw_out = NULL;
1862         options.iso_out = NULL;
1863         options.xiso_out = NULL;
1864         options.iso_requested = false;
1865         options.xiso_requested = false;
1866         options.xbox_filename_override = false;
1867         options.xbox_auto_filename = false;
1868         options.no_hashing = false;
1869         options.resume = false;
1870         options.dump_method = -1;
1871         options.command = -1;
1872         options.start_sector = -1;
1873         options.sectors_no = -1;
1874         options.speed = -1;
1875         options.disctype = -1;
1876         options.sec_disc = -1;
1877         options.sec_mem = -1;
1878         options.no_unscrambling = false;
1879         options.no_flushing = false;
1880         options.stop_unit = false;
1881         options.allmethods = false;
1882         options.hlds_e7_scan = false;
1883         options.hlds_e7_subcmd_sweep = false;
1884         options.hlds_e7_memrange_sweep = false;
1885         options.hlds_e7_scan_log = NULL;
1886         options.hlds_e7_scan_dump_prefix = NULL;
1887         options.hlds_profile_report = NULL;
1888         options.redump_dat_dir = NULL;
1889         options.redump_report = NULL;
1890         options.no_redump_verify = false;
1891         options.xgd1_layout_probe = false;
1892         options.xgd1_layout_probe_report = NULL;
1893         options.xgd1_raw_id_probe = false;
1894         options.xgd1_raw_id_probe_report = NULL;
1895         options.native_report_json = NULL;
1896         options.native_report_dir = NULL;
1897         options.firmware_modified_note = NULL;
1898
1899         do {
1900 #ifdef DEBUG
1901                 c = getopt_long (argc, argv, "hpagd:r:i::X::u:Hs0::1::2::3::4::5::6::789Dc:t:S:x:T:Anf", long_options, &option_index);
1902 #else
1903                 c = getopt_long (argc, argv, "hpagd:r:i::X::u:Hs0::1::2::3::4::5::6::789Dc:t:S:x:T:A", long_options, &option_index);
1904 #endif
1905
1906                 switch (c) {
1907                         case 'h':
1908                                 help ();
1909                                 exit (1);
1910                                 break;
1911                         case 'p':
1912                                 options.stop_unit = true;
1913                                 break;
1914                         case 'a':
1915                                 options.autodump = true;
1916                                 options.resume = true;
1917                                 break;
1918                         case 'g':
1919                                 options.gui = true;
1920                                 break;
1921                         case 'd':
1922                                 my_strdup (options.device, optarg);
1923                                 break;
1924                         case 'r':
1925                                 my_strdup (options.raw_out, optarg);
1926                                 break;
1927                         case 'i':
1928                                 options.iso_requested = true;
1929                                 /* Preserve the old `-i file.iso` syntax even though Xbox now also
1930                                  * supports bare `-i` for XBE/DMI-derived names. */
1931                                 if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-')
1932                                         optarg = argv[optind++];
1933                                 if (optarg) {
1934                                         my_strdup (options.iso_out, optarg);
1935                                         options.xbox_filename_override = true;
1936                                 } else {
1937                                         options.xbox_auto_filename = true;
1938                                 }
1939                                 break;
1940                         case 'X':
1941                                 options.xiso_requested = true;
1942                                 /* Preserve the old `-X file.xiso` syntax while allowing bare `-X`. */
1943                                 if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-')
1944                                         optarg = argv[optind++];
1945                                 if (optarg) {
1946                                         my_strdup (options.xiso_out, optarg);
1947                                         options.xbox_filename_override = true;
1948                                 } else {
1949                                         options.xbox_auto_filename = true;
1950                                 }
1951                                 break;
1952                         case 'u':
1953                                 my_strdup (options.raw_in, optarg);
1954                                 break;
1955                         case 'H':
1956                                 options.no_hashing = true;
1957                                 break;
1958                         case 's':
1959                                 options.resume = true;
1960                                 break;
1961                         case '0':
1962                         case '1':
1963                         case '2':
1964                         case '3':
1965                         case '4':
1966                         case '5':
1967                         case '6':
1968                                 options.dump_method = c - '0';
1969                                 if (optarg) {
1970                                         result = strtok(optarg, ",");
1971                                         result = strtok(NULL, ",");
1972                                         options.sec_disc = atol(strpbrk(optarg,"1234567890"));
1973                                         if (result) options.sec_mem = atol(result);
1974                                         else {
1975                                                 help ();
1976                                                 exit (1);
1977                                         }
1978                                 }
1979                                 break;
1980                         case '7':
1981                         case '8':
1982                         case '9':
1983                                 options.dump_method = c - '0';
1984                                 break;
1985                         case 'D':
1986                                 options.disctype = DISC_TYPE_DVD;
1987                                 unscrambler_set_disctype (DISC_TYPE_DVD);
1988                                 break;
1989                         case 'c':
1990                                 options.command = atol (optarg);
1991                                 if (options.command > 4) {
1992                                         help ();
1993                                         exit (1);
1994                                 };
1995                                 break;
1996                         case 't':
1997                                 options.start_sector = atol (optarg);
1998                                 break;
1999                         case 'S':
2000                                 options.sectors_no = atol (optarg);
2001                                 break;
2002                         case 'x':
2003                                 options.speed = atol (optarg);
2004                                 break;
2005                         case 'T':
2006                                 options.disctype = atol (optarg);
2007                                 if (options.disctype > 4) {
2008                                         help ();
2009                                         exit (1);
2010                                 };
2011                                 if (options.disctype <= DISC_TYPE_DVD)
2012                                         unscrambler_set_disctype (options.disctype);
2013                                 break;
2014                         case 'A':
2015                                 options.allmethods = true;
2016                                 options.resume = true;
2017                                 break;
2018 #ifdef DEBUG
2019                         case 'n':
2020                                 options.no_unscrambling = true;
2021                                 break;
2022                         case 'f':
2023                                 options.no_flushing = true;
2024                                 break;
2025 #endif
2026                         case 1000:
2027                                 options.hlds_e7_scan = true;
2028                                 break;
2029                         case 1003:
2030                                 options.hlds_e7_subcmd_sweep = true;
2031                                 break;
2032                         case 1004:
2033                                 options.hlds_e7_memrange_sweep = true;
2034                                 break;
2035                         case 1001:
2036                                 my_strdup (options.hlds_e7_scan_log, optarg);
2037                                 break;
2038                         case 1002:
2039                                 my_strdup (options.hlds_e7_scan_dump_prefix, optarg);
2040                                 break;
2041                         case 1005:
2042                                 my_strdup (options.hlds_profile_report, optarg);
2043                                 break;
2044                         case 1006:
2045                                 my_strdup (options.redump_dat_dir, optarg);
2046                                 break;
2047                         case 1007:
2048                                 my_strdup (options.redump_report, optarg);
2049                                 break;
2050                         case 1008:
2051                                 options.no_redump_verify = true;
2052                                 break;
2053                         case 1009:
2054                                 options.xgd1_layout_probe = true;
2055                                 my_strdup (options.xgd1_layout_probe_report, optarg);
2056                                 options.disctype = DISC_TYPE_XBOX;
2057                                 break;
2058                         case 1010:
2059                                 options.xgd1_raw_id_probe = true;
2060                                 my_strdup (options.xgd1_raw_id_probe_report, optarg);
2061                                 options.disctype = DISC_TYPE_XBOX;
2062                                 break;
2063                         case 1011:
2064                                 my_strdup (options.native_report_json, optarg);
2065                                 break;
2066                         case 1012:
2067                                 my_strdup (options.native_report_dir, optarg);
2068                                 break;
2069                         case 1013:
2070                                 my_strdup (options.firmware_modified_note, optarg);
2071                                 break;
2072                         case -1:
2073                                 break;
2074                         default:
2075 //                              fprintf (stderr, "?? getopt returned character code 0%o ??\n", c);
2076                                 exit (7);
2077                                 break;
2078                 }
2079         } while (c != -1);
2080
2081         if (optind < argc) {
2082                 /* Command-line arguments remaining. Ignore them, warning the user. */
2083                 fprintf (stderr, "WARNING: Extra parameters ignored\n");
2084         }
2085
2086         if (options.xgd1_layout_probe || options.xgd1_raw_id_probe)
2087                 options.disctype = DISC_TYPE_XBOX;
2088
2089         /* Sanity checks... */
2090         out = false;
2091         if (!options.device && !options.raw_in) {
2092                 fprintf (stderr, "No operation specified. Please use the -d or -u options.\n");
2093         } else if (options.raw_in && options.raw_out) {
2094                 fprintf (stderr,
2095                         "Are you sure you want to convert a raw image to another raw image? ;)\n"
2096                         "Take a look at the -i and -a options!\n"
2097                 );
2098         } else if (options.autodump && (options.raw_out || options.iso_requested || options.xiso_requested)) {
2099                 fprintf (stderr, "The -r, -i and -X options cannot be used together with -a.\n");
2100         } else if (options.xiso_requested && (options.raw_out || options.iso_requested)) {
2101                 fprintf (stderr, "The -X/--xiso option is a separate Xbox output mode and cannot be combined with -r or -i.\n");
2102         } else if ((options.xgd1_layout_probe || options.xgd1_raw_id_probe) &&
2103                    (options.autodump || options.raw_in || options.raw_out ||
2104                     options.iso_requested || options.xiso_requested ||
2105                     options.allmethods || options.hlds_e7_scan ||
2106                     options.hlds_e7_subcmd_sweep || options.hlds_e7_memrange_sweep ||
2107                     (options.xgd1_layout_probe && options.xgd1_raw_id_probe))) {
2108                 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");
2109         } else if ((options.native_report_json && !options.native_report_json[0]) ||
2110                    (options.native_report_dir && !options.native_report_dir[0])) {
2111                 fprintf (stderr, "Native report file and directory arguments must not be empty.\n");
2112         } else if ((options.native_report_json && strlen(options.native_report_json) >= FRIIDUMP_REPORT_PATH_MAX) ||
2113                    (options.native_report_dir && strlen(options.native_report_dir) >= FRIIDUMP_REPORT_PATH_MAX)) {
2114                 fprintf (stderr, "Native report path is too long.\n");
2115         } else if (options.firmware_modified_note &&
2116                    !options.firmware_modified_note[0]) {
2117                 fprintf (stderr, "--firmware-modified requires a non-empty explanation.\n");
2118         } else if (options.firmware_modified_note &&
2119                    strlen(options.firmware_modified_note) > 1000) {
2120                 fprintf (stderr, "--firmware-modified note exceeds the 1000-byte report limit.\n");
2121         } else if (friidump_native_report_option_requested() &&
2122                    !friidump_native_report_operation_supported()) {
2123                 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");
2124         } else if (options.device &&
2125                    !options.allmethods && !options.stop_unit &&
2126                    !options.hlds_e7_scan && !options.hlds_e7_subcmd_sweep &&
2127                    !options.hlds_e7_memrange_sweep && !options.xgd1_layout_probe &&
2128                    !options.xgd1_raw_id_probe &&
2129                    options.raw_out && options.iso_requested) {
2130                 fprintf (stderr, "Native compatibility reports are created by default and require exactly one primary output; do not combine -r and -i.\n");
2131         } else {
2132                 /* Specified options seem to make sense */
2133                 out = true;
2134         }
2135                 
2136         return (out);
2137 }
2138
2139 int dologic (disc *d, progstats *stats) {
2140         disc_type type_id;
2141         char *type, *game_id, *region, *maker_id, *maker, *version, *title, tmp[0x03E0 + 4 + 1];
2142         bool drive_supported;
2143         bool xbox_forced;
2144         bool xbox_output_requested;
2145         bool dump_attempted;
2146         int out;
2147         dumper *dmp;
2148         u_int32_t current_sector = 0;
2149         
2150         xbox_forced = (options.disctype == DISC_TYPE_XBOX);
2151         xbox_output_requested = xbox_forced || options.xiso_requested || options.xgd1_layout_probe || options.xgd1_raw_id_probe;
2152         dump_attempted = false;
2153         out = false;
2154         g_native_cancel_requested = 0;
2155         
2156         
2157
2158                                 if (options.stop_unit) { //stop rotation, if requested
2159                                         fprintf (stderr, "Issuing STOP command... %s\n", (disc_stop_unit (d, false)) ? "OK" : "Failed");
2160                                         exit (1);
2161                                 }
2162                                 else disc_stop_unit(d, true); //else start rotation
2163
2164                                 drive_supported = disc_get_drive_support_status (d);
2165                                 fprintf (stderr,
2166                                         "\n"
2167                                         "Drive information:\n"
2168                                         "----------------------------------------------------------------------\n"
2169                                         "Drive model........: %s\n"
2170                                         "Supported..........: %s\n", disc_get_drive_model_string (d), drive_supported ? "Yes" : "No"
2171                                 );
2172
2173                                         if (xbox_output_requested && !disc_is_xbox_unlock_drive (d)) {
2174                                                 if (friidump_native_report_is_enabled(&g_native_report))
2175                                                         friidump_native_report_add_note(&g_native_report, "The selected drive does not expose a supported Xbox unlock profile; dumping was not attempted.");
2176                                                 fprintf (stderr,
2177                                                         "Xbox/XGD output is limited to the supported Xbox unlock profiles "
2178                                                         "currently wired into this branch: GDR-8050L, GDR-3120L, "
2179                                                         "and known Samsung/Kreon-style vendor-unlock drives.\n"
2180                                                         "Refusing to fall back to FriiDump GC/Wii methods for Xbox mode on this drive.\n");
2181                                                 return false;
2182                                         }
2183
2184                                         /* Xbox/XGD (-T 4 or -X) is a direct MMC/SCSI READ(10) path.
2185                                          * Do not let the detected GC/Wii vendor memdump method (for example
2186                                          * Hitachi command 2 / method 9 on GDR-8050L) drive sector reads. */
2187                                         if (xbox_output_requested && options.dump_method == -1)
2188                                                 options.dump_method = 10;
2189
2190                                         init_range(d, options.sec_disc, options.sec_mem);
2191
2192                                         if (!(disc_set_read_method (d, options.dump_method))) {
2193                                                 if (friidump_native_report_is_enabled(&g_native_report))
2194                                                         friidump_native_report_add_note(&g_native_report, "FriiDump could not select the requested read method; dumping was not attempted.");
2195                                                 return false;
2196                                         }
2197
2198                                         if (xbox_output_requested && disc_get_method(d) == 10) {
2199                                                 fprintf (stderr, "Command............: Xbox direct MMC/SCSI path\n");
2200                                                 fprintf (stderr, "Method.............: 10 (Xbox READ(10))\n");
2201                                         } else {
2202                                                 if (options.command!=-1) fprintf (stderr, 
2203                                                         "Command............: %d (forced)\n", disc_get_command(d));
2204                                                 else fprintf (stderr, 
2205                                                         "Command............: %d\n", disc_get_command(d));
2206                                                 if (disc_get_def_method(d)!=disc_get_method(d)) fprintf (stderr, 
2207                                                         "Method.............: %d (forced)\n", disc_get_method(d));
2208                                                 else fprintf (stderr, 
2209                                                         "Method.............: %d\n", disc_get_method(d));
2210                                                 if (disc_get_hlds_e7_type (d) != 0) {
2211                                                         fprintf (stderr, "HLDS 0xE7 profile..: %s\n", disc_get_hlds_e7_profile_name (d));
2212                                                         fprintf (stderr, "HLDS support tier..: %s\n", disc_get_hlds_e7_support_tier (d));
2213                                                         fprintf (stderr, "HLDS family........: %s\n", disc_get_hlds_e7_family (d));
2214                                                         fprintf (stderr, "HLDS E7 tokens.....: %s\n", disc_get_hlds_e7_tokens (d));
2215                                                         fprintf (stderr, "HLDS Stage5B row...: %s\n", disc_get_hlds_e7_record_id (d));
2216                                                         fprintf (stderr, "Static CDB/gate....: 0x%03x / 0x%08x\n", disc_get_hlds_e7_static_cdb_base (d), disc_get_hlds_e7_static_gate (d));
2217                                                         fprintf (stderr, "Cache base.........: 0x%08x\n", disc_get_hlds_e7_cache_base (d));
2218                                                         fprintf (stderr, "Memory windows.....: %u\n", disc_get_hlds_e7_mem_blocks (d));
2219                                                         if (disc_get_hlds_e7_notes (d) && disc_get_hlds_e7_notes (d)[0])
2220                                                                 fprintf (stderr, "HLDS notes.........: %s\n", disc_get_hlds_e7_notes (d));
2221                                                 }
2222                                         }
2223                                         if (options.hlds_profile_report) {
2224                                                 if (friidump_write_hlds_profile_report(d, options.hlds_profile_report))
2225                                                         fprintf(stderr, "HLDS profile report: %s\n", options.hlds_profile_report);
2226                                         }
2227                                         options.dump_method=disc_get_method(d);
2228                                         friidump_summary_begin(d);
2229                                         friidump_native_set_profile(d, xbox_output_requested);
2230 #ifndef WIN32
2231                                         if (!friidump_linux_rawio_preflight(
2232                                                         d,
2233                                                         xbox_output_requested,
2234                                                         drive_supported,
2235                                                         options.hlds_e7_scan ||
2236                                                         options.hlds_e7_subcmd_sweep ||
2237                                                         options.hlds_e7_memrange_sweep))
2238                                                 return false;
2239 #endif
2240                                 if ((options.dump_method==0) 
2241                                 || (options.dump_method==1) || (options.dump_method==2) || (options.dump_method==3)
2242                                 || (options.dump_method==4) || (options.dump_method==5) || (options.dump_method==6)
2243                                 ){
2244                                         fprintf (stderr, 
2245                                         "Requested sectors..: %d\n", disc_get_sec_disc(d));
2246                                         fprintf (stderr, 
2247                                         "Expected sectors...: %d\n", disc_get_sec_mem(d));
2248                                 }
2249
2250                                 fprintf (stderr, "\nPress Ctrl+C at any time to terminate\n");
2251
2252                                 //set speed for 1st time
2253                                 if (options.speed != -1) disc_set_speed(d, options.speed * 177);
2254                                 if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177);
2255 //                              disc_set_speed(d, 0xffff);
2256
2257                                 /* Windows may attach filesystem/autoplay polling to odd GC/Wii discs and
2258                                  * steal the drive during the HLDS 0xE7 seed phase, especially on Type1
2259                                  * GCC-4160N/GCC-4240N.  Reuse the same volume guard mechanism that the
2260                                  * Xbox path already uses, but apply it before disc_init()/seed reads for
2261                                  * all HLDS 0xE7 GC/Wii profiles.  Failure is warning-only because Explorer
2262                                  * may already have a transient handle; the read path itself remains the
2263                                  * authority. */
2264                                 if (!xbox_output_requested && disc_get_hlds_e7_type (d) != 0) {
2265                                         if (disc_get_hlds_e7_type (d) == 44 || disc_get_hlds_e7_type (d) == 45) {
2266                                                 fprintf (stderr,
2267                                                         "\nGDR-8050L modified-firmware warning:\n"
2268                                                         "  This GC/Wii 0xE7 path assumes a cross-flashed or modified GDR-8050L firmware with 0xE7 memdump support added.\n"
2269                                                         "  Stock GDR-8050L firmware is still supported for Xbox ripping, but it is not expected to dump GC/Wii discs through this path.\n");
2270                                         }
2271 #ifdef WIN32
2272                                         fprintf (stderr,
2273                                                 "\nWindows AutoPlay warning:\n"
2274                                                 "  HLDS 0xE7 GC/Wii seed reads are sensitive to Windows polling.\n"
2275                                                 "  Disable AutoPlay for this drive/media and close File Explorer or any \"insert a disc\" dialogs before dumping.\n"
2276                                                 "  FriiDump will try to lock the volume, but AutoPlay can still interfere before or during seed retrieval.\n");
2277                                         fprintf (stderr, "Applying Windows volume lock guard for HLDS 0xE7 seed reads... ");
2278 #else
2279                                         fprintf (stderr,
2280                                                 "\nLinux media-polling warning:\n"
2281                                                 "  HLDS 0xE7 GC/Wii seed reads can be disturbed by desktop automount and media polling.\n"
2282                                                 "  Unmount the disc and close file managers or media players before dumping.\n");
2283 #endif
2284                                         disc_refresh_volume (d);
2285                                         {
2286                                                 int volume_lock_result = disc_lock_volume (d);
2287 #ifdef WIN32
2288                                                 if (volume_lock_result < 0)
2289                                                         fprintf (stderr, "Warning: failed; disable AutoPlay, close File Explorer/AutoPlay dialogs for this drive, then rerun.\n");
2290                                                 else
2291                                                         fprintf (stderr, "OK\n");
2292 #else
2293                                                 if (volume_lock_result > 0)
2294                                                         fprintf (stderr, "Linux exclusive optical-device lock: unavailable; continuing without one.\n");
2295                                                 else if (volume_lock_result < 0)
2296                                                         fprintf (stderr, "Linux exclusive optical-device lock: failed; continuing without one.\n");
2297                                                 else
2298                                                         fprintf (stderr, "Linux exclusive optical-device lock: acquired.\n");
2299 #endif
2300                                         }
2301                                 }
2302
2303                                 if (options.xgd1_layout_probe || options.xgd1_raw_id_probe) {
2304 #ifdef WIN32
2305                                         int media_rc;
2306                                         int media_sense_key;
2307                                         int media_asc;
2308                                         int media_ascq;
2309                                         int probe_status;
2310                                         bool stop_ok;
2311                                         const char *probe_name;
2312
2313                                         probe_name = options.xgd1_raw_id_probe ? "XGD1 raw-sector ID" : "XGD1 logical-boundary";
2314
2315                                         if (!disc_is_xbox_challenge_drive (d)) {
2316                                                 fprintf (stderr,
2317                                                          "%s probe currently supports only the GDR-8050L challenge-handshake profile.\n",
2318                                                          probe_name);
2319                                                 return false;
2320                                         }
2321
2322                                         if (options.xgd1_raw_id_probe &&
2323                                             !(disc_get_hlds_e7_type (d) == 44 ||
2324                                               disc_get_hlds_e7_type (d) == 45 ||
2325                                               disc_get_hlds_e7_type (d) == 442 ||
2326                                               disc_get_hlds_e7_type (d) == 443 ||
2327                                               disc_get_hlds_e7_type (d) == 445)) {
2328                                                 fprintf (stderr,
2329                                                          "--xgd1-raw-id-probe requires the modified GDR-8050L HIT 0xE7 memdump profile.\n");
2330                                                 return false;
2331                                         }
2332
2333                                         fprintf (stderr, "\nChecking for ready Xbox media before %s probing... ", probe_name);
2334                                         media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
2335                                         if (media_rc <= 0) {
2336                                                 fprintf (stderr,
2337                                                          "Failed (sense %02X/%02X/%02X). Insert the disc, wait for spin-up, close AutoPlay/File Explorer, and retry.\n",
2338                                                          media_sense_key, media_asc, media_ascq);
2339                                                 return false;
2340                                         }
2341                                         fprintf (stderr, "OK\n");
2342
2343                                         gettimeofday (&(stats -> start_time), NULL);
2344                                         if (options.xgd1_raw_id_probe)
2345                                                 probe_status = xbox_ref_xgd1_raw_id_probe_with_handle (
2346                                                                 disc_get_native_handle (d), disc_get_device (d), options.xgd1_raw_id_probe_report);
2347                                         else
2348                                                 probe_status = xbox_ref_xgd1_layout_probe_with_handle (
2349                                                                 disc_get_native_handle (d), disc_get_device (d), options.xgd1_layout_probe_report);
2350
2351                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after %s probe... ", probe_name);
2352                                         stop_ok = disc_stop_unit (d, false);
2353                                         fprintf (stderr, "%s\n", stop_ok ? "OK" : "Failed");
2354                                         gettimeofday (&(stats -> end_time), NULL);
2355                                         fprintf (stderr, "%s probe status: %s\n", probe_name, probe_status == 0 ? "OK" : "FAILED");
2356                                         friidump_summary_reset();
2357                                         return probe_status == 0;
2358 #else
2359                                         fprintf (stderr, "XGD1 probe modes are available only in the Windows build.\n");
2360                                         return false;
2361 #endif
2362                                 }
2363
2364                                 if (options.hlds_e7_scan) {
2365                                         out = disc_hlds_e7_scan (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
2366                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 scan... ");
2367                                         fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
2368                                         return out;
2369                                 }
2370
2371                                 if (options.hlds_e7_subcmd_sweep) {
2372                                         out = disc_hlds_e7_subcmd_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
2373                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 subcommand sweep... ");
2374                                         fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
2375                                         return out;
2376                                 }
2377
2378                                 if (options.hlds_e7_memrange_sweep) {
2379                                         out = disc_hlds_e7_memrange_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
2380                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 memory-range sweep... ");
2381                                         fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
2382                                         return out;
2383                                 }
2384
2385                                 {
2386                                         int media_rc, media_sense_key, media_asc, media_ascq;
2387                                         fprintf (stderr, "\nChecking for ready media before disc seed retrieval... ");
2388                                         media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
2389                                         if (media_rc <= 0) {
2390                                                 friidump_native_set_preflight_failure(
2391                                                         media_rc, media_sense_key, media_asc, media_ascq);
2392                                                 if (media_rc == 0)
2393                                                         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);
2394                                                 else
2395                                                         fprintf (stderr, "Drive/media not ready (sense %02X/%02X/%02X). Wait for spin-up, close AutoPlay/File Explorer, and retry.\n", media_sense_key, media_asc, media_ascq);
2396                                                 return false;
2397                                         }
2398                                         fprintf (stderr, "OK\n");
2399
2400                                         struct timeval seed_start, seed_end;
2401                                         double seed_elapsed;
2402                                         char seed_elapsed_buf[32];
2403
2404                                         if (xbox_output_requested)
2405                                                 fprintf (stderr, "\nInitializing Xbox/XGD disc state... ");
2406                                         else
2407                                                 fprintf (stderr, "\nRetrieving disc seeds, this might take a while... ");
2408
2409                                         gettimeofday(&seed_start, NULL);
2410                                         if (!disc_init (d, options.disctype, options.sectors_no)) {
2411                                                 gettimeofday(&seed_end, NULL);
2412                                                 seed_elapsed = (double)(seed_end.tv_sec - seed_start.tv_sec) +
2413                                                         (double)(seed_end.tv_usec - seed_start.tv_usec) / (double)USECS_PER_SEC;
2414                                                 if (seed_elapsed < 0.0) seed_elapsed = 0.0;
2415                                                 friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
2416                                                 if (!xbox_output_requested)
2417                                                         fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
2418                                                 if (g_validation_summary.active) {
2419                                                         g_validation_summary.have_seed_duration = true;
2420                                                         g_validation_summary.seed_duration = seed_elapsed;
2421                                                 }
2422                                                 if (friidump_native_report_is_enabled(&g_native_report)) {
2423                                                         if (g_native_cancel_requested) {
2424                                                                 friidump_native_report_set_seed(&g_native_report, true, "fail", true, seed_elapsed);
2425                                                                 friidump_native_report_set_outcome(&g_native_report, "other", "fail");
2426                                                                 friidump_native_report_add_note(&g_native_report, "The invocation was cancelled by the user during media initialization or seed retrieval.");
2427                                                         } else if (xbox_output_requested) {
2428                                                                 friidump_native_report_set_seed(&g_native_report, false, "not_applicable", false, 0.0);
2429                                                                 friidump_native_report_set_outcome(&g_native_report, "other", "fail");
2430                                                                 friidump_native_report_add_note(&g_native_report, "Xbox/media initialization failed before dumping.");
2431                                                         } else {
2432                                                                 friidump_native_report_set_seed(&g_native_report, true, "fail", true, seed_elapsed);
2433                                                                 friidump_native_report_set_outcome(&g_native_report, "seed_only", "fail");
2434                                                                 friidump_native_report_add_note(&g_native_report, "Disc initialization or seed retrieval failed.");
2435                                                         }
2436                                                         friidump_native_report_set_dump(&g_native_report, false, "not_attempted", false, 0, false, 0, false, 0.0, NULL, false, 0, NULL);
2437                                                 }
2438                                                 if (!xbox_output_requested) {
2439                                                         bool stop_ok;
2440                                                         friidump_capture_seed_diagnostic (d);
2441                                                         stop_ok = disc_stop_unit (d, false);
2442                                                         fprintf (stderr, "\nIssuing STOP UNIT / spin-down after seed failure... %s\n", stop_ok ? "OK" : "Failed");
2443                                                         if (g_validation_summary.active) {
2444                                                                 g_validation_summary.stop_attempted = true;
2445                                                                 g_validation_summary.stop_ok = stop_ok;
2446                                                         }
2447                                                         if (friidump_native_report_is_enabled(&g_native_report))
2448                                                                 friidump_native_report_add_note(&g_native_report,
2449                                                                         stop_ok ? "STOP UNIT after seed failure succeeded." : "STOP UNIT after seed failure failed.");
2450                                                 }
2451                                                 fprintf (stderr, "Failed\n");
2452                                                 out = false;
2453                                         } else {
2454                                                 gettimeofday(&seed_end, NULL);
2455                                                 seed_elapsed = (double)(seed_end.tv_sec - seed_start.tv_sec) +
2456                                                         (double)(seed_end.tv_usec - seed_start.tv_usec) / (double)USECS_PER_SEC;
2457                                                 if (seed_elapsed < 0.0) seed_elapsed = 0.0;
2458                                                 friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
2459                                                 if (!xbox_output_requested)
2460                                                         fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
2461                                                 fprintf (stderr, "OK\n");
2462                                                 if (g_validation_summary.active) {
2463                                                         g_validation_summary.seed_ok = true;
2464                                                         g_validation_summary.have_seed_duration = true;
2465                                                         g_validation_summary.seed_duration = seed_elapsed;
2466                                                 }
2467                                         disc_get_type (d, &type_id, &type);
2468                                         disc_get_gameid (d, &game_id);
2469                                         disc_get_region (d, NULL, &region);
2470                                         disc_get_maker (d, &maker_id, &maker);
2471                                         disc_get_version (d, NULL, &version);
2472                                         disc_get_title (d, &title);
2473                                         if (g_validation_summary.active) {
2474                                                 friidump_summary_copy(g_validation_summary.disc_type, sizeof(g_validation_summary.disc_type), type);
2475                                                 friidump_summary_copy(g_validation_summary.game_id, sizeof(g_validation_summary.game_id), game_id);
2476                                                 friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), title);
2477                                                 g_validation_summary.sectors = disc_get_sectors_no(d);
2478                                         }
2479                                         friidump_native_set_media(type_id, title,
2480                                                         type_id == DISC_TYPE_XBOX ? NULL : region, game_id);
2481                                         if (friidump_native_report_is_enabled(&g_native_report)) {
2482                                                 if (type_id == DISC_TYPE_GAMECUBE || type_id == DISC_TYPE_WII || type_id == DISC_TYPE_WII_DL)
2483                                                         friidump_native_report_set_seed(&g_native_report, true, "pass", true, seed_elapsed);
2484                                                 else
2485                                                         friidump_native_report_set_seed(&g_native_report, false, "not_applicable", false, 0.0);
2486                                         }
2487                                         if (g_native_cancel_requested) {
2488                                                 friidump_native_report_set_dump(&g_native_report, false, "not_attempted", false, 0, false, 0, false, 0.0, NULL, false, 0, NULL);
2489                                                 friidump_native_report_set_outcome(&g_native_report, "other", "fail");
2490                                                 friidump_native_report_add_note(&g_native_report, "The invocation was cancelled by the user before dumping began.");
2491                                                 return false;
2492                                         }
2493                                         fprintf (stderr, 
2494                                                 "\n"
2495                                                 "Disc information:\n"
2496                                                 "----------------------------------------------------------------------\n");
2497
2498                                         if (options.disctype!=-1) fprintf (stderr, 
2499                                                 "Disc type..........: %s (forced)\n", type);
2500                                         else fprintf (stderr, 
2501                                                 "Disc type..........: %s\n", type);
2502                                         if (options.sectors_no!=-1) fprintf (stderr, 
2503                                                 "Disc size..........: %d (forced)\n", disc_get_sectors_no(d));
2504                                         else fprintf (stderr, 
2505                                                 "Disc size..........: %d\n", disc_get_sectors_no(d));
2506
2507                                         if (disc_get_layerbreak(d)>0 && type_id==DISC_TYPE_DVD) fprintf (stderr, 
2508                                                 "Layer break........: %d\n", disc_get_layerbreak(d));
2509
2510                                         if ((type_id==DISC_TYPE_GAMECUBE) || (type_id==DISC_TYPE_WII) || (type_id==DISC_TYPE_WII_DL)) fprintf (stderr, 
2511                                                 "Game ID............: %s\n"
2512                                                 "Region.............: %s\n"
2513                                                 "Maker..............: %s - %s\n"
2514                                                 "Version............: %s\n"
2515                                                 "Game title.........: %s\n", game_id, region, maker_id, maker, version, title
2516                                         );
2517
2518                                         if (type_id == DISC_TYPE_WII || type_id == DISC_TYPE_WII_DL)
2519                                                 fprintf (stderr, "Contains update....: %s\n" , disc_get_update (d) ? "Yes" : "No");
2520                                         fprintf (stderr, "\n");
2521                                         
2522                                         disc_set_unscrambling (d, !options.no_unscrambling);
2523
2524                                         if (type_id <= DISC_TYPE_DVD)
2525                                                 unscrambler_set_disctype (type_id);
2526
2527                                         if (options.autodump) {
2528                                                 snprintf (tmp, sizeof (tmp), "%s.iso", title);
2529                                                 my_strdup (options.iso_out, tmp);
2530                                                 options.iso_requested = true;
2531                                         }
2532
2533                                         /* Xbox/GDR-8050L supports default XBE/DMI-derived names.
2534                                          * Use an empty-string placeholder so the copied reference path can
2535                                          * derive Title[MediaID].iso/.xiso after the first unlock/XBE probe. */
2536                                         if (type_id == DISC_TYPE_XBOX && options.iso_requested && !options.iso_out)
2537                                                 my_strdup (options.iso_out, "");
2538                                         if (type_id == DISC_TYPE_XBOX && options.xiso_requested && !options.xiso_out)
2539                                                 my_strdup (options.xiso_out, "");
2540
2541                                         //set speed 2nd time after rotation is started and some sectors read
2542                                         if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177);
2543                                         if (options.speed != -1) disc_set_speed(d, options.speed * 177);
2544
2545                                         /* If at least an output file was specified, proceed dumping, otherwise stop here */
2546                                         if (options.xiso_requested) {
2547                                                 if (type_id != DISC_TYPE_XBOX) {
2548                                                         fprintf (stderr, "Xbox XISO output requires Xbox/XGD disc type. Use -T 4 or an Xbox-capable drive/disc.\n");
2549                                                         out = false;
2550                                                 } else {
2551                                                         fprintf (stderr, options.xiso_out && options.xiso_out[0] ? "Writing to file \"%s\" in Xbox XISO format\n\n" : "Writing Xbox XISO using XBE-derived filename (override with -X <file>)\n\n", (options.xiso_out && options.xiso_out[0]) ? options.xiso_out : "");
2552
2553                                                         dmp = dumper_new (d);
2554                                                         if (friidump_native_report_is_enabled(&g_native_report))
2555                                                                 dumper_set_cancel_callback(dmp, friidump_native_cancel_callback, NULL);
2556                                                         dumper_set_hashing (dmp, !options.no_hashing);
2557                                                         dumper_set_flushing (dmp, !options.no_flushing);
2558
2559                                                         if (!dumper_set_xiso_output_file (dmp, options.xiso_out, options.resume)) {
2560                                                                 fprintf (stderr, "Cannot setup Xbox XISO output file\n");
2561                                                         } else if (!dumper_prepare_xiso (dmp)) {
2562                                                                 fprintf (stderr, "Cannot prepare Xbox XISO dumper");
2563                                                         } else {
2564                                                                 if (options.gui)
2565                                                                         dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats);
2566                                                                 else
2567                                                                         dumper_set_progress_callback (dmp, (progress_func) progress, stats);
2568
2569                                                                 dump_attempted = true;
2570                                                                 if (g_validation_summary.active)
2571                                                                         g_validation_summary.dump_attempted = true;
2572                                                                 out = dumper_dump_xiso (dmp, &current_sector);
2573                                                                 gettimeofday(&(stats -> end_time), NULL);
2574                                                                 if (out) {
2575                                                                         fprintf (stderr, "Xbox XISO dump completed successfully!\n");
2576                                                                         if (!options.no_hashing && !friidump_uses_windows_xbox_reference_path(d, type_id))
2577                                                                                 fprintf (stderr,
2578                                                                                 "XISO image hashes:\n"
2579                                                                                 "CRC32...: %s\n"
2580                                                                                 "MD5.....: %s\n"
2581                                                                                 "SHA-1...: %s\n"
2582                                                                                 "SHA-256.: %s\n",
2583                                                                                 dumper_get_xiso_crc32 (dmp), dumper_get_xiso_md5 (dmp),
2584                                                                                 dumper_get_xiso_sha1 (dmp), dumper_get_xiso_sha2 (dmp)
2585                                                                         );
2586                                                                 out = true;
2587                                                                 if (g_validation_summary.active)
2588                                                                         g_validation_summary.dump_ok = true;
2589                                                                 } else {
2590                                                                         if (dumper_was_cancelled(dmp))
2591                                                                                 fprintf (stderr, "\nXbox XISO dump cancelled at output sector: %u\n", current_sector);
2592                                                                         else
2593                                                                                 fprintf (stderr, "\nXbox XISO dump failed at output sector: %u\n", current_sector);
2594                                                                         out = false;
2595                                                                         if (g_validation_summary.active) {
2596                                                                                 g_validation_summary.dump_ok = false;
2597                                                                                 g_validation_summary.fail_sector = current_sector;
2598                                                                         }
2599                                                                 }
2600                                                         }
2601
2602                                                         if (dump_attempted)
2603                                                                 friidump_native_capture_output(dmp, type_id, true, out, dumper_was_cancelled(dmp), current_sector, disc_get_sectors_no(d));
2604                                                         else if (friidump_native_report_is_enabled(&g_native_report)) {
2605                                                                 friidump_native_report_set_outcome(&g_native_report, "other", "fail");
2606                                                                 friidump_native_report_add_note(&g_native_report, "Xbox XISO output could not be prepared; dumping was not attempted.");
2607                                                         }
2608                                                         dmp = dumper_destroy (dmp);
2609                                                 }
2610                                                 } else if (options.raw_out || options.iso_requested) {
2611                                                         if (type_id == DISC_TYPE_XBOX && options.raw_out)
2612                                                                 fprintf (stderr, "Xbox/XGD output does not support -r/raw together with the redump-style ISO path. Use -i or -X.\n");
2613                                                         else if (options.raw_out)
2614                                                                 fprintf (stderr, "Writing to file \"%s\" in raw format\n", options.raw_out);
2615                                                         if (options.iso_out) {
2616                                                                 if (type_id == DISC_TYPE_XBOX)
2617                                                                         fprintf (stderr, options.iso_out && options.iso_out[0] ? "Writing to file \"%s\" in Xbox/XGD redump-style ISO format\n" : "Writing Xbox/XGD redump-style ISO using XBE-derived filename (override with -i <file>)\n", (options.iso_out && options.iso_out[0]) ? options.iso_out : "");
2618                                                                 else
2619                                                                         fprintf (stderr, "Writing to file \"%s\" in ISO format\n", options.iso_out);
2620                                                         }
2621                                                         fprintf (stderr, "\n");
2622
2623                                                 dmp = dumper_new (d);
2624                                                 if (friidump_native_report_is_enabled(&g_native_report))
2625                                                         dumper_set_cancel_callback(dmp, friidump_native_cancel_callback, NULL);
2626
2627                                                 dumper_set_hashing (dmp, !options.no_hashing);
2628                                                 dumper_set_flushing (dmp, !options.no_flushing);
2629
2630                                                 if (!dumper_set_raw_output_file (dmp, options.raw_out, options.resume)) {
2631                                                         fprintf (stderr, "Cannot setup raw output file\n");
2632                                                 } else if (!dumper_set_iso_output_file (dmp, options.iso_out, options.resume)) {
2633                                                         fprintf (stderr, "Cannot setup ISO output file\n");
2634                                                 } else if (!dumper_prepare (dmp)) {
2635                                                         fprintf (stderr, "Cannot prepare dumper");
2636                                                 } else {
2637 //                                                      fprintf (stderr, "Starting dump process from sector %u...\n", dmp -> start_sector);
2638 //                                                      opdd.start_sector = options.start_sector;
2639
2640                                                         if (options.gui)
2641                                                                 dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats);
2642                                                         else
2643                                                                 dumper_set_progress_callback (dmp, (progress_func) progress, stats);
2644
2645                                                         dump_attempted = true;
2646                                                         if (g_validation_summary.active)
2647                                                                 g_validation_summary.dump_attempted = true;
2648                                                         out = dumper_dump (dmp, &current_sector);
2649                                                         gettimeofday(&(stats -> end_time), NULL);
2650                                                         if (out) {
2651                                                                 fprintf (stderr, "Dump completed successfully!\n");
2652                                                                 if (!options.no_hashing && options.raw_out)
2653                                                                         fprintf (stderr,
2654                                                                                 "Raw image hashes:\n"
2655                                                                                 "CRC32...: %s\n"
2656                                                                                 //"MD4.....: %s\n"
2657                                                                                 "MD5.....: %s\n"
2658                                                                                 "SHA-1...: %s\n"
2659                                                                                 "SHA-256.: %s\n"
2660                                                                                 /*"ED2K....: %s\n"*/,
2661                                                                                 dumper_get_raw_crc32 (dmp), /*dumper_get_raw_md4 (dmp),*/ dumper_get_raw_md5 (dmp),
2662                                                                                 dumper_get_raw_sha1 (dmp), dumper_get_raw_sha2 (dmp)/*, dumper_get_raw_ed2k (dmp)*/
2663                                                                         );
2664                                                                 if (!options.no_hashing && options.iso_out && !friidump_uses_windows_xbox_reference_path(d, type_id))
2665                                                                         fprintf (stderr,
2666                                                                                 "ISO image hashes:\n"
2667                                                                                 "CRC32...: %s\n"
2668                                                                                 //"MD4.....: %s\n"
2669                                                                                 "MD5.....: %s\n"
2670                                                                                 "SHA-1...: %s\n"
2671                                                                                 "SHA-256.: %s\n"
2672                                                                                 /*"ED2K....: %s\n"*/,
2673                                                                                 dumper_get_iso_crc32 (dmp), /*dumper_get_iso_md4 (dmp),*/ dumper_get_iso_md5 (dmp),
2674                                                                                 dumper_get_iso_sha1 (dmp), dumper_get_iso_sha2 (dmp)/*, dumper_get_iso_ed2k (dmp)*/
2675                                                                         );
2676
2677                                                                 if (type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d) && options.iso_requested)
2678                                                                         friidump_verify_redump_xbox_reference(dmp);
2679                                                                 else if (options.iso_out)
2680                                                                         friidump_verify_redump_iso(dmp, type_id);
2681
2682                                                                 out = true;
2683                                                                 if (g_validation_summary.active)
2684                                                                         g_validation_summary.dump_ok = true;
2685                                                         } else {
2686                                                                 if (g_validation_summary.active) {
2687                                                                         g_validation_summary.dump_ok = false;
2688                                                                         g_validation_summary.fail_sector = current_sector;
2689                                                                 }
2690                                                                 if (dumper_was_cancelled(dmp))
2691                                                                         fprintf (stderr, "\nDump cancelled at sector: %u\n", current_sector);
2692                                                                 else if (current_sector == 0xFFFFFFFFU)
2693                                                                         fprintf (stderr, "\nXbox reference dumper failed; see the Xbox-specific message above.\n");
2694                                                                 else
2695                                                                         fprintf (stderr, "\nDump failed at sectors: %u..%u\n", current_sector, current_sector+15);
2696                                                                 out = false;
2697                                                                 //disc_stop_unit (d, 0);
2698                                                         }
2699                                                 }
2700
2701                                                 if (dump_attempted)
2702                                                         friidump_native_capture_output(dmp, type_id, false, out, dumper_was_cancelled(dmp), current_sector, disc_get_sectors_no(d));
2703                                                 else if (friidump_native_report_is_enabled(&g_native_report)) {
2704                                                         friidump_native_report_set_outcome(&g_native_report, "other", "fail");
2705                                                         friidump_native_report_add_note(&g_native_report, "Dump output could not be prepared; dumping was not attempted.");
2706                                                 }
2707                                                 dmp = dumper_destroy (dmp);
2708                                         } else {
2709                                                 fprintf (stderr, "No output file for dumping specified, please take a look at the -i, -r, -X and -a options\n");
2710                                         }
2711                                 }
2712                         }
2713         if (dump_attempted) {
2714                 bool stop_ok = disc_stop_unit (d, false);
2715                 fprintf (stderr, "\nIssuing STOP UNIT / spin-down after dump attempt... %s\n", stop_ok ? "OK" : "Failed");
2716                 if (g_validation_summary.active) {
2717                         g_validation_summary.stop_attempted = true;
2718                         g_validation_summary.stop_ok = stop_ok;
2719                 }
2720         }
2721
2722         return out;
2723 }
2724
2725
2726 static int try_all_methods (progstats *stats) {
2727         u_int32_t saved_command;
2728         int saved_method;
2729         u_int32_t command;
2730         int method;
2731         int out;
2732         disc *d;
2733
2734         saved_command = options.command;
2735         saved_method = options.dump_method;
2736         out = false;
2737
2738         fprintf (stderr, "Trying all command/method combinations... This will take a LOOOONG time and generate an insanely long console output :p\n");
2739
2740         for (command = 0; command <= 4 && !out; command++) {
2741                 for (method = 0; method <= 10 && !out; method++) {
2742                         options.command = command;
2743                         options.dump_method = method;
2744                         memset (stats, 0, sizeof (*stats));
2745
2746                         fprintf (stderr, "\nTrying with command %u, method %d\n", command, method);
2747                         fprintf (stderr, "Initializing DVD drive... ");
2748
2749                         d = disc_new (options.device, options.command);
2750                         if (!d) {
2751                                 fprintf (stderr, "Failed\n");
2752 #ifndef WIN32
2753                                 fprintf (stderr,
2754                                         "Probably you do not have access to the DVD device. Ask the administrator\n"
2755                                         "to add you to the proper device-access group. Do not run FriiDump as root.\n"
2756                                 );
2757 #endif
2758                                 continue;
2759                         }
2760
2761                         fprintf (stderr, "OK\n");
2762                         out = dologic (d, stats);
2763                         d = disc_destroy (d);
2764
2765                         if (out)
2766                                 fprintf (stderr, "Command %u and method %d combination worked!\n", command, method);
2767                 }
2768         }
2769
2770         if (!out) {
2771                 options.command = saved_command;
2772                 options.dump_method = saved_method;
2773         }
2774
2775         return out;
2776 }
2777
2778 int main (int argc, char *argv[]) {
2779         disc *d;
2780         progstats stats;
2781         double duration;
2782         bool have_duration;
2783         int out, ret;
2784         unscrambler *u;
2785         unscrambler_progress_func pfunc;
2786         u_int32_t current_sector;
2787
2788         /* First of all... */
2789         drop_euid ();
2790         friidump_native_report_init(&g_native_report, PACKAGE_VERSION);
2791         friidump_init_executable_dir((argc > 0) ? argv[0] : NULL);
2792         xbox_ref_log_open_for_target(NULL, 0);
2793         
2794         welcome ();
2795
2796         memset (&stats, 0, sizeof (stats));
2797         d = NULL;
2798         out = false;
2799         ret = EXIT_FAILURE;
2800         if (optparse (argc, argv)) {
2801                 friidump_native_configure();
2802                 friidump_retarget_log_from_options();
2803                 if (options.device) {
2804                         if (options.allmethods) {
2805                                 out = try_all_methods (&stats);
2806                         } else {
2807                                 /* Dump DVD to file */
2808                                 fprintf (stderr, "Initializing DVD drive... ");
2809
2810                                 if (!(d = disc_new (options.device, options.command))) {
2811                                         fprintf (stderr, "Failed\n");
2812 #ifndef WIN32
2813                                         fprintf (stderr,
2814                                                 "Probably you do not have access to the DVD device. Ask the administrator\n"
2815                                                 "to add you to the proper device-access group. Do not run FriiDump as root.\n"
2816                                         );
2817 #endif
2818                                 } else {
2819                                         fprintf (stderr, "OK\n");
2820                                         friidump_native_set_drive(d);
2821                                         out = dologic (d, &stats);
2822                                         d = disc_destroy (d);
2823                                 }
2824                         }
2825                 } else if (options.raw_in) {
2826                         /* Convert raw image to ISO format */
2827                         u = unscrambler_new ();
2828                         
2829                         if (options.gui)
2830                                 pfunc = (unscrambler_progress_func) progress_for_guis;
2831                         else
2832                                 pfunc = (unscrambler_progress_func) progress;
2833
2834                         if ((out = unscrambler_unscramble_file (u, options.raw_in, options.iso_out, pfunc, &stats, &current_sector)))
2835                                 fprintf (stderr, "Unscrambling completed successfully!\n");
2836                         else
2837                                 fprintf (stderr, "\nUnscrambling failed at sectors: %u..%u\n", current_sector, current_sector+15);
2838
2839                         u = unscrambler_destroy (u);
2840                 } else {
2841                         MY_ASSERT (0);
2842                 }
2843
2844                 duration = 0.0;
2845                 have_duration = friidump_elapsed_from_stats(&stats, &duration);
2846                 if (g_operation_duration_override_valid) {
2847                         duration = g_operation_duration_override;
2848                         have_duration = true;
2849                 }
2850
2851                 if (out) {
2852                         if (have_duration)
2853                                 fprintf (stderr, "Operation took %.2f seconds\n", duration);
2854                         friidump_print_validation_summary(duration, have_duration);
2855                         ret = EXIT_SUCCESS;
2856                 } else {
2857                         friidump_print_validation_summary(duration, have_duration);
2858                         ret = EXIT_FAILURE;
2859                 }
2860
2861                 friidump_native_publish(duration, have_duration);
2862
2863                 my_free (options.device);
2864                 my_free (options.iso_out);
2865                 my_free (options.xiso_out);
2866                 my_free (options.raw_out);
2867                 my_free (options.raw_in);
2868                 my_free (options.hlds_profile_report);
2869                 my_free (options.xgd1_layout_probe_report);
2870                 my_free (options.xgd1_raw_id_probe_report);
2871                 my_free (options.native_report_json);
2872                 my_free (options.native_report_dir);
2873                 my_free (options.firmware_modified_note);
2874         }
2875
2876         if (xbox_ref_log_path())
2877                 fprintf (stderr, "Log file complete: %s\n", xbox_ref_log_path());
2878         xbox_ref_log_close();
2879
2880         return (ret);
2881 }