]> FriiDump Source - friidump.git/blobdiff - libfriidump/dumper.c
Harden GCC-4243N A102 HLDS E7 profile
[friidump.git] / libfriidump / dumper.c
index 36ac682aeacbd2322884bae58773ea1888d028d0..1b956235f859ffb2e0a7f01a8cc1973a7b743da8 100644 (file)
 #include "disc.h"
 #include "dumper.h"
 #include "xbox_ref_bridge.h"
+#include "xbox_portable.h"
 #include "xbox_ref/xbox_ref_log.h"
 
 #ifndef WIN32
 #include <unistd.h>
 #include <sys/types.h>
+#include <sys/time.h>
 #endif
 
 struct dumper_s {
@@ -58,10 +60,232 @@ struct dumper_s {
        
        progress_func progress;
        void *progress_data;
+       dumper_cancel_func cancel;
+       void *cancel_data;
+       bool cancelled;
 };
 
 #define DUMPER_READ_BLOCK_RETRIES 3
 
+static bool dumper_cancel_requested (dumper *dmp) {
+       if (!dmp || !dmp -> cancel)
+               return false;
+       if (!dmp -> cancel (dmp -> cancel_data))
+               return false;
+       dmp -> cancelled = true;
+       return true;
+}
+
+static int dumper_xbox_ref_cancel_requested (void *cancel_data) {
+       return dumper_cancel_requested ((dumper *) cancel_data) ? 1 : 0;
+}
+
+static double xbox_portable_now_seconds (void) {
+       struct timeval now;
+       gettimeofday (&now, NULL);
+       return (double) now.tv_sec + ((double) now.tv_usec / 1000000.0);
+}
+
+static u_int64_t xbox_portable_file_size (const char *path) {
+       FILE *file;
+       my_off_t position;
+
+       if (!path || path[0] == '\0')
+               return 0;
+       file = fopen (path, "rb");
+       if (!file)
+               return 0;
+       if (my_fseek (file, 0, SEEK_END) != 0) {
+               fclose (file);
+               return 0;
+       }
+       position = my_ftell (file);
+       fclose (file);
+       if (position < 0)
+               return 0;
+       return (u_int64_t) position;
+}
+
+static void xbox_portable_copy_string (char *destination, size_t destination_size, const char *source) {
+       if (!destination || destination_size == 0)
+               return;
+       if (!source)
+               source = "";
+       snprintf (destination, destination_size, "%s", source);
+}
+
+static void xbox_portable_result_begin (dumper *dmp, const char *path, char mode) {
+       if (!dmp)
+               return;
+       xbox_ref_dump_result_init (&(dmp -> xbox_ref_result));
+       dmp -> xbox_ref_result.attempted = 1;
+       dmp -> xbox_ref_result.mode = mode;
+       xbox_portable_copy_string (dmp -> xbox_ref_result.output_path,
+                       sizeof (dmp -> xbox_ref_result.output_path), path);
+}
+
+static void xbox_portable_sanitize_filename_component (char *value) {
+       size_t read_index;
+       size_t write_index;
+       bool previous_space;
+
+       if (!value)
+               return;
+
+       write_index = 0;
+       previous_space = true;
+       for (read_index = 0; value[read_index] != '\0'; ++read_index) {
+               unsigned char c = (unsigned char) value[read_index];
+               bool forbidden = c < 32 || c == '"' || c == '*' || c == '/' ||
+                       c == ':' || c == '<' || c == '>' || c == '?' || c == '\\' || c == '|';
+
+               if (forbidden)
+                       continue;
+               if (c == ' ') {
+                       if (previous_space)
+                               continue;
+                       value[write_index++] = ' ';
+                       previous_space = true;
+               } else {
+                       value[write_index++] = (char) c;
+                       previous_space = false;
+               }
+       }
+
+       while (write_index > 0 && (value[write_index - 1] == ' ' ||
+                       value[write_index - 1] == '.'))
+               --write_index;
+       value[write_index] = '\0';
+}
+
+static bool xbox_portable_resolve_output_path (dumper *dmp, bool xiso_mode) {
+       xbox_portable_metadata metadata;
+       char title[XBOX_PORTABLE_TITLE_MAX];
+       char path[XBOX_REF_RESULT_PATH_MAX];
+       char **target;
+       const char *extension;
+
+       if (!dmp)
+               return false;
+
+       target = xiso_mode ? &(dmp -> outfile_xiso) : &(dmp -> outfile_iso);
+       if (!*target || (*target)[0] != '\0')
+               return true;
+
+       xbox_ref_log_fprintf (stderr,
+               "[XBOX-PORTABLE] Resolving the default output filename from the unlocked XBE/DMI view.\n");
+       disc_xbox_wait_ready (dmp -> dsk, 30000);
+       if (disc_xbox_unlock (dmp -> dsk) < 0)
+               xbox_ref_log_fprintf (stderr,
+                       "[XBOX-PORTABLE][WARN] Filename preflight unlock returned failure; attempting metadata reads anyway.\n");
+       disc_xbox_refresh_volume (dmp -> dsk);
+       disc_xbox_wait_ready (dmp -> dsk, 30000);
+
+       xbox_portable_metadata_init (&metadata);
+       if (!xbox_portable_read_metadata_from_disc (dmp -> dsk, &metadata)) {
+               error ("Could not derive an Xbox output filename because XBE/DMI metadata was unavailable; provide an explicit output path");
+               return false;
+       }
+
+       xbox_portable_copy_string (title, sizeof (title),
+               metadata.title[0] ? metadata.title : "Xbox DVD");
+       xbox_portable_sanitize_filename_component (title);
+       if (!title[0])
+               xbox_portable_copy_string (title, sizeof (title), "Xbox DVD");
+       extension = xiso_mode ? ".xiso" : ".iso";
+
+       if (metadata.media_id[0])
+               snprintf (path, sizeof (path), "%s[%s]%s", title, metadata.media_id, extension);
+       else
+               snprintf (path, sizeof (path), "%s%s", title, extension);
+
+       my_free (*target);
+       my_strdup (*target, path);
+       xbox_ref_log_retarget (*target);
+       xbox_ref_log_fprintf (stderr,
+               "[XBOX-PORTABLE] Derived output filename: %s\n", *target);
+       return true;
+}
+
+static void xbox_portable_result_capture_metadata (dumper *dmp) {
+       xbox_portable_metadata metadata;
+
+       if (!dmp || (dmp -> xbox_ref_result.title[0] &&
+                       dmp -> xbox_ref_result.have_game_region))
+               return;
+
+       xbox_portable_metadata_init (&metadata);
+       if (!xbox_portable_read_metadata_from_disc (dmp -> dsk, &metadata)) {
+               xbox_ref_log_fprintf (stderr,
+                       "[XBOX-PORTABLE][WARN] XBE title/region metadata was not available from the unlocked view.\n");
+               return;
+       }
+
+       xbox_portable_copy_string (dmp -> xbox_ref_result.title,
+                       sizeof (dmp -> xbox_ref_result.title), metadata.title);
+       xbox_portable_copy_string (dmp -> xbox_ref_result.media_id,
+                       sizeof (dmp -> xbox_ref_result.media_id), metadata.media_id);
+       xbox_portable_copy_string (dmp -> xbox_ref_result.region,
+                       sizeof (dmp -> xbox_ref_result.region), metadata.region);
+       dmp -> xbox_ref_result.have_game_region = metadata.have_game_region;
+       dmp -> xbox_ref_result.game_region = metadata.game_region;
+
+       xbox_ref_log_fprintf (stderr,
+               "[XBOX-PORTABLE] XBE metadata: title='%s', media_id='%s', region='%s', GameRegion=0x%08x.\n",
+               dmp -> xbox_ref_result.title[0] ? dmp -> xbox_ref_result.title : "(unknown)",
+               dmp -> xbox_ref_result.media_id[0] ? dmp -> xbox_ref_result.media_id : "(unknown)",
+               dmp -> xbox_ref_result.region[0] ? dmp -> xbox_ref_result.region : "(unknown)",
+               dmp -> xbox_ref_result.game_region);
+}
+
+static void xbox_portable_result_finish (dumper *dmp,
+               bool success,
+               u_int32_t expected_output_sectors,
+               double started_seconds,
+               bool xiso_mode) {
+       xbox_ref_dump_result *result;
+       const char *crc32;
+       const char *md5;
+       const char *sha1;
+       const char *sha256;
+
+       if (!dmp || !(dmp -> xbox_ref_result).attempted)
+               return;
+
+       result = &(dmp -> xbox_ref_result);
+       result -> dump_success = success ? 1 : 0;
+       result -> cancelled = dmp -> cancelled ? 1 : 0;
+       result -> output_sectors = expected_output_sectors;
+       result -> output_size = xbox_portable_file_size (result -> output_path);
+       result -> completed_sectors = (u_int32_t) (result -> output_size / SECTOR_SIZE);
+       result -> elapsed_seconds = xbox_portable_now_seconds () - started_seconds;
+
+       if (!dmp -> hashing || result -> output_size == 0)
+               return;
+
+       if (xiso_mode) {
+               crc32 = dumper_get_xiso_crc32 (dmp);
+               md5 = dumper_get_xiso_md5 (dmp);
+               sha1 = dumper_get_xiso_sha1 (dmp);
+               sha256 = dumper_get_xiso_sha2 (dmp);
+       } else {
+               crc32 = dumper_get_iso_crc32 (dmp);
+               md5 = dumper_get_iso_md5 (dmp);
+               sha1 = dumper_get_iso_sha1 (dmp);
+               sha256 = dumper_get_iso_sha2 (dmp);
+       }
+
+       if (!crc32 || !md5 || !sha1 || !sha256 ||
+               !crc32[0] || !md5[0] || !sha1[0] || !sha256[0])
+               return;
+
+       xbox_portable_copy_string (result -> crc32, sizeof (result -> crc32), crc32);
+       xbox_portable_copy_string (result -> md5, sizeof (result -> md5), md5);
+       xbox_portable_copy_string (result -> sha1, sizeof (result -> sha1), sha1);
+       xbox_portable_copy_string (result -> sha256, sizeof (result -> sha256), sha256);
+       result -> hashes_complete = 1;
+}
+
 static bool dumper_read_sector_with_retry (dumper *dmp, u_int32_t sector_no, u_int8_t **isobuf, u_int8_t **rawbuf) {
        int attempt;
        bool need_iso, need_raw;
@@ -257,12 +481,25 @@ static bool xbox_write_binary_file (const char *path, const u_int8_t *data, size
 }
 
 static void xbox_capture_pfi_dmi (disc *d, xbox_redump_metadata *meta) {
+       int rc;
        if (!d || !meta)
                return;
-       if (!meta -> has_pfi)
-               meta -> has_pfi = disc_xbox_read_dvd_structure (d, 0x00, 0x00, meta -> pfi, sizeof (meta -> pfi)) >= 0;
-       if (!meta -> has_dmi)
-               meta -> has_dmi = disc_xbox_read_dvd_structure (d, 0x04, 0x00, meta -> dmi, sizeof (meta -> dmi)) >= 0;
+       if (!meta -> has_pfi) {
+               rc = disc_xbox_read_dvd_structure (d, 0x00, 0x00, meta -> pfi, sizeof (meta -> pfi));
+               meta -> has_pfi = rc >= 0;
+               xbox_ref_log_fprintf (
+                       stderr,
+                       "[XBOX-DVD-STRUCTURE] format=0x00 name=PFI cdb_format_byte=7 result=%s\n",
+                       meta -> has_pfi ? "PASS" : "FAIL");
+       }
+       if (!meta -> has_dmi) {
+               rc = disc_xbox_read_dvd_structure (d, 0x04, 0x00, meta -> dmi, sizeof (meta -> dmi));
+               meta -> has_dmi = rc >= 0;
+               xbox_ref_log_fprintf (
+                       stderr,
+                       "[XBOX-DVD-STRUCTURE] format=0x04 name=DMI cdb_format_byte=7 result=%s\n",
+                       meta -> has_dmi ? "PASS" : "FAIL");
+       }
 }
 
 static void xbox_write_redump_metadata (dumper *dmp, xbox_redump_metadata *meta) {
@@ -321,6 +558,10 @@ static void xbox_write_redump_metadata (dumper *dmp, xbox_redump_metadata *meta)
        fprintf (json, "    \"pregame_padding_sector_count\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS);
        fprintf (json, "    \"game_output_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA);
        fprintf (json, "    \"game_leadin_sector_count\": %u,\n", XBOX_XISO_LEADIN_SECTORS);
+       fprintf (json, "    \"game_leadin_unlocked_source_start_lba\": %u,\n",
+               (meta -> game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ?
+               meta -> game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0);
+       fprintf (json, "    \"game_leadin_source_policy\": \"drive_read10_with_per_sector_zero_fallback\",\n");
        fprintf (json, "    \"game_unlocked_source_start_lba\": %u,\n", meta -> game_source_lba);
        fprintf (json, "    \"game_unlocked_source_sector_count\": %u,\n", meta -> game_source_sectors);
        fprintf (json, "    \"postgame_padding_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS);
@@ -339,8 +580,13 @@ static void xbox_write_redump_metadata (dumper *dmp, xbox_redump_metadata *meta)
        fprintf (json, "  },\n");
        fprintf (json, "  \"lead_in_capture\": {\n");
        fprintf (json, "    \"game_leadin_attempted\": true,\n");
+       fprintf (json, "    \"unlocked_source_start_lba\": %u,\n",
+               (meta -> game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ?
+               meta -> game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0);
+       fprintf (json, "    \"sector_count\": %u,\n", XBOX_XISO_LEADIN_SECTORS);
        fprintf (json, "    \"readable_sectors\": %u,\n", meta -> game_leadin_read_sectors);
-       fprintf (json, "    \"zero_filled_sectors\": %u\n", meta -> game_leadin_zero_sectors);
+       fprintf (json, "    \"zero_filled_sectors\": %u,\n", meta -> game_leadin_zero_sectors);
+       fprintf (json, "    \"physical_geometry_evidence\": \"friidump_0.5.3.13_cache_aligned_raw_id_probe\"\n");
        fprintf (json, "  },\n");
        fprintf (json, "  \"dvd_structures\": {\n");
        fprintf (json, "    \"pfi\": "); if (pfi_leaf) xbox_json_string (json, pfi_leaf); else fprintf (json, "null"); fprintf (json, ",\n");
@@ -348,7 +594,8 @@ static void xbox_write_redump_metadata (dumper *dmp, xbox_redump_metadata *meta)
        fprintf (json, "  },\n");
        fprintf (json, "  \"notes\": [\n");
        fprintf (json, "    \"PFI and DMI are stored as separate binary READ DVD STRUCTURE captures because they are not READ(10) user-data sectors.\",\n");
-       fprintf (json, "    \"Pregame and postgame padding are zero-filled unless a future workflow proves readable source sectors for those ranges.\"\n");
+       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");
+       fprintf (json, "    \"Pregame and postgame padding retain zero-filled placeholder content; their physical locations are resolved but their inaccessible bytes are not.\"\n");
        fprintf (json, "  ]\n");
        fprintf (json, "}\n");
        fclose (json);
@@ -367,6 +614,8 @@ static void xbox_write_redump_metadata (dumper *dmp, xbox_redump_metadata *meta)
 }
 
 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) {
+       if (dumper_cancel_requested (dmp))
+               return false;
        if (!dmp || !dmp -> fp_iso || !buf)
                return false;
        clearerr (dmp -> fp_iso);
@@ -389,7 +638,10 @@ static bool xbox_write_zero_iso_range (dumper *dmp, u_int32_t sectors, u_int32_t
        memset (zero, 0, sizeof (zero));
        debug ("Writing Xbox %s zero-fill: output LBA %u, sectors %u", label ? label : "range", output_lba, sectors);
        while (done < sectors) {
-               u_int32_t todo = sectors - done;
+               u_int32_t todo;
+               if (dumper_cancel_requested (dmp))
+                       return false;
+               todo = sectors - done;
                if (todo > SECTORS_PER_BLOCK)
                        todo = SECTORS_PER_BLOCK;
                if (!xbox_write_iso_sectors (dmp, zero, todo, output_lba + done, total_sectors))
@@ -399,23 +651,23 @@ static bool xbox_write_zero_iso_range (dumper *dmp, u_int32_t sectors, u_int32_t
        return true;
 }
 
-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, bool auth_recovery, u_int32_t *current_sector) {
+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) {
        u_int8_t buf[BLOCK_SIZE];
        u_int8_t one[SECTOR_SIZE];
        u_int32_t done = 0;
        debug ("Reading Xbox %s: source LBA %u, output LBA %u, sectors %u", label ? label : "range", source_lba, output_lba, sectors);
-       disc_xbox_recovery_kick (dmp -> dsk, auth_recovery);
        while (done < sectors) {
-               u_int32_t todo = sectors - done;
+               u_int32_t todo;
                int retry;
                bool ok = false;
+               if (dumper_cancel_requested (dmp))
+                       return false;
+               todo = sectors - done;
                if (todo > SECTORS_PER_BLOCK)
                        todo = SECTORS_PER_BLOCK;
                if (current_sector)
                        *current_sector = output_lba + done;
                for (retry = 0; retry < 3 && !ok; retry++) {
-                       if (retry > 0)
-                               disc_xbox_recovery_kick (dmp -> dsk, auth_recovery);
                        memset (buf, 0, sizeof (buf));
                        if (disc_xbox_read_10 (dmp -> dsk, source_lba + done, todo, buf, sizeof (buf)) >= 0)
                                ok = true;
@@ -428,9 +680,9 @@ static bool xbox_dump_read10_iso_range (dumper *dmp, u_int32_t source_lba, u_int
                        bool singles_ok = true;
                        for (i = 0; i < todo; i++) {
                                bool one_ok = false;
+                               if (dumper_cancel_requested (dmp))
+                                       return false;
                                for (retry = 0; retry < 3 && !one_ok; retry++) {
-                                       if (retry > 0)
-                                               disc_xbox_recovery_kick (dmp -> dsk, auth_recovery);
                                        memset (one, 0, sizeof (one));
                                        if (disc_xbox_read_10 (dmp -> dsk, source_lba + done + i, 1, one, sizeof (one)) >= 0)
                                                one_ok = true;
@@ -461,23 +713,23 @@ static bool xbox_dump_read10_iso_range (dumper *dmp, u_int32_t source_lba, u_int
        return true;
 }
 
-static bool xbox_read_range_to_memory (disc *d, u_int32_t source_lba, u_int32_t sectors, u_int8_t *out, size_t out_size, const char *label, bool auth_recovery) {
+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) {
        u_int8_t one[SECTOR_SIZE];
        u_int32_t done = 0;
-       if (!d || !out || out_size < (size_t) sectors * SECTOR_SIZE)
+       if (!dmp || !dmp -> dsk || !out || out_size < (size_t) sectors * SECTOR_SIZE)
                return false;
        debug ("Capturing Xbox %s to memory: source LBA %u, sectors %u", label ? label : "range", source_lba, sectors);
-       disc_xbox_recovery_kick (d, auth_recovery);
        while (done < sectors) {
-               u_int32_t todo = sectors - done;
+               u_int32_t todo;
                int retry;
                bool ok = false;
+               if (dumper_cancel_requested (dmp))
+                       return false;
+               todo = sectors - done;
                if (todo > SECTORS_PER_BLOCK)
                        todo = SECTORS_PER_BLOCK;
                for (retry = 0; retry < 3 && !ok; retry++) {
-                       if (retry > 0)
-                               disc_xbox_recovery_kick (d, auth_recovery);
-                       if (disc_xbox_read_10 (d, source_lba + done, todo, out + ((size_t) done * SECTOR_SIZE), out_size - ((size_t) done * SECTOR_SIZE)) >= 0)
+                       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)
                                ok = true;
                }
                if (!ok && todo > 1) {
@@ -485,11 +737,11 @@ static bool xbox_read_range_to_memory (disc *d, u_int32_t source_lba, u_int32_t
                        bool singles_ok = true;
                        for (i = 0; i < todo; i++) {
                                bool one_ok = false;
+                               if (dumper_cancel_requested (dmp))
+                                       return false;
                                for (retry = 0; retry < 3 && !one_ok; retry++) {
-                                       if (retry > 0)
-                                               disc_xbox_recovery_kick (d, auth_recovery);
                                        memset (one, 0, sizeof (one));
-                                       if (disc_xbox_read_10 (d, source_lba + done + i, 1, one, sizeof (one)) >= 0)
+                                       if (disc_xbox_read_10 (dmp -> dsk, source_lba + done + i, 1, one, sizeof (one)) >= 0)
                                                one_ok = true;
                                }
                                if (!one_ok) {
@@ -512,16 +764,17 @@ static bool xbox_read_range_to_memory (disc *d, u_int32_t source_lba, u_int32_t
        return true;
 }
 
-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, bool auth_recovery, u_int32_t *current_sector) {
+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) {
        u_int8_t buf[SECTOR_SIZE];
        u_int8_t zero[SECTOR_SIZE];
        u_int32_t i;
        bool warned = false;
        memset (zero, 0, sizeof (zero));
        debug ("Attempting Xbox %s lead-in from source LBA %u..%u", label ? label : "game", source_lba, source_lba + XBOX_XISO_LEADIN_SECTORS - 1);
-       disc_xbox_recovery_kick (dmp -> dsk, auth_recovery);
        for (i = 0; i < XBOX_XISO_LEADIN_SECTORS; i++) {
                const u_int8_t *src = buf;
+               if (dumper_cancel_requested (dmp))
+                       return false;
                if (current_sector)
                        *current_sector = output_lba + i;
                memset (buf, 0, sizeof (buf));
@@ -762,6 +1015,11 @@ bool dumper_prepare_xiso (dumper *dmp) {
 
        dmp -> start_sector = dmp -> start_sector_xiso;
 
+#ifndef WIN32
+       if (!xbox_portable_resolve_output_path (dmp, true))
+               return false;
+#endif
+
 #ifdef WIN32
        if (dmp -> outfile_xiso[0] == '\0' && disc_is_xbox_challenge_drive (dmp -> dsk)) {
                if (dmp -> hashing)
@@ -821,6 +1079,13 @@ bool dumper_prepare (dumper *dmp) {
                MY_ASSERT (0);
        }
 
+#ifndef WIN32
+       if (dmp -> outfile_iso && dmp -> outfile_iso[0] == '\0' &&
+                       disc_is_xbox_challenge_drive (dmp -> dsk) &&
+                       !xbox_portable_resolve_output_path (dmp, false))
+               return false;
+#endif
+
 #ifdef WIN32
        if (dmp -> outfile_iso && dmp -> outfile_iso[0] == '\0' && disc_is_xbox_challenge_drive (dmp -> dsk)) {
                if (dmp -> hashing) {
@@ -909,6 +1174,8 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
        bool locked_view_is_xdfs;
        disc_type type_id;
        char *type = NULL;
+       double portable_start = 0.0;
+       int volume_lock_result;
 
        if (!dmp)
                return false;
@@ -937,7 +1204,9 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
                if (current_sector)
                        *current_sector = 0;
                xbox_ref_log_fprintf (stderr, "[XBOX] Dispatching GDR-8050L -i directly to copied original dumper code using FriiDump's existing drive HANDLE.\n");
-               ref_status = xbox_ref_gdr8050l_dump_with_handle (disc_get_native_handle (dmp -> dsk), disc_get_device (dmp -> dsk), dmp -> outfile_iso, '1', &(dmp -> xbox_ref_result));
+               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));
+               if (current_sector)
+                       *current_sector = dmp -> xbox_ref_result.completed_sectors;
                return ref_status == 0;
        }
 #endif
@@ -946,6 +1215,8 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
                return false;
 
        memset (&meta, 0, sizeof (meta));
+       portable_start = xbox_portable_now_seconds ();
+       xbox_portable_result_begin (dmp, dmp -> outfile_iso, '1');
 
        /* Original GDR-8050L dumper order for redump-style output:
         *   1. unlock/authenticate into the game view;
@@ -956,26 +1227,19 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
         * The video partition is not encrypted/locked; "lock" here only means the
         * drive-visible DVD-video state before the Xbox game-view unlock. */
        if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
-               xbox_ref_log_fprintf (stderr, "[XBOX] GDR-8050L flow follows original main.c/DumpXboxGameDisc option 1.\n");
-               xbox_ref_log_fprintf (stderr, "[XBOX] Stage 1/6: primary UnlockDrive() handshake.\n");
-               disc_xbox_wait_ready (dmp -> dsk, 30000);
-               if (disc_xbox_unlock (dmp -> dsk) < 0)
-                       xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Stage 1 UnlockDrive() returned failure; continuing like the reference dumper and letting later reads prove state.\n");
-               xbox_ref_log_fprintf (stderr, "[XBOX] Stage 2/6: media cycle after primary handshake.\n");
-               if (disc_xbox_media_cycle (dmp -> dsk) < 0) {
-                       xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Stage 2 failed: required media-cycle failed.\n");
-                       return false;
+               xbox_ref_log_fprintf (stderr, "[XBOX] GDR-8050L Linux flow mirrors the proven Windows state-aware sequence.\n");
+               xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 1/4: establish and verify the game view.\n");
+               if (disc_xbox_prepare_game_view (dmp -> dsk, &unlocked_sectors, &sector_size) < 0) {
+                       xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Windows-sequence game-view preparation failed.\n");
+                       goto cleanup;
                }
-               xbox_ref_log_fprintf (stderr, "[XBOX] Stage 3/6: re-apply UnlockDrive() after media change.\n");
-               if (disc_xbox_unlock (dmp -> dsk) < 0)
-                       xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Stage 3 UnlockDrive() returned failure; continuing like the reference dumper and letting GetXboxGameInfo/XDVDFS probe prove state.\n");
 
-               xbox_ref_log_fprintf (stderr, "[XBOX] Stage 4/6: RefreshVolume, settle, EnsureDriveReady, SetDriveSpeedMax, and volume lock before metadata probe.\n");
-               disc_xbox_refresh_volume (dmp -> dsk);
-               disc_xbox_wait_ready (dmp -> dsk, 30000);
-               disc_set_speed (dmp -> dsk, 0xFFFF);
-               if (disc_xbox_lock_volume (dmp -> dsk) < 0)
-                       xbox_ref_log_fprintf (stderr, "[XBOX][WARN] FSCTL_LOCK_VOLUME equivalent failed; continuing like reference dumper warning path.\n");
+               xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 2/4: acquire the volume lock and probe metadata.\n");
+               volume_lock_result = disc_xbox_lock_volume (dmp -> dsk);
+               if (volume_lock_result < 0)
+                       xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Exclusive volume lock failed; continuing like reference dumper warning path.\n");
+               else if (volume_lock_result > 0)
+                       xbox_ref_log_fprintf (stderr, "[XBOX][INFO] Linux exclusive optical-device lock is unavailable; continuing without one.\n");
                disc_xbox_read_capacity_10 (dmp -> dsk, &unlocked_sectors, &sector_size);
                meta.unlocked_visible_sectors = unlocked_sectors;
                xbox_capture_pfi_dmi (dmp -> dsk, &meta);
@@ -986,13 +1250,16 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
                        meta.game_source_lba = XBOX_XISO_START_LBA_MAGIC;
                        meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS;
                }
+               xbox_portable_result_capture_metadata (dmp);
 
-               xbox_ref_log_fprintf (stderr, "[XBOX] Stage 5/6: media cycle back to visible DVD-video view before sector 0 capture.\n");
+               xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 3/4: media cycle back to visible DVD-video view before sector 0 capture.\n");
                if (disc_xbox_media_cycle (dmp -> dsk) < 0) {
                        xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Stage 5 failed: could not restore visible DVD-video view.\n");
-                       return false;
+                       goto cleanup;
                }
-               disc_xbox_wait_ready (dmp -> dsk, 45000);
+               /* Windows raw path: AutomateTrayCycle(); RefreshVolume(); SetDriveSpeedMax(). */
+               disc_xbox_refresh_volume (dmp -> dsk);
+               disc_set_speed (dmp -> dsk, 0xFFFF);
        } else if (disc_is_xbox_vendor_unlock_drive (dmp -> dsk)) {
                /* GDR-3120L/Kreon-style FF 08 01 profiles can explicitly select
                 * state 0 for the visible DVD-video view. */
@@ -1007,12 +1274,12 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
        locked_view_is_xdfs = xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_STANDARD_GAME_LBA, NULL, NULL);
        if (locked_view_is_xdfs && disc_is_xbox_challenge_drive (dmp -> dsk)) {
                error ("Current GDR-8050L view still exposes XDVDFS at LBA 32 after media cycle; cannot capture visible DVD-video view for redump-style ISO");
-               return false;
+               goto cleanup;
        }
 
        if (!xbox_redump_layout_constants_match_dic ()) {
                error ("Xbox redump layout constants do not match the DiscImageCreator-compatible XGD1 layout");
-               return false;
+               goto cleanup;
        }
 
        meta.output_sectors = XBOX_XGD1_FULL_REDUMP_SECTORS;
@@ -1025,28 +1292,32 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
        video_l1 = (u_int8_t *) malloc ((size_t) XBOX_XGD1_VIDEO_L1_SECTORS * SECTOR_SIZE);
        if (!video_l1) {
                error ("Could not allocate Xbox VIDEO-L1 capture buffer");
-               return false;
+               goto cleanup;
        }
 
        /* Capture the tail of the visible DVD-video view before switching to the game view. */
-       if (!xbox_read_range_to_memory (dmp -> dsk, XBOX_XGD1_VIDEO_L0_SECTORS, XBOX_XGD1_VIDEO_L1_SECTORS,
-                       video_l1, (size_t) XBOX_XGD1_VIDEO_L1_SECTORS * SECTOR_SIZE, "VIDEO-L1", false))
+       if (!xbox_read_range_to_memory (dmp, XBOX_XGD1_VIDEO_L0_SECTORS, XBOX_XGD1_VIDEO_L1_SECTORS,
+                       video_l1, (size_t) XBOX_XGD1_VIDEO_L1_SECTORS * SECTOR_SIZE, "VIDEO-L1"))
                goto cleanup;
 
-       if (!xbox_dump_read10_iso_range (dmp, 0, XBOX_XGD1_VIDEO_L0_SECTORS, 0, meta.output_sectors, "VIDEO-L0", false, current_sector))
+       if (!xbox_dump_read10_iso_range (dmp, 0, XBOX_XGD1_VIDEO_L0_SECTORS, 0, meta.output_sectors, "VIDEO-L0", current_sector))
                goto cleanup;
 
        if (current_sector) *current_sector = XBOX_XGD1_VIDEO_L0_SECTORS;
        if (!xbox_write_zero_iso_range (dmp, pregame_padding, XBOX_XGD1_VIDEO_L0_SECTORS, meta.output_sectors, "PREGAME-PADDING"))
                goto cleanup;
 
-       xbox_ref_log_fprintf (stderr, "[XBOX] Stage 6/6: re-apply UnlockDrive() for unlocked game/XDVDFS data.\n");
-       if (disc_xbox_unlock (dmp -> dsk) < 0)
-               xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Stage 6 UnlockDrive() returned failure; continuing until the XDVDFS probe/read proves failure.\n");
-       disc_xbox_refresh_volume (dmp -> dsk);
-       disc_xbox_wait_ready (dmp -> dsk, 30000);
-       disc_set_speed (dmp -> dsk, 0xFFFF);
-       disc_xbox_read_capacity_10 (dmp -> dsk, &unlocked_sectors, &sector_size);
+       xbox_ref_log_fprintf (stderr, "[XBOX] Windows sequence 4/4: re-establish and verify the game view for XDVDFS data.\n");
+       if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
+               if (disc_xbox_prepare_game_view (dmp -> dsk, &unlocked_sectors, &sector_size) < 0) {
+                       xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Final Windows-sequence game-view preparation failed.\n");
+                       goto cleanup;
+               }
+       } else {
+               if (disc_xbox_unlock (dmp -> dsk) < 0)
+                       xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Xbox vendor unlock returned failure; XDVDFS probe remains authoritative.\n");
+               disc_xbox_read_capacity_10 (dmp -> dsk, &unlocked_sectors, &sector_size);
+       }
        meta.unlocked_visible_sectors = unlocked_sectors;
        xbox_capture_pfi_dmi (dmp -> dsk, &meta);
 
@@ -1061,27 +1332,26 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
 
        meta.game_source_lba = game_source_lba;
        meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS;
+       xbox_portable_result_capture_metadata (dmp);
        if (game_source_lba != XBOX_XISO_STANDARD_GAME_LBA && disc_is_xbox_challenge_drive (dmp -> dsk))
                warning ("Xbox XDVDFS was detected at LBA %u, not the expected GDR-8050L game-view LBA 32", game_source_lba);
 
-       if (disc_is_xbox_challenge_drive (dmp -> dsk)) {
-               /* Match the original GDR-8050L dumper's option-1 redump path: the
-                * 32-sector XISO/game lead-in area is synthetic zero-fill, and the real
-                * XDVDFS data begins at unlocked source LBA 32. */
-               xbox_ref_log_fprintf (stderr, "[XBOX] Writing 32-sector game lead-in as zero-fill for GDR-8050L redump flow.\n");
-               meta.game_leadin_zero_sectors += XBOX_XISO_LEADIN_SECTORS;
-               if (current_sector) *current_sector = XBOX_XGD1_GAME_OUTPUT_START_LBA;
-               if (!xbox_write_zero_iso_range (dmp, XBOX_XISO_LEADIN_SECTORS, XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, "GAME-XISO-LEADIN"))
-                       goto cleanup;
-       } else {
-               if (!xbox_write_leadin_iso_range (dmp,
-                               (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
-                               XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, &meta, "redump game", true, current_sector))
-                       goto cleanup;
-       }
+       /* FriiDump 0.5.3.13 cache-aligned raw-ID validation proved that the
+        * unlocked GDR-8050L view exposes source LBA 0..31 as the physical
+        * game-region lead-in immediately preceding the XDVDFS header at LBA 32.
+        * Read those sectors from the drive; retain the existing per-sector
+        * zero-fill fallback only for genuinely unreadable sectors. */
+       xbox_ref_log_fprintf (stderr,
+               "[XBOX] Capturing 32-sector game lead-in from unlocked source LBA %u..%u.\n",
+               (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
+               (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - 1 : XBOX_XISO_LEADIN_SECTORS - 1);
+       if (!xbox_write_leadin_iso_range (dmp,
+                       (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0,
+                       XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, &meta, "redump game", current_sector))
+               goto cleanup;
 
        if (!xbox_dump_read10_iso_range (dmp, game_source_lba, XBOX_XGD1_GAME_SOURCE_SECTORS,
-                       XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XISO_LEADIN_SECTORS, meta.output_sectors, "GAME-XDVDFS", true, current_sector))
+                       XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XISO_LEADIN_SECTORS, meta.output_sectors, "GAME-XDVDFS", current_sector))
                goto cleanup;
 
        if (current_sector) *current_sector = XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS;
@@ -1103,8 +1373,22 @@ static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector)
        out = true;
 
 cleanup:
+       if (!out) {
+               /* A failed or cancelled Xbox reconstruction still owns an open output
+                * stream. Finalize the partial streaming hash and close the file so the
+                * native report can observe the exact completed byte count and the caller
+                * never leaks the handle. */
+               if (dmp -> hashing)
+                       multihash_finish (&(dmp -> hash_iso));
+               if (dmp -> fp_iso) {
+                       fflush (dmp -> fp_iso);
+                       fclose (dmp -> fp_iso);
+                       dmp -> fp_iso = NULL;
+               }
+       }
        if (video_l1)
                free (video_l1);
+       xbox_portable_result_finish (dmp, out, meta.output_sectors, portable_start, false);
        /* Keep current_sector at the last phase-specific LBA on failure so the
         * CLI can report a useful range instead of collapsing every setup failure
         * back to 0..15. */
@@ -1161,6 +1445,12 @@ int dumper_dump (dumper *dmp, u_int32_t *current_sector) {
                last_sector=sectors_no-1;
                
                for (i = dmp -> start_sector, out = true; i < sectors_no && out; i++) {
+                       if (dumper_cancel_requested (dmp)) {
+                               out = false;
+                               if (current_sector)
+                                       *current_sector = i;
+                               break;
+                       }
                        if (!dumper_read_sector_with_retry (dmp, i, &isobuf, &rawbuf)) {
                                error ("Read failed after dump-level retries at sectors %u..%u", i, i + SECTORS_PER_BLOCK - 1);
                                out = false;
@@ -1243,6 +1533,7 @@ int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector) {
        u_int8_t sector_buf[SECTOR_SIZE];
        u_int32_t source_lba, source_sectors, target_sectors, i, source_sector, last_sector, leadin_source_lba;
        bool leadin_fallback_warned;
+       double portable_start = 0.0;
 
        if (!dmp) {
                error ("XISO dumper is not prepared");
@@ -1259,7 +1550,9 @@ int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector) {
                if (current_sector)
                        *current_sector = 0;
                xbox_ref_log_fprintf (stderr, "[XBOX] Dispatching GDR-8050L -X directly to copied original dumper code using FriiDump's existing drive HANDLE.\n");
-               ref_status = xbox_ref_gdr8050l_dump_with_handle (disc_get_native_handle (dmp -> dsk), disc_get_device (dmp -> dsk), dmp -> outfile_xiso, '2', &(dmp -> xbox_ref_result));
+               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));
+               if (current_sector)
+                       *current_sector = dmp -> xbox_ref_result.completed_sectors;
                return ref_status == 0;
        }
 #endif
@@ -1269,16 +1562,22 @@ int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector) {
                return false;
        }
 
+       portable_start = xbox_portable_now_seconds ();
+       xbox_portable_result_begin (dmp, dmp -> outfile_xiso, '2');
+
        if (!xbox_get_xiso_range (dmp -> dsk, &source_lba, &source_sectors, &target_sectors)) {
                if (current_sector)
                        *current_sector = dmp -> start_sector;
+               xbox_portable_result_finish (dmp, false, 0, portable_start, true);
                return false;
        }
+       xbox_portable_result_capture_metadata (dmp);
 
        if (dmp -> start_sector >= target_sectors) {
                error ("Cannot resume Xbox XISO at output sector %u; target has only %u sectors", dmp -> start_sector, target_sectors);
                if (current_sector)
                        *current_sector = dmp -> start_sector;
+               xbox_portable_result_finish (dmp, false, target_sectors, portable_start, true);
                return false;
        }
 
@@ -1294,6 +1593,12 @@ int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector) {
                dmp -> progress (true, dmp -> start_sector, target_sectors, dmp -> progress_data);
 
        for (i = dmp -> start_sector; i < target_sectors && out; i++) {
+               if (dumper_cancel_requested (dmp)) {
+                       out = false;
+                       if (current_sector)
+                               *current_sector = i;
+                       break;
+               }
                if (i < XBOX_XISO_LEADIN_SECTORS) {
                        isobuf = NULL;
                        rawbuf = NULL;
@@ -1360,6 +1665,8 @@ int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector) {
                dmp -> fp_xiso = NULL;
        }
 
+       xbox_portable_result_finish (dmp, out, target_sectors, portable_start, true);
+
        return (out);
 }
 
@@ -1386,6 +1693,19 @@ void dumper_set_progress_callback (dumper *dmp, progress_func progress, void *pr
 }
 
 
+void dumper_set_cancel_callback (dumper *dmp, dumper_cancel_func cancel, void *cancel_data) {
+       if (!dmp)
+               return;
+       dmp -> cancel = cancel;
+       dmp -> cancel_data = cancel_data;
+}
+
+
+bool dumper_was_cancelled (dumper *dmp) {
+       return dmp && dmp -> cancelled;
+}
+
+
 void dumper_set_hashing (dumper *dmp, bool h) {
        dmp -> hashing = h;
        debug ("Hashing %s", h ? "enabled" : "disabled");
@@ -1497,3 +1817,7 @@ bool dumper_get_xbox_reference_result (dumper *dmp, xbox_ref_dump_result *result
        *result = dmp -> xbox_ref_result;
        return true;
 }
+
+u_int32_t dumper_get_start_sector (dumper *dmp) {
+       return dmp ? dmp -> start_sector : 0;
+}