]> FriiDump Source - friidump.git/blob - src/friidump.c
FriiDump 0.5.3.15: close XGD1 geometry and lead-in capture
[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 <stdlib.h>
25 #include <time.h>
26 #include <string.h>
27 #include "disc.h"
28 #include "dumper.h"
29 #include "unscrambler.h"
30 #include "xbox_ref/xbox_ref_log.h"
31 #include "xbox_ref_bridge.h"
32 #include "redump_dat.h"
33
34 #ifdef WIN32
35 #include <sys/stat.h>
36 #include <io.h>
37 #include <windows.h>
38 #else
39 #include <sys/stat.h>
40 #include <unistd.h>
41 #endif
42
43 #define printf xbox_ref_printf
44 #define fprintf xbox_ref_log_fprintf
45
46 #define USECS_PER_SEC   1000000
47
48
49 static const char *friidump_requested_output_target(void);
50
51 typedef struct {
52         bool active;
53         bool dump_attempted;
54         bool seed_ok;
55         bool seed_applicable;
56         bool have_seed_duration;
57         double seed_duration;
58         bool dump_ok;
59         bool stop_attempted;
60         bool stop_ok;
61         char model[256];
62         char disc_type[64];
63         char game_id[64];
64         char title[256];
65         char output[512];
66         u_int32_t command;
67         int method;
68         u_int32_t sectors;
69         u_int32_t fail_sector;
70         u_int32_t hlds_type;
71         u_int32_t cache_base;
72         u_int32_t mem_blocks;
73         const char *profile;
74 } friidump_validation_summary;
75
76 static friidump_validation_summary g_validation_summary;
77 static redump_verify_result g_redump_result;
78 static bool g_redump_attempted = false;
79 static bool g_operation_duration_override_valid = false;
80 static double g_operation_duration_override = 0.0;
81 static char g_executable_dir[1024];
82
83 static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
84         if (!dst || dst_size == 0)
85                 return;
86         if (!src)
87                 src = "";
88         snprintf(dst, dst_size, "%s", src);
89 }
90
91 static void friidump_summary_reset(void) {
92         memset(&g_validation_summary, 0, sizeof(g_validation_summary));
93         g_validation_summary.fail_sector = 0xFFFFFFFFU;
94         g_validation_summary.seed_applicable = true;
95         g_operation_duration_override_valid = false;
96         g_operation_duration_override = 0.0;
97 }
98
99 static void friidump_format_hms(double seconds, char *buf, size_t buf_size) {
100         long total, hours, minutes, secs;
101         if (!buf || buf_size == 0)
102                 return;
103         if (seconds < 0.0)
104                 seconds = 0.0;
105         total = (long) (seconds + 0.5);
106         hours = total / 3600;
107         minutes = (total / 60) % 60;
108         secs = total % 60;
109         snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hours, minutes, secs);
110 }
111
112
113 static void friidump_summary_begin(disc *d) {
114         const char *target;
115         if (!d || disc_get_hlds_e7_type(d) == 0)
116                 return;
117         friidump_summary_reset();
118         g_validation_summary.active = true;
119         friidump_summary_copy(g_validation_summary.model, sizeof(g_validation_summary.model), disc_get_drive_model_string(d));
120         g_validation_summary.command = disc_get_command(d);
121         g_validation_summary.method = disc_get_method(d);
122         g_validation_summary.hlds_type = disc_get_hlds_e7_type(d);
123         g_validation_summary.profile = disc_get_hlds_e7_profile_name(d);
124         g_validation_summary.cache_base = disc_get_hlds_e7_cache_base(d);
125         g_validation_summary.mem_blocks = disc_get_hlds_e7_mem_blocks(d);
126         target = friidump_requested_output_target();
127         friidump_summary_copy(g_validation_summary.output, sizeof(g_validation_summary.output), target ? target : "(none)");
128 }
129
130 static void friidump_print_validation_summary(double duration, bool have_duration) {
131         double mib_total, mib_per_hour;
132         const char *seed_status;
133         if (!g_validation_summary.active)
134                 return;
135         seed_status = g_validation_summary.seed_applicable
136                 ? (g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED")
137                 : "N/A (Xbox reference auth path)";
138         mib_total = (double) g_validation_summary.sectors * 2048.0 / 1024.0 / 1024.0;
139         mib_per_hour = (have_duration && duration > 0.0) ? (mib_total / duration * 3600.0) : 0.0;
140         fprintf(stderr,
141                 "\nHLDS 0xE7 validation summary:\n"
142                 "  Model.............: %s\n"
143                 "  Profile...........: %s\n"
144                 "  Command/method....: %u / %d\n"
145                 "  Cache base........: 0x%08x\n"
146                 "  Memory windows....: %u\n"
147                 "  Disc type.........: %s\n"
148                 "  Game/Media ID.....: %s\n"
149                 "  Title.............: %s\n"
150                 "  Output............: %s\n"
151                 "  Seed read.........: %s\n"
152                 "  Dump status.......: %s\n"
153                 "  STOP UNIT.........: %s\n",
154                 g_validation_summary.model[0] ? g_validation_summary.model : "(unknown)",
155                 g_validation_summary.profile ? g_validation_summary.profile : "(unknown)",
156                 g_validation_summary.command, g_validation_summary.method,
157                 g_validation_summary.cache_base, g_validation_summary.mem_blocks,
158                 g_validation_summary.disc_type[0] ? g_validation_summary.disc_type : "(unknown)",
159                 g_validation_summary.game_id[0] ? g_validation_summary.game_id : "(unknown)",
160                 g_validation_summary.title[0] ? g_validation_summary.title : "(unknown)",
161                 g_validation_summary.output[0] ? g_validation_summary.output : "(none)",
162                 seed_status,
163                 g_validation_summary.dump_attempted ? (g_validation_summary.dump_ok ? "OK" : "FAILED") : "NOT ATTEMPTED",
164                 g_validation_summary.stop_attempted ? (g_validation_summary.stop_ok ? "OK" : "FAILED") : "NOT ATTEMPTED");
165         if (g_validation_summary.have_seed_duration) {
166                 char seed_buf[32];
167                 friidump_format_hms(g_validation_summary.seed_duration, seed_buf, sizeof(seed_buf));
168                 fprintf(stderr, "  Seed elapsed......: %s (%.2f seconds)\n", seed_buf, g_validation_summary.seed_duration);
169         }
170         if (g_validation_summary.dump_attempted && !g_validation_summary.dump_ok && g_validation_summary.fail_sector != 0xFFFFFFFFU)
171                 fprintf(stderr, "  Failure sector....: %u..%u\n", g_validation_summary.fail_sector, g_validation_summary.fail_sector + 15);
172         if (g_validation_summary.sectors)
173                 fprintf(stderr, "  Sectors...........: %u\n", g_validation_summary.sectors);
174         if (have_duration)
175                 fprintf(stderr, "  Duration..........: %.2f seconds\n", duration);
176         if (have_duration && mib_per_hour > 0.0)
177                 fprintf(stderr, "  Observed average..: %.2f MiB/h over %.2f MiB ISO payload\n", mib_per_hour, mib_total);
178         if (g_validation_summary.dump_ok) {
179                 if (g_redump_attempted) {
180                         fprintf(stderr, "  Redump verify.....: %s\n", redump_verify_overall_string(&g_redump_result));
181                         if (g_redump_result.status == REDUMP_VERIFY_MATCH)
182                                 fprintf(stderr, "  Redump title......: %s\n", g_redump_result.game_name);
183                 } else {
184                         fprintf(stderr, "  Redump verify.....: NOT RUN\n");
185                 }
186         }
187 }
188
189
190 /* Name of package */
191 #define PACKAGE "friidump"
192
193 /* Define to the address where bug reports for this package should be sent. */
194 #define PACKAGE_BUGREPORT "arep@no.net"
195
196 /* Define to the full name of this package. */
197 #define PACKAGE_NAME "FriiDump"
198
199 /* Define to the version of this package. */
200 #define PACKAGE_VERSION "0.5.3.15"
201
202
203 #ifdef WIN32
204
205 #include "getopt-win32.h"
206
207
208 #else
209 #include <sys/time.h>
210 #include <getopt.h>
211 #endif
212
213
214 /* Struct for program options */
215 struct {
216         char *device;
217         bool autodump;
218         bool gui;
219         char *raw_in;
220         char *raw_out;
221         char *iso_out;
222         char *xiso_out;
223         bool iso_requested;
224         bool xiso_requested;
225         bool xbox_filename_override;
226         bool xbox_auto_filename;
227         bool resume;
228         int dump_method;
229         u_int32_t command;
230         u_int32_t start_sector;
231         u_int32_t sectors_no;
232         u_int32_t speed;
233         u_int32_t disctype;
234         u_int32_t sec_disc;
235         u_int32_t sec_mem;
236         bool no_hashing;
237         bool no_unscrambling;
238         bool no_flushing;
239         bool stop_unit;
240         bool allmethods;
241         bool hlds_e7_scan;
242         bool hlds_e7_subcmd_sweep;
243         bool hlds_e7_memrange_sweep;
244         char *hlds_e7_scan_log;
245         char *hlds_e7_scan_dump_prefix;
246         char *hlds_profile_report;
247         char *redump_dat_dir;
248         char *redump_report;
249         bool no_redump_verify;
250         bool xgd1_layout_probe;
251         char *xgd1_layout_probe_report;
252         bool xgd1_raw_id_probe;
253         char *xgd1_raw_id_probe_report;
254 } options;
255
256
257 /* Struct for progress data */
258 typedef struct {
259         struct timeval start_time;
260         struct timeval end_time;
261         double mb_total;
262         double mb_total_real;
263         u_int32_t sectors_skipped;
264 } progstats;
265
266
267 static char friidump_drive_letter_from_device(const char *device) {
268         if (!device || !device[0]) return 0;
269         if (device[0] && device[1] == ':') return device[0];
270         if (device[0] == '\\' && device[1] == '\\' && device[2] == '.' && device[3] == '\\' && device[4] && device[5] == ':') return device[4];
271         return device[0];
272 }
273
274
275 static const char *friidump_requested_output_target(void) {
276         if (options.xgd1_raw_id_probe_report && options.xgd1_raw_id_probe_report[0]) return options.xgd1_raw_id_probe_report;
277         if (options.xgd1_layout_probe_report && options.xgd1_layout_probe_report[0]) return options.xgd1_layout_probe_report;
278         if (options.iso_out && options.iso_out[0]) return options.iso_out;
279         if (options.xiso_out && options.xiso_out[0]) return options.xiso_out;
280         if (options.raw_out && options.raw_out[0]) return options.raw_out;
281         return NULL;
282 }
283
284
285 static void friidump_json_string(FILE *f, const char *s) {
286         const unsigned char *p;
287         fputc('"', f);
288         if (s) {
289                 for (p = (const unsigned char *) s; *p; p++) {
290                         switch (*p) {
291                                 case '\\': fputs("\\\\", f); break;
292                                 case '"': fputs("\\\"", f); break;
293                                 case '\b': fputs("\\b", f); break;
294                                 case '\f': fputs("\\f", f); break;
295                                 case '\n': fputs("\\n", f); break;
296                                 case '\r': fputs("\\r", f); break;
297                                 case '\t': fputs("\\t", f); break;
298                                 default:
299                                         if (*p < 0x20) {
300                                                 char tmp[8];
301                                                 snprintf(tmp, sizeof(tmp), "\\u%04x", (unsigned int) *p);
302                                                 fputs(tmp, f);
303                                         } else {
304                                                 fputc(*p, f);
305                                         }
306                                         break;
307                         }
308                 }
309         }
310         fputc('"', f);
311 }
312
313 static void friidump_json_kv_string(FILE *f, const char *key, const char *value, bool comma) {
314         fputs("  ", f);
315         friidump_json_string(f, key);
316         fputs(": ", f);
317         friidump_json_string(f, value ? value : "");
318         fputs(comma ? ",\n" : "\n", f);
319 }
320
321 static void friidump_json_kv_u32_hex(FILE *f, const char *key, u_int32_t value, bool comma) {
322         char tmp[32];
323         snprintf(tmp, sizeof(tmp), "0x%08x", value);
324         friidump_json_kv_string(f, key, value ? tmp : "", comma);
325 }
326
327 static void friidump_json_kv_int(FILE *f, const char *key, int value, bool comma) {
328         char tmp[32];
329         fputs("  ", f);
330         friidump_json_string(f, key);
331         snprintf(tmp, sizeof(tmp), ": %d%s\n", value, comma ? "," : "");
332         fputs(tmp, f);
333 }
334
335 static void friidump_json_kv_bool(FILE *f, const char *key, bool value, bool comma) {
336         fputs("  ", f);
337         friidump_json_string(f, key);
338         fputs(value ? ": true" : ": false", f);
339         fputs(comma ? ",\n" : "\n", f);
340 }
341
342 static bool friidump_write_hlds_profile_report(disc *d, const char *path) {
343         FILE *f;
344         if (!d || !path || !path[0])
345                 return false;
346         f = fopen(path, "wb");
347         if (!f) {
348                 fprintf(stderr, "WARNING: could not write HLDS profile report: %s\n", path);
349                 return false;
350         }
351         fputs("{\n", f);
352         friidump_json_kv_string(f, "schema", "friidump_hlds_profile_report_v1", true);
353         friidump_json_kv_string(f, "drive_model", disc_get_drive_model_string(d), true);
354         friidump_json_kv_string(f, "profile_name", disc_get_hlds_e7_profile_name(d), true);
355         friidump_json_kv_string(f, "support_tier", disc_get_hlds_e7_support_tier(d), true);
356         friidump_json_kv_string(f, "family", disc_get_hlds_e7_family(d), true);
357         friidump_json_kv_string(f, "tokens", disc_get_hlds_e7_tokens(d), true);
358         friidump_json_kv_string(f, "stage5b_record_id", disc_get_hlds_e7_record_id(d), true);
359         friidump_json_kv_u32_hex(f, "static_e7_cdb_base", disc_get_hlds_e7_static_cdb_base(d), true);
360         friidump_json_kv_u32_hex(f, "static_e7_gate", disc_get_hlds_e7_static_gate(d), true);
361         friidump_json_kv_int(f, "runtime_e7_type", (int) disc_get_hlds_e7_type(d), true);
362         friidump_json_kv_u32_hex(f, "runtime_cache_base", disc_get_hlds_e7_cache_base(d), true);
363         friidump_json_kv_int(f, "runtime_mem_windows", (int) disc_get_hlds_e7_mem_blocks(d), true);
364         friidump_json_kv_int(f, "preferred_method", disc_get_hlds_e7_preferred_method(d), true);
365         friidump_json_kv_int(f, "selected_method", (int) disc_get_method(d), true);
366         friidump_json_kv_int(f, "selected_command", (int) disc_get_command(d), true);
367         friidump_json_kv_bool(f, "live_safe_from_static_only", false, true);
368         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);
369         friidump_json_kv_string(f, "notes", disc_get_hlds_e7_notes(d), false);
370         fputs("}\n", f);
371         fclose(f);
372         return true;
373 }
374
375
376 static void friidump_retarget_log_from_options(void) {
377         const char *target = friidump_requested_output_target();
378         if (target) {
379                 xbox_ref_log_retarget(target);
380         } else if (options.device && options.device[0]) {
381                 xbox_ref_log_open_for_target(NULL, friidump_drive_letter_from_device(options.device));
382         }
383 }
384
385
386 void progress_for_guis (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) {
387         int perc;
388         double elapsed, mb_done, mb_done_real, mb_hour, seconds_left;
389         struct timeval now;
390         time_t eta;
391         struct tm etatm;
392         char buf[50];
393         
394         if (start) {
395                 gettimeofday (&(stats -> start_time), NULL);
396                 stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024;
397                 stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024;
398                 stats -> sectors_skipped = sectors_done;
399         } else {
400                 perc = (int) (100.0 * sectors_done / total_sectors);
401                 gettimeofday (&now, NULL);
402                 elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec);
403                 mb_done = (double) sectors_done * 2064 / 1024 / 1024;
404                 mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024;
405                 mb_hour = mb_done_real / elapsed * 60 * 60;
406                 seconds_left = stats -> mb_total_real / mb_hour * 60 * 60;
407                 eta = (time_t) ((stats -> start_time).tv_sec + seconds_left);
408                 if (localtime_r (&eta, &etatm))
409                         strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm);
410                 else
411                         sprintf (buf, "N/A");
412
413                 /* This is the only thing we print to stdout, so that other programs can easily capture and parse our output */
414                 fprintf (stdout, "%d%%|%u/%u sectors|%.2lf/%.0lf MB|%.0lf/%.0lf seconds|%.2lf MB/h|%s\n",
415                          perc, sectors_done, total_sectors, mb_done, stats -> mb_total, elapsed, seconds_left, mb_hour, buf);
416                 fflush (stdout);
417         }
418
419         /* Save return time, in case this will be the last call */
420         gettimeofday (&(stats -> end_time), NULL);
421
422         return;
423 }
424
425
426 void progress (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) {
427         int perc, i;
428         double elapsed, mb_done, mb_done_real, mb_hour, seconds_left;
429         struct timeval now;
430         time_t eta;
431         struct tm etatm;
432         char buf[50];
433         
434         if (start) {
435                 gettimeofday (&(stats -> start_time), NULL);
436                 stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024;
437                 stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024;
438                 stats -> sectors_skipped = sectors_done;
439         } else {
440                 perc = (int) (100.0 * sectors_done / total_sectors);
441                 gettimeofday (&now, NULL);
442                 elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec);
443                 mb_done = (double) sectors_done * 2064 / 1024 / 1024;
444                 mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024;
445                 mb_hour = mb_done_real / elapsed * 60 * 60;
446                 seconds_left = stats -> mb_total_real / mb_hour * 60 * 60;
447                 eta = (time_t) ((stats -> start_time).tv_sec + seconds_left);
448                 if (localtime_r (&eta, &etatm))
449                         strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm);
450                 else
451                         sprintf (buf, "N/A");
452
453                 fprintf (stdout, "\r%3d%% ", perc);
454                 fprintf (stdout, "|");
455                 for (i = 0; i < 100 / 3; i++) {
456                         if (i == perc / 3)
457                                 fprintf (stdout, "*");
458                         else
459                                 fprintf (stdout, "-");
460                 }
461                 fprintf (stdout, "| ");
462                 fprintf (stdout, "%.2lf MB/h, ETA: %s", mb_hour, buf);
463                 fflush (stdout);
464         }
465
466         if (sectors_done == total_sectors)
467                 printf ("\n");
468
469         /* Save return time, in case this will be the last call */
470         gettimeofday (&(stats -> end_time), NULL);
471
472         return;
473 }
474
475
476
477 void welcome (void) {
478         /* Welcome text */
479         fprintf (stderr,
480                 "FriiDump " PACKAGE_VERSION " - Copyright (C) 2007 Arep\n"
481                 "This software comes with ABSOLUTELY NO WARRANTY.\n"
482                 "This is free software, and you are welcome to redistribute it\n"
483                 "under certain conditions; see COPYING for details.\n"
484                 "\n"
485                 "Official support forum: http://wii.console-tribe.com\n"
486                 "\n"
487                 "Forum for this UNOFFICIAL VERSION: http://forum.redump.org\n"
488                 "\n"
489                 );
490         fflush (stderr);
491
492         return;
493 }
494
495
496
497 static void friidump_init_executable_dir(const char *argv0) {
498     char path[1024];
499     size_t length = 0;
500     char *slash;
501     char *backslash;
502     char *separator;
503
504     g_executable_dir[0] = '\0';
505     path[0] = '\0';
506
507 #ifdef WIN32
508     {
509         DWORD result = GetModuleFileNameA(NULL, path, (DWORD)sizeof(path));
510         if (result > 0 && result < sizeof(path)) {
511             path[result] = '\0';
512             length = (size_t)result;
513         }
514     }
515 #else
516 #if defined(__linux__)
517     {
518         ssize_t result = readlink("/proc/self/exe", path, sizeof(path) - 1);
519         if (result > 0 && (size_t)result < sizeof(path)) {
520             path[result] = '\0';
521             length = (size_t)result;
522         }
523     }
524 #endif
525     if (length == 0 && argv0 && argv0[0] &&
526         (strchr(argv0, '/') || strchr(argv0, '\\'))) {
527         char *resolved = realpath(argv0, path);
528         if (resolved)
529             length = strlen(path);
530     }
531 #endif
532
533     if (length == 0)
534         return;
535
536     slash = strrchr(path, '/');
537     backslash = strrchr(path, '\\');
538     separator = slash;
539     if (backslash && (!separator || backslash > separator))
540         separator = backslash;
541
542     if (!separator)
543         return;
544
545     *separator = '\0';
546     if (path[0])
547         snprintf(g_executable_dir, sizeof(g_executable_dir), "%s", path);
548 }
549
550 static const char *friidump_redump_dat_basename(disc_type type_id) {
551     switch (type_id) {
552         case DISC_TYPE_GAMECUBE:
553             return "Nintendo - GameCube.dat";
554         case DISC_TYPE_WII:
555         case DISC_TYPE_WII_DL:
556             return "Nintendo - Wii.dat";
557         case DISC_TYPE_XBOX:
558             return "Microsoft - Xbox.dat";
559         default:
560             return NULL;
561     }
562 }
563
564 static uint64_t friidump_file_size(const char *path) {
565     if (!path || !path[0])
566         return 0;
567 #ifdef WIN32
568     {
569         struct _stat64 st;
570         if (_stat64(path, &st) == 0)
571             return (uint64_t)st.st_size;
572     }
573 #else
574     {
575         struct stat st;
576         if (stat(path, &st) == 0)
577             return (uint64_t)st.st_size;
578     }
579 #endif
580     return 0;
581 }
582
583 static bool friidump_sync_report_file(FILE *f) {
584     if (!f || fflush(f) != 0)
585         return false;
586 #ifdef WIN32
587     return _commit(_fileno(f)) == 0;
588 #else
589     return fsync(fileno(f)) == 0;
590 #endif
591 }
592
593 static bool friidump_atomic_replace(const char *temporary_path, const char *final_path) {
594     if (!temporary_path || !final_path)
595         return false;
596 #ifdef WIN32
597     return MoveFileExA(temporary_path, final_path,
598                        MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0;
599 #else
600     return rename(temporary_path, final_path) == 0;
601 #endif
602 }
603
604 static void friidump_json_write_string_value(FILE *f, const char *prefix,
605                                              const char *value, const char *suffix) {
606     fputs(prefix, f);
607     friidump_json_string(f, value ? value : "");
608     fputs(suffix, f);
609 }
610
611 static void friidump_write_redump_report(const redump_verify_result *result,
612                                          const char *output_path,
613                                          uint64_t output_size,
614                                          const char *crc32,
615                                          const char *md5,
616                                          const char *sha1,
617                                          const char *sha256,
618                                          const char *hash_source,
619                                          const char *representation_note) {
620     FILE *f;
621     char *temporary_path;
622     char line[256];
623     size_t temporary_path_size;
624     bool write_ok;
625
626     if (!options.redump_report || !options.redump_report[0] || !result)
627         return;
628
629     temporary_path_size = strlen(options.redump_report) + 5;
630     temporary_path = (char *)malloc(temporary_path_size);
631     if (!temporary_path) {
632         fprintf(stderr, "Redump report.......: memory allocation failed\n");
633         return;
634     }
635     snprintf(temporary_path, temporary_path_size, "%s.tmp", options.redump_report);
636     remove(temporary_path);
637
638     f = fopen(temporary_path, "wb");
639     if (!f) {
640         fprintf(stderr, "Redump report.......: could not write temporary file %s\n", temporary_path);
641         free(temporary_path);
642         return;
643     }
644
645     /* Use fputs/fputc rather than the logged fprintf wrapper. Report contents
646      * belong in the JSON artifact, not as a malformed partial mirror in the
647      * human-readable run log. */
648     fputs("{\n", f);
649     fputs("  \"schema\": 2,\n", f);
650     friidump_json_write_string_value(f, "  \"producer\": ", "friidump-" PACKAGE_VERSION, ",\n");
651     friidump_json_write_string_value(f, "  \"status\": ", redump_verify_status_string(result->status), ",\n");
652     friidump_json_write_string_value(f, "  \"overall\": ", redump_verify_overall_string(result), ",\n");
653     friidump_json_write_string_value(f, "  \"confidence\": ", redump_verify_confidence_string(result), ",\n");
654     friidump_json_write_string_value(f, "  \"output\": ", output_path ? output_path : "", ",\n");
655     snprintf(line, sizeof(line), "  \"output_size\": %llu,\n", (unsigned long long)output_size);
656     fputs(line, f);
657     friidump_json_write_string_value(f, "  \"crc32\": ", crc32 ? crc32 : "", ",\n");
658     friidump_json_write_string_value(f, "  \"md5\": ", md5 ? md5 : "", ",\n");
659     friidump_json_write_string_value(f, "  \"sha1\": ", sha1 ? sha1 : "", ",\n");
660     friidump_json_write_string_value(f, "  \"sha256\": ", sha256 ? sha256 : "", ",\n");
661     friidump_json_write_string_value(f, "  \"hash_source\": ", hash_source ? hash_source : "", ",\n");
662     friidump_json_write_string_value(f, "  \"representation_note\": ", representation_note ? representation_note : "", ",\n");
663     friidump_json_write_string_value(f, "  \"dat_path\": ", result->dat_path, ",\n");
664     snprintf(line, sizeof(line), "  \"entries_scanned\": %lu,\n", result->entries_scanned);
665     fputs(line, f);
666     snprintf(line, sizeof(line), "  \"exact_matches\": %lu,\n", result->exact_matches);
667     fputs(line, f);
668     friidump_json_write_string_value(f, "  \"match_kind\": ", redump_verify_match_kind_string(result), ",\n");
669     friidump_json_write_string_value(f, "  \"game_name\": ", result->game_name, ",\n");
670     friidump_json_write_string_value(f, "  \"rom_name\": ", result->rom_name, ",\n");
671
672     fputs("  \"field_match_counts\": {\n", f);
673     snprintf(line, sizeof(line), "    \"size\": %lu,\n", result->size_matches); fputs(line, f);
674     snprintf(line, sizeof(line), "    \"crc32\": %lu,\n", result->crc32_matches); fputs(line, f);
675     snprintf(line, sizeof(line), "    \"md5\": %lu,\n", result->md5_matches); fputs(line, f);
676     snprintf(line, sizeof(line), "    \"sha1\": %lu\n", result->sha1_matches); fputs(line, f);
677     fputs("  },\n", f);
678
679     fputs("  \"expected\": {\n", f);
680     friidump_json_write_string_value(f, "    \"size\": ", result->expected_size, ",\n");
681     friidump_json_write_string_value(f, "    \"crc32\": ", result->expected_crc32, ",\n");
682     friidump_json_write_string_value(f, "    \"md5\": ", result->expected_md5, ",\n");
683     friidump_json_write_string_value(f, "    \"sha1\": ", result->expected_sha1, "\n");
684     fputs("  },\n", f);
685
686     fputs("  \"evidence\": {\n", f);
687     friidump_json_write_string_value(f, "    \"size\": ", redump_field_status_string(result->size_status), ",\n");
688     friidump_json_write_string_value(f, "    \"crc32\": ", redump_field_status_string(result->crc32_status), ",\n");
689     friidump_json_write_string_value(f, "    \"md5\": ", redump_field_status_string(result->md5_status), ",\n");
690     friidump_json_write_string_value(f, "    \"sha1\": ", redump_field_status_string(result->sha1_status), "\n");
691     fputs("  },\n", f);
692     friidump_json_write_string_value(f, "  \"detail\": ", result->detail, "\n");
693     fputs("}\n", f);
694
695     write_ok = !ferror(f) && friidump_sync_report_file(f);
696     if (fclose(f) != 0)
697         write_ok = false;
698
699     if (!write_ok) {
700         remove(temporary_path);
701         fprintf(stderr, "Redump report.......: write/flush failed; final report was not replaced\n");
702         free(temporary_path);
703         return;
704     }
705
706     if (!friidump_atomic_replace(temporary_path, options.redump_report)) {
707         fprintf(stderr, "Redump report.......: could not replace %s; complete temporary report retained at %s\n",
708                 options.redump_report, temporary_path);
709         free(temporary_path);
710         return;
711     }
712
713     fprintf(stderr, "Redump report.......: %s (atomic write)\n", options.redump_report);
714     free(temporary_path);
715 }
716
717 static void friidump_print_redump_field(const char *label, redump_field_status status) {
718     fprintf(stderr, "%-20s %s\n", label, redump_field_status_string(status));
719 }
720
721 static void friidump_verify_redump_values(disc_type type_id,
722                                             const char *output_path,
723                                             uint64_t output_size,
724                                             const char *crc32,
725                                             const char *md5,
726                                             const char *sha1,
727                                             const char *sha256,
728                                             const char *hash_source) {
729     const char *basename;
730     const char *dat_dir;
731     const char *representation_note;
732     char dat_path[1024];
733
734     redump_verify_result_init(&g_redump_result);
735     g_redump_attempted = false;
736     representation_note = (type_id == DISC_TYPE_XBOX)
737         ? "XGD1 acquisition success and exact Redump hash identity are separate claims; unresolved zero-filled pregame/postgame content may prevent an exact match."
738         : "";
739
740     if (options.no_redump_verify) {
741         fprintf(stderr, "Redump verification: DISABLED (--no-redump-verify)\n");
742         return;
743     }
744     if (options.no_hashing) {
745         fprintf(stderr, "Redump verification: SKIPPED (hashing disabled)\n");
746         return;
747     }
748     if (!output_path || !output_path[0]) {
749         fprintf(stderr, "Redump verification: SKIPPED (final output path unavailable)\n");
750         return;
751     }
752     if (!crc32 || !crc32[0] || !md5 || !md5[0] || !sha1 || !sha1[0]) {
753         fprintf(stderr, "Redump verification: SKIPPED (finalized CRC32/MD5/SHA-1 evidence incomplete)\n");
754         return;
755     }
756
757     basename = friidump_redump_dat_basename(type_id);
758     if (!basename) {
759         fprintf(stderr, "Redump verification: SKIPPED (no DAT mapping for this disc type)\n");
760         return;
761     }
762
763     dat_dir = (options.redump_dat_dir && options.redump_dat_dir[0])
764         ? options.redump_dat_dir
765         : NULL;
766     redump_resolve_dat_path(dat_dir, g_executable_dir, basename,
767                             dat_path, sizeof(dat_path));
768     if (output_size == 0)
769         output_size = friidump_file_size(output_path);
770
771     g_redump_attempted = true;
772     redump_verify_dat_file(dat_path, output_size, crc32, md5, sha1, &g_redump_result);
773
774     fprintf(stderr, "\nRedump verification\n");
775     fprintf(stderr, "------------------------------------------------------------\n");
776     fprintf(stderr, "%-20s %s\n", "DAT", g_redump_result.dat_path);
777     fprintf(stderr, "%-20s %s\n", "Hash source", hash_source ? hash_source : "Finalized output hashes");
778     fprintf(stderr, "%-20s %lu\n", "Entries scanned", g_redump_result.entries_scanned);
779
780     if (g_redump_result.status == REDUMP_VERIFY_MATCH) {
781         fprintf(stderr, "%-20s %s\n", "Matched entry", g_redump_result.game_name);
782         fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name);
783         friidump_print_redump_field("Image size", g_redump_result.size_status);
784         friidump_print_redump_field("CRC32", g_redump_result.crc32_status);
785         friidump_print_redump_field("MD5", g_redump_result.md5_status);
786         friidump_print_redump_field("SHA-1", g_redump_result.sha1_status);
787         if (g_redump_result.exact_matches > 1)
788             fprintf(stderr, "%-20s %lu exact entries\n", "Duplicate matches", g_redump_result.exact_matches);
789     } else if (g_redump_result.candidate_available) {
790         fprintf(stderr, "%-20s %s\n", "Closest candidate", g_redump_result.game_name);
791         fprintf(stderr, "%-20s %s\n", "ROM", g_redump_result.rom_name);
792         friidump_print_redump_field("Image size", g_redump_result.size_status);
793         friidump_print_redump_field("CRC32", g_redump_result.crc32_status);
794         friidump_print_redump_field("MD5", g_redump_result.md5_status);
795         friidump_print_redump_field("SHA-1", g_redump_result.sha1_status);
796     } else {
797         fprintf(stderr, "%-20s %s\n", "Closest candidate", "None (no hash-correlated entry)");
798         fprintf(stderr, "%-20s %lu entries\n", "Size matches", g_redump_result.size_matches);
799         fprintf(stderr, "%-20s %lu entries\n", "CRC32 matches", g_redump_result.crc32_matches);
800         fprintf(stderr, "%-20s %lu entries\n", "MD5 matches", g_redump_result.md5_matches);
801         fprintf(stderr, "%-20s %lu entries\n", "SHA-1 matches", g_redump_result.sha1_matches);
802     }
803
804     fprintf(stderr, "%-20s %s\n", "Overall", redump_verify_overall_string(&g_redump_result));
805     fprintf(stderr, "%-20s %s\n", "Confidence", redump_verify_confidence_string(&g_redump_result));
806     if (g_redump_result.status != REDUMP_VERIFY_MATCH)
807         fprintf(stderr, "%-20s %s\n", "Detail", g_redump_result.detail);
808     if (type_id == DISC_TYPE_XBOX)
809         fprintf(stderr, "%-20s %s\n", "Representation note", representation_note);
810
811     friidump_write_redump_report(&g_redump_result, output_path, output_size,
812                                  crc32, md5, sha1, sha256, hash_source,
813                                  representation_note);
814 }
815
816 static void friidump_verify_redump_iso(dumper *dmp, disc_type type_id) {
817     const char *output_path;
818
819     if (!dmp)
820         return;
821
822     output_path = options.iso_out;
823     friidump_verify_redump_values(type_id,
824                                   output_path,
825                                   friidump_file_size(output_path),
826                                   dumper_get_iso_crc32(dmp),
827                                   dumper_get_iso_md5(dmp),
828                                   dumper_get_iso_sha1(dmp),
829                                   dumper_get_iso_sha2(dmp),
830                                   "FriiDump finalized ISO multihash");
831 }
832
833 static void friidump_verify_redump_xbox_reference(dumper *dmp) {
834     xbox_ref_dump_result result;
835
836     xbox_ref_dump_result_init(&result);
837     if (!dumper_get_xbox_reference_result(dmp, &result)) {
838         fprintf(stderr, "Redump verification: SKIPPED (Xbox reference result handoff unavailable)\n");
839         return;
840     }
841     if (result.mode != '1') {
842         fprintf(stderr, "Redump verification: SKIPPED (Xbox XISO is not a full Redump disc image)\n");
843         return;
844     }
845     if (!result.dump_success) {
846         fprintf(stderr, "Redump verification: SKIPPED (Xbox reference dump did not complete)\n");
847         return;
848     }
849     if (!result.hashes_complete) {
850         fprintf(stderr, "Redump verification: SKIPPED (Xbox finalized full-file hashes incomplete)\n");
851         return;
852     }
853
854     if (g_validation_summary.active) {
855         if (result.output_path[0])
856             friidump_summary_copy(g_validation_summary.output, sizeof(g_validation_summary.output), result.output_path);
857         if (result.title[0])
858             friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), result.title);
859         if (result.media_id[0])
860             friidump_summary_copy(g_validation_summary.game_id, sizeof(g_validation_summary.game_id), result.media_id);
861         if (result.output_sectors)
862             g_validation_summary.sectors = result.output_sectors;
863         g_validation_summary.seed_applicable = false;
864         g_validation_summary.have_seed_duration = false;
865     }
866     if (result.elapsed_seconds > 0.0) {
867         g_operation_duration_override_valid = true;
868         g_operation_duration_override = result.elapsed_seconds;
869     }
870
871     friidump_verify_redump_values(DISC_TYPE_XBOX,
872                                   result.output_path,
873                                   result.output_size,
874                                   result.crc32,
875                                   result.md5,
876                                   result.sha1,
877                                   result.sha256,
878                                   "GDR-8050L reference finalized full-file hashes");
879 }
880
881 void help (void) {
882         /* 80 cols guide:
883          *      |-------------------------------------------------------------------------------|
884          */
885         fprintf (stderr, "\n"
886                 "Available command line options:\n"
887                 "\n"
888                 " -h, --help                    Show this help\n"
889                 " -a, --autodump                        Dump the disc to an ISO file with an\n"
890                 "                               automatically-generated name, resuming the dump\n"
891                 "                               if possible\n"
892                 " -g, --gui                     Use more verbose output that can be easily\n"
893                 "                               parsed by a GUI frontend\n"
894                 " -d, --device <device>         Dump disc from device <device>\n"
895                 " -p, --stop                    Instruct device to stop disc rotation\n"
896                 " -D, --dvd                     Force standard DVD-ROM mode for any drive\n"
897                 "                               (equivalent to -T 3; uses FriiDump original\n"
898                 "                               DVD/raw/ISO paths, not Xbox mode)\n"
899                 " -c, --command <nr>            Force memory dump command:\n"
900                 "                               0 - vanilla 2064\n"
901                 "                               1 - vanilla 2384\n"
902                 "                               2 - Hitachi\n"
903                 "                               3 - Lite-On\n"
904                 "                               4 - Renesas\n"
905                 " -x, --speed <x>               Set streaming speed (1, 24, 32, 64, etc.,\n"
906                 "                               where 1 = 150 KiB/s and so on)\n"
907                 " -T, --type <nr>               Force disc type:\n"
908                 "                               0 - GameCube\n"
909                 "                               1 - Wii\n"
910                 "                               2 - Wii_DL\n"
911                 "                               3 - DVD\n"
912                 "                               4 - Xbox/XGD 2048-byte-sector mode\n"
913                 "                                   Native profiles: GDR-8050L and GDR-3120L.\n"
914                 "                                   Other drives keep normal FriiDump behavior\n"
915                 "                                   unless Xbox mode is explicitly forced.\n"
916                 " -S, --size <sectors>          Force disc size\n"
917                 " -r, --raw <file>              Output to file <file> in raw format (2064-byte\n"
918                 "                               sectors)\n"
919                 " -i, --iso[=<file>]            Output to file <file> in ISO format (2048-byte\n"
920                 "                               sectors). For Xbox/GDR-8050L, omitting <file>\n"
921                 "                               derives Title[MediaID].iso from the XBE/DMI;\n"
922                 "                               providing <file> is an explicit override. For\n"
923                 "                               Xbox/XGD this reconstructs the redump-style\n"
924                 "                               XGD1 layout and writes .pfi.bin, .dmi.bin, and\n"
925                 "                               .redump.json metadata when possible\n"
926                 " -X, --xiso[=<file>]   Output Xbox/XGD game partition as XISO (.xiso).\n"
927                 "                               For Xbox/GDR-8050L, omitting <file> derives\n"
928                 "                               Title[MediaID].xiso from the XBE/DMI; providing\n"
929                 "                               <file> is an explicit override. Attempts to read\n"
930                 "                               the 32-sector game lead-in from drive-readable\n"
931                 "                               sectors and zero-fills only unreadable sectors.\n"
932                 " -u, --unscramble <file>       Convert (unscramble) raw image contained in\n"
933                 "                               <file> to ISO format\n"
934                 " -H, --nohash                  Do not compute CRC32/MD5/SHA-1/SHA-256 hashes\n"
935                 "                               for generated files\n"
936                 " -s, --resume                  Resume partial dump\n"
937                 "                               -  General  -----------------------------------\n"
938                 " -0, --method0[=<req>,<exp>]   Use dumping method 0 (Optional argument\n"
939                 "                               specifies how many sectors to request from disc\n"
940                 "                               and read from cache at a time. Values should be\n"
941                 "                               separated with a comma. Default 16,16)\n"
942                 "                               -  Non-Streaming  -----------------------------\n"
943                 " -1, --method1[=<req>,<exp>]   Use dumping method 1 (Default 16,16)\n"
944                 " -2, --method2[=<req>,<exp>]   Use dumping method 2 (Default 16,16)\n"
945                 " -3, --method3[=<req>,<exp>]   Use dumping method 3 (Default 16,16)\n"
946                 "                               -  Streaming  ---------------------------------\n"
947                 " -4, --method4[=<req>,<exp>]   Use dumping method 4 (Default 27,27)\n"
948                 " -5, --method5[=<req>,<exp>]   Use dumping method 5 (Default 27,27)\n"
949                 " -6, --method6[=<req>,<exp>]   Use dumping method 6 (Default 27,27)\n"
950                 "                               -  Hitachi  -----------------------------------\n"
951                 " -7, --method7                 Use dumping method 7 (Read and dump 5 blocks\n"
952                 "                               at a time, using streaming read)\n"
953                 " -8, --method8                 Use dumping method 8 (Read and dump 5 blocks\n"
954                 "                               at a time, using streaming read, using DMA)\n"
955                 " -9, --method9                 Use dumping method 9 (Read and dump 5 blocks\n"
956                 "                               at a time, using streaming read, using DMA and\n"
957                 "                               some speed tricks)\n"
958                 "     --hlds-e7-scan            Probe HLDS HIT 0xE7 cache/memdump bases only;\n"
959                 "                               writes JSON and does not crack seeds or dump data\n"
960                 "     --hlds-e7-subcmd-sweep    Probe HIT 0xE7 subcommands/address candidates only;\n"
961                 "                               writes JSON and does not crack seeds or dump data\n"
962                 "     --hlds-e7-memrange-sweep  Sweep wider HIT 0xE7 subcmd 0x01 address ranges;\n"
963                 "                               writes JSON and does not crack seeds or dump data\n"
964                 "     --scan-log <file>         JSON output path for --hlds-e7-scan\n"
965                 "     --scan-dump-prefix <prefix>       Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n"
966                 "     --hlds-profile-report <file> Write selected HLDS profile/evidence JSON\n"
967                 "     --redump-dat-dir <dir> Directory containing canonical Redump DAT files\n"
968                 "                              (default: executable-relative redump_dat, then current directory)\n"
969                 "     --redump-report <file>   Write atomic Redump evidence JSON\n"
970                 "     --no-redump-verify       Disable automatic post-dump DAT verification\n"
971                 "     --xgd1-layout-probe <file> Read-only locked/unlocked XGD1 boundary probe;\n"
972                 "                              writes atomic JSON and does not create an ISO\n"
973                 "     --xgd1-raw-id-probe <file> Modified-firmware cache-flushed, block-aligned raw-ID probe;\n"
974                 "                              maps logical LBAs to decoded physical sector IDs\n"
975                 " -A, --allmethods              Try all known command/method combinations until\n"
976                 "                               one works. Reopens the drive for each command so\n"
977                 "                               command-specific vendor handlers are rebound.\n"
978 #ifdef DEBUG
979                 " -n, --donottunscramble                Do not try unscrambling to check EDC. Only\n"
980                 "                               useful for testing the raw performance of the\n"
981                 "                               different methods\n"
982                 " -f, --donottflush             Do not call fflush() after every fwrite()\n"
983 #endif
984         );
985
986         return;
987 }
988
989
990 bool optparse (int argc, char **argv) {
991         bool out;
992         char *result = NULL;
993         int c;
994         int option_index = 0;
995         static struct option long_options[] = {
996                 {"help", 0, 0, 'h'},    //0 - no_argument
997                 {"autodump", 0, 0, 'a'},
998                 {"gui", 0, 0, 'g'},
999                 {"device", 1, 0, 'd'},  //1 - required_argument
1000                 {"raw", 1, 0, 'r'},
1001                 {"iso", 2, 0, 'i'},
1002                 {"xiso", 2, 0, 'X'},
1003                 {"unscramble", 1, 0, 'u'},
1004                 {"nohash", 0, 0, 'H'},
1005                 {"resume", 0, 0, 's'},
1006                 {"method0", 2, 0, '0'}, //2 - optional_argument
1007                 {"method1", 2, 0, '1'},
1008                 {"method2", 2, 0, '2'},
1009                 {"method3", 2, 0, '3'},
1010                 {"method4", 2, 0, '4'},
1011                 {"method5", 2, 0, '5'},
1012                 {"method6", 2, 0, '6'},
1013                 {"method7", 0, 0, '7'},
1014                 {"method8", 0, 0, '8'},
1015                 {"method9", 0, 0, '9'},
1016                 {"stop", 0, 0, 'p'},
1017                 {"dvd", 0, 0, 'D'},
1018                 {"command", 1, 0, 'c'},
1019                 {"startsector", 1, 0, 't'},
1020                 {"size", 1, 0, 'S'},
1021                 {"speed", 1, 0, 'x'},
1022                 {"type", 1, 0, 'T'},
1023                 {"allmethods", 0, 0, 'A'},
1024                 {"hlds-e7-scan", 0, 0, 1000},
1025                 {"hlds-e7-subcmd-sweep", 0, 0, 1003},
1026                 {"hlds-e7-memrange-sweep", 0, 0, 1004},
1027                 {"scan-log", 1, 0, 1001},
1028                 {"scan-dump-prefix", 1, 0, 1002},
1029                 {"hlds-profile-report", 1, 0, 1005},
1030                 {"redump-dat-dir", 1, 0, 1006},
1031                 {"redump-report", 1, 0, 1007},
1032                 {"no-redump-verify", 0, 0, 1008},
1033                 {"xgd1-layout-probe", 1, 0, 1009},
1034                 {"xgd1-raw-id-probe", 1, 0, 1010},
1035 #ifdef DEBUG
1036                 /* We don't want newbies to generate and put into circulation bad dumps, so this options are disabled for releases */
1037                 {"donottunscramble", 0, 0, 'n'},
1038                 {"donottflush", 0, 0, 'f'},
1039 #endif
1040                 {0, 0, 0, 0}
1041         };
1042
1043         if (argc == 1) {
1044                 help ();
1045                 exit (1);
1046         }
1047         
1048         /* Init options to default values */
1049         options.device = NULL;
1050         options.autodump = false;
1051         options.gui = false;
1052         options.raw_in = NULL;
1053         options.raw_out = NULL;
1054         options.iso_out = NULL;
1055         options.xiso_out = NULL;
1056         options.iso_requested = false;
1057         options.xiso_requested = false;
1058         options.xbox_filename_override = false;
1059         options.xbox_auto_filename = false;
1060         options.no_hashing = false;
1061         options.resume = false;
1062         options.dump_method = -1;
1063         options.command = -1;
1064         options.start_sector = -1;
1065         options.sectors_no = -1;
1066         options.speed = -1;
1067         options.disctype = -1;
1068         options.sec_disc = -1;
1069         options.sec_mem = -1;
1070         options.no_unscrambling = false;
1071         options.no_flushing = false;
1072         options.stop_unit = false;
1073         options.allmethods = false;
1074         options.hlds_e7_scan = false;
1075         options.hlds_e7_subcmd_sweep = false;
1076         options.hlds_e7_memrange_sweep = false;
1077         options.hlds_e7_scan_log = NULL;
1078         options.hlds_e7_scan_dump_prefix = NULL;
1079         options.hlds_profile_report = NULL;
1080         options.redump_dat_dir = NULL;
1081         options.redump_report = NULL;
1082         options.no_redump_verify = false;
1083         options.xgd1_layout_probe = false;
1084         options.xgd1_layout_probe_report = NULL;
1085         options.xgd1_raw_id_probe = false;
1086         options.xgd1_raw_id_probe_report = NULL;
1087
1088         do {
1089 #ifdef DEBUG
1090                 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);
1091 #else
1092                 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);
1093 #endif
1094
1095                 switch (c) {
1096                         case 'h':
1097                                 help ();
1098                                 exit (1);
1099                                 break;
1100                         case 'p':
1101                                 options.stop_unit = true;
1102                                 break;
1103                         case 'a':
1104                                 options.autodump = true;
1105                                 options.resume = true;
1106                                 break;
1107                         case 'g':
1108                                 options.gui = true;
1109                                 break;
1110                         case 'd':
1111                                 my_strdup (options.device, optarg);
1112                                 break;
1113                         case 'r':
1114                                 my_strdup (options.raw_out, optarg);
1115                                 break;
1116                         case 'i':
1117                                 options.iso_requested = true;
1118                                 /* Preserve the old `-i file.iso` syntax even though Xbox now also
1119                                  * supports bare `-i` for XBE/DMI-derived names. */
1120                                 if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-')
1121                                         optarg = argv[optind++];
1122                                 if (optarg) {
1123                                         my_strdup (options.iso_out, optarg);
1124                                         options.xbox_filename_override = true;
1125                                 } else {
1126                                         options.xbox_auto_filename = true;
1127                                 }
1128                                 break;
1129                         case 'X':
1130                                 options.xiso_requested = true;
1131                                 /* Preserve the old `-X file.xiso` syntax while allowing bare `-X`. */
1132                                 if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-')
1133                                         optarg = argv[optind++];
1134                                 if (optarg) {
1135                                         my_strdup (options.xiso_out, optarg);
1136                                         options.xbox_filename_override = true;
1137                                 } else {
1138                                         options.xbox_auto_filename = true;
1139                                 }
1140                                 break;
1141                         case 'u':
1142                                 my_strdup (options.raw_in, optarg);
1143                                 break;
1144                         case 'H':
1145                                 options.no_hashing = true;
1146                                 break;
1147                         case 's':
1148                                 options.resume = true;
1149                                 break;
1150                         case '0':
1151                         case '1':
1152                         case '2':
1153                         case '3':
1154                         case '4':
1155                         case '5':
1156                         case '6':
1157                                 options.dump_method = c - '0';
1158                                 if (optarg) {
1159                                         result = strtok(optarg, ",");
1160                                         result = strtok(NULL, ",");
1161                                         options.sec_disc = atol(strpbrk(optarg,"1234567890"));
1162                                         if (result) options.sec_mem = atol(result);
1163                                         else {
1164                                                 help ();
1165                                                 exit (1);
1166                                         }
1167                                 }
1168                                 break;
1169                         case '7':
1170                         case '8':
1171                         case '9':
1172                                 options.dump_method = c - '0';
1173                                 break;
1174                         case 'D':
1175                                 options.disctype = DISC_TYPE_DVD;
1176                                 unscrambler_set_disctype (DISC_TYPE_DVD);
1177                                 break;
1178                         case 'c':
1179                                 options.command = atol (optarg);
1180                                 if (options.command > 4) {
1181                                         help ();
1182                                         exit (1);
1183                                 };
1184                                 break;
1185                         case 't':
1186                                 options.start_sector = atol (optarg);
1187                                 break;
1188                         case 'S':
1189                                 options.sectors_no = atol (optarg);
1190                                 break;
1191                         case 'x':
1192                                 options.speed = atol (optarg);
1193                                 break;
1194                         case 'T':
1195                                 options.disctype = atol (optarg);
1196                                 if (options.disctype > 4) {
1197                                         help ();
1198                                         exit (1);
1199                                 };
1200                                 if (options.disctype <= DISC_TYPE_DVD)
1201                                         unscrambler_set_disctype (options.disctype);
1202                                 break;
1203                         case 'A':
1204                                 options.allmethods = true;
1205                                 options.resume = true;
1206                                 break;
1207 #ifdef DEBUG
1208                         case 'n':
1209                                 options.no_unscrambling = true;
1210                                 break;
1211                         case 'f':
1212                                 options.no_flushing = true;
1213                                 break;
1214 #endif
1215                         case 1000:
1216                                 options.hlds_e7_scan = true;
1217                                 break;
1218                         case 1003:
1219                                 options.hlds_e7_subcmd_sweep = true;
1220                                 break;
1221                         case 1004:
1222                                 options.hlds_e7_memrange_sweep = true;
1223                                 break;
1224                         case 1001:
1225                                 my_strdup (options.hlds_e7_scan_log, optarg);
1226                                 break;
1227                         case 1002:
1228                                 my_strdup (options.hlds_e7_scan_dump_prefix, optarg);
1229                                 break;
1230                         case 1005:
1231                                 my_strdup (options.hlds_profile_report, optarg);
1232                                 break;
1233                         case 1006:
1234                                 my_strdup (options.redump_dat_dir, optarg);
1235                                 break;
1236                         case 1007:
1237                                 my_strdup (options.redump_report, optarg);
1238                                 break;
1239                         case 1008:
1240                                 options.no_redump_verify = true;
1241                                 break;
1242                         case 1009:
1243                                 options.xgd1_layout_probe = true;
1244                                 my_strdup (options.xgd1_layout_probe_report, optarg);
1245                                 options.disctype = DISC_TYPE_XBOX;
1246                                 break;
1247                         case 1010:
1248                                 options.xgd1_raw_id_probe = true;
1249                                 my_strdup (options.xgd1_raw_id_probe_report, optarg);
1250                                 options.disctype = DISC_TYPE_XBOX;
1251                                 break;
1252                         case -1:
1253                                 break;
1254                         default:
1255 //                              fprintf (stderr, "?? getopt returned character code 0%o ??\n", c);
1256                                 exit (7);
1257                                 break;
1258                 }
1259         } while (c != -1);
1260
1261         if (optind < argc) {
1262                 /* Command-line arguments remaining. Ignore them, warning the user. */
1263                 fprintf (stderr, "WARNING: Extra parameters ignored\n");
1264         }
1265
1266         if (options.xgd1_layout_probe || options.xgd1_raw_id_probe)
1267                 options.disctype = DISC_TYPE_XBOX;
1268
1269         /* Sanity checks... */
1270         out = false;
1271         if (!options.device && !options.raw_in) {
1272                 fprintf (stderr, "No operation specified. Please use the -d or -u options.\n");
1273         } else if (options.raw_in && options.raw_out) {
1274                 fprintf (stderr,
1275                         "Are you sure you want to convert a raw image to another raw image? ;)\n"
1276                         "Take a look at the -i and -a options!\n"
1277                 );
1278         } else if (options.autodump && (options.raw_out || options.iso_requested || options.xiso_requested)) {
1279                 fprintf (stderr, "The -r, -i and -X options cannot be used together with -a.\n");
1280         } else if (options.xiso_requested && (options.raw_out || options.iso_requested)) {
1281                 fprintf (stderr, "The -X/--xiso option is a separate Xbox output mode and cannot be combined with -r or -i.\n");
1282         } else if ((options.xgd1_layout_probe || options.xgd1_raw_id_probe) &&
1283                    (options.autodump || options.raw_in || options.raw_out ||
1284                     options.iso_requested || options.xiso_requested ||
1285                     options.allmethods || options.hlds_e7_scan ||
1286                     options.hlds_e7_subcmd_sweep || options.hlds_e7_memrange_sweep ||
1287                     (options.xgd1_layout_probe && options.xgd1_raw_id_probe))) {
1288                 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");
1289         } else {
1290                 /* Specified options seem to make sense */
1291                 out = true;
1292         }
1293                 
1294         return (out);
1295 }
1296
1297 int dologic (disc *d, progstats *stats) {
1298         disc_type type_id;
1299         char *type, *game_id, *region, *maker_id, *maker, *version, *title, tmp[0x03E0 + 4 + 1];
1300         bool drive_supported;
1301         bool xbox_forced;
1302         bool xbox_output_requested;
1303         bool dump_attempted;
1304         int out;
1305         dumper *dmp;
1306         u_int32_t current_sector = 0;
1307         
1308         xbox_forced = (options.disctype == DISC_TYPE_XBOX);
1309         xbox_output_requested = xbox_forced || options.xiso_requested || options.xgd1_layout_probe || options.xgd1_raw_id_probe;
1310         dump_attempted = false;
1311         
1312         
1313
1314                                 if (options.stop_unit) { //stop rotation, if requested
1315                                         fprintf (stderr, "Issuing STOP command... %s\n", (disc_stop_unit (d, false)) ? "OK" : "Failed");
1316                                         exit (1);
1317                                 }
1318                                 else disc_stop_unit(d, true); //else start rotation
1319
1320                                 drive_supported = disc_get_drive_support_status (d);
1321                                 fprintf (stderr,
1322                                         "\n"
1323                                         "Drive information:\n"
1324                                         "----------------------------------------------------------------------\n"
1325                                         "Drive model........: %s\n"
1326                                         "Supported..........: %s\n", disc_get_drive_model_string (d), drive_supported ? "Yes" : "No"
1327                                 );
1328
1329                                         if (xbox_output_requested && !disc_is_xbox_unlock_drive (d)) {
1330                                                 fprintf (stderr,
1331                                                         "Xbox/XGD output is limited to the supported Xbox unlock profiles "
1332                                                         "currently wired into this branch: GDR-8050L, GDR-3120L, "
1333                                                         "and known Samsung/Kreon-style vendor-unlock drives.\n"
1334                                                         "Refusing to fall back to FriiDump GC/Wii methods for Xbox mode on this drive.\n");
1335                                                 return false;
1336                                         }
1337
1338                                         /* Xbox/XGD (-T 4 or -X) is a direct MMC/SCSI READ(10) path.
1339                                          * Do not let the detected GC/Wii vendor memdump method (for example
1340                                          * Hitachi command 2 / method 9 on GDR-8050L) drive sector reads. */
1341                                         if (xbox_output_requested && options.dump_method == -1)
1342                                                 options.dump_method = 10;
1343
1344                                         init_range(d, options.sec_disc, options.sec_mem);
1345
1346                                         if (!(disc_set_read_method (d, options.dump_method)))
1347                                                 exit (2);
1348
1349                                         if (xbox_output_requested && disc_get_method(d) == 10) {
1350                                                 fprintf (stderr, "Command............: Xbox direct MMC/SCSI path\n");
1351                                                 fprintf (stderr, "Method.............: 10 (Xbox READ(10))\n");
1352                                         } else {
1353                                                 if (options.command!=-1) fprintf (stderr, 
1354                                                         "Command............: %d (forced)\n", disc_get_command(d));
1355                                                 else fprintf (stderr, 
1356                                                         "Command............: %d\n", disc_get_command(d));
1357                                                 if (disc_get_def_method(d)!=disc_get_method(d)) fprintf (stderr, 
1358                                                         "Method.............: %d (forced)\n", disc_get_method(d));
1359                                                 else fprintf (stderr, 
1360                                                         "Method.............: %d\n", disc_get_method(d));
1361                                                 if (disc_get_hlds_e7_type (d) != 0) {
1362                                                         fprintf (stderr, "HLDS 0xE7 profile..: %s\n", disc_get_hlds_e7_profile_name (d));
1363                                                         fprintf (stderr, "HLDS support tier..: %s\n", disc_get_hlds_e7_support_tier (d));
1364                                                         fprintf (stderr, "HLDS family........: %s\n", disc_get_hlds_e7_family (d));
1365                                                         fprintf (stderr, "HLDS E7 tokens.....: %s\n", disc_get_hlds_e7_tokens (d));
1366                                                         fprintf (stderr, "HLDS Stage5B row...: %s\n", disc_get_hlds_e7_record_id (d));
1367                                                         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));
1368                                                         fprintf (stderr, "Cache base.........: 0x%08x\n", disc_get_hlds_e7_cache_base (d));
1369                                                         fprintf (stderr, "Memory windows.....: %u\n", disc_get_hlds_e7_mem_blocks (d));
1370                                                         if (disc_get_hlds_e7_notes (d) && disc_get_hlds_e7_notes (d)[0])
1371                                                                 fprintf (stderr, "HLDS notes.........: %s\n", disc_get_hlds_e7_notes (d));
1372                                                 }
1373                                         }
1374                                         if (options.hlds_profile_report) {
1375                                                 if (friidump_write_hlds_profile_report(d, options.hlds_profile_report))
1376                                                         fprintf(stderr, "HLDS profile report: %s\n", options.hlds_profile_report);
1377                                         }
1378                                         options.dump_method=disc_get_method(d);
1379                                         friidump_summary_begin(d);
1380                                 if ((options.dump_method==0) 
1381                                 || (options.dump_method==1) || (options.dump_method==2) || (options.dump_method==3)
1382                                 || (options.dump_method==4) || (options.dump_method==5) || (options.dump_method==6)
1383                                 ){
1384                                         fprintf (stderr, 
1385                                         "Requested sectors..: %d\n", disc_get_sec_disc(d));
1386                                         fprintf (stderr, 
1387                                         "Expected sectors...: %d\n", disc_get_sec_mem(d));
1388                                 }
1389
1390                                 fprintf (stderr, "\nPress Ctrl+C at any time to terminate\n");
1391
1392                                 //set speed for 1st time
1393                                 if (options.speed != -1) disc_set_speed(d, options.speed * 177);
1394                                 if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177);
1395 //                              disc_set_speed(d, 0xffff);
1396
1397                                 /* Windows may attach filesystem/autoplay polling to odd GC/Wii discs and
1398                                  * steal the drive during the HLDS 0xE7 seed phase, especially on Type1
1399                                  * GCC-4160N/GCC-4240N.  Reuse the same volume guard mechanism that the
1400                                  * Xbox path already uses, but apply it before disc_init()/seed reads for
1401                                  * all HLDS 0xE7 GC/Wii profiles.  Failure is warning-only because Explorer
1402                                  * may already have a transient handle; the read path itself remains the
1403                                  * authority. */
1404                                 if (!xbox_output_requested && disc_get_hlds_e7_type (d) != 0) {
1405                                         if (disc_get_hlds_e7_type (d) == 44 || disc_get_hlds_e7_type (d) == 45) {
1406                                                 fprintf (stderr,
1407                                                         "\nGDR-8050L modified-firmware warning:\n"
1408                                                         "  This GC/Wii 0xE7 path assumes a cross-flashed or modified GDR-8050L firmware with 0xE7 memdump support added.\n"
1409                                                         "  Stock GDR-8050L firmware is still supported for Xbox ripping, but it is not expected to dump GC/Wii discs through this path.\n");
1410                                         }
1411                                         fprintf (stderr,
1412                                                 "\nWindows AutoPlay warning:\n"
1413                                                 "  HLDS 0xE7 GC/Wii seed reads are sensitive to Windows polling.\n"
1414                                                 "  Disable AutoPlay for this drive/media and close File Explorer or any \"insert a disc\" dialogs before dumping.\n"
1415                                                 "  FriiDump will try to lock the volume, but AutoPlay can still interfere before or during seed retrieval.\n");
1416                                         fprintf (stderr, "Applying Windows volume lock guard for HLDS 0xE7 seed reads... ");
1417                                         disc_refresh_volume (d);
1418                                         if (disc_lock_volume (d) < 0)
1419                                                 fprintf (stderr, "Warning: failed; disable AutoPlay, close File Explorer/AutoPlay dialogs for this drive, then rerun.\n");
1420                                         else
1421                                                 fprintf (stderr, "OK\n");
1422                                 }
1423
1424                                 if (options.xgd1_layout_probe || options.xgd1_raw_id_probe) {
1425 #ifdef WIN32
1426                                         int media_rc;
1427                                         int media_sense_key;
1428                                         int media_asc;
1429                                         int media_ascq;
1430                                         int probe_status;
1431                                         bool stop_ok;
1432                                         const char *probe_name;
1433
1434                                         probe_name = options.xgd1_raw_id_probe ? "XGD1 raw-sector ID" : "XGD1 logical-boundary";
1435
1436                                         if (!disc_is_xbox_challenge_drive (d)) {
1437                                                 fprintf (stderr,
1438                                                          "%s probe currently supports only the GDR-8050L challenge-handshake profile.\n",
1439                                                          probe_name);
1440                                                 return false;
1441                                         }
1442
1443                                         if (options.xgd1_raw_id_probe &&
1444                                             !(disc_get_hlds_e7_type (d) == 44 ||
1445                                               disc_get_hlds_e7_type (d) == 45 ||
1446                                               disc_get_hlds_e7_type (d) == 442 ||
1447                                               disc_get_hlds_e7_type (d) == 443 ||
1448                                               disc_get_hlds_e7_type (d) == 445)) {
1449                                                 fprintf (stderr,
1450                                                          "--xgd1-raw-id-probe requires the modified GDR-8050L HIT 0xE7 memdump profile.\n");
1451                                                 return false;
1452                                         }
1453
1454                                         fprintf (stderr, "\nChecking for ready Xbox media before %s probing... ", probe_name);
1455                                         media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
1456                                         if (media_rc <= 0) {
1457                                                 fprintf (stderr,
1458                                                          "Failed (sense %02X/%02X/%02X). Insert the disc, wait for spin-up, close AutoPlay/File Explorer, and retry.\n",
1459                                                          media_sense_key, media_asc, media_ascq);
1460                                                 return false;
1461                                         }
1462                                         fprintf (stderr, "OK\n");
1463
1464                                         gettimeofday (&(stats -> start_time), NULL);
1465                                         if (options.xgd1_raw_id_probe)
1466                                                 probe_status = xbox_ref_xgd1_raw_id_probe_with_handle (
1467                                                                 disc_get_native_handle (d), disc_get_device (d), options.xgd1_raw_id_probe_report);
1468                                         else
1469                                                 probe_status = xbox_ref_xgd1_layout_probe_with_handle (
1470                                                                 disc_get_native_handle (d), disc_get_device (d), options.xgd1_layout_probe_report);
1471
1472                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after %s probe... ", probe_name);
1473                                         stop_ok = disc_stop_unit (d, false);
1474                                         fprintf (stderr, "%s\n", stop_ok ? "OK" : "Failed");
1475                                         gettimeofday (&(stats -> end_time), NULL);
1476                                         fprintf (stderr, "%s probe status: %s\n", probe_name, probe_status == 0 ? "OK" : "FAILED");
1477                                         friidump_summary_reset();
1478                                         return probe_status == 0;
1479 #else
1480                                         fprintf (stderr, "XGD1 probe modes are available only in the Windows build.\n");
1481                                         return false;
1482 #endif
1483                                 }
1484
1485                                 if (options.hlds_e7_scan) {
1486                                         out = disc_hlds_e7_scan (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
1487                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 scan... ");
1488                                         fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
1489                                         return out;
1490                                 }
1491
1492                                 if (options.hlds_e7_subcmd_sweep) {
1493                                         out = disc_hlds_e7_subcmd_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
1494                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 subcommand sweep... ");
1495                                         fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
1496                                         return out;
1497                                 }
1498
1499                                 if (options.hlds_e7_memrange_sweep) {
1500                                         out = disc_hlds_e7_memrange_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
1501                                         fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 memory-range sweep... ");
1502                                         fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
1503                                         return out;
1504                                 }
1505
1506                                 {
1507                                         int media_rc, media_sense_key, media_asc, media_ascq;
1508                                         fprintf (stderr, "\nChecking for ready media before disc seed retrieval... ");
1509                                         media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
1510                                         if (media_rc <= 0) {
1511                                                 if (media_rc == 0)
1512                                                         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);
1513                                                 else
1514                                                         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);
1515                                                 return false;
1516                                         }
1517                                         fprintf (stderr, "OK\n");
1518
1519                                         time_t seed_start, seed_end;
1520                                         double seed_elapsed;
1521                                         char seed_elapsed_buf[32];
1522
1523                                         if (xbox_output_requested)
1524                                                 fprintf (stderr, "\nInitializing Xbox/XGD disc state... ");
1525                                         else
1526                                                 fprintf (stderr, "\nRetrieving disc seeds, this might take a while... ");
1527
1528                                         seed_start = time(NULL);
1529                                         if (!disc_init (d, options.disctype, options.sectors_no)) {
1530                                                 seed_end = time(NULL);
1531                                                 seed_elapsed = difftime(seed_end, seed_start);
1532                                                 friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
1533                                                 if (!xbox_output_requested)
1534                                                         fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
1535                                                 if (g_validation_summary.active) {
1536                                                         g_validation_summary.have_seed_duration = true;
1537                                                         g_validation_summary.seed_duration = seed_elapsed;
1538                                                 }
1539                                                 fprintf (stderr, "Failed\n");
1540                                                 out = false;
1541                                         } else {
1542                                                 seed_end = time(NULL);
1543                                                 seed_elapsed = difftime(seed_end, seed_start);
1544                                                 friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
1545                                                 if (!xbox_output_requested)
1546                                                         fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
1547                                                 fprintf (stderr, "OK\n");
1548                                                 if (g_validation_summary.active) {
1549                                                         g_validation_summary.seed_ok = true;
1550                                                         g_validation_summary.have_seed_duration = true;
1551                                                         g_validation_summary.seed_duration = seed_elapsed;
1552                                                 }
1553                                         disc_get_type (d, &type_id, &type);
1554                                         disc_get_gameid (d, &game_id);
1555                                         disc_get_region (d, NULL, &region);
1556                                         disc_get_maker (d, &maker_id, &maker);
1557                                         disc_get_version (d, NULL, &version);
1558                                         disc_get_title (d, &title);
1559                                         if (g_validation_summary.active) {
1560                                                 friidump_summary_copy(g_validation_summary.disc_type, sizeof(g_validation_summary.disc_type), type);
1561                                                 friidump_summary_copy(g_validation_summary.game_id, sizeof(g_validation_summary.game_id), game_id);
1562                                                 friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), title);
1563                                                 g_validation_summary.sectors = disc_get_sectors_no(d);
1564                                         }
1565                                         fprintf (stderr, 
1566                                                 "\n"
1567                                                 "Disc information:\n"
1568                                                 "----------------------------------------------------------------------\n");
1569
1570                                         if (options.disctype!=-1) fprintf (stderr, 
1571                                                 "Disc type..........: %s (forced)\n", type);
1572                                         else fprintf (stderr, 
1573                                                 "Disc type..........: %s\n", type);
1574                                         if (options.sectors_no!=-1) fprintf (stderr, 
1575                                                 "Disc size..........: %d (forced)\n", disc_get_sectors_no(d));
1576                                         else fprintf (stderr, 
1577                                                 "Disc size..........: %d\n", disc_get_sectors_no(d));
1578
1579                                         if (disc_get_layerbreak(d)>0 && type_id==DISC_TYPE_DVD) fprintf (stderr, 
1580                                                 "Layer break........: %d\n", disc_get_layerbreak(d));
1581
1582                                         if ((type_id==DISC_TYPE_GAMECUBE) || (type_id==DISC_TYPE_WII) || (type_id==DISC_TYPE_WII_DL)) fprintf (stderr, 
1583                                                 "Game ID............: %s\n"
1584                                                 "Region.............: %s\n"
1585                                                 "Maker..............: %s - %s\n"
1586                                                 "Version............: %s\n"
1587                                                 "Game title.........: %s\n", game_id, region, maker_id, maker, version, title
1588                                         );
1589
1590                                         if (type_id == DISC_TYPE_WII || type_id == DISC_TYPE_WII_DL)
1591                                                 fprintf (stderr, "Contains update....: %s\n" , disc_get_update (d) ? "Yes" : "No");
1592                                         fprintf (stderr, "\n");
1593                                         
1594                                         disc_set_unscrambling (d, !options.no_unscrambling);
1595
1596                                         if (type_id <= DISC_TYPE_DVD)
1597                                                 unscrambler_set_disctype (type_id);
1598
1599                                         if (options.autodump) {
1600                                                 snprintf (tmp, sizeof (tmp), "%s.iso", title);
1601                                                 my_strdup (options.iso_out, tmp);
1602                                                 options.iso_requested = true;
1603                                         }
1604
1605                                         /* Xbox/GDR-8050L supports default XBE/DMI-derived names.
1606                                          * Use an empty-string placeholder so the copied reference path can
1607                                          * derive Title[MediaID].iso/.xiso after the first unlock/XBE probe. */
1608                                         if (type_id == DISC_TYPE_XBOX && options.iso_requested && !options.iso_out)
1609                                                 my_strdup (options.iso_out, "");
1610                                         if (type_id == DISC_TYPE_XBOX && options.xiso_requested && !options.xiso_out)
1611                                                 my_strdup (options.xiso_out, "");
1612
1613                                         //set speed 2nd time after rotation is started and some sectors read
1614                                         if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177);
1615                                         if (options.speed != -1) disc_set_speed(d, options.speed * 177);
1616
1617                                         /* If at least an output file was specified, proceed dumping, otherwise stop here */
1618                                         if (options.xiso_requested) {
1619                                                 if (type_id != DISC_TYPE_XBOX) {
1620                                                         fprintf (stderr, "Xbox XISO output requires Xbox/XGD disc type. Use -T 4 or an Xbox-capable drive/disc.\n");
1621                                                         out = false;
1622                                                 } else {
1623                                                         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 : "");
1624
1625                                                         dmp = dumper_new (d);
1626                                                         dumper_set_hashing (dmp, !options.no_hashing);
1627                                                         dumper_set_flushing (dmp, !options.no_flushing);
1628
1629                                                         if (!dumper_set_xiso_output_file (dmp, options.xiso_out, options.resume)) {
1630                                                                 fprintf (stderr, "Cannot setup Xbox XISO output file\n");
1631                                                         } else if (!dumper_prepare_xiso (dmp)) {
1632                                                                 fprintf (stderr, "Cannot prepare Xbox XISO dumper");
1633                                                         } else {
1634                                                                 if (options.gui)
1635                                                                         dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats);
1636                                                                 else
1637                                                                         dumper_set_progress_callback (dmp, (progress_func) progress, stats);
1638
1639                                                                 dump_attempted = true;
1640                                                                 if (g_validation_summary.active)
1641                                                                         g_validation_summary.dump_attempted = true;
1642                                                                 if (dumper_dump_xiso (dmp, &current_sector)) {
1643                                                                         fprintf (stderr, "Xbox XISO dump completed successfully!\n");
1644                                                                         if (!options.no_hashing && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d)))
1645                                                                                 fprintf (stderr,
1646                                                                                 "XISO image hashes:\n"
1647                                                                                 "CRC32...: %s\n"
1648                                                                                 "MD5.....: %s\n"
1649                                                                                 "SHA-1...: %s\n"
1650                                                                                 "SHA-256.: %s\n",
1651                                                                                 dumper_get_xiso_crc32 (dmp), dumper_get_xiso_md5 (dmp),
1652                                                                                 dumper_get_xiso_sha1 (dmp), dumper_get_xiso_sha2 (dmp)
1653                                                                         );
1654                                                                 out = true;
1655                                                                 if (g_validation_summary.active)
1656                                                                         g_validation_summary.dump_ok = true;
1657                                                                 } else {
1658                                                                         fprintf (stderr, "\nXbox XISO dump failed at output sector: %u\n", current_sector);
1659                                                                         out = false;
1660                                                                         if (g_validation_summary.active) {
1661                                                                                 g_validation_summary.dump_ok = false;
1662                                                                                 g_validation_summary.fail_sector = current_sector;
1663                                                                         }
1664                                                                 }
1665                                                         }
1666
1667                                                         dmp = dumper_destroy (dmp);
1668                                                 }
1669                                                 } else if (options.raw_out || options.iso_requested) {
1670                                                         if (type_id == DISC_TYPE_XBOX && options.raw_out)
1671                                                                 fprintf (stderr, "Xbox/XGD output does not support -r/raw together with the redump-style ISO path. Use -i or -X.\n");
1672                                                         else if (options.raw_out)
1673                                                                 fprintf (stderr, "Writing to file \"%s\" in raw format\n", options.raw_out);
1674                                                         if (options.iso_out) {
1675                                                                 if (type_id == DISC_TYPE_XBOX)
1676                                                                         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 : "");
1677                                                                 else
1678                                                                         fprintf (stderr, "Writing to file \"%s\" in ISO format\n", options.iso_out);
1679                                                         }
1680                                                         fprintf (stderr, "\n");
1681
1682                                                 dmp = dumper_new (d);
1683
1684                                                 dumper_set_hashing (dmp, !options.no_hashing);
1685                                                 dumper_set_flushing (dmp, !options.no_flushing);
1686
1687                                                 if (!dumper_set_raw_output_file (dmp, options.raw_out, options.resume)) {
1688                                                         fprintf (stderr, "Cannot setup raw output file\n");
1689                                                 } else if (!dumper_set_iso_output_file (dmp, options.iso_out, options.resume)) {
1690                                                         fprintf (stderr, "Cannot setup ISO output file\n");
1691                                                 } else if (!dumper_prepare (dmp)) {
1692                                                         fprintf (stderr, "Cannot prepare dumper");
1693                                                 } else {
1694 //                                                      fprintf (stderr, "Starting dump process from sector %u...\n", dmp -> start_sector);
1695 //                                                      opdd.start_sector = options.start_sector;
1696
1697                                                         if (options.gui)
1698                                                                 dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats);
1699                                                         else
1700                                                                 dumper_set_progress_callback (dmp, (progress_func) progress, stats);
1701
1702                                                         dump_attempted = true;
1703                                                         if (g_validation_summary.active)
1704                                                                 g_validation_summary.dump_attempted = true;
1705                                                         if (dumper_dump (dmp, &current_sector)) {
1706                                                                 fprintf (stderr, "Dump completed successfully!\n");
1707                                                                 if (!options.no_hashing && options.raw_out)
1708                                                                         fprintf (stderr,
1709                                                                                 "Raw image hashes:\n"
1710                                                                                 "CRC32...: %s\n"
1711                                                                                 //"MD4.....: %s\n"
1712                                                                                 "MD5.....: %s\n"
1713                                                                                 "SHA-1...: %s\n"
1714                                                                                 "SHA-256.: %s\n"
1715                                                                                 /*"ED2K....: %s\n"*/,
1716                                                                                 dumper_get_raw_crc32 (dmp), /*dumper_get_raw_md4 (dmp),*/ dumper_get_raw_md5 (dmp),
1717                                                                                 dumper_get_raw_sha1 (dmp), dumper_get_raw_sha2 (dmp)/*, dumper_get_raw_ed2k (dmp)*/
1718                                                                         );
1719                                                                 if (!options.no_hashing && options.iso_out && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d)))
1720                                                                         fprintf (stderr,
1721                                                                                 "ISO image hashes:\n"
1722                                                                                 "CRC32...: %s\n"
1723                                                                                 //"MD4.....: %s\n"
1724                                                                                 "MD5.....: %s\n"
1725                                                                                 "SHA-1...: %s\n"
1726                                                                                 "SHA-256.: %s\n"
1727                                                                                 /*"ED2K....: %s\n"*/,
1728                                                                                 dumper_get_iso_crc32 (dmp), /*dumper_get_iso_md4 (dmp),*/ dumper_get_iso_md5 (dmp),
1729                                                                                 dumper_get_iso_sha1 (dmp), dumper_get_iso_sha2 (dmp)/*, dumper_get_iso_ed2k (dmp)*/
1730                                                                         );
1731
1732                                                                 if (type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d) && options.iso_requested)
1733                                                                         friidump_verify_redump_xbox_reference(dmp);
1734                                                                 else if (options.iso_out)
1735                                                                         friidump_verify_redump_iso(dmp, type_id);
1736
1737                                                                 out = true;
1738                                                                 if (g_validation_summary.active)
1739                                                                         g_validation_summary.dump_ok = true;
1740                                                         } else {
1741                                                                 if (g_validation_summary.active) {
1742                                                                         g_validation_summary.dump_ok = false;
1743                                                                         g_validation_summary.fail_sector = current_sector;
1744                                                                 }
1745                                                                 if (current_sector == 0xFFFFFFFFU)
1746                                                                         fprintf (stderr, "\nXbox reference dumper failed; see the Xbox-specific message above.\n");
1747                                                                 else
1748                                                                         fprintf (stderr, "\nDump failed at sectors: %u..%u\n", current_sector, current_sector+15);
1749                                                                 out = false;
1750                                                                 //disc_stop_unit (d, 0);
1751                                                         }
1752                                                 }
1753
1754                                                 dmp = dumper_destroy (dmp);
1755                                         } else {
1756                                                 fprintf (stderr, "No output file for dumping specified, please take a look at the -i, -r, -X and -a options\n");
1757                                         }
1758                                 }
1759                         }
1760         if (dump_attempted) {
1761                 bool stop_ok = disc_stop_unit (d, false);
1762                 fprintf (stderr, "\nIssuing STOP UNIT / spin-down after dump attempt... %s\n", stop_ok ? "OK" : "Failed");
1763                 if (g_validation_summary.active) {
1764                         g_validation_summary.stop_attempted = true;
1765                         g_validation_summary.stop_ok = stop_ok;
1766                 }
1767         }
1768
1769         return out;
1770 }
1771
1772
1773 static int try_all_methods (progstats *stats) {
1774         u_int32_t saved_command;
1775         int saved_method;
1776         u_int32_t command;
1777         int method;
1778         int out;
1779         disc *d;
1780
1781         saved_command = options.command;
1782         saved_method = options.dump_method;
1783         out = false;
1784
1785         fprintf (stderr, "Trying all command/method combinations... This will take a LOOOONG time and generate an insanely long console output :p\n");
1786
1787         for (command = 0; command <= 4 && !out; command++) {
1788                 for (method = 0; method <= 10 && !out; method++) {
1789                         options.command = command;
1790                         options.dump_method = method;
1791                         memset (stats, 0, sizeof (*stats));
1792
1793                         fprintf (stderr, "\nTrying with command %u, method %d\n", command, method);
1794                         fprintf (stderr, "Initializing DVD drive... ");
1795
1796                         d = disc_new (options.device, options.command);
1797                         if (!d) {
1798                                 fprintf (stderr, "Failed\n");
1799 #ifndef WIN32
1800                                 fprintf (stderr,
1801                                         "Probably you do not have access to the DVD device. Ask the administrator\n"
1802                                         "to add you to the proper group, or use 'sudo'.\n"
1803                                 );
1804 #endif
1805                                 continue;
1806                         }
1807
1808                         fprintf (stderr, "OK\n");
1809                         out = dologic (d, stats);
1810                         d = disc_destroy (d);
1811
1812                         if (out)
1813                                 fprintf (stderr, "Command %u and method %d combination worked!\n", command, method);
1814                 }
1815         }
1816
1817         if (!out) {
1818                 options.command = saved_command;
1819                 options.dump_method = saved_method;
1820         }
1821
1822         return out;
1823 }
1824
1825 int main (int argc, char *argv[]) {
1826         disc *d;
1827         progstats stats;
1828         double duration;
1829         suseconds_t us;
1830         int out, ret;
1831         unscrambler *u;
1832         unscrambler_progress_func pfunc;
1833         u_int32_t current_sector;
1834
1835         /* First of all... */
1836         drop_euid ();
1837         friidump_init_executable_dir((argc > 0) ? argv[0] : NULL);
1838         xbox_ref_log_open_for_target(NULL, 0);
1839         
1840         welcome ();
1841
1842         memset (&stats, 0, sizeof (stats));
1843         d = NULL;
1844         out = false;
1845         ret = EXIT_FAILURE;
1846         if (optparse (argc, argv)) {
1847                 friidump_retarget_log_from_options();
1848                 if (options.device) {
1849                         if (options.allmethods) {
1850                                 out = try_all_methods (&stats);
1851                         } else {
1852                                 /* Dump DVD to file */
1853                                 fprintf (stderr, "Initializing DVD drive... ");
1854
1855                                 if (!(d = disc_new (options.device, options.command))) {
1856                                         fprintf (stderr, "Failed\n");
1857 #ifndef WIN32
1858                                         fprintf (stderr,
1859                                                 "Probably you do not have access to the DVD device. Ask the administrator\n"
1860                                                 "to add you to the proper group, or use 'sudo'.\n"
1861                                         );
1862 #endif
1863                                 } else {
1864                                         fprintf (stderr, "OK\n");
1865                                         out = dologic (d, &stats);
1866                                         d = disc_destroy (d);
1867                                 }
1868                         }
1869                 } else if (options.raw_in) {
1870                         /* Convert raw image to ISO format */
1871                         u = unscrambler_new ();
1872                         
1873                         if (options.gui)
1874                                 pfunc = (unscrambler_progress_func) progress_for_guis;
1875                         else
1876                                 pfunc = (unscrambler_progress_func) progress;
1877
1878                         if ((out = unscrambler_unscramble_file (u, options.raw_in, options.iso_out, pfunc, &stats, &current_sector)))
1879                                 fprintf (stderr, "Unscrambling completed successfully!\n");
1880                         else
1881                                 fprintf (stderr, "\nUnscrambling failed at sectors: %u..%u\n", current_sector, current_sector+15);
1882
1883                         u = unscrambler_destroy (u);
1884                 } else {
1885                         MY_ASSERT (0);
1886                 }
1887
1888                 if (out) {
1889                         duration = stats.end_time.tv_sec - stats.start_time.tv_sec;
1890                         if (stats.end_time.tv_usec >= stats.start_time.tv_usec) {
1891                                 us = stats.end_time.tv_usec - stats.start_time.tv_usec;
1892                         } else {
1893                                 if (duration > 0)
1894                                         duration--;
1895                                 us = USECS_PER_SEC + stats.end_time.tv_usec - stats.start_time.tv_usec;
1896                         }
1897                         duration += ((double) us / (double) USECS_PER_SEC);
1898                         if (duration < 0)
1899                                 duration = 0;
1900                         if (g_operation_duration_override_valid)
1901                                 duration = g_operation_duration_override;
1902                         fprintf (stderr, "Operation took %.2f seconds\n", duration);
1903                         friidump_print_validation_summary(duration, true);
1904
1905                         ret = EXIT_SUCCESS;
1906                 } else {
1907                         friidump_print_validation_summary(0.0, false);
1908                         ret = EXIT_FAILURE;
1909                 }
1910                 
1911                 my_free (options.device);
1912                 my_free (options.iso_out);
1913                 my_free (options.xiso_out);
1914                 my_free (options.raw_out);
1915                 my_free (options.raw_in);
1916                 my_free (options.hlds_profile_report);
1917                 my_free (options.xgd1_layout_probe_report);
1918                 my_free (options.xgd1_raw_id_probe_report);
1919         }
1920
1921         if (xbox_ref_log_path())
1922                 fprintf (stderr, "Log file complete: %s\n", xbox_ref_log_path());
1923         xbox_ref_log_close();
1924
1925         return (ret);
1926 }