1 /***************************************************************************
2 * Copyright (C) 2007 by Arep *
3 * Support is provided through the forums at *
4 * http://wii.console-tribe.com *
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. *
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. *
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 ***************************************************************************/
29 #include "unscrambler.h"
30 #include "xbox_ref/xbox_ref_log.h"
32 #define printf xbox_ref_printf
33 #define fprintf xbox_ref_log_fprintf
35 #define USECS_PER_SEC 1000000
38 static const char *friidump_requested_output_target(void);
44 bool have_seed_duration;
57 u_int32_t fail_sector;
62 } friidump_validation_summary;
64 static friidump_validation_summary g_validation_summary;
66 static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) {
67 if (!dst || dst_size == 0)
71 snprintf(dst, dst_size, "%s", src);
74 static void friidump_summary_reset(void) {
75 memset(&g_validation_summary, 0, sizeof(g_validation_summary));
76 g_validation_summary.fail_sector = 0xFFFFFFFFU;
79 static void friidump_format_hms(double seconds, char *buf, size_t buf_size) {
80 long total, hours, minutes, secs;
81 if (!buf || buf_size == 0)
85 total = (long) (seconds + 0.5);
87 minutes = (total / 60) % 60;
89 snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hours, minutes, secs);
93 static void friidump_summary_begin(disc *d) {
95 if (!d || disc_get_hlds_e7_type(d) == 0)
97 friidump_summary_reset();
98 g_validation_summary.active = true;
99 friidump_summary_copy(g_validation_summary.model, sizeof(g_validation_summary.model), disc_get_drive_model_string(d));
100 g_validation_summary.command = disc_get_command(d);
101 g_validation_summary.method = disc_get_method(d);
102 g_validation_summary.hlds_type = disc_get_hlds_e7_type(d);
103 g_validation_summary.profile = disc_get_hlds_e7_profile_name(d);
104 g_validation_summary.cache_base = disc_get_hlds_e7_cache_base(d);
105 g_validation_summary.mem_blocks = disc_get_hlds_e7_mem_blocks(d);
106 target = friidump_requested_output_target();
107 friidump_summary_copy(g_validation_summary.output, sizeof(g_validation_summary.output), target ? target : "(none)");
110 static void friidump_print_validation_summary(double duration, bool have_duration) {
111 double mib_total, mib_per_hour;
112 if (!g_validation_summary.active)
114 mib_total = (double) g_validation_summary.sectors * 2048.0 / 1024.0 / 1024.0;
115 mib_per_hour = (have_duration && duration > 0.0) ? (mib_total / duration * 3600.0) : 0.0;
117 "\nHLDS 0xE7 validation summary:\n"
118 " Model.............: %s\n"
119 " Profile...........: %s\n"
120 " Command/method....: %u / %d\n"
121 " Cache base........: 0x%08x\n"
122 " Memory windows....: %u\n"
123 " Disc type.........: %s\n"
124 " Game ID...........: %s\n"
125 " Title.............: %s\n"
126 " Output............: %s\n"
127 " Seed read.........: %s\n"
128 " Dump status.......: %s\n"
129 " STOP UNIT.........: %s\n",
130 g_validation_summary.model[0] ? g_validation_summary.model : "(unknown)",
131 g_validation_summary.profile ? g_validation_summary.profile : "(unknown)",
132 g_validation_summary.command, g_validation_summary.method,
133 g_validation_summary.cache_base, g_validation_summary.mem_blocks,
134 g_validation_summary.disc_type[0] ? g_validation_summary.disc_type : "(unknown)",
135 g_validation_summary.game_id[0] ? g_validation_summary.game_id : "(unknown)",
136 g_validation_summary.title[0] ? g_validation_summary.title : "(unknown)",
137 g_validation_summary.output[0] ? g_validation_summary.output : "(none)",
138 g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED",
139 g_validation_summary.dump_attempted ? (g_validation_summary.dump_ok ? "OK" : "FAILED") : "NOT ATTEMPTED",
140 g_validation_summary.stop_attempted ? (g_validation_summary.stop_ok ? "OK" : "FAILED") : "NOT ATTEMPTED");
141 if (g_validation_summary.have_seed_duration) {
143 friidump_format_hms(g_validation_summary.seed_duration, seed_buf, sizeof(seed_buf));
144 fprintf(stderr, " Seed elapsed......: %s (%.2f seconds)\n", seed_buf, g_validation_summary.seed_duration);
146 if (g_validation_summary.dump_attempted && !g_validation_summary.dump_ok && g_validation_summary.fail_sector != 0xFFFFFFFFU)
147 fprintf(stderr, " Failure sector....: %u..%u\n", g_validation_summary.fail_sector, g_validation_summary.fail_sector + 15);
148 if (g_validation_summary.sectors)
149 fprintf(stderr, " Sectors...........: %u\n", g_validation_summary.sectors);
151 fprintf(stderr, " Duration..........: %.2f seconds\n", duration);
152 if (have_duration && mib_per_hour > 0.0)
153 fprintf(stderr, " Observed average..: %.2f MiB/h over %.2f MiB ISO payload\n", mib_per_hour, mib_total);
154 if (g_validation_summary.dump_ok)
155 fprintf(stderr, " Hash compare......: compare printed hashes against known-good target for this disc\n");
159 /* Name of package */
160 #define PACKAGE "friidump"
162 /* Define to the address where bug reports for this package should be sent. */
163 #define PACKAGE_BUGREPORT "arep@no.net"
165 /* Define to the full name of this package. */
166 #define PACKAGE_NAME "FriiDump"
168 /* Define to the version of this package. */
169 #define PACKAGE_VERSION "0.5.3.5"
174 #include "getopt-win32.h"
178 #include <sys/time.h>
183 /* Struct for program options */
194 bool xbox_filename_override;
195 bool xbox_auto_filename;
199 u_int32_t start_sector;
200 u_int32_t sectors_no;
206 bool no_unscrambling;
211 bool hlds_e7_subcmd_sweep;
212 bool hlds_e7_memrange_sweep;
213 char *hlds_e7_scan_log;
214 char *hlds_e7_scan_dump_prefix;
215 char *hlds_profile_report;
219 /* Struct for progress data */
221 struct timeval start_time;
222 struct timeval end_time;
224 double mb_total_real;
225 u_int32_t sectors_skipped;
229 static char friidump_drive_letter_from_device(const char *device) {
230 if (!device || !device[0]) return 0;
231 if (device[0] && device[1] == ':') return device[0];
232 if (device[0] == '\\' && device[1] == '\\' && device[2] == '.' && device[3] == '\\' && device[4] && device[5] == ':') return device[4];
237 static const char *friidump_requested_output_target(void) {
238 if (options.iso_out && options.iso_out[0]) return options.iso_out;
239 if (options.xiso_out && options.xiso_out[0]) return options.xiso_out;
240 if (options.raw_out && options.raw_out[0]) return options.raw_out;
245 static void friidump_json_string(FILE *f, const char *s) {
246 const unsigned char *p;
249 for (p = (const unsigned char *) s; *p; p++) {
251 case '\\': fputs("\\\\", f); break;
252 case '"': fputs("\\\"", f); break;
253 case '\b': fputs("\\b", f); break;
254 case '\f': fputs("\\f", f); break;
255 case '\n': fputs("\\n", f); break;
256 case '\r': fputs("\\r", f); break;
257 case '\t': fputs("\\t", f); break;
261 snprintf(tmp, sizeof(tmp), "\\u%04x", (unsigned int) *p);
273 static void friidump_json_kv_string(FILE *f, const char *key, const char *value, bool comma) {
275 friidump_json_string(f, key);
277 friidump_json_string(f, value ? value : "");
278 fputs(comma ? ",\n" : "\n", f);
281 static void friidump_json_kv_u32_hex(FILE *f, const char *key, u_int32_t value, bool comma) {
283 snprintf(tmp, sizeof(tmp), "0x%08x", value);
284 friidump_json_kv_string(f, key, value ? tmp : "", comma);
287 static void friidump_json_kv_int(FILE *f, const char *key, int value, bool comma) {
290 friidump_json_string(f, key);
291 snprintf(tmp, sizeof(tmp), ": %d%s\n", value, comma ? "," : "");
295 static void friidump_json_kv_bool(FILE *f, const char *key, bool value, bool comma) {
297 friidump_json_string(f, key);
298 fputs(value ? ": true" : ": false", f);
299 fputs(comma ? ",\n" : "\n", f);
302 static bool friidump_write_hlds_profile_report(disc *d, const char *path) {
304 if (!d || !path || !path[0])
306 f = fopen(path, "wb");
308 fprintf(stderr, "WARNING: could not write HLDS profile report: %s\n", path);
312 friidump_json_kv_string(f, "schema", "friidump_hlds_profile_report_v1", true);
313 friidump_json_kv_string(f, "drive_model", disc_get_drive_model_string(d), true);
314 friidump_json_kv_string(f, "profile_name", disc_get_hlds_e7_profile_name(d), true);
315 friidump_json_kv_string(f, "support_tier", disc_get_hlds_e7_support_tier(d), true);
316 friidump_json_kv_string(f, "family", disc_get_hlds_e7_family(d), true);
317 friidump_json_kv_string(f, "tokens", disc_get_hlds_e7_tokens(d), true);
318 friidump_json_kv_string(f, "stage5b_record_id", disc_get_hlds_e7_record_id(d), true);
319 friidump_json_kv_u32_hex(f, "static_e7_cdb_base", disc_get_hlds_e7_static_cdb_base(d), true);
320 friidump_json_kv_u32_hex(f, "static_e7_gate", disc_get_hlds_e7_static_gate(d), true);
321 friidump_json_kv_int(f, "runtime_e7_type", (int) disc_get_hlds_e7_type(d), true);
322 friidump_json_kv_u32_hex(f, "runtime_cache_base", disc_get_hlds_e7_cache_base(d), true);
323 friidump_json_kv_int(f, "runtime_mem_windows", (int) disc_get_hlds_e7_mem_blocks(d), true);
324 friidump_json_kv_int(f, "preferred_method", disc_get_hlds_e7_preferred_method(d), true);
325 friidump_json_kv_int(f, "selected_method", (int) disc_get_method(d), true);
326 friidump_json_kv_int(f, "selected_command", (int) disc_get_command(d), true);
327 friidump_json_kv_bool(f, "live_safe_from_static_only", false, true);
328 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);
329 friidump_json_kv_string(f, "notes", disc_get_hlds_e7_notes(d), false);
336 static void friidump_retarget_log_from_options(void) {
337 const char *target = friidump_requested_output_target();
339 xbox_ref_log_retarget(target);
340 } else if (options.device && options.device[0]) {
341 xbox_ref_log_open_for_target(NULL, friidump_drive_letter_from_device(options.device));
346 void progress_for_guis (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) {
348 double elapsed, mb_done, mb_done_real, mb_hour, seconds_left;
355 gettimeofday (&(stats -> start_time), NULL);
356 stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024;
357 stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024;
358 stats -> sectors_skipped = sectors_done;
360 perc = (int) (100.0 * sectors_done / total_sectors);
361 gettimeofday (&now, NULL);
362 elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec);
363 mb_done = (double) sectors_done * 2064 / 1024 / 1024;
364 mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024;
365 mb_hour = mb_done_real / elapsed * 60 * 60;
366 seconds_left = stats -> mb_total_real / mb_hour * 60 * 60;
367 eta = (time_t) ((stats -> start_time).tv_sec + seconds_left);
368 if (localtime_r (&eta, &etatm))
369 strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm);
371 sprintf (buf, "N/A");
373 /* This is the only thing we print to stdout, so that other programs can easily capture and parse our output */
374 fprintf (stdout, "%d%%|%u/%u sectors|%.2lf/%.0lf MB|%.0lf/%.0lf seconds|%.2lf MB/h|%s\n",
375 perc, sectors_done, total_sectors, mb_done, stats -> mb_total, elapsed, seconds_left, mb_hour, buf);
379 /* Save return time, in case this will be the last call */
380 gettimeofday (&(stats -> end_time), NULL);
386 void progress (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) {
388 double elapsed, mb_done, mb_done_real, mb_hour, seconds_left;
395 gettimeofday (&(stats -> start_time), NULL);
396 stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024;
397 stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024;
398 stats -> sectors_skipped = sectors_done;
400 perc = (int) (100.0 * sectors_done / total_sectors);
401 gettimeofday (&now, NULL);
402 elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec);
403 mb_done = (double) sectors_done * 2064 / 1024 / 1024;
404 mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024;
405 mb_hour = mb_done_real / elapsed * 60 * 60;
406 seconds_left = stats -> mb_total_real / mb_hour * 60 * 60;
407 eta = (time_t) ((stats -> start_time).tv_sec + seconds_left);
408 if (localtime_r (&eta, &etatm))
409 strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm);
411 sprintf (buf, "N/A");
413 fprintf (stdout, "\r%3d%% ", perc);
414 fprintf (stdout, "|");
415 for (i = 0; i < 100 / 3; i++) {
417 fprintf (stdout, "*");
419 fprintf (stdout, "-");
421 fprintf (stdout, "| ");
422 fprintf (stdout, "%.2lf MB/h, ETA: %s", mb_hour, buf);
426 if (sectors_done == total_sectors)
429 /* Save return time, in case this will be the last call */
430 gettimeofday (&(stats -> end_time), NULL);
437 void welcome (void) {
440 "FriiDump " PACKAGE_VERSION " - Copyright (C) 2007 Arep\n"
441 "This software comes with ABSOLUTELY NO WARRANTY.\n"
442 "This is free software, and you are welcome to redistribute it\n"
443 "under certain conditions; see COPYING for details.\n"
445 "Official support forum: http://wii.console-tribe.com\n"
447 "Forum for this UNOFFICIAL VERSION: http://forum.redump.org\n"
458 * |-------------------------------------------------------------------------------|
460 fprintf (stderr, "\n"
461 "Available command line options:\n"
463 " -h, --help Show this help\n"
464 " -a, --autodump Dump the disc to an ISO file with an\n"
465 " automatically-generated name, resuming the dump\n"
467 " -g, --gui Use more verbose output that can be easily\n"
468 " parsed by a GUI frontend\n"
469 " -d, --device <device> Dump disc from device <device>\n"
470 " -p, --stop Instruct device to stop disc rotation\n"
471 " -D, --dvd Force standard DVD-ROM mode for any drive\n"
472 " (equivalent to -T 3; uses FriiDump original\n"
473 " DVD/raw/ISO paths, not Xbox mode)\n"
474 " -c, --command <nr> Force memory dump command:\n"
475 " 0 - vanilla 2064\n"
476 " 1 - vanilla 2384\n"
480 " -x, --speed <x> Set streaming speed (1, 24, 32, 64, etc.,\n"
481 " where 1 = 150 KiB/s and so on)\n"
482 " -T, --type <nr> Force disc type:\n"
487 " 4 - Xbox/XGD 2048-byte-sector mode\n"
488 " Native profiles: GDR-8050L and GDR-3120L.\n"
489 " Other drives keep normal FriiDump behavior\n"
490 " unless Xbox mode is explicitly forced.\n"
491 " -S, --size <sectors> Force disc size\n"
492 " -r, --raw <file> Output to file <file> in raw format (2064-byte\n"
494 " -i, --iso[=<file>] Output to file <file> in ISO format (2048-byte\n"
495 " sectors). For Xbox/GDR-8050L, omitting <file>\n"
496 " derives Title[MediaID].iso from the XBE/DMI;\n"
497 " providing <file> is an explicit override. For\n"
498 " Xbox/XGD this reconstructs the redump-style\n"
499 " XGD1 layout and writes .pfi.bin, .dmi.bin, and\n"
500 " .redump.json metadata when possible\n"
501 " -X, --xiso[=<file>] Output Xbox/XGD game partition as XISO (.xiso).\n"
502 " For Xbox/GDR-8050L, omitting <file> derives\n"
503 " Title[MediaID].xiso from the XBE/DMI; providing\n"
504 " <file> is an explicit override. Attempts to read\n"
505 " the 32-sector game lead-in from drive-readable\n"
506 " sectors and zero-fills only unreadable sectors.\n"
507 " -u, --unscramble <file> Convert (unscramble) raw image contained in\n"
508 " <file> to ISO format\n"
509 " -H, --nohash Do not compute CRC32/MD5/SHA-1/SHA-256 hashes\n"
510 " for generated files\n"
511 " -s, --resume Resume partial dump\n"
512 " - General -----------------------------------\n"
513 " -0, --method0[=<req>,<exp>] Use dumping method 0 (Optional argument\n"
514 " specifies how many sectors to request from disc\n"
515 " and read from cache at a time. Values should be\n"
516 " separated with a comma. Default 16,16)\n"
517 " - Non-Streaming -----------------------------\n"
518 " -1, --method1[=<req>,<exp>] Use dumping method 1 (Default 16,16)\n"
519 " -2, --method2[=<req>,<exp>] Use dumping method 2 (Default 16,16)\n"
520 " -3, --method3[=<req>,<exp>] Use dumping method 3 (Default 16,16)\n"
521 " - Streaming ---------------------------------\n"
522 " -4, --method4[=<req>,<exp>] Use dumping method 4 (Default 27,27)\n"
523 " -5, --method5[=<req>,<exp>] Use dumping method 5 (Default 27,27)\n"
524 " -6, --method6[=<req>,<exp>] Use dumping method 6 (Default 27,27)\n"
525 " - Hitachi -----------------------------------\n"
526 " -7, --method7 Use dumping method 7 (Read and dump 5 blocks\n"
527 " at a time, using streaming read)\n"
528 " -8, --method8 Use dumping method 8 (Read and dump 5 blocks\n"
529 " at a time, using streaming read, using DMA)\n"
530 " -9, --method9 Use dumping method 9 (Read and dump 5 blocks\n"
531 " at a time, using streaming read, using DMA and\n"
532 " some speed tricks)\n"
533 " --hlds-e7-scan Probe HLDS HIT 0xE7 cache/memdump bases only;\n"
534 " writes JSON and does not crack seeds or dump data\n"
535 " --hlds-e7-subcmd-sweep Probe HIT 0xE7 subcommands/address candidates only;\n"
536 " writes JSON and does not crack seeds or dump data\n"
537 " --hlds-e7-memrange-sweep Sweep wider HIT 0xE7 subcmd 0x01 address ranges;\n"
538 " writes JSON and does not crack seeds or dump data\n"
539 " --scan-log <file> JSON output path for --hlds-e7-scan\n"
540 " --scan-dump-prefix <prefix> Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n"
541 " --hlds-profile-report <file> Write selected HLDS profile/evidence JSON\n"
542 " -A, --allmethods Try all known command/method combinations until\n"
543 " one works. Reopens the drive for each command so\n"
544 " command-specific vendor handlers are rebound.\n"
546 " -n, --donottunscramble Do not try unscrambling to check EDC. Only\n"
547 " useful for testing the raw performance of the\n"
548 " different methods\n"
549 " -f, --donottflush Do not call fflush() after every fwrite()\n"
557 bool optparse (int argc, char **argv) {
561 int option_index = 0;
562 static struct option long_options[] = {
563 {"help", 0, 0, 'h'}, //0 - no_argument
564 {"autodump", 0, 0, 'a'},
566 {"device", 1, 0, 'd'}, //1 - required_argument
570 {"unscramble", 1, 0, 'u'},
571 {"nohash", 0, 0, 'H'},
572 {"resume", 0, 0, 's'},
573 {"method0", 2, 0, '0'}, //2 - optional_argument
574 {"method1", 2, 0, '1'},
575 {"method2", 2, 0, '2'},
576 {"method3", 2, 0, '3'},
577 {"method4", 2, 0, '4'},
578 {"method5", 2, 0, '5'},
579 {"method6", 2, 0, '6'},
580 {"method7", 0, 0, '7'},
581 {"method8", 0, 0, '8'},
582 {"method9", 0, 0, '9'},
585 {"command", 1, 0, 'c'},
586 {"startsector", 1, 0, 't'},
588 {"speed", 1, 0, 'x'},
590 {"allmethods", 0, 0, 'A'},
591 {"hlds-e7-scan", 0, 0, 1000},
592 {"hlds-e7-subcmd-sweep", 0, 0, 1003},
593 {"hlds-e7-memrange-sweep", 0, 0, 1004},
594 {"scan-log", 1, 0, 1001},
595 {"scan-dump-prefix", 1, 0, 1002},
596 {"hlds-profile-report", 1, 0, 1005},
598 /* We don't want newbies to generate and put into circulation bad dumps, so this options are disabled for releases */
599 {"donottunscramble", 0, 0, 'n'},
600 {"donottflush", 0, 0, 'f'},
610 /* Init options to default values */
611 options.device = NULL;
612 options.autodump = false;
614 options.raw_in = NULL;
615 options.raw_out = NULL;
616 options.iso_out = NULL;
617 options.xiso_out = NULL;
618 options.iso_requested = false;
619 options.xiso_requested = false;
620 options.xbox_filename_override = false;
621 options.xbox_auto_filename = false;
622 options.no_hashing = false;
623 options.resume = false;
624 options.dump_method = -1;
625 options.command = -1;
626 options.start_sector = -1;
627 options.sectors_no = -1;
629 options.disctype = -1;
630 options.sec_disc = -1;
631 options.sec_mem = -1;
632 options.no_unscrambling = false;
633 options.no_flushing = false;
634 options.stop_unit = false;
635 options.allmethods = false;
636 options.hlds_e7_scan = false;
637 options.hlds_e7_subcmd_sweep = false;
638 options.hlds_e7_memrange_sweep = false;
639 options.hlds_e7_scan_log = NULL;
640 options.hlds_e7_scan_dump_prefix = NULL;
641 options.hlds_profile_report = NULL;
645 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);
647 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);
656 options.stop_unit = true;
659 options.autodump = true;
660 options.resume = true;
666 my_strdup (options.device, optarg);
669 my_strdup (options.raw_out, optarg);
672 options.iso_requested = true;
673 /* Preserve the old `-i file.iso` syntax even though Xbox now also
674 * supports bare `-i` for XBE/DMI-derived names. */
675 if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-')
676 optarg = argv[optind++];
678 my_strdup (options.iso_out, optarg);
679 options.xbox_filename_override = true;
681 options.xbox_auto_filename = true;
685 options.xiso_requested = true;
686 /* Preserve the old `-X file.xiso` syntax while allowing bare `-X`. */
687 if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-')
688 optarg = argv[optind++];
690 my_strdup (options.xiso_out, optarg);
691 options.xbox_filename_override = true;
693 options.xbox_auto_filename = true;
697 my_strdup (options.raw_in, optarg);
700 options.no_hashing = true;
703 options.resume = true;
712 options.dump_method = c - '0';
714 result = strtok(optarg, ",");
715 result = strtok(NULL, ",");
716 options.sec_disc = atol(strpbrk(optarg,"1234567890"));
717 if (result) options.sec_mem = atol(result);
727 options.dump_method = c - '0';
730 options.disctype = DISC_TYPE_DVD;
731 unscrambler_set_disctype (DISC_TYPE_DVD);
734 options.command = atol (optarg);
735 if (options.command > 4) {
741 options.start_sector = atol (optarg);
744 options.sectors_no = atol (optarg);
747 options.speed = atol (optarg);
750 options.disctype = atol (optarg);
751 if (options.disctype > 4) {
755 if (options.disctype <= DISC_TYPE_DVD)
756 unscrambler_set_disctype (options.disctype);
759 options.allmethods = true;
760 options.resume = true;
764 options.no_unscrambling = true;
767 options.no_flushing = true;
771 options.hlds_e7_scan = true;
774 options.hlds_e7_subcmd_sweep = true;
777 options.hlds_e7_memrange_sweep = true;
780 my_strdup (options.hlds_e7_scan_log, optarg);
783 my_strdup (options.hlds_e7_scan_dump_prefix, optarg);
786 my_strdup (options.hlds_profile_report, optarg);
791 // fprintf (stderr, "?? getopt returned character code 0%o ??\n", c);
798 /* Command-line arguments remaining. Ignore them, warning the user. */
799 fprintf (stderr, "WARNING: Extra parameters ignored\n");
802 /* Sanity checks... */
804 if (!options.device && !options.raw_in) {
805 fprintf (stderr, "No operation specified. Please use the -d or -u options.\n");
806 } else if (options.raw_in && options.raw_out) {
808 "Are you sure you want to convert a raw image to another raw image? ;)\n"
809 "Take a look at the -i and -a options!\n"
811 } else if (options.autodump && (options.raw_out || options.iso_requested || options.xiso_requested)) {
812 fprintf (stderr, "The -r, -i and -X options cannot be used together with -a.\n");
813 } else if (options.xiso_requested && (options.raw_out || options.iso_requested)) {
814 fprintf (stderr, "The -X/--xiso option is a separate Xbox output mode and cannot be combined with -r or -i.\n");
816 /* Specified options seem to make sense */
823 int dologic (disc *d, progstats *stats) {
825 char *type, *game_id, *region, *maker_id, *maker, *version, *title, tmp[0x03E0 + 4 + 1];
826 bool drive_supported;
828 bool xbox_output_requested;
832 u_int32_t current_sector = 0;
834 xbox_forced = (options.disctype == DISC_TYPE_XBOX);
835 xbox_output_requested = xbox_forced || options.xiso_requested;
836 dump_attempted = false;
840 if (options.stop_unit) { //stop rotation, if requested
841 fprintf (stderr, "Issuing STOP command... %s\n", (disc_stop_unit (d, false)) ? "OK" : "Failed");
844 else disc_stop_unit(d, true); //else start rotation
846 drive_supported = disc_get_drive_support_status (d);
849 "Drive information:\n"
850 "----------------------------------------------------------------------\n"
851 "Drive model........: %s\n"
852 "Supported..........: %s\n", disc_get_drive_model_string (d), drive_supported ? "Yes" : "No"
855 if (xbox_output_requested && !disc_is_xbox_unlock_drive (d)) {
857 "Xbox/XGD output is limited to the supported Xbox unlock profiles "
858 "currently wired into this branch: GDR-8050L, GDR-3120L, "
859 "and known Samsung/Kreon-style vendor-unlock drives.\n"
860 "Refusing to fall back to FriiDump GC/Wii methods for Xbox mode on this drive.\n");
864 /* Xbox/XGD (-T 4 or -X) is a direct MMC/SCSI READ(10) path.
865 * Do not let the detected GC/Wii vendor memdump method (for example
866 * Hitachi command 2 / method 9 on GDR-8050L) drive sector reads. */
867 if (xbox_output_requested && options.dump_method == -1)
868 options.dump_method = 10;
870 init_range(d, options.sec_disc, options.sec_mem);
872 if (!(disc_set_read_method (d, options.dump_method)))
875 if (xbox_output_requested && disc_get_method(d) == 10) {
876 fprintf (stderr, "Command............: Xbox direct MMC/SCSI path\n");
877 fprintf (stderr, "Method.............: 10 (Xbox READ(10))\n");
879 if (options.command!=-1) fprintf (stderr,
880 "Command............: %d (forced)\n", disc_get_command(d));
881 else fprintf (stderr,
882 "Command............: %d\n", disc_get_command(d));
883 if (disc_get_def_method(d)!=disc_get_method(d)) fprintf (stderr,
884 "Method.............: %d (forced)\n", disc_get_method(d));
885 else fprintf (stderr,
886 "Method.............: %d\n", disc_get_method(d));
887 if (disc_get_hlds_e7_type (d) != 0) {
888 fprintf (stderr, "HLDS 0xE7 profile..: %s\n", disc_get_hlds_e7_profile_name (d));
889 fprintf (stderr, "HLDS support tier..: %s\n", disc_get_hlds_e7_support_tier (d));
890 fprintf (stderr, "HLDS family........: %s\n", disc_get_hlds_e7_family (d));
891 fprintf (stderr, "HLDS E7 tokens.....: %s\n", disc_get_hlds_e7_tokens (d));
892 fprintf (stderr, "HLDS Stage5B row...: %s\n", disc_get_hlds_e7_record_id (d));
893 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));
894 fprintf (stderr, "Cache base.........: 0x%08x\n", disc_get_hlds_e7_cache_base (d));
895 fprintf (stderr, "Memory windows.....: %u\n", disc_get_hlds_e7_mem_blocks (d));
896 if (disc_get_hlds_e7_notes (d) && disc_get_hlds_e7_notes (d)[0])
897 fprintf (stderr, "HLDS notes.........: %s\n", disc_get_hlds_e7_notes (d));
900 if (options.hlds_profile_report) {
901 if (friidump_write_hlds_profile_report(d, options.hlds_profile_report))
902 fprintf(stderr, "HLDS profile report: %s\n", options.hlds_profile_report);
904 options.dump_method=disc_get_method(d);
905 friidump_summary_begin(d);
906 if ((options.dump_method==0)
907 || (options.dump_method==1) || (options.dump_method==2) || (options.dump_method==3)
908 || (options.dump_method==4) || (options.dump_method==5) || (options.dump_method==6)
911 "Requested sectors..: %d\n", disc_get_sec_disc(d));
913 "Expected sectors...: %d\n", disc_get_sec_mem(d));
916 fprintf (stderr, "\nPress Ctrl+C at any time to terminate\n");
918 //set speed for 1st time
919 if (options.speed != -1) disc_set_speed(d, options.speed * 177);
920 if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177);
921 // disc_set_speed(d, 0xffff);
923 /* Windows may attach filesystem/autoplay polling to odd GC/Wii discs and
924 * steal the drive during the HLDS 0xE7 seed phase, especially on Type1
925 * GCC-4160N/GCC-4240N. Reuse the same volume guard mechanism that the
926 * Xbox path already uses, but apply it before disc_init()/seed reads for
927 * all HLDS 0xE7 GC/Wii profiles. Failure is warning-only because Explorer
928 * may already have a transient handle; the read path itself remains the
930 if (!xbox_output_requested && disc_get_hlds_e7_type (d) != 0) {
931 if (disc_get_hlds_e7_type (d) == 44 || disc_get_hlds_e7_type (d) == 45) {
933 "\nGDR-8050L modified-firmware warning:\n"
934 " This GC/Wii 0xE7 path assumes a cross-flashed or modified GDR-8050L firmware with 0xE7 memdump support added.\n"
935 " Stock GDR-8050L firmware is still supported for Xbox ripping, but it is not expected to dump GC/Wii discs through this path.\n");
938 "\nWindows AutoPlay warning:\n"
939 " HLDS 0xE7 GC/Wii seed reads are sensitive to Windows polling.\n"
940 " Disable AutoPlay for this drive/media and close File Explorer or any \"insert a disc\" dialogs before dumping.\n"
941 " FriiDump will try to lock the volume, but AutoPlay can still interfere before or during seed retrieval.\n");
942 fprintf (stderr, "Applying Windows volume lock guard for HLDS 0xE7 seed reads... ");
943 disc_refresh_volume (d);
944 if (disc_lock_volume (d) < 0)
945 fprintf (stderr, "Warning: failed; disable AutoPlay, close File Explorer/AutoPlay dialogs for this drive, then rerun.\n");
947 fprintf (stderr, "OK\n");
950 if (options.hlds_e7_scan) {
951 out = disc_hlds_e7_scan (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
952 fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 scan... ");
953 fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
957 if (options.hlds_e7_subcmd_sweep) {
958 out = disc_hlds_e7_subcmd_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
959 fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 subcommand sweep... ");
960 fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
964 if (options.hlds_e7_memrange_sweep) {
965 out = disc_hlds_e7_memrange_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix);
966 fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 memory-range sweep... ");
967 fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed");
972 int media_rc, media_sense_key, media_asc, media_ascq;
973 fprintf (stderr, "\nChecking for ready media before disc seed retrieval... ");
974 media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq);
977 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);
979 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);
982 fprintf (stderr, "OK\n");
984 time_t seed_start, seed_end;
986 char seed_elapsed_buf[32];
988 if (xbox_output_requested)
989 fprintf (stderr, "\nInitializing Xbox/XGD disc state... ");
991 fprintf (stderr, "\nRetrieving disc seeds, this might take a while... ");
993 seed_start = time(NULL);
994 if (!disc_init (d, options.disctype, options.sectors_no)) {
995 seed_end = time(NULL);
996 seed_elapsed = difftime(seed_end, seed_start);
997 friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
998 if (!xbox_output_requested)
999 fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
1000 if (g_validation_summary.active) {
1001 g_validation_summary.have_seed_duration = true;
1002 g_validation_summary.seed_duration = seed_elapsed;
1004 fprintf (stderr, "Failed\n");
1007 seed_end = time(NULL);
1008 seed_elapsed = difftime(seed_end, seed_start);
1009 friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf));
1010 if (!xbox_output_requested)
1011 fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf);
1012 fprintf (stderr, "OK\n");
1013 if (g_validation_summary.active) {
1014 g_validation_summary.seed_ok = true;
1015 g_validation_summary.have_seed_duration = true;
1016 g_validation_summary.seed_duration = seed_elapsed;
1018 disc_get_type (d, &type_id, &type);
1019 disc_get_gameid (d, &game_id);
1020 disc_get_region (d, NULL, ®ion);
1021 disc_get_maker (d, &maker_id, &maker);
1022 disc_get_version (d, NULL, &version);
1023 disc_get_title (d, &title);
1024 if (g_validation_summary.active) {
1025 friidump_summary_copy(g_validation_summary.disc_type, sizeof(g_validation_summary.disc_type), type);
1026 friidump_summary_copy(g_validation_summary.game_id, sizeof(g_validation_summary.game_id), game_id);
1027 friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), title);
1028 g_validation_summary.sectors = disc_get_sectors_no(d);
1032 "Disc information:\n"
1033 "----------------------------------------------------------------------\n");
1035 if (options.disctype!=-1) fprintf (stderr,
1036 "Disc type..........: %s (forced)\n", type);
1037 else fprintf (stderr,
1038 "Disc type..........: %s\n", type);
1039 if (options.sectors_no!=-1) fprintf (stderr,
1040 "Disc size..........: %d (forced)\n", disc_get_sectors_no(d));
1041 else fprintf (stderr,
1042 "Disc size..........: %d\n", disc_get_sectors_no(d));
1044 if (disc_get_layerbreak(d)>0 && type_id==DISC_TYPE_DVD) fprintf (stderr,
1045 "Layer break........: %d\n", disc_get_layerbreak(d));
1047 if ((type_id==DISC_TYPE_GAMECUBE) || (type_id==DISC_TYPE_WII) || (type_id==DISC_TYPE_WII_DL)) fprintf (stderr,
1048 "Game ID............: %s\n"
1049 "Region.............: %s\n"
1050 "Maker..............: %s - %s\n"
1051 "Version............: %s\n"
1052 "Game title.........: %s\n", game_id, region, maker_id, maker, version, title
1055 if (type_id == DISC_TYPE_WII || type_id == DISC_TYPE_WII_DL)
1056 fprintf (stderr, "Contains update....: %s\n" , disc_get_update (d) ? "Yes" : "No");
1057 fprintf (stderr, "\n");
1059 disc_set_unscrambling (d, !options.no_unscrambling);
1061 if (type_id <= DISC_TYPE_DVD)
1062 unscrambler_set_disctype (type_id);
1064 if (options.autodump) {
1065 snprintf (tmp, sizeof (tmp), "%s.iso", title);
1066 my_strdup (options.iso_out, tmp);
1067 options.iso_requested = true;
1070 /* Xbox/GDR-8050L supports default XBE/DMI-derived names.
1071 * Use an empty-string placeholder so the copied reference path can
1072 * derive Title[MediaID].iso/.xiso after the first unlock/XBE probe. */
1073 if (type_id == DISC_TYPE_XBOX && options.iso_requested && !options.iso_out)
1074 my_strdup (options.iso_out, "");
1075 if (type_id == DISC_TYPE_XBOX && options.xiso_requested && !options.xiso_out)
1076 my_strdup (options.xiso_out, "");
1078 //set speed 2nd time after rotation is started and some sectors read
1079 if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177);
1080 if (options.speed != -1) disc_set_speed(d, options.speed * 177);
1082 /* If at least an output file was specified, proceed dumping, otherwise stop here */
1083 if (options.xiso_requested) {
1084 if (type_id != DISC_TYPE_XBOX) {
1085 fprintf (stderr, "Xbox XISO output requires Xbox/XGD disc type. Use -T 4 or an Xbox-capable drive/disc.\n");
1088 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 : "");
1090 dmp = dumper_new (d);
1091 dumper_set_hashing (dmp, !options.no_hashing);
1092 dumper_set_flushing (dmp, !options.no_flushing);
1094 if (!dumper_set_xiso_output_file (dmp, options.xiso_out, options.resume)) {
1095 fprintf (stderr, "Cannot setup Xbox XISO output file\n");
1096 } else if (!dumper_prepare_xiso (dmp)) {
1097 fprintf (stderr, "Cannot prepare Xbox XISO dumper");
1100 dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats);
1102 dumper_set_progress_callback (dmp, (progress_func) progress, stats);
1104 dump_attempted = true;
1105 if (g_validation_summary.active)
1106 g_validation_summary.dump_attempted = true;
1107 if (dumper_dump_xiso (dmp, ¤t_sector)) {
1108 fprintf (stderr, "Xbox XISO dump completed successfully!\n");
1109 if (!options.no_hashing && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d)))
1111 "XISO image hashes:\n"
1116 dumper_get_xiso_crc32 (dmp), dumper_get_xiso_md5 (dmp),
1117 dumper_get_xiso_sha1 (dmp), dumper_get_xiso_sha2 (dmp)
1120 if (g_validation_summary.active)
1121 g_validation_summary.dump_ok = true;
1123 fprintf (stderr, "\nXbox XISO dump failed at output sector: %u\n", current_sector);
1125 if (g_validation_summary.active) {
1126 g_validation_summary.dump_ok = false;
1127 g_validation_summary.fail_sector = current_sector;
1132 dmp = dumper_destroy (dmp);
1134 } else if (options.raw_out || options.iso_requested) {
1135 if (type_id == DISC_TYPE_XBOX && options.raw_out)
1136 fprintf (stderr, "Xbox/XGD output does not support -r/raw together with the redump-style ISO path. Use -i or -X.\n");
1137 else if (options.raw_out)
1138 fprintf (stderr, "Writing to file \"%s\" in raw format\n", options.raw_out);
1139 if (options.iso_out) {
1140 if (type_id == DISC_TYPE_XBOX)
1141 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 : "");
1143 fprintf (stderr, "Writing to file \"%s\" in ISO format\n", options.iso_out);
1145 fprintf (stderr, "\n");
1147 dmp = dumper_new (d);
1149 dumper_set_hashing (dmp, !options.no_hashing);
1150 dumper_set_flushing (dmp, !options.no_flushing);
1152 if (!dumper_set_raw_output_file (dmp, options.raw_out, options.resume)) {
1153 fprintf (stderr, "Cannot setup raw output file\n");
1154 } else if (!dumper_set_iso_output_file (dmp, options.iso_out, options.resume)) {
1155 fprintf (stderr, "Cannot setup ISO output file\n");
1156 } else if (!dumper_prepare (dmp)) {
1157 fprintf (stderr, "Cannot prepare dumper");
1159 // fprintf (stderr, "Starting dump process from sector %u...\n", dmp -> start_sector);
1160 // opdd.start_sector = options.start_sector;
1163 dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats);
1165 dumper_set_progress_callback (dmp, (progress_func) progress, stats);
1167 dump_attempted = true;
1168 if (g_validation_summary.active)
1169 g_validation_summary.dump_attempted = true;
1170 if (dumper_dump (dmp, ¤t_sector)) {
1171 fprintf (stderr, "Dump completed successfully!\n");
1172 if (!options.no_hashing && options.raw_out)
1174 "Raw image hashes:\n"
1180 /*"ED2K....: %s\n"*/,
1181 dumper_get_raw_crc32 (dmp), /*dumper_get_raw_md4 (dmp),*/ dumper_get_raw_md5 (dmp),
1182 dumper_get_raw_sha1 (dmp), dumper_get_raw_sha2 (dmp)/*, dumper_get_raw_ed2k (dmp)*/
1184 if (!options.no_hashing && options.iso_out && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d)))
1186 "ISO image hashes:\n"
1192 /*"ED2K....: %s\n"*/,
1193 dumper_get_iso_crc32 (dmp), /*dumper_get_iso_md4 (dmp),*/ dumper_get_iso_md5 (dmp),
1194 dumper_get_iso_sha1 (dmp), dumper_get_iso_sha2 (dmp)/*, dumper_get_iso_ed2k (dmp)*/
1198 if (g_validation_summary.active)
1199 g_validation_summary.dump_ok = true;
1201 if (g_validation_summary.active) {
1202 g_validation_summary.dump_ok = false;
1203 g_validation_summary.fail_sector = current_sector;
1205 if (current_sector == 0xFFFFFFFFU)
1206 fprintf (stderr, "\nXbox reference dumper failed; see the Xbox-specific message above.\n");
1208 fprintf (stderr, "\nDump failed at sectors: %u..%u\n", current_sector, current_sector+15);
1210 //disc_stop_unit (d, 0);
1214 dmp = dumper_destroy (dmp);
1216 fprintf (stderr, "No output file for dumping specified, please take a look at the -i, -r, -X and -a options\n");
1220 if (dump_attempted) {
1221 bool stop_ok = disc_stop_unit (d, false);
1222 fprintf (stderr, "\nIssuing STOP UNIT / spin-down after dump attempt... %s\n", stop_ok ? "OK" : "Failed");
1223 if (g_validation_summary.active) {
1224 g_validation_summary.stop_attempted = true;
1225 g_validation_summary.stop_ok = stop_ok;
1233 static int try_all_methods (progstats *stats) {
1234 u_int32_t saved_command;
1241 saved_command = options.command;
1242 saved_method = options.dump_method;
1245 fprintf (stderr, "Trying all command/method combinations... This will take a LOOOONG time and generate an insanely long console output :p\n");
1247 for (command = 0; command <= 4 && !out; command++) {
1248 for (method = 0; method <= 10 && !out; method++) {
1249 options.command = command;
1250 options.dump_method = method;
1251 memset (stats, 0, sizeof (*stats));
1253 fprintf (stderr, "\nTrying with command %u, method %d\n", command, method);
1254 fprintf (stderr, "Initializing DVD drive... ");
1256 d = disc_new (options.device, options.command);
1258 fprintf (stderr, "Failed\n");
1261 "Probably you do not have access to the DVD device. Ask the administrator\n"
1262 "to add you to the proper group, or use 'sudo'.\n"
1268 fprintf (stderr, "OK\n");
1269 out = dologic (d, stats);
1270 d = disc_destroy (d);
1273 fprintf (stderr, "Command %u and method %d combination worked!\n", command, method);
1278 options.command = saved_command;
1279 options.dump_method = saved_method;
1285 int main (int argc, char *argv[]) {
1292 unscrambler_progress_func pfunc;
1293 u_int32_t current_sector;
1295 /* First of all... */
1297 xbox_ref_log_open_for_target(NULL, 0);
1301 memset (&stats, 0, sizeof (stats));
1305 if (optparse (argc, argv)) {
1306 friidump_retarget_log_from_options();
1307 if (options.device) {
1308 if (options.allmethods) {
1309 out = try_all_methods (&stats);
1311 /* Dump DVD to file */
1312 fprintf (stderr, "Initializing DVD drive... ");
1314 if (!(d = disc_new (options.device, options.command))) {
1315 fprintf (stderr, "Failed\n");
1318 "Probably you do not have access to the DVD device. Ask the administrator\n"
1319 "to add you to the proper group, or use 'sudo'.\n"
1323 fprintf (stderr, "OK\n");
1324 out = dologic (d, &stats);
1325 d = disc_destroy (d);
1328 } else if (options.raw_in) {
1329 /* Convert raw image to ISO format */
1330 u = unscrambler_new ();
1333 pfunc = (unscrambler_progress_func) progress_for_guis;
1335 pfunc = (unscrambler_progress_func) progress;
1337 if ((out = unscrambler_unscramble_file (u, options.raw_in, options.iso_out, pfunc, &stats, ¤t_sector)))
1338 fprintf (stderr, "Unscrambling completed successfully!\n");
1340 fprintf (stderr, "\nUnscrambling failed at sectors: %u..%u\n", current_sector, current_sector+15);
1342 u = unscrambler_destroy (u);
1348 duration = stats.end_time.tv_sec - stats.start_time.tv_sec;
1349 if (stats.end_time.tv_usec >= stats.start_time.tv_usec) {
1350 us = stats.end_time.tv_usec - stats.start_time.tv_usec;
1354 us = USECS_PER_SEC + stats.end_time.tv_usec - stats.start_time.tv_usec;
1356 duration += ((double) us / (double) USECS_PER_SEC);
1359 fprintf (stderr, "Operation took %.2f seconds\n", duration);
1360 friidump_print_validation_summary(duration, true);
1364 friidump_print_validation_summary(0.0, false);
1368 my_free (options.device);
1369 my_free (options.iso_out);
1370 my_free (options.xiso_out);
1371 my_free (options.raw_out);
1372 my_free (options.raw_in);
1373 my_free (options.hlds_profile_report);
1376 if (xbox_ref_log_path())
1377 fprintf (stderr, "Log file complete: %s\n", xbox_ref_log_path());
1378 xbox_ref_log_close();