X-Git-Url: https://git.jtryba.com/browse/friidump.git/blobdiff_plain/6e43cd3fd4f7324a177331a01d653b4c19c4bca7..7d2aa073558a13f9f8406087b8225f3545c0b7c2:/libfriidump/dumper.c?ds=inline diff --git a/libfriidump/dumper.c b/libfriidump/dumper.c index 36ac682..1b95623 100644 --- a/libfriidump/dumper.c +++ b/libfriidump/dumper.c @@ -29,11 +29,13 @@ #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 #include +#include #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, §or_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, §or_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, §or_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, §or_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, §or_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; +}