]> FriiDump Source - friidump.git/blob - libfriidump/dumper.c
Document exact-release Linux hardware validation
[friidump.git] / libfriidump / dumper.c
1 /***************************************************************************
2  *   Copyright (C) 2007 by Arep                                            *
3  *   Support is provided through the forums at                             *
4  *   http://www.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 <inttypes.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <multihash.h>
28 #include "constants.h"
29 #include "disc.h"
30 #include "dumper.h"
31 #include "xbox_ref_bridge.h"
32 #include "xbox_portable.h"
33 #include "xbox_ref/xbox_ref_log.h"
34
35 #ifndef WIN32
36 #include <unistd.h>
37 #include <sys/types.h>
38 #include <sys/time.h>
39 #endif
40
41 struct dumper_s {
42         disc *dsk;
43         char *outfile_raw;
44         u_int32_t start_sector_raw;
45         FILE *fp_raw;
46         char *outfile_iso;
47         u_int32_t start_sector_iso;
48         FILE *fp_iso;
49         char *outfile_xiso;
50         u_int32_t start_sector_xiso;
51         FILE *fp_xiso;
52         u_int32_t start_sector;
53         bool hashing;
54         bool flushing;
55
56         multihash hash_raw;
57         multihash hash_iso;
58         multihash hash_xiso;
59         xbox_ref_dump_result xbox_ref_result;
60         
61         progress_func progress;
62         void *progress_data;
63         dumper_cancel_func cancel;
64         void *cancel_data;
65         bool cancelled;
66 };
67
68 #define DUMPER_READ_BLOCK_RETRIES 3
69
70 static bool dumper_cancel_requested (dumper *dmp) {
71         if (!dmp || !dmp -> cancel)
72                 return false;
73         if (!dmp -> cancel (dmp -> cancel_data))
74                 return false;
75         dmp -> cancelled = true;
76         return true;
77 }
78
79 static int dumper_xbox_ref_cancel_requested (void *cancel_data) {
80         return dumper_cancel_requested ((dumper *) cancel_data) ? 1 : 0;
81 }
82
83 static double xbox_portable_now_seconds (void) {
84         struct timeval now;
85         gettimeofday (&now, NULL);
86         return (double) now.tv_sec + ((double) now.tv_usec / 1000000.0);
87 }
88
89 static u_int64_t xbox_portable_file_size (const char *path) {
90         FILE *file;
91         my_off_t position;
92
93         if (!path || path[0] == '\0')
94                 return 0;
95         file = fopen (path, "rb");
96         if (!file)
97                 return 0;
98         if (my_fseek (file, 0, SEEK_END) != 0) {
99                 fclose (file);
100                 return 0;
101         }
102         position = my_ftell (file);
103         fclose (file);
104         if (position < 0)
105                 return 0;
106         return (u_int64_t) position;
107 }
108
109 static void xbox_portable_copy_string (char *destination, size_t destination_size, const char *source) {
110         if (!destination || destination_size == 0)
111                 return;
112         if (!source)
113                 source = "";
114         snprintf (destination, destination_size, "%s", source);
115 }
116
117 static void xbox_portable_result_begin (dumper *dmp, const char *path, char mode) {
118         if (!dmp)
119                 return;
120         xbox_ref_dump_result_init (&(dmp -> xbox_ref_result));
121         dmp -> xbox_ref_result.attempted = 1;
122         dmp -> xbox_ref_result.mode = mode;
123         xbox_portable_copy_string (dmp -> xbox_ref_result.output_path,
124                         sizeof (dmp -> xbox_ref_result.output_path), path);
125 }
126
127 static void xbox_portable_sanitize_filename_component (char *value) {
128         size_t read_index;
129         size_t write_index;
130         bool previous_space;
131
132         if (!value)
133                 return;
134
135         write_index = 0;
136         previous_space = true;
137         for (read_index = 0; value[read_index] != '\0'; ++read_index) {
138                 unsigned char c = (unsigned char) value[read_index];
139                 bool forbidden = c < 32 || c == '"' || c == '*' || c == '/' ||
140                         c == ':' || c == '<' || c == '>' || c == '?' || c == '\\' || c == '|';
141
142                 if (forbidden)
143                         continue;
144                 if (c == ' ') {
145                         if (previous_space)
146                                 continue;
147                         value[write_index++] = ' ';
148                         previous_space = true;
149                 } else {
150                         value[write_index++] = (char) c;
151                         previous_space = false;
152                 }
153         }
154
155         while (write_index > 0 && (value[write_index - 1] == ' ' ||
156                         value[write_index - 1] == '.'))
157                 --write_index;
158         value[write_index] = '\0';
159 }
160
161 static bool xbox_portable_resolve_output_path (dumper *dmp, bool xiso_mode) {
162         xbox_portable_metadata metadata;
163         char title[XBOX_PORTABLE_TITLE_MAX];
164         char path[XBOX_REF_RESULT_PATH_MAX];
165         char **target;
166         const char *extension;
167
168         if (!dmp)
169                 return false;
170
171         target = xiso_mode ? &(dmp -> outfile_xiso) : &(dmp -> outfile_iso);
172         if (!*target || (*target)[0] != '\0')
173                 return true;
174
175         xbox_ref_log_fprintf (stderr,
176                 "[XBOX-PORTABLE] Resolving the default output filename from the unlocked XBE/DMI view.\n");
177         disc_xbox_wait_ready (dmp -> dsk, 30000);
178         if (disc_xbox_unlock (dmp -> dsk) < 0)
179                 xbox_ref_log_fprintf (stderr,
180                         "[XBOX-PORTABLE][WARN] Filename preflight unlock returned failure; attempting metadata reads anyway.\n");
181         disc_xbox_refresh_volume (dmp -> dsk);
182         disc_xbox_wait_ready (dmp -> dsk, 30000);
183
184         xbox_portable_metadata_init (&metadata);
185         if (!xbox_portable_read_metadata_from_disc (dmp -> dsk, &metadata)) {
186                 error ("Could not derive an Xbox output filename because XBE/DMI metadata was unavailable; provide an explicit output path");
187                 return false;
188         }
189
190         xbox_portable_copy_string (title, sizeof (title),
191                 metadata.title[0] ? metadata.title : "Xbox DVD");
192         xbox_portable_sanitize_filename_component (title);
193         if (!title[0])
194                 xbox_portable_copy_string (title, sizeof (title), "Xbox DVD");
195         extension = xiso_mode ? ".xiso" : ".iso";
196
197         if (metadata.media_id[0])
198                 snprintf (path, sizeof (path), "%s[%s]%s", title, metadata.media_id, extension);
199         else
200                 snprintf (path, sizeof (path), "%s%s", title, extension);
201
202         my_free (*target);
203         my_strdup (*target, path);
204         xbox_ref_log_retarget (*target);
205         xbox_ref_log_fprintf (stderr,
206                 "[XBOX-PORTABLE] Derived output filename: %s\n", *target);
207         return true;
208 }
209
210 static void xbox_portable_result_capture_metadata (dumper *dmp) {
211         xbox_portable_metadata metadata;
212
213         if (!dmp || (dmp -> xbox_ref_result.title[0] &&
214                         dmp -> xbox_ref_result.have_game_region))
215                 return;
216
217         xbox_portable_metadata_init (&metadata);
218         if (!xbox_portable_read_metadata_from_disc (dmp -> dsk, &metadata)) {
219                 xbox_ref_log_fprintf (stderr,
220                         "[XBOX-PORTABLE][WARN] XBE title/region metadata was not available from the unlocked view.\n");
221                 return;
222         }
223
224         xbox_portable_copy_string (dmp -> xbox_ref_result.title,
225                         sizeof (dmp -> xbox_ref_result.title), metadata.title);
226         xbox_portable_copy_string (dmp -> xbox_ref_result.media_id,
227                         sizeof (dmp -> xbox_ref_result.media_id), metadata.media_id);
228         xbox_portable_copy_string (dmp -> xbox_ref_result.region,
229                         sizeof (dmp -> xbox_ref_result.region), metadata.region);
230         dmp -> xbox_ref_result.have_game_region = metadata.have_game_region;
231         dmp -> xbox_ref_result.game_region = metadata.game_region;
232
233         xbox_ref_log_fprintf (stderr,
234                 "[XBOX-PORTABLE] XBE metadata: title='%s', media_id='%s', region='%s', GameRegion=0x%08x.\n",
235                 dmp -> xbox_ref_result.title[0] ? dmp -> xbox_ref_result.title : "(unknown)",
236                 dmp -> xbox_ref_result.media_id[0] ? dmp -> xbox_ref_result.media_id : "(unknown)",
237                 dmp -> xbox_ref_result.region[0] ? dmp -> xbox_ref_result.region : "(unknown)",
238                 dmp -> xbox_ref_result.game_region);
239 }
240
241 static void xbox_portable_result_finish (dumper *dmp,
242                 bool success,
243                 u_int32_t expected_output_sectors,
244                 double started_seconds,
245                 bool xiso_mode) {
246         xbox_ref_dump_result *result;
247         const char *crc32;
248         const char *md5;
249         const char *sha1;
250         const char *sha256;
251
252         if (!dmp || !(dmp -> xbox_ref_result).attempted)
253                 return;
254
255         result = &(dmp -> xbox_ref_result);
256         result -> dump_success = success ? 1 : 0;
257         result -> cancelled = dmp -> cancelled ? 1 : 0;
258         result -> output_sectors = expected_output_sectors;
259         result -> output_size = xbox_portable_file_size (result -> output_path);
260         result -> completed_sectors = (u_int32_t) (result -> output_size / SECTOR_SIZE);
261         result -> elapsed_seconds = xbox_portable_now_seconds () - started_seconds;
262
263         if (!dmp -> hashing || result -> output_size == 0)
264                 return;
265
266         if (xiso_mode) {
267                 crc32 = dumper_get_xiso_crc32 (dmp);
268                 md5 = dumper_get_xiso_md5 (dmp);
269                 sha1 = dumper_get_xiso_sha1 (dmp);
270                 sha256 = dumper_get_xiso_sha2 (dmp);
271         } else {
272                 crc32 = dumper_get_iso_crc32 (dmp);
273                 md5 = dumper_get_iso_md5 (dmp);
274                 sha1 = dumper_get_iso_sha1 (dmp);
275                 sha256 = dumper_get_iso_sha2 (dmp);
276         }
277
278         if (!crc32 || !md5 || !sha1 || !sha256 ||
279                 !crc32[0] || !md5[0] || !sha1[0] || !sha256[0])
280                 return;
281
282         xbox_portable_copy_string (result -> crc32, sizeof (result -> crc32), crc32);
283         xbox_portable_copy_string (result -> md5, sizeof (result -> md5), md5);
284         xbox_portable_copy_string (result -> sha1, sizeof (result -> sha1), sha1);
285         xbox_portable_copy_string (result -> sha256, sizeof (result -> sha256), sha256);
286         result -> hashes_complete = 1;
287 }
288
289 static bool dumper_read_sector_with_retry (dumper *dmp, u_int32_t sector_no, u_int8_t **isobuf, u_int8_t **rawbuf) {
290         int attempt;
291         bool need_iso, need_raw;
292
293         if (!dmp || !dmp -> dsk)
294                 return false;
295
296         need_iso = dmp -> fp_iso != NULL;
297         need_raw = dmp -> fp_raw != NULL;
298
299         for (attempt = 0; attempt < DUMPER_READ_BLOCK_RETRIES; attempt++) {
300                 if (isobuf) *isobuf = NULL;
301                 if (rawbuf) *rawbuf = NULL;
302
303                 disc_read_sector (dmp -> dsk, sector_no, isobuf, rawbuf);
304                 if ((!need_iso || (isobuf && *isobuf)) && (!need_raw || (rawbuf && *rawbuf))) {
305                         if (attempt > 0)
306                                 warning ("Recovered sectors %u..%u after dump-level retry %d", sector_no, sector_no + SECTORS_PER_BLOCK - 1, attempt);
307                         return true;
308                 }
309
310                 if (attempt + 1 < DUMPER_READ_BLOCK_RETRIES)
311                         warning ("Dump-level read retry %d/%d for sectors %u..%u", attempt + 1, DUMPER_READ_BLOCK_RETRIES - 1, sector_no, sector_no + SECTORS_PER_BLOCK - 1);
312         }
313
314         return false;
315 }
316
317 #define XBOX_XISO_LEADIN_SECTORS 32
318 #define XBOX_XISO_STANDARD_GAME_LBA 32
319 #define XBOX_XISO_START_LBA_MAGIC 306112
320 #define XBOX_XISO_DUAL_LAYER_END_LBA 1913920
321
322 /* DiscImageCreator-compatible Original Xbox/XGD1 2048-byte-sector layout
323  * constants. DIC uses XBOX_SIZE=3820880, XBOX_LAYER_BREAK=1913776,
324  * DVD start PSN 0x30000, and Xbox start PSN 0x60600. The virtual redump
325  * layout below follows those constants: video L0, middle/padding, game
326  * partition, middle/padding, and video L1 tail.
327  */
328 #define XBOX_XGD1_DIC_DVD_START_PSN 0x30000
329 #define XBOX_XGD1_DIC_XBOX_START_PSN 0x60600
330 #define XBOX_XGD1_DIC_XBOX_SIZE 3820880
331 #define XBOX_XGD1_DIC_LAYER_BREAK 1913776
332 #define XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS 3431264
333 #define XBOX_XGD1_FULL_REDUMP_SECTORS XBOX_XGD1_DIC_XBOX_SIZE
334 #define XBOX_XGD1_GAME_OUTPUT_START_LBA (XBOX_XGD1_DIC_XBOX_START_PSN - XBOX_XGD1_DIC_DVD_START_PSN)
335 #define XBOX_XGD1_VIDEO_L0_SECTORS 6832
336 #define XBOX_XGD1_VIDEO_L1_SECTORS 160
337 #define XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA (XBOX_XGD1_FULL_REDUMP_SECTORS - XBOX_XGD1_VIDEO_L1_SECTORS)
338 #define XBOX_XGD1_GAME_SOURCE_SECTORS (XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS - XBOX_XISO_LEADIN_SECTORS)
339 #define XBOX_XGD1_LAYER_THRESHOLD 2000000
340 #define XBOX_METADATA_PATH_MAX 4096
341
342 static u_int32_t read_le32 (const u_int8_t *p) {
343         return ((u_int32_t) p[0]) | ((u_int32_t) p[1] << 8) | ((u_int32_t) p[2] << 16) | ((u_int32_t) p[3] << 24);
344 }
345
346 static bool xbox_probe_xdfs_volume (disc *d, u_int32_t lba, u_int32_t *root_lba, u_int32_t *volume_size_raw) {
347         u_int8_t sector[SECTOR_SIZE];
348
349         memset (sector, 0, sizeof (sector));
350         if (disc_xbox_read_10 (d, lba, 1, sector, sizeof (sector)) < 0)
351                 return false;
352
353         if (memcmp (sector, "MICROSOFT", 9) != 0)
354                 return false;
355
356         if (root_lba)
357                 *root_lba = read_le32 (sector + 0x14);
358         if (volume_size_raw)
359                 *volume_size_raw = read_le32 (sector + 0x1C);
360
361         return true;
362 }
363
364 static bool xbox_redump_layout_constants_match_dic (void) {
365         return XBOX_XGD1_FULL_REDUMP_SECTORS == XBOX_XGD1_DIC_XBOX_SIZE &&
366                XBOX_XGD1_DIC_LAYER_BREAK == 1913776 &&
367                XBOX_XGD1_GAME_OUTPUT_START_LBA == 0x30600 &&
368                XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA == 3820720 &&
369                XBOX_XGD1_VIDEO_L0_SECTORS +
370                (XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS) +
371                XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS +
372                (XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA -
373                 (XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS)) +
374                XBOX_XGD1_VIDEO_L1_SECTORS == XBOX_XGD1_FULL_REDUMP_SECTORS;
375 }
376
377
378 typedef struct {
379         bool has_pfi;
380         bool has_dmi;
381         bool pfi_written;
382         bool dmi_written;
383         u_int8_t pfi[2048];
384         u_int8_t dmi[2048];
385         u_int32_t locked_visible_sectors;
386         u_int32_t unlocked_visible_sectors;
387         u_int32_t output_sectors;
388         u_int32_t game_source_lba;
389         u_int32_t game_source_sectors;
390         u_int32_t game_leadin_read_sectors;
391         u_int32_t game_leadin_zero_sectors;
392         char pfi_path[XBOX_METADATA_PATH_MAX];
393         char dmi_path[XBOX_METADATA_PATH_MAX];
394         char json_path[XBOX_METADATA_PATH_MAX];
395 } xbox_redump_metadata;
396
397 static void xbox_make_related_path (const char *filename, const char *suffix, char *out, size_t out_size) {
398         const char *slash1, *slash2, *slash, *dot;
399         size_t suffix_len, base_len;
400
401         if (!out || out_size == 0)
402                 return;
403         out[0] = '\0';
404         if (!filename || !suffix)
405                 return;
406
407         suffix_len = strlen (suffix);
408         if (out_size <= suffix_len + 1)
409                 return;
410
411         slash1 = strrchr (filename, '/');
412         slash2 = strrchr (filename, '\\');
413         if (slash1 && slash2)
414                 slash = slash1 > slash2 ? slash1 : slash2;
415         else
416                 slash = slash1 ? slash1 : slash2;
417
418         dot = strrchr (filename, '.');
419         if (!dot || (slash && dot < slash))
420                 dot = filename + strlen (filename);
421
422         base_len = (size_t) (dot - filename);
423         if (base_len + suffix_len + 1 > out_size)
424                 base_len = out_size - suffix_len - 1;
425         memcpy (out, filename, base_len);
426         out[base_len] = '\0';
427         strncat (out, suffix, out_size - strlen (out) - 1);
428 }
429
430 static const char *xbox_path_leaf (const char *path) {
431         const char *slash1, *slash2;
432         if (!path)
433                 return "";
434         slash1 = strrchr (path, '/');
435         slash2 = strrchr (path, '\\');
436         if (slash1 && slash2)
437                 return (slash1 > slash2 ? slash1 : slash2) + 1;
438         if (slash1)
439                 return slash1 + 1;
440         if (slash2)
441                 return slash2 + 1;
442         return path;
443 }
444
445 static void xbox_json_string (FILE *f, const char *s) {
446         fputc ('"', f);
447         if (s) {
448                 while (*s) {
449                         unsigned char c = (unsigned char) *s++;
450                         if (c == '"' || c == '\\') {
451                                 fputc ('\\', f);
452                                 fputc (c, f);
453                         } else if (c == '\n') {
454                                 fputs ("\\n", f);
455                         } else if (c == '\r') {
456                                 fputs ("\\r", f);
457                         } else if (c == '\t') {
458                                 fputs ("\\t", f);
459                         } else if (c < 0x20) {
460                                 fprintf (f, "\\u%04x", c);
461                         } else {
462                                 fputc (c, f);
463                         }
464                 }
465         }
466         fputc ('"', f);
467 }
468
469 static bool xbox_write_binary_file (const char *path, const u_int8_t *data, size_t len) {
470         FILE *f;
471         if (!path || !data || len == 0)
472                 return false;
473         f = fopen (path, "wb");
474         if (!f)
475                 return false;
476         if (fwrite (data, 1, len, f) != len) {
477                 fclose (f);
478                 return false;
479         }
480         return fclose (f) == 0;
481 }
482
483 static void xbox_capture_pfi_dmi (disc *d, xbox_redump_metadata *meta) {
484         int rc;
485         if (!d || !meta)
486                 return;
487         if (!meta -> has_pfi) {
488                 rc = disc_xbox_read_dvd_structure (d, 0x00, 0x00, meta -> pfi, sizeof (meta -> pfi));
489                 meta -> has_pfi = rc >= 0;
490                 xbox_ref_log_fprintf (
491                         stderr,
492                         "[XBOX-DVD-STRUCTURE] format=0x00 name=PFI cdb_format_byte=7 result=%s\n",
493                         meta -> has_pfi ? "PASS" : "FAIL");
494         }
495         if (!meta -> has_dmi) {
496                 rc = disc_xbox_read_dvd_structure (d, 0x04, 0x00, meta -> dmi, sizeof (meta -> dmi));
497                 meta -> has_dmi = rc >= 0;
498                 xbox_ref_log_fprintf (
499                         stderr,
500                         "[XBOX-DVD-STRUCTURE] format=0x04 name=DMI cdb_format_byte=7 result=%s\n",
501                         meta -> has_dmi ? "PASS" : "FAIL");
502         }
503 }
504
505 static void xbox_write_redump_metadata (dumper *dmp, xbox_redump_metadata *meta) {
506         FILE *json;
507         const char *pfi_leaf;
508         const char *dmi_leaf;
509
510         if (!dmp || !meta || !dmp -> outfile_iso)
511                 return;
512
513         xbox_make_related_path (dmp -> outfile_iso, ".pfi.bin", meta -> pfi_path, sizeof (meta -> pfi_path));
514         xbox_make_related_path (dmp -> outfile_iso, ".dmi.bin", meta -> dmi_path, sizeof (meta -> dmi_path));
515         xbox_make_related_path (dmp -> outfile_iso, ".redump.json", meta -> json_path, sizeof (meta -> json_path));
516
517         if (meta -> has_pfi)
518                 meta -> pfi_written = xbox_write_binary_file (meta -> pfi_path, meta -> pfi, sizeof (meta -> pfi));
519         if (meta -> has_dmi)
520                 meta -> dmi_written = xbox_write_binary_file (meta -> dmi_path, meta -> dmi, sizeof (meta -> dmi));
521
522         json = fopen (meta -> json_path, "wb");
523         if (!json) {
524                 warning ("Could not write Xbox redump metadata JSON");
525                 return;
526         }
527
528         pfi_leaf = meta -> pfi_written ? xbox_path_leaf (meta -> pfi_path) : NULL;
529         dmi_leaf = meta -> dmi_written ? xbox_path_leaf (meta -> dmi_path) : NULL;
530
531         fprintf (json, "{\n");
532         fprintf (json, "  \"format\": \"friidump-xbox-redump\",\n");
533         fprintf (json, "  \"version\": 1,\n");
534         fprintf (json, "  \"image\": {\n");
535         fprintf (json, "    \"file\": "); xbox_json_string (json, xbox_path_leaf (dmp -> outfile_iso)); fprintf (json, ",\n");
536         fprintf (json, "    \"sector_size\": %u,\n", SECTOR_SIZE);
537         fprintf (json, "    \"sector_count\": %u,\n", meta -> output_sectors);
538         fprintf (json, "    \"byte_count\": %llu,\n", (unsigned long long) meta -> output_sectors * SECTOR_SIZE);
539         fprintf (json, "    \"hashes\": {\n");
540         fprintf (json, "      \"crc32\": "); xbox_json_string (json, dumper_get_iso_crc32 (dmp)); fprintf (json, ",\n");
541         fprintf (json, "      \"md5\": "); xbox_json_string (json, dumper_get_iso_md5 (dmp)); fprintf (json, ",\n");
542         fprintf (json, "      \"sha1\": "); xbox_json_string (json, dumper_get_iso_sha1 (dmp)); fprintf (json, ",\n");
543         fprintf (json, "      \"sha256\": "); xbox_json_string (json, dumper_get_iso_sha2 (dmp)); fprintf (json, "\n");
544         fprintf (json, "    }\n");
545         fprintf (json, "  },\n");
546         fprintf (json, "  \"drive\": {\n");
547         fprintf (json, "    \"model\": "); xbox_json_string (json, disc_get_drive_model_string (dmp -> dsk)); fprintf (json, ",\n");
548         fprintf (json, "    \"command\": %u,\n", disc_get_command (dmp -> dsk));
549         fprintf (json, "    \"method\": %u\n", disc_get_method (dmp -> dsk));
550         fprintf (json, "  },\n");
551         fprintf (json, "  \"layout\": {\n");
552         fprintf (json, "    \"layout_type\": \"original_xbox_xgd1_redump_style_2048\",\n");
553         fprintf (json, "    \"locked_visible_sector_count\": %u,\n", meta -> locked_visible_sectors);
554         fprintf (json, "    \"unlocked_visible_sector_count\": %u,\n", meta -> unlocked_visible_sectors);
555         fprintf (json, "    \"video_l0_start_lba\": 0,\n");
556         fprintf (json, "    \"video_l0_sector_count\": %u,\n", XBOX_XGD1_VIDEO_L0_SECTORS);
557         fprintf (json, "    \"pregame_padding_start_lba\": %u,\n", XBOX_XGD1_VIDEO_L0_SECTORS);
558         fprintf (json, "    \"pregame_padding_sector_count\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS);
559         fprintf (json, "    \"game_output_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA);
560         fprintf (json, "    \"game_leadin_sector_count\": %u,\n", XBOX_XISO_LEADIN_SECTORS);
561         fprintf (json, "    \"game_leadin_unlocked_source_start_lba\": %u,\n",
562                 (meta -> game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ?
563                 meta -> game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0);
564         fprintf (json, "    \"game_leadin_source_policy\": \"drive_read10_with_per_sector_zero_fallback\",\n");
565         fprintf (json, "    \"game_unlocked_source_start_lba\": %u,\n", meta -> game_source_lba);
566         fprintf (json, "    \"game_unlocked_source_sector_count\": %u,\n", meta -> game_source_sectors);
567         fprintf (json, "    \"postgame_padding_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS);
568         fprintf (json, "    \"postgame_padding_sector_count\": %u,\n", XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA - (XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS));
569         fprintf (json, "    \"video_l1_start_lba\": %u,\n", XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA);
570         fprintf (json, "    \"video_l1_sector_count\": %u,\n", XBOX_XGD1_VIDEO_L1_SECTORS);
571         fprintf (json, "    \"layer_break_lba\": %u\n", XBOX_XGD1_DIC_LAYER_BREAK);
572         fprintf (json, "  },\n");
573         fprintf (json, "  \"discimagecreator_reference\": {\n");
574         fprintf (json, "    \"xbox_size_sectors\": %u,\n", XBOX_XGD1_DIC_XBOX_SIZE);
575         fprintf (json, "    \"xbox_layer_break_lba\": %u,\n", XBOX_XGD1_DIC_LAYER_BREAK);
576         fprintf (json, "    \"dvd_start_psn\": %u,\n", XBOX_XGD1_DIC_DVD_START_PSN);
577         fprintf (json, "    \"xbox_start_psn\": %u,\n", XBOX_XGD1_DIC_XBOX_START_PSN);
578         fprintf (json, "    \"game_output_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA);
579         fprintf (json, "    \"layout_constants_match\": %s\n", xbox_redump_layout_constants_match_dic () ? "true" : "false");
580         fprintf (json, "  },\n");
581         fprintf (json, "  \"lead_in_capture\": {\n");
582         fprintf (json, "    \"game_leadin_attempted\": true,\n");
583         fprintf (json, "    \"unlocked_source_start_lba\": %u,\n",
584                 (meta -> game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ?
585                 meta -> game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0);
586         fprintf (json, "    \"sector_count\": %u,\n", XBOX_XISO_LEADIN_SECTORS);
587         fprintf (json, "    \"readable_sectors\": %u,\n", meta -> game_leadin_read_sectors);
588         fprintf (json, "    \"zero_filled_sectors\": %u,\n", meta -> game_leadin_zero_sectors);
589         fprintf (json, "    \"physical_geometry_evidence\": \"friidump_0.5.3.13_cache_aligned_raw_id_probe\"\n");
590         fprintf (json, "  },\n");
591         fprintf (json, "  \"dvd_structures\": {\n");
592         fprintf (json, "    \"pfi\": "); if (pfi_leaf) xbox_json_string (json, pfi_leaf); else fprintf (json, "null"); fprintf (json, ",\n");
593         fprintf (json, "    \"dmi\": "); if (dmi_leaf) xbox_json_string (json, dmi_leaf); else fprintf (json, "null"); fprintf (json, "\n");
594         fprintf (json, "  },\n");
595         fprintf (json, "  \"notes\": [\n");
596         fprintf (json, "    \"PFI and DMI are stored as separate binary READ DVD STRUCTURE captures because they are not READ(10) user-data sectors.\",\n");
597         fprintf (json, "    \"The 32-sector game lead-in is captured from unlocked source LBA 0..31; only unreadable lead-in sectors use zero-fill fallback.\",\n");
598         fprintf (json, "    \"Pregame and postgame padding retain zero-filled placeholder content; their physical locations are resolved but their inaccessible bytes are not.\"\n");
599         fprintf (json, "  ]\n");
600         fprintf (json, "}\n");
601         fclose (json);
602
603         if (meta -> pfi_written) {
604                 debug ("Wrote Xbox PFI: %s", meta -> pfi_path);
605         } else {
606                 warning ("Xbox PFI was not captured or could not be written");
607         }
608         if (meta -> dmi_written) {
609                 debug ("Wrote Xbox DMI: %s", meta -> dmi_path);
610         } else {
611                 warning ("Xbox DMI was not captured or could not be written");
612         }
613         debug ("Wrote Xbox redump metadata: %s", meta -> json_path);
614 }
615
616 static bool xbox_write_iso_sectors (dumper *dmp, const u_int8_t *buf, u_int32_t sectors, u_int32_t output_lba, u_int32_t total_sectors) {
617         if (dumper_cancel_requested (dmp))
618                 return false;
619         if (!dmp || !dmp -> fp_iso || !buf)
620                 return false;
621         clearerr (dmp -> fp_iso);
622         if (fwrite (buf, SECTOR_SIZE, sectors, dmp -> fp_iso) != sectors || ferror (dmp -> fp_iso)) {
623                 error ("fwrite() to Xbox redump ISO failed at output LBA %u", output_lba);
624                 return false;
625         }
626         if (dmp -> hashing)
627                 multihash_update (&(dmp -> hash_iso), (u_int8_t *) buf, (size_t) sectors * SECTOR_SIZE);
628         if (dmp -> flushing)
629                 fflush (dmp -> fp_iso);
630         if (dmp -> progress && ((output_lba % 320) == 0 || output_lba + sectors >= total_sectors))
631                 dmp -> progress (false, output_lba + sectors, total_sectors, dmp -> progress_data);
632         return true;
633 }
634
635 static bool xbox_write_zero_iso_range (dumper *dmp, u_int32_t sectors, u_int32_t output_lba, u_int32_t total_sectors, const char *label) {
636         u_int8_t zero[BLOCK_SIZE];
637         u_int32_t done = 0;
638         memset (zero, 0, sizeof (zero));
639         debug ("Writing Xbox %s zero-fill: output LBA %u, sectors %u", label ? label : "range", output_lba, sectors);
640         while (done < sectors) {
641                 u_int32_t todo;
642                 if (dumper_cancel_requested (dmp))
643                         return false;
644                 todo = sectors - done;
645                 if (todo > SECTORS_PER_BLOCK)
646                         todo = SECTORS_PER_BLOCK;
647                 if (!xbox_write_iso_sectors (dmp, zero, todo, output_lba + done, total_sectors))
648                         return false;
649                 done += todo;
650         }
651         return true;
652 }
653
654 static bool xbox_dump_read10_iso_range (dumper *dmp, u_int32_t source_lba, u_int32_t sectors, u_int32_t output_lba, u_int32_t total_sectors, const char *label, u_int32_t *current_sector) {
655         u_int8_t buf[BLOCK_SIZE];
656         u_int8_t one[SECTOR_SIZE];
657         u_int32_t done = 0;
658         debug ("Reading Xbox %s: source LBA %u, output LBA %u, sectors %u", label ? label : "range", source_lba, output_lba, sectors);
659         while (done < sectors) {
660                 u_int32_t todo;
661                 int retry;
662                 bool ok = false;
663                 if (dumper_cancel_requested (dmp))
664                         return false;
665                 todo = sectors - done;
666                 if (todo > SECTORS_PER_BLOCK)
667                         todo = SECTORS_PER_BLOCK;
668                 if (current_sector)
669                         *current_sector = output_lba + done;
670                 for (retry = 0; retry < 3 && !ok; retry++) {
671                         memset (buf, 0, sizeof (buf));
672                         if (disc_xbox_read_10 (dmp -> dsk, source_lba + done, todo, buf, sizeof (buf)) >= 0)
673                                 ok = true;
674                 }
675                 if (!ok && todo > 1) {
676                         /* Some Xbox-capable drives are more reliable at visible DVD-video edges
677                          * and layer/layout boundaries with single-sector READ(10) commands.
678                          * Fall back before declaring the whole 16-sector batch bad. */
679                         u_int32_t i;
680                         bool singles_ok = true;
681                         for (i = 0; i < todo; i++) {
682                                 bool one_ok = false;
683                                 if (dumper_cancel_requested (dmp))
684                                         return false;
685                                 for (retry = 0; retry < 3 && !one_ok; retry++) {
686                                         memset (one, 0, sizeof (one));
687                                         if (disc_xbox_read_10 (dmp -> dsk, source_lba + done + i, 1, one, sizeof (one)) >= 0)
688                                                 one_ok = true;
689                                 }
690                                 if (!one_ok) {
691                                         if (current_sector)
692                                                 *current_sector = output_lba + done + i;
693                                         xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed for %s at source LBA %u / output LBA %u\n", label ? label : "range", source_lba + done + i, output_lba + done + i);
694                                         error ("Xbox READ(10) failed for %s at source LBA %u", label ? label : "range", source_lba + done + i);
695                                         singles_ok = false;
696                                         break;
697                                 }
698                                 memcpy (buf + ((size_t) i * SECTOR_SIZE), one, SECTOR_SIZE);
699                         }
700                         ok = singles_ok;
701                 }
702                 if (!ok) {
703                         if (current_sector)
704                                 *current_sector = output_lba + done;
705                         xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed for %s at source LBA %u / output LBA %u\n", label ? label : "range", source_lba + done, output_lba + done);
706                         error ("Xbox READ(10) failed for %s at source LBA %u", label ? label : "range", source_lba + done);
707                         return false;
708                 }
709                 if (!xbox_write_iso_sectors (dmp, buf, todo, output_lba + done, total_sectors))
710                         return false;
711                 done += todo;
712         }
713         return true;
714 }
715
716 static bool xbox_read_range_to_memory (dumper *dmp, u_int32_t source_lba, u_int32_t sectors, u_int8_t *out, size_t out_size, const char *label) {
717         u_int8_t one[SECTOR_SIZE];
718         u_int32_t done = 0;
719         if (!dmp || !dmp -> dsk || !out || out_size < (size_t) sectors * SECTOR_SIZE)
720                 return false;
721         debug ("Capturing Xbox %s to memory: source LBA %u, sectors %u", label ? label : "range", source_lba, sectors);
722         while (done < sectors) {
723                 u_int32_t todo;
724                 int retry;
725                 bool ok = false;
726                 if (dumper_cancel_requested (dmp))
727                         return false;
728                 todo = sectors - done;
729                 if (todo > SECTORS_PER_BLOCK)
730                         todo = SECTORS_PER_BLOCK;
731                 for (retry = 0; retry < 3 && !ok; retry++) {
732                         if (disc_xbox_read_10 (dmp -> dsk, source_lba + done, todo, out + ((size_t) done * SECTOR_SIZE), out_size - ((size_t) done * SECTOR_SIZE)) >= 0)
733                                 ok = true;
734                 }
735                 if (!ok && todo > 1) {
736                         u_int32_t i;
737                         bool singles_ok = true;
738                         for (i = 0; i < todo; i++) {
739                                 bool one_ok = false;
740                                 if (dumper_cancel_requested (dmp))
741                                         return false;
742                                 for (retry = 0; retry < 3 && !one_ok; retry++) {
743                                         memset (one, 0, sizeof (one));
744                                         if (disc_xbox_read_10 (dmp -> dsk, source_lba + done + i, 1, one, sizeof (one)) >= 0)
745                                                 one_ok = true;
746                                 }
747                                 if (!one_ok) {
748                                         xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed while capturing %s at source LBA %u\n", label ? label : "range", source_lba + done + i);
749                                         error ("Xbox READ(10) failed while capturing %s at source LBA %u", label ? label : "range", source_lba + done + i);
750                                         singles_ok = false;
751                                         break;
752                                 }
753                                 memcpy (out + ((size_t) (done + i) * SECTOR_SIZE), one, SECTOR_SIZE);
754                         }
755                         ok = singles_ok;
756                 }
757                 if (!ok) {
758                         xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed while capturing %s at source LBA %u\n", label ? label : "range", source_lba + done);
759                         error ("Xbox READ(10) failed while capturing %s at source LBA %u", label ? label : "range", source_lba + done);
760                         return false;
761                 }
762                 done += todo;
763         }
764         return true;
765 }
766
767 static bool xbox_write_leadin_iso_range (dumper *dmp, u_int32_t source_lba, u_int32_t output_lba, u_int32_t total_sectors, xbox_redump_metadata *meta, const char *label, u_int32_t *current_sector) {
768         u_int8_t buf[SECTOR_SIZE];
769         u_int8_t zero[SECTOR_SIZE];
770         u_int32_t i;
771         bool warned = false;
772         memset (zero, 0, sizeof (zero));
773         debug ("Attempting Xbox %s lead-in from source LBA %u..%u", label ? label : "game", source_lba, source_lba + XBOX_XISO_LEADIN_SECTORS - 1);
774         for (i = 0; i < XBOX_XISO_LEADIN_SECTORS; i++) {
775                 const u_int8_t *src = buf;
776                 if (dumper_cancel_requested (dmp))
777                         return false;
778                 if (current_sector)
779                         *current_sector = output_lba + i;
780                 memset (buf, 0, sizeof (buf));
781                 if (disc_xbox_read_10 (dmp -> dsk, source_lba + i, 1, buf, sizeof (buf)) < 0) {
782                         if (!warned) {
783                                 warning ("Drive could not read one or more Xbox lead-in sectors; zero-filling only unreadable lead-in sectors");
784                                 warned = true;
785                         }
786                         src = zero;
787                         if (meta) meta -> game_leadin_zero_sectors++;
788                 } else {
789                         if (meta) meta -> game_leadin_read_sectors++;
790                 }
791                 if (!xbox_write_iso_sectors (dmp, src, 1, output_lba + i, total_sectors))
792                         return false;
793         }
794         return true;
795 }
796
797 static bool xbox_get_xiso_range (disc *d, u_int32_t *source_lba, u_int32_t *source_sectors, u_int32_t *target_sectors) {
798         u_int32_t total_sectors, root_lba, volume_size_raw, volume_sectors;
799
800         if (!d || !source_lba || !source_sectors || !target_sectors)
801                 return false;
802
803         if (disc_xbox_unlock (d) < 0) {
804                 error ("Could not switch Xbox drive into the game/XDVDFS view");
805                 return false;
806         }
807
808         total_sectors = disc_get_sectors_no (d);
809         root_lba = 0;
810         volume_size_raw = 0;
811
812         /* Try the historical physical/game-partition XDVDFS location first,
813          * then the unlocked/XISO-visible LBA 0x20. GDR-8050L after the
814          * native Xbox handshake normally resolves at 0x20.
815          */
816         if (xbox_probe_xdfs_volume (d, XBOX_XISO_START_LBA_MAGIC, &root_lba, &volume_size_raw)) {
817                 *source_lba = XBOX_XISO_START_LBA_MAGIC;
818         } else if (xbox_probe_xdfs_volume (d, XBOX_XISO_STANDARD_GAME_LBA, &root_lba, &volume_size_raw)) {
819                 *source_lba = XBOX_XISO_STANDARD_GAME_LBA;
820         } else {
821                 error ("No Xbox XDVDFS volume found at LBA %u or %u", XBOX_XISO_START_LBA_MAGIC, XBOX_XISO_STANDARD_GAME_LBA);
822                 return false;
823         }
824
825         if (total_sectors > 3300000) {
826                 /* Retail dual-layer XISO copies from the detected game/XDVDFS
827                  * start LBA through logical LBA 1913920, then prepends 32
828                  * output lead-in sectors.
829                  */
830                 if (*source_lba >= XBOX_XISO_DUAL_LAYER_END_LBA) {
831                         error ("Xbox XISO source LBA %u is beyond dual-layer XISO end LBA %u", *source_lba, XBOX_XISO_DUAL_LAYER_END_LBA);
832                         return false;
833                 }
834                 *source_sectors = XBOX_XISO_DUAL_LAYER_END_LBA - *source_lba;
835         } else {
836                 /* For single-layer/homebrew-style media, XDVDFS VolumeSize is a
837                  * byte count, so convert it to 2048-byte sectors.
838                  */
839                 if (volume_size_raw == 0) {
840                         error ("Xbox XDVDFS header reported a zero volume size");
841                         return false;
842                 }
843
844                 volume_sectors = volume_size_raw / SECTOR_SIZE;
845                 if (volume_sectors == 0) {
846                         error ("Xbox XDVDFS volume size %u is smaller than one sector", volume_size_raw);
847                         return false;
848                 }
849                 if (*source_lba + volume_sectors > total_sectors) {
850                         warning ("Xbox XISO volume size extends beyond visible capacity; clipping to remaining visible sectors");
851                         if (total_sectors > *source_lba)
852                                 volume_sectors = total_sectors - *source_lba;
853                         else
854                                 return false;
855                 }
856                 *source_sectors = volume_sectors;
857         }
858
859         *target_sectors = *source_sectors + XBOX_XISO_LEADIN_SECTORS;
860         debug ("Xbox XISO range: source LBA %u, descriptor RootLBA %u, source sectors %u, output sectors %u", *source_lba, root_lba, *source_sectors, *target_sectors);
861         return true;
862 }
863
864
865 /**
866  * Tries to open the output file for writing and to find out if it contains valid data so that the dump can continue.
867  * @param dvd 
868  * @param outfile 
869  * @param[out] fp The file pointer to write to.
870  * @param[out] start_sector The sector to start reading from.
871  * @return true if the dumping can start/continue, false otherwise (for instance if outfile cannot be written to).
872  */
873 bool dumper_set_raw_output_file (dumper *dmp, char *outfile_raw, bool resume) {
874         bool out;
875         my_off_t filesize;
876         FILE *fp;
877
878         if (!outfile_raw) {
879                 /* Raw output disabled */
880                 out = true;
881                 dmp -> start_sector_raw = -1;
882                 my_free (dmp -> outfile_raw);
883                 dmp -> outfile_raw = NULL;
884         } else if (dmp -> outfile_raw) {
885                 error ("Raw output file already defined");
886                 out = false;
887         } else if (!(fp = fopen (outfile_raw, "rb"))) {         /** @todo Maybe we could not open file for permission problems */
888                 /* Raw output file does not exist, start from scratch */
889                 out = true;
890                 dmp -> start_sector_raw = 0;
891                 my_strdup (dmp -> outfile_raw, outfile_raw);
892         } else if (resume) {
893                 /* Raw output file exists and resume was requested, so see how many dumped sectors it contains */
894                 my_fseek (fp, 0, SEEK_END);
895                 filesize = my_ftell (fp);
896                 fclose (fp);
897                 out = true;
898                 dmp -> start_sector_raw = (u_int32_t) (filesize / RAW_SECTOR_SIZE / SECTORS_PER_BLOCK) * SECTORS_PER_BLOCK;
899                 debug ("Raw output can restart from sector %u", dmp -> start_sector_raw);
900                 my_strdup (dmp -> outfile_raw, outfile_raw);
901         } else {
902                 /* Raw output file exists but resume was not requested, error */
903                 fclose (fp);
904                 error ("Raw output file exists, but resume was not requested.");
905                 out = false;
906                 dmp -> start_sector_raw = -1;
907                 my_free (dmp -> outfile_raw);
908                 dmp -> outfile_raw = NULL;
909         }
910
911         return (out);
912 }
913
914
915 bool dumper_set_iso_output_file (dumper *dmp, char *outfile_iso, bool resume) {
916         bool out;
917         my_off_t filesize;
918         FILE *fp;
919
920         if (!outfile_iso) {
921                 /* ISO output disabled */
922                 out = true;
923                 dmp -> start_sector_iso = -1;
924                 my_free (dmp -> outfile_iso);
925                 dmp -> outfile_iso = NULL;
926         } else if (outfile_iso[0] == '\0') {
927                 /* Xbox/GDR-8050L auto-name placeholder; the copied reference path opens the final file. */
928                 out = true;
929                 dmp -> start_sector_iso = 0;
930                 my_strdup (dmp -> outfile_iso, outfile_iso);
931         } else if (dmp -> outfile_iso) {
932                 error ("ISO output file already defined");
933                 out = false;
934         } else if (!(fp = fopen (outfile_iso, "rb"))) {         /** @todo Maybe we could not open file for permission problems */
935                 /* Raw output file does not exist, start from scratch */
936                 out = true;
937                 dmp -> start_sector_iso = 0;
938                 my_strdup (dmp -> outfile_iso, outfile_iso);
939         } else if (resume) {
940                 /* Raw output file exists and resume was requested, so see how many dumped sectors it contains */
941                 my_fseek (fp, 0, SEEK_END);
942                 filesize = my_ftell (fp);
943                 fclose (fp);
944                 out = true;
945                 dmp -> start_sector_iso = (u_int32_t) (filesize / SECTOR_SIZE / SECTORS_PER_BLOCK) * SECTORS_PER_BLOCK;
946                 debug ("ISO output can restart from sector %u", dmp -> start_sector_iso);
947                 my_strdup (dmp -> outfile_iso, outfile_iso);
948         } else {
949                 /* Raw output file exists but resume was not requested, error */
950                 fclose (fp);
951                 error ("ISO output file exists, but resume was not requested.");
952                 out = false;
953                 dmp -> start_sector_iso = -1;
954                 my_free (dmp -> outfile_iso);
955                 dmp -> outfile_iso = NULL;
956         }
957
958         return (out);
959 }
960
961
962 bool dumper_set_xiso_output_file (dumper *dmp, char *outfile_xiso, bool resume) {
963         bool out;
964         my_off_t filesize;
965         FILE *fp;
966
967         if (!outfile_xiso) {
968                 out = true;
969                 dmp -> start_sector_xiso = -1;
970                 my_free (dmp -> outfile_xiso);
971                 dmp -> outfile_xiso = NULL;
972         } else if (outfile_xiso[0] == '\0') {
973                 /* Xbox/GDR-8050L auto-name placeholder; the copied reference path opens the final file. */
974                 out = true;
975                 dmp -> start_sector_xiso = 0;
976                 my_strdup (dmp -> outfile_xiso, outfile_xiso);
977         } else if (dmp -> outfile_xiso) {
978                 error ("XISO output file already defined");
979                 out = false;
980         } else if (!(fp = fopen (outfile_xiso, "rb"))) {
981                 out = true;
982                 dmp -> start_sector_xiso = 0;
983                 my_strdup (dmp -> outfile_xiso, outfile_xiso);
984         } else if (resume) {
985                 my_fseek (fp, 0, SEEK_END);
986                 filesize = my_ftell (fp);
987                 fclose (fp);
988                 out = true;
989                 dmp -> start_sector_xiso = (u_int32_t) (filesize / SECTOR_SIZE / SECTORS_PER_BLOCK) * SECTORS_PER_BLOCK;
990                 debug ("XISO output can restart from output sector %u", dmp -> start_sector_xiso);
991                 my_strdup (dmp -> outfile_xiso, outfile_xiso);
992         } else {
993                 fclose (fp);
994                 error ("XISO output file exists, but resume was not requested.");
995                 out = false;
996                 dmp -> start_sector_xiso = -1;
997                 my_free (dmp -> outfile_xiso);
998                 dmp -> outfile_xiso = NULL;
999         }
1000
1001         return (out);
1002 }
1003
1004
1005 bool dumper_prepare_xiso (dumper *dmp) {
1006         bool out;
1007         u_int8_t buf[SECTOR_SIZE];
1008         size_t r;
1009         u_int32_t i;
1010
1011         if (!dmp -> outfile_xiso) {
1012                 error ("No XISO output file defined");
1013                 return false;
1014         }
1015
1016         dmp -> start_sector = dmp -> start_sector_xiso;
1017
1018 #ifndef WIN32
1019         if (!xbox_portable_resolve_output_path (dmp, true))
1020                 return false;
1021 #endif
1022
1023 #ifdef WIN32
1024         if (dmp -> outfile_xiso[0] == '\0' && disc_is_xbox_challenge_drive (dmp -> dsk)) {
1025                 if (dmp -> hashing)
1026                         multihash_init (&(dmp -> hash_xiso));
1027                 dmp -> fp_xiso = NULL;
1028                 return true;
1029         }
1030 #endif
1031
1032         if (dmp -> hashing)
1033                 multihash_init (&(dmp -> hash_xiso));
1034
1035         dmp -> fp_xiso = fopen (dmp -> outfile_xiso, "a+b");
1036         if (!dmp -> fp_xiso)
1037                 return false;
1038
1039         if (dmp -> hashing) {
1040                 debug ("Calculating hashes for pre-existing XISO dump data");
1041                 if (dmp -> start_sector > 0) {
1042                         for (i = 0; i < dmp -> start_sector && (r = fread (buf, SECTOR_SIZE, 1, dmp -> fp_xiso)) > 0; i++)
1043                                 multihash_update (&(dmp -> hash_xiso), buf, SECTOR_SIZE);
1044                         MY_ASSERT (r > 0);
1045                 }
1046         }
1047
1048         if (my_fseek (dmp -> fp_xiso, dmp -> start_sector * SECTOR_SIZE, SEEK_SET) == 0 &&
1049             ftruncate (fileno (dmp -> fp_xiso), (int64_t) dmp -> start_sector * SECTOR_SIZE) == 0) {
1050                 out = true;
1051                 debug ("Writing to file \"%s\" in Xbox XISO format (fseeked() to %lld)", dmp -> outfile_xiso, my_ftell (dmp -> fp_xiso));
1052         } else {
1053                 out = false;
1054                 fclose (dmp -> fp_xiso);
1055                 dmp -> fp_xiso = NULL;
1056         }
1057
1058         return (out);
1059 }
1060
1061
1062 bool dumper_prepare (dumper *dmp) {
1063         bool out;
1064         u_int8_t buf[RAW_SECTOR_SIZE];
1065         size_t r;
1066         u_int32_t i;
1067
1068         /* Outputting to both files, resume must start from the file with the least sectors. Hopefully they will have the same number of sectors, anyway... */
1069         if (dmp -> outfile_raw && dmp -> outfile_iso && dmp -> start_sector_raw != dmp -> start_sector_iso) {
1070                 if (dmp -> start_sector_raw < dmp -> start_sector_iso)
1071                         dmp -> start_sector = dmp -> start_sector_raw;
1072                 else
1073                         dmp -> start_sector = dmp -> start_sector_iso;
1074         } else if (dmp -> outfile_raw) {
1075                 dmp -> start_sector = dmp -> start_sector_raw;
1076         } else if (dmp -> outfile_iso) {
1077                 dmp -> start_sector = dmp -> start_sector_iso;
1078         } else {
1079                 MY_ASSERT (0);
1080         }
1081
1082 #ifndef WIN32
1083         if (dmp -> outfile_iso && dmp -> outfile_iso[0] == '\0' &&
1084                         disc_is_xbox_challenge_drive (dmp -> dsk) &&
1085                         !xbox_portable_resolve_output_path (dmp, false))
1086                 return false;
1087 #endif
1088
1089 #ifdef WIN32
1090         if (dmp -> outfile_iso && dmp -> outfile_iso[0] == '\0' && disc_is_xbox_challenge_drive (dmp -> dsk)) {
1091                 if (dmp -> hashing) {
1092                         multihash_init (&(dmp -> hash_raw));
1093                         multihash_init (&(dmp -> hash_iso));
1094                 }
1095                 dmp -> fp_raw = NULL;
1096                 dmp -> fp_iso = NULL;
1097                 return true;
1098         }
1099 #endif
1100
1101         /* Prepare hashes */
1102         if (dmp -> hashing) {
1103                 multihash_init (&(dmp -> hash_raw));
1104                 multihash_init (&(dmp -> hash_iso));
1105         }
1106
1107         /* Setup raw output file */
1108         if (dmp -> outfile_raw) {
1109                 dmp -> fp_raw = fopen (dmp -> outfile_raw, "a+b");
1110
1111                 if (dmp -> hashing) {
1112                         debug ("Calculating hashes for pre-existing raw dump data");
1113                         if (dmp -> start_sector > 0) {
1114                                 for (i = 0; i < dmp -> start_sector && (r = fread (buf, RAW_SECTOR_SIZE, 1, dmp -> fp_raw)) > 0; i++)
1115                                         multihash_update (&(dmp -> hash_raw), buf, RAW_SECTOR_SIZE);
1116                                 MY_ASSERT (r > 0);
1117                         }
1118                 }
1119
1120                 /* Now call fseek as file will only be written, from now on */
1121                 if (my_fseek (dmp -> fp_raw, dmp -> start_sector * RAW_SECTOR_SIZE, SEEK_SET) == 0 &&
1122                     ftruncate (fileno (dmp -> fp_raw), (int64_t) dmp -> start_sector * RAW_SECTOR_SIZE) == 0) {
1123                         out = true;
1124                         debug ("Writing to file \"%s\" in raw format (fseeked() to %lld)", dmp -> outfile_raw, my_ftell (dmp -> fp_raw));
1125                 } else {
1126                         out = false;
1127                         fclose (dmp -> fp_raw);
1128                         dmp -> fp_raw = NULL;
1129                 }
1130         } else {
1131                 dmp -> fp_raw = NULL;
1132         }
1133
1134         /* Setup ISO output file */
1135         if (dmp -> outfile_iso) {
1136                 dmp -> fp_iso = fopen (dmp -> outfile_iso, "a+b");
1137
1138                 if (dmp -> hashing) {
1139                         debug ("Calculating hashes for pre-existing ISO dump data");
1140                         if (dmp -> start_sector > 0) {
1141                                 for (i = 0; i < dmp -> start_sector && (r = fread (buf, SECTOR_SIZE, 1, dmp -> fp_iso)) > 0; i++)
1142                                         multihash_update (&(dmp -> hash_iso), buf, SECTOR_SIZE);
1143                                 MY_ASSERT (r > 0);
1144                         }
1145                 }
1146
1147                 if (my_fseek (dmp -> fp_iso, dmp -> start_sector * SECTOR_SIZE, SEEK_SET) == 0 &&
1148                     ftruncate (fileno (dmp -> fp_iso), (int64_t) dmp -> start_sector * SECTOR_SIZE) == 0) {
1149                         out = true;
1150                         debug ("Writing to file \"%s\" in ISO format (fseeked() to %lld)", dmp -> outfile_iso, my_ftell (dmp -> fp_iso));
1151                 } else {
1152                         out = false;
1153                         fclose (dmp -> fp_iso);
1154                         dmp -> fp_iso = NULL;
1155                 }
1156         } else {
1157                 dmp -> fp_iso = NULL;
1158         }
1159
1160         return (out);
1161 }
1162
1163
1164
1165 static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector) {
1166         xbox_redump_metadata meta;
1167         u_int8_t *video_l1 = NULL;
1168         u_int32_t locked_sectors = 0, unlocked_sectors = 0, sector_size = 0;
1169         u_int32_t game_source_lba = 0xFFFFFFFFU;
1170         u_int32_t root_lba = 0, volume_size_raw = 0;
1171         u_int32_t pregame_padding = XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS;
1172         u_int32_t postgame_padding = XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA - (XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS);
1173         bool out = false;
1174         bool locked_view_is_xdfs;
1175         disc_type type_id;
1176         char *type = NULL;
1177         double portable_start = 0.0;
1178         int volume_lock_result;
1179
1180         if (!dmp)
1181                 return false;
1182
1183         disc_get_type (dmp -> dsk, &type_id, &type);
1184         if (type_id != DISC_TYPE_XBOX)
1185                 return false;
1186
1187         if (dmp -> fp_raw) {
1188                 error ("Xbox redump-style ISO output cannot be combined with -r/raw output in one run");
1189                 return false;
1190         }
1191
1192         if (dmp -> start_sector != 0) {
1193                 error ("Resume is not supported for Xbox redump-style ISO reconstruction because the drive view changes mid-dump");
1194                 return false;
1195         }
1196
1197 #ifdef WIN32
1198         if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
1199                 int ref_status;
1200                 if (dmp -> fp_iso) {
1201                         fclose (dmp -> fp_iso);
1202                         dmp -> fp_iso = NULL;
1203                 }
1204                 if (current_sector)
1205                         *current_sector = 0;
1206                 xbox_ref_log_fprintf (stderr, "[XBOX] Dispatching GDR-8050L -i directly to copied original dumper code using FriiDump's existing drive HANDLE.\n");
1207                 ref_status = xbox_ref_gdr8050l_dump_with_handle (disc_get_native_handle (dmp -> dsk), disc_get_device (dmp -> dsk), dmp -> outfile_iso, '1', dumper_xbox_ref_cancel_requested, dmp, &(dmp -> xbox_ref_result));
1208                 if (current_sector)
1209                         *current_sector = dmp -> xbox_ref_result.completed_sectors;
1210                 return ref_status == 0;
1211         }
1212 #endif
1213
1214         if (!dmp -> fp_iso)
1215                 return false;
1216
1217         memset (&meta, 0, sizeof (meta));
1218         portable_start = xbox_portable_now_seconds ();
1219         xbox_portable_result_begin (dmp, dmp -> outfile_iso, '1');
1220
1221         /* Original GDR-8050L dumper order for redump-style output:
1222          *   1. unlock/authenticate into the game view;
1223          *   2. read XDVDFS/game metadata while that view is available;
1224          *   3. perform a real media transition to return to the visible DVD-video view;
1225          *   4. dump video/front/tail, unlock again, then dump game data.
1226          *
1227          * The video partition is not encrypted/locked; "lock" here only means the
1228          * drive-visible DVD-video state before the Xbox game-view unlock. */
1229         if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
1230                 xbox_ref_log_fprintf (stderr, "[XBOX] GDR-8050L Linux flow mirrors the proven Windows state-aware sequence.\n");
1231                 xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 1/4: establish and verify the game view.\n");
1232                 if (disc_xbox_prepare_game_view (dmp -> dsk, &unlocked_sectors, &sector_size) < 0) {
1233                         xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Windows-sequence game-view preparation failed.\n");
1234                         goto cleanup;
1235                 }
1236
1237                 xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 2/4: acquire the volume lock and probe metadata.\n");
1238                 volume_lock_result = disc_xbox_lock_volume (dmp -> dsk);
1239                 if (volume_lock_result < 0)
1240                         xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Exclusive volume lock failed; continuing like reference dumper warning path.\n");
1241                 else if (volume_lock_result > 0)
1242                         xbox_ref_log_fprintf (stderr, "[XBOX][INFO] Linux exclusive optical-device lock is unavailable; continuing without one.\n");
1243                 disc_xbox_read_capacity_10 (dmp -> dsk, &unlocked_sectors, &sector_size);
1244                 meta.unlocked_visible_sectors = unlocked_sectors;
1245                 xbox_capture_pfi_dmi (dmp -> dsk, &meta);
1246                 if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_STANDARD_GAME_LBA, &root_lba, &volume_size_raw)) {
1247                         meta.game_source_lba = XBOX_XISO_STANDARD_GAME_LBA;
1248                         meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS;
1249                 } else if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_START_LBA_MAGIC, &root_lba, &volume_size_raw)) {
1250                         meta.game_source_lba = XBOX_XISO_START_LBA_MAGIC;
1251                         meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS;
1252                 }
1253                 xbox_portable_result_capture_metadata (dmp);
1254
1255                 xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 3/4: media cycle back to visible DVD-video view before sector 0 capture.\n");
1256                 if (disc_xbox_media_cycle (dmp -> dsk) < 0) {
1257                         xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Stage 5 failed: could not restore visible DVD-video view.\n");
1258                         goto cleanup;
1259                 }
1260                 /* Windows raw path: AutomateTrayCycle(); RefreshVolume(); SetDriveSpeedMax(). */
1261                 disc_xbox_refresh_volume (dmp -> dsk);
1262                 disc_set_speed (dmp -> dsk, 0xFFFF);
1263         } else if (disc_is_xbox_vendor_unlock_drive (dmp -> dsk)) {
1264                 /* GDR-3120L/Kreon-style FF 08 01 profiles can explicitly select
1265                  * state 0 for the visible DVD-video view. */
1266                 if (disc_xbox_lock (dmp -> dsk) < 0)
1267                         warning ("Could not set Xbox vendor lock state 0; continuing with the current visible view");
1268         }
1269
1270         disc_xbox_read_capacity_10 (dmp -> dsk, &locked_sectors, &sector_size);
1271         meta.locked_visible_sectors = locked_sectors;
1272         xbox_capture_pfi_dmi (dmp -> dsk, &meta);
1273
1274         locked_view_is_xdfs = xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_STANDARD_GAME_LBA, NULL, NULL);
1275         if (locked_view_is_xdfs && disc_is_xbox_challenge_drive (dmp -> dsk)) {
1276                 error ("Current GDR-8050L view still exposes XDVDFS at LBA 32 after media cycle; cannot capture visible DVD-video view for redump-style ISO");
1277                 goto cleanup;
1278         }
1279
1280         if (!xbox_redump_layout_constants_match_dic ()) {
1281                 error ("Xbox redump layout constants do not match the DiscImageCreator-compatible XGD1 layout");
1282                 goto cleanup;
1283         }
1284
1285         meta.output_sectors = XBOX_XGD1_FULL_REDUMP_SECTORS;
1286         if (dmp -> progress)
1287                 dmp -> progress (true, 0, meta.output_sectors, dmp -> progress_data);
1288
1289         debug ("Xbox redump-style layout: video L0=%u, pregame padding=%u, game view=%u, postgame padding=%u, video L1=%u",
1290                 XBOX_XGD1_VIDEO_L0_SECTORS, pregame_padding, XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS, postgame_padding, XBOX_XGD1_VIDEO_L1_SECTORS);
1291
1292         video_l1 = (u_int8_t *) malloc ((size_t) XBOX_XGD1_VIDEO_L1_SECTORS * SECTOR_SIZE);
1293         if (!video_l1) {
1294                 error ("Could not allocate Xbox VIDEO-L1 capture buffer");
1295                 goto cleanup;
1296         }
1297
1298         /* Capture the tail of the visible DVD-video view before switching to the game view. */
1299         if (!xbox_read_range_to_memory (dmp, XBOX_XGD1_VIDEO_L0_SECTORS, XBOX_XGD1_VIDEO_L1_SECTORS,
1300                         video_l1, (size_t) XBOX_XGD1_VIDEO_L1_SECTORS * SECTOR_SIZE, "VIDEO-L1"))
1301                 goto cleanup;
1302
1303         if (!xbox_dump_read10_iso_range (dmp, 0, XBOX_XGD1_VIDEO_L0_SECTORS, 0, meta.output_sectors, "VIDEO-L0", current_sector))
1304                 goto cleanup;
1305
1306         if (current_sector) *current_sector = XBOX_XGD1_VIDEO_L0_SECTORS;
1307         if (!xbox_write_zero_iso_range (dmp, pregame_padding, XBOX_XGD1_VIDEO_L0_SECTORS, meta.output_sectors, "PREGAME-PADDING"))
1308                 goto cleanup;
1309
1310         xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 4/4: re-establish and verify the game view for XDVDFS data.\n");
1311         if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
1312                 if (disc_xbox_prepare_game_view (dmp -> dsk, &unlocked_sectors, &sector_size) < 0) {
1313                         xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Final Windows-sequence game-view preparation failed.\n");
1314                         goto cleanup;
1315                 }
1316         } else {
1317                 if (disc_xbox_unlock (dmp -> dsk) < 0)
1318                         xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Xbox vendor unlock returned failure; XDVDFS probe remains authoritative.\n");
1319                 disc_xbox_read_capacity_10 (dmp -> dsk, &unlocked_sectors, &sector_size);
1320         }
1321         meta.unlocked_visible_sectors = unlocked_sectors;
1322         xbox_capture_pfi_dmi (dmp -> dsk, &meta);
1323
1324         if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_STANDARD_GAME_LBA, &root_lba, &volume_size_raw))
1325                 game_source_lba = XBOX_XISO_STANDARD_GAME_LBA;
1326         else if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_START_LBA_MAGIC, &root_lba, &volume_size_raw))
1327                 game_source_lba = XBOX_XISO_START_LBA_MAGIC;
1328         else {
1329                 error ("Could not locate Xbox XDVDFS header after unlock at LBA %u or %u", XBOX_XISO_STANDARD_GAME_LBA, XBOX_XISO_START_LBA_MAGIC);
1330                 goto cleanup;
1331         }
1332
1333         meta.game_source_lba = game_source_lba;
1334         meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS;
1335         xbox_portable_result_capture_metadata (dmp);
1336         if (game_source_lba != XBOX_XISO_STANDARD_GAME_LBA && disc_is_xbox_challenge_drive (dmp -> dsk))
1337                 warning ("Xbox XDVDFS was detected at LBA %u, not the expected GDR-8050L game-view LBA 32", game_source_lba);
1338
1339         /* FriiDump 0.5.3.13 cache-aligned raw-ID validation proved that the
1340          * unlocked GDR-8050L view exposes source LBA 0..31 as the physical
1341          * game-region lead-in immediately preceding the XDVDFS header at LBA 32.
1342          * Read those sectors from the drive; retain the existing per-sector
1343          * zero-fill fallback only for genuinely unreadable sectors. */
1344         xbox_ref_log_fprintf (stderr,
1345                 "[XBOX] Capturing 32-sector game lead-in from unlocked source LBA %u..%u.\n",
1346                 (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
1347                 (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - 1 : XBOX_XISO_LEADIN_SECTORS - 1);
1348         if (!xbox_write_leadin_iso_range (dmp,
1349                         (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
1350                         XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, &meta, "redump game", current_sector))
1351                 goto cleanup;
1352
1353         if (!xbox_dump_read10_iso_range (dmp, game_source_lba, XBOX_XGD1_GAME_SOURCE_SECTORS,
1354                         XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XISO_LEADIN_SECTORS, meta.output_sectors, "GAME-XDVDFS", current_sector))
1355                 goto cleanup;
1356
1357         if (current_sector) *current_sector = XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS;
1358         if (!xbox_write_zero_iso_range (dmp, postgame_padding, XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS,
1359                         meta.output_sectors, "POSTGAME-PADDING"))
1360                 goto cleanup;
1361
1362         if (!xbox_write_iso_sectors (dmp, video_l1, XBOX_XGD1_VIDEO_L1_SECTORS, XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA, meta.output_sectors))
1363                 goto cleanup;
1364
1365         if (dmp -> hashing)
1366                 multihash_finish (&(dmp -> hash_iso));
1367         if (dmp -> fp_iso) {
1368                 fclose (dmp -> fp_iso);
1369                 dmp -> fp_iso = NULL;
1370         }
1371
1372         xbox_write_redump_metadata (dmp, &meta);
1373         out = true;
1374
1375 cleanup:
1376         if (!out) {
1377                 /* A failed or cancelled Xbox reconstruction still owns an open output
1378                  * stream. Finalize the partial streaming hash and close the file so the
1379                  * native report can observe the exact completed byte count and the caller
1380                  * never leaks the handle. */
1381                 if (dmp -> hashing)
1382                         multihash_finish (&(dmp -> hash_iso));
1383                 if (dmp -> fp_iso) {
1384                         fflush (dmp -> fp_iso);
1385                         fclose (dmp -> fp_iso);
1386                         dmp -> fp_iso = NULL;
1387                 }
1388         }
1389         if (video_l1)
1390                 free (video_l1);
1391         xbox_portable_result_finish (dmp, out, meta.output_sectors, portable_start, false);
1392         /* Keep current_sector at the last phase-specific LBA on failure so the
1393          * CLI can report a useful range instead of collapsing every setup failure
1394          * back to 0..15. */
1395         return out;
1396 }
1397
1398 int dumper_dump (dumper *dmp, u_int32_t *current_sector) {
1399         bool out;
1400         u_int8_t *rawbuf, *isobuf;
1401         u_int32_t i, sectors_no, last_sector;
1402         disc_type type_id;
1403         char *type_string = NULL;
1404 #ifdef DEBUGaa
1405         bool no_unscrambling;
1406 #endif
1407
1408 #ifdef DEBUGaa
1409         no_unscrambling = dd -> no_unscrambling;
1410         if (fp_iso && no_unscrambling) {
1411                 warning ("Output to ISO format requested, ignoring no_unscrambling!");
1412                 no_unscrambling = false;
1413         }
1414 #endif
1415
1416         disc_get_type (dmp -> dsk, &type_id, &type_string);
1417         if (type_id == DISC_TYPE_XBOX && dmp -> fp_iso)
1418                 return dumper_dump_xbox_redump_iso (dmp, current_sector);
1419
1420         sectors_no = disc_get_sectors_no (dmp -> dsk);
1421 #if 0
1422         if (dd -> start_sector != -1) {
1423                 if (dd -> start_sector >= sectors_no) {
1424                         error ("Cannot start dumping from sector %u as the inserted disc only has %u sectors\n", dd -> start_sector, sectors_no);
1425                         out = false;
1426                 } else {
1427                         warning ("Start sector forced to %u\n", dd -> start_sector);
1428                         ss = dd -> start_sector;
1429
1430                         if (fp_iso)
1431                                 fseek (fp_iso, ss * 2048, SEEK_SET);
1432                         if (fp_raw)
1433                                 fseek (fp_raw, ss * 2064, SEEK_SET);
1434                 }
1435         }
1436 #endif
1437         
1438         if (true) {
1439                 debug ("Starting dump process from sector %u...\n", dmp -> start_sector);
1440
1441                 /* First call to progress function */
1442                 if (dmp -> progress)
1443                         dmp -> progress (true, dmp -> start_sector, sectors_no, dmp -> progress_data);
1444
1445                 last_sector=sectors_no-1;
1446                 
1447                 for (i = dmp -> start_sector, out = true; i < sectors_no && out; i++) {
1448                         if (dumper_cancel_requested (dmp)) {
1449                                 out = false;
1450                                 if (current_sector)
1451                                         *current_sector = i;
1452                                 break;
1453                         }
1454                         if (!dumper_read_sector_with_retry (dmp, i, &isobuf, &rawbuf)) {
1455                                 error ("Read failed after dump-level retries at sectors %u..%u", i, i + SECTORS_PER_BLOCK - 1);
1456                                 out = false;
1457                                 *(current_sector) = i;
1458                         }
1459
1460                         if (out && dmp -> fp_raw) {
1461                                 clearerr (dmp -> fp_raw);
1462
1463                                 if (!rawbuf) {
1464                                         error ("NULL buffer");
1465                                         out = false;
1466                                         *(current_sector) = i;
1467                                 }
1468                                 else fwrite (rawbuf, RAW_SECTOR_SIZE, 1, dmp -> fp_raw);
1469                                 if (ferror (dmp -> fp_raw)) {
1470                                         error ("fwrite() to raw output file failed");
1471                                         out = false;
1472                                         *(current_sector) = i;
1473                                 }
1474
1475                                 if (dmp -> flushing)
1476                                         fflush (dmp -> fp_raw);
1477
1478                                 if (dmp -> hashing && out)
1479                                         multihash_update (&(dmp -> hash_raw), rawbuf, RAW_SECTOR_SIZE);
1480                         }
1481
1482                         if (out && dmp -> fp_iso) {
1483                                 clearerr (dmp -> fp_iso);
1484
1485                                 if (!isobuf) {
1486                                         error ("NULL buffer");
1487                                         out = false;
1488                                         *(current_sector) = i;
1489                                 }
1490                                 else fwrite (isobuf, SECTOR_SIZE, 1, dmp -> fp_iso);
1491
1492                                 if (ferror (dmp -> fp_iso)) {
1493                                         error ("fwrite() to ISO output file failed");
1494                                         out = false;
1495                                         *(current_sector) = i;
1496                                 }
1497
1498                                 if (dmp -> flushing)
1499                                         fflush (dmp -> fp_iso);
1500
1501                                 if (dmp -> hashing && out)
1502                                         multihash_update (&(dmp -> hash_iso), isobuf, SECTOR_SIZE);
1503                         }
1504
1505                         if ((i % 320 == 0) || (i == last_sector)) { //speedhack
1506                                 if (dmp -> progress)
1507                                         dmp -> progress (false, i + 1, sectors_no, dmp -> progress_data);               /* i + 1 'cause sectors range from 0 to N */
1508                         }
1509                 }
1510
1511                 if (dmp -> hashing) {
1512                         multihash_finish (&(dmp -> hash_raw));
1513                         multihash_finish (&(dmp -> hash_iso));
1514                 }
1515
1516                 if (dmp -> fp_raw)
1517                         fclose (dmp -> fp_raw);
1518                 if (dmp -> fp_iso)
1519                         fclose (dmp -> fp_iso);
1520                 if (out) {
1521
1522
1523                 }
1524         }
1525
1526         return (out);
1527 }
1528
1529
1530 int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector) {
1531         bool out;
1532         u_int8_t *rawbuf, *isobuf;
1533         u_int8_t sector_buf[SECTOR_SIZE];
1534         u_int32_t source_lba, source_sectors, target_sectors, i, source_sector, last_sector, leadin_source_lba;
1535         bool leadin_fallback_warned;
1536         double portable_start = 0.0;
1537
1538         if (!dmp) {
1539                 error ("XISO dumper is not prepared");
1540                 return false;
1541         }
1542
1543 #ifdef WIN32
1544         if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
1545                 int ref_status;
1546                 if (dmp -> fp_xiso) {
1547                         fclose (dmp -> fp_xiso);
1548                         dmp -> fp_xiso = NULL;
1549                 }
1550                 if (current_sector)
1551                         *current_sector = 0;
1552                 xbox_ref_log_fprintf (stderr, "[XBOX] Dispatching GDR-8050L -X directly to copied original dumper code using FriiDump's existing drive HANDLE.\n");
1553                 ref_status = xbox_ref_gdr8050l_dump_with_handle (disc_get_native_handle (dmp -> dsk), disc_get_device (dmp -> dsk), dmp -> outfile_xiso, '2', dumper_xbox_ref_cancel_requested, dmp, &(dmp -> xbox_ref_result));
1554                 if (current_sector)
1555                         *current_sector = dmp -> xbox_ref_result.completed_sectors;
1556                 return ref_status == 0;
1557         }
1558 #endif
1559
1560         if (!dmp -> fp_xiso) {
1561                 error ("XISO dumper is not prepared");
1562                 return false;
1563         }
1564
1565         portable_start = xbox_portable_now_seconds ();
1566         xbox_portable_result_begin (dmp, dmp -> outfile_xiso, '2');
1567
1568         if (!xbox_get_xiso_range (dmp -> dsk, &source_lba, &source_sectors, &target_sectors)) {
1569                 if (current_sector)
1570                         *current_sector = dmp -> start_sector;
1571                 xbox_portable_result_finish (dmp, false, 0, portable_start, true);
1572                 return false;
1573         }
1574         xbox_portable_result_capture_metadata (dmp);
1575
1576         if (dmp -> start_sector >= target_sectors) {
1577                 error ("Cannot resume Xbox XISO at output sector %u; target has only %u sectors", dmp -> start_sector, target_sectors);
1578                 if (current_sector)
1579                         *current_sector = dmp -> start_sector;
1580                 xbox_portable_result_finish (dmp, false, target_sectors, portable_start, true);
1581                 return false;
1582         }
1583
1584         memset (sector_buf, 0, sizeof (sector_buf));
1585         leadin_source_lba = (source_lba >= XBOX_XISO_LEADIN_SECTORS) ? (source_lba - XBOX_XISO_LEADIN_SECTORS) : 0;
1586         leadin_fallback_warned = false;
1587         out = true;
1588         last_sector = target_sectors - 1;
1589
1590         debug ("Starting Xbox XISO dump from output sector %u...", dmp -> start_sector);
1591         debug ("Xbox XISO lead-in source LBA %u..%u will be attempted before zero-fill fallback", leadin_source_lba, leadin_source_lba + XBOX_XISO_LEADIN_SECTORS - 1);
1592         if (dmp -> progress)
1593                 dmp -> progress (true, dmp -> start_sector, target_sectors, dmp -> progress_data);
1594
1595         for (i = dmp -> start_sector; i < target_sectors && out; i++) {
1596                 if (dumper_cancel_requested (dmp)) {
1597                         out = false;
1598                         if (current_sector)
1599                                 *current_sector = i;
1600                         break;
1601                 }
1602                 if (i < XBOX_XISO_LEADIN_SECTORS) {
1603                         isobuf = NULL;
1604                         rawbuf = NULL;
1605                         if (disc_xbox_read_10 (dmp -> dsk, leadin_source_lba + i, 1, sector_buf, sizeof (sector_buf)) >= 0) {
1606                                 isobuf = sector_buf;
1607                         } else {
1608                                 if (!leadin_fallback_warned) {
1609                                         warning ("Drive could not read one or more Xbox XISO lead-in sectors; using zero-fill fallback for unreadable lead-in sectors");
1610                                         leadin_fallback_warned = true;
1611                                 }
1612                                 memset (sector_buf, 0, sizeof (sector_buf));
1613                                 isobuf = sector_buf;
1614                         }
1615
1616                         clearerr (dmp -> fp_xiso);
1617                         fwrite (isobuf, SECTOR_SIZE, 1, dmp -> fp_xiso);
1618                         if (ferror (dmp -> fp_xiso)) {
1619                                 error ("fwrite() to XISO output file failed");
1620                                 out = false;
1621                                 if (current_sector)
1622                                         *current_sector = i;
1623                         }
1624
1625                         if (dmp -> hashing && out)
1626                                 multihash_update (&(dmp -> hash_xiso), isobuf, SECTOR_SIZE);
1627                 } else {
1628                         source_sector = source_lba + (i - XBOX_XISO_LEADIN_SECTORS);
1629                         isobuf = sector_buf;
1630                         rawbuf = NULL;
1631                         if (disc_xbox_read_10 (dmp -> dsk, source_sector, 1, sector_buf, sizeof (sector_buf)) < 0) {
1632                                 error ("Xbox XISO READ(10) failed at source LBA %u", source_sector);
1633                                 out = false;
1634                                 if (current_sector)
1635                                         *current_sector = i;
1636                         } else {
1637                                 clearerr (dmp -> fp_xiso);
1638                                 fwrite (isobuf, SECTOR_SIZE, 1, dmp -> fp_xiso);
1639                                 if (ferror (dmp -> fp_xiso)) {
1640                                         error ("fwrite() to XISO output file failed");
1641                                         out = false;
1642                                         if (current_sector)
1643                                                 *current_sector = i;
1644                                 }
1645
1646                                 if (dmp -> hashing && out)
1647                                         multihash_update (&(dmp -> hash_xiso), isobuf, SECTOR_SIZE);
1648                         }
1649                 }
1650
1651                 if (dmp -> flushing)
1652                         fflush (dmp -> fp_xiso);
1653
1654                 if ((i % 320 == 0) || (i == last_sector)) {
1655                         if (dmp -> progress)
1656                                 dmp -> progress (false, i + 1, target_sectors, dmp -> progress_data);
1657                 }
1658         }
1659
1660         if (dmp -> hashing)
1661                 multihash_finish (&(dmp -> hash_xiso));
1662
1663         if (dmp -> fp_xiso) {
1664                 fclose (dmp -> fp_xiso);
1665                 dmp -> fp_xiso = NULL;
1666         }
1667
1668         xbox_portable_result_finish (dmp, out, target_sectors, portable_start, true);
1669
1670         return (out);
1671 }
1672
1673
1674 dumper *dumper_new (disc *d) {
1675         dumper *dmp;
1676
1677         dmp = (dumper *) malloc (sizeof (dumper));
1678         memset (dmp, 0, sizeof (dumper));
1679         dmp -> dsk = d;
1680         xbox_ref_dump_result_init (&(dmp -> xbox_ref_result));
1681         dumper_set_hashing (dmp, true);
1682         dumper_set_flushing (dmp, true);
1683
1684         return (dmp);
1685 }
1686
1687
1688 void dumper_set_progress_callback (dumper *dmp, progress_func progress, void *progress_data) {
1689         dmp -> progress = progress;
1690         dmp -> progress_data = progress_data;
1691
1692         return;
1693 }
1694
1695
1696 void dumper_set_cancel_callback (dumper *dmp, dumper_cancel_func cancel, void *cancel_data) {
1697         if (!dmp)
1698                 return;
1699         dmp -> cancel = cancel;
1700         dmp -> cancel_data = cancel_data;
1701 }
1702
1703
1704 bool dumper_was_cancelled (dumper *dmp) {
1705         return dmp && dmp -> cancelled;
1706 }
1707
1708
1709 void dumper_set_hashing (dumper *dmp, bool h) {
1710         dmp -> hashing = h;
1711         debug ("Hashing %s", h ? "enabled" : "disabled");
1712
1713         return;
1714 }
1715
1716
1717 void dumper_set_flushing (dumper *dmp, bool f) {
1718         dmp -> flushing = f;
1719         debug ("Flushing %s", f ? "enabled" : "disabled");
1720
1721         return;
1722 }
1723
1724 void *dumper_destroy (dumper *dmp) {
1725         my_free (dmp -> outfile_raw);
1726         my_free (dmp -> outfile_iso);
1727         my_free (dmp -> outfile_xiso);
1728         my_free (dmp);
1729
1730         return (NULL);
1731 }
1732
1733
1734 char *dumper_get_iso_crc32 (dumper *dmp) {
1735         return ((dmp -> hash_iso).crc32_s);
1736 }
1737
1738
1739 char *dumper_get_raw_crc32 (dumper *dmp) {
1740         return ((dmp -> hash_raw).crc32_s);
1741 }
1742
1743 char *dumper_get_xiso_crc32 (dumper *dmp) {
1744         return ((dmp -> hash_xiso).crc32_s);
1745 }
1746
1747
1748 char *dumper_get_iso_md4 (dumper *dmp) {
1749         return ((dmp -> hash_iso).md4_s);
1750 }
1751
1752
1753 char *dumper_get_raw_md4 (dumper *dmp) {
1754         return ((dmp -> hash_raw).md4_s);
1755 }
1756
1757 char *dumper_get_xiso_md4 (dumper *dmp) {
1758         return ((dmp -> hash_xiso).md4_s);
1759 }
1760
1761
1762 char *dumper_get_iso_md5 (dumper *dmp) {
1763         return ((dmp -> hash_iso).md5_s);
1764 }
1765
1766
1767 char *dumper_get_raw_md5 (dumper *dmp) {
1768         return ((dmp -> hash_raw).md5_s);
1769 }
1770
1771 char *dumper_get_xiso_md5 (dumper *dmp) {
1772         return ((dmp -> hash_xiso).md5_s);
1773 }
1774
1775
1776 char *dumper_get_iso_ed2k (dumper *dmp) {
1777         return ((dmp -> hash_iso).ed2k_s);
1778 }
1779
1780
1781 char *dumper_get_raw_ed2k (dumper *dmp) {
1782         return ((dmp -> hash_raw).ed2k_s);
1783 }
1784
1785
1786 char *dumper_get_iso_sha1 (dumper *dmp) {
1787         return ((dmp -> hash_iso).sha1_s);
1788 }
1789
1790
1791 char *dumper_get_raw_sha1 (dumper *dmp) {
1792         return ((dmp -> hash_raw).sha1_s);
1793 }
1794
1795 char *dumper_get_xiso_sha1 (dumper *dmp) {
1796         return ((dmp -> hash_xiso).sha1_s);
1797 }
1798
1799
1800 char *dumper_get_iso_sha2 (dumper *dmp) {
1801         return ((dmp -> hash_iso).sha2_s);
1802 }
1803
1804
1805 char *dumper_get_raw_sha2 (dumper *dmp) {
1806         return ((dmp -> hash_raw).sha2_s);
1807 }
1808
1809 char *dumper_get_xiso_sha2 (dumper *dmp) {
1810         return ((dmp -> hash_xiso).sha2_s);
1811 }
1812
1813
1814 bool dumper_get_xbox_reference_result (dumper *dmp, xbox_ref_dump_result *result) {
1815         if (!dmp || !result || !(dmp -> xbox_ref_result).attempted)
1816                 return false;
1817         *result = dmp -> xbox_ref_result;
1818         return true;
1819 }
1820
1821 u_int32_t dumper_get_start_sector (dumper *dmp) {
1822         return dmp ? dmp -> start_sector : 0;
1823 }