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