]> FriiDump Source - friidump.git/blobdiff - libfriidump/xbox_ref/utils.c
Harden GCC-4243N A102 HLDS E7 profile
[friidump.git] / libfriidump / xbox_ref / utils.c
index 3cf28114b01b0df5f00cc14de1003f7e4e6a4cb1..f437450a55835fcd9c254e602a77700798ee4561 100644 (file)
 
 #define GDR_8163B OL23
 
 
 #define GDR_8163B OL23
 
+/* The copied Xbox dumper is single-threaded.  FriiDump installs one
+ * per-invocation cancellation callback before entering the copied path and
+ * clears it during cleanup. */
+static int (*g_xbox_dump_cancel)(void *cancel_data) = NULL;
+static void *g_xbox_dump_cancel_data = NULL;
+static xbox_ref_dump_result *g_xbox_dump_cancel_result = NULL;
+
+static BOOL XboxDumpCancelRequested(void)
+{
+    if (!g_xbox_dump_cancel || !g_xbox_dump_cancel(g_xbox_dump_cancel_data))
+        return FALSE;
+
+    if (g_xbox_dump_cancel_result)
+        g_xbox_dump_cancel_result->cancelled = 1;
+
+    return TRUE;
+}
+
 HANDLE OpenDrive(char driveLetter)
 {
     char devicePath[16];
 HANDLE OpenDrive(char driveLetter)
 {
     char devicePath[16];
@@ -1232,23 +1250,6 @@ static uint32_t DetectXboxVolumeStart(HANDLE hDevice)
     return 0xFFFFFFFFu;
 }
 
     return 0xFFFFFFFFu;
 }
 
-static void RecoveryKick(HANDLE hDevice, BOOL authRecovery)
-{
-    unsigned char dummy[2048] = {0};
-
-    if (authRecovery)
-        KickXboxMediaAuth(hDevice);
-
-    SetDriveSpeedMax(hDevice);
-
-    for (int i = 0; i < 10; i++)
-    {
-        ScsiReadSectors(hDevice, 0, 1, dummy);
-        Sleep(50);
-    }
-}
-
-
 static void GetDirectoryForPath(const char *filename, char *outDir, DWORD outDirSize)
 {
     DWORD len;
 static void GetDirectoryForPath(const char *filename, char *outDir, DWORD outDirSize)
 {
     DWORD len;
@@ -1436,8 +1437,7 @@ static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
                                      uint32_t sourceStartLba,
                                      uint32_t sectorsToRead,
                                      uint32_t outputBaseLba,
                                      uint32_t sourceStartLba,
                                      uint32_t sectorsToRead,
                                      uint32_t outputBaseLba,
-                                     const char *phaseName,
-                                     BOOL authRecovery)
+                                     const char *phaseName)
 {
     const uint32_t batchSize = 32;
     unsigned char *buffer = NULL;
 {
     const uint32_t batchSize = 32;
     unsigned char *buffer = NULL;
@@ -1459,11 +1459,19 @@ static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
     printf("\n--- STARTING %s RANGE ---\n", phaseName);
     printf("Source LBA: %u | Output LBA: %u | Sectors: %u\n", sourceStartLba, outputBaseLba, sectorsToRead);
 
     printf("\n--- STARTING %s RANGE ---\n", phaseName);
     printf("Source LBA: %u | Output LBA: %u | Sectors: %u\n", sourceStartLba, outputBaseLba, sectorsToRead);
 
-    RecoveryKick(hDevice, authRecovery);
-
     while (sectorsDone < sectorsToRead)
     {
     while (sectorsDone < sectorsToRead)
     {
-        uint32_t currentLba = sourceStartLba + sectorsDone;
+        uint32_t currentLba;
+
+        if (XboxDumpCancelRequested())
+        {
+            printf("\n[CANCEL] User cancellation requested during %s at output LBA %u.\n",
+                   phaseName, outputBaseLba + sectorsDone);
+            VirtualFree(buffer, 0, MEM_RELEASE);
+            return FALSE;
+        }
+
+        currentLba = sourceStartLba + sectorsDone;
         const char *currentLayerStr = "L0";
         uint32_t burstLimit = batchSize;
         uint32_t toRead;
         const char *currentLayerStr = "L0";
         uint32_t burstLimit = batchSize;
         uint32_t toRead;
@@ -1492,6 +1500,14 @@ static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
 
         for (int retry = 0; retry <= MAX_RETRIES; retry++)
         {
 
         for (int retry = 0; retry <= MAX_RETRIES; retry++)
         {
+            if (XboxDumpCancelRequested())
+            {
+                printf("\n[CANCEL] User cancellation requested during %s retry at output LBA %u.\n",
+                       phaseName, outputBaseLba + sectorsDone);
+                VirtualFree(buffer, 0, MEM_RELEASE);
+                return FALSE;
+            }
+
             if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, buffer))
             {
                 if (!WriteOutputBytes(outFile, buffer, (size_t)toRead * 2048U, phaseName, currentLba, outputBaseLba + sectorsDone))
             if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, buffer))
             {
                 if (!WriteOutputBytes(outFile, buffer, (size_t)toRead * 2048U, phaseName, currentLba, outputBaseLba + sectorsDone))
@@ -1505,7 +1521,6 @@ static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
                 break;
             }
 
                 break;
             }
 
-            RecoveryKick(hDevice, authRecovery);
             Sleep(500);
         }
 
             Sleep(500);
         }
 
@@ -1528,6 +1543,15 @@ static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
 
                 for (int sRetry = 0; sRetry <= MAX_RETRIES; sRetry++)
                 {
 
                 for (int sRetry = 0; sRetry <= MAX_RETRIES; sRetry++)
                 {
+                    if (XboxDumpCancelRequested())
+                    {
+                        printf("\n[CANCEL] User cancellation requested during %s sector recovery at output LBA %u.\n",
+                               phaseName, outputBaseLba + sectorsDone);
+                        VirtualFree(smallBuffer, 0, MEM_RELEASE);
+                        VirtualFree(buffer, 0, MEM_RELEASE);
+                        return FALSE;
+                    }
+
                     if (ScsiReadSectors(hDevice, currentLba + i, 1, smallBuffer))
                     {
                         if (!WriteOutputBytes(outFile, smallBuffer, 2048, phaseName, currentLba + i, outputBaseLba + sectorsDone))
                     if (ScsiReadSectors(hDevice, currentLba + i, 1, smallBuffer))
                     {
                         if (!WriteOutputBytes(outFile, smallBuffer, 2048, phaseName, currentLba + i, outputBaseLba + sectorsDone))
@@ -1542,7 +1566,6 @@ static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
                         break;
                     }
 
                         break;
                     }
 
-                    RecoveryKick(hDevice, authRecovery);
                     Sleep(500);
                 }
 
                     Sleep(500);
                 }
 
@@ -1613,7 +1636,17 @@ static BOOL WriteZeroSectorsOutput(FILE *outFile,
 
     while (sectorsDone < sectorCount)
     {
 
     while (sectorsDone < sectorCount)
     {
-        uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
+        uint32_t toWrite;
+
+        if (XboxDumpCancelRequested())
+        {
+            printf("\n[CANCEL] User cancellation requested during %s at output LBA %u.\n",
+                   phaseName ? phaseName : "ZERO", outputBaseLba + sectorsDone);
+            VirtualFree(zeroBuffer, 0, MEM_RELEASE);
+            return FALSE;
+        }
+
+        toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
         uint32_t outputLba = outputBaseLba + sectorsDone;
 
         if (!WriteOutputBytes(outFile, zeroBuffer, (size_t)toWrite * 2048U, phaseName ? phaseName : "ZERO", 0, outputLba))
         uint32_t outputLba = outputBaseLba + sectorsDone;
 
         if (!WriteOutputBytes(outFile, zeroBuffer, (size_t)toWrite * 2048U, phaseName ? phaseName : "ZERO", 0, outputLba))
@@ -1981,7 +2014,7 @@ static void JsonWriteValidationWarnings(FILE *f, const XboxDvdSidecarCapture *ca
     if (!payloadFilesPresent)
         WRITE_WARNING("payload_files_not_fully_present");
     if (redumpStyleZeroFilledPadding)
     if (!payloadFilesPresent)
         WRITE_WARNING("payload_files_not_fully_present");
     if (redumpStyleZeroFilledPadding)
-        WRITE_WARNING("redump_style_padding_zero_filled_pending_hardware_capture");
+        WRITE_WARNING("redump_style_padding_zero_filled_unresolved_content");
 
 #undef WRITE_WARNING
 
 
 #undef WRITE_WARNING
 
@@ -2476,6 +2509,9 @@ static BOOL WriteXboxDvdMediaProfileFile(const char *isoFilename,
         fprintf(json, "    \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
         fprintf(json, "    \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
         fprintf(json, "    \"game_output_start_lba\": %u,\n", gameOutputLba);
         fprintf(json, "    \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
         fprintf(json, "    \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
         fprintf(json, "    \"game_output_start_lba\": %u,\n", gameOutputLba);
+        fprintf(json, "    \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
+        fprintf(json, "    \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
+        fprintf(json, "    \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
         fprintf(json, "    \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
         fprintf(json, "    \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
         fprintf(json, "    \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
         fprintf(json, "    \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
         fprintf(json, "    \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
         fprintf(json, "    \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
@@ -2494,10 +2530,12 @@ static BOOL WriteXboxDvdMediaProfileFile(const char *isoFilename,
 
     fprintf(json, "  \"reconstruction\": {\n");
     fprintf(json, "    \"is_reconstructed_layout\": %s,\n", redumpStyle ? "true" : "false");
 
     fprintf(json, "  \"reconstruction\": {\n");
     fprintf(json, "    \"is_reconstructed_layout\": %s,\n", redumpStyle ? "true" : "false");
-    fprintf(json, "    \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "zero_fill_until_drive_can_read_filler" : "not_applicable"); fprintf(json, ",\n");
+    fprintf(json, "    \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "pregame_and_postgame_zero_fill_content_placeholder" : "not_applicable"); fprintf(json, ",\n");
+    fprintf(json, "    \"filler_geometry_verified\": %s,\n", redumpStyle ? "true" : "false");
     fprintf(json, "    \"filler_verified_from_disc\": false,\n");
     fprintf(json, "    \"filler_byte_value\": %s,\n", redumpStyle ? "0" : "null");
     fprintf(json, "    \"filler_verified_from_disc\": false,\n");
     fprintf(json, "    \"filler_byte_value\": %s,\n", redumpStyle ? "0" : "null");
-    fprintf(json, "    \"pending_hardware_capture\": %s,\n", redumpStyle ? "true" : "false");
+    fprintf(json, "    \"pending_hardware_capture\": false,\n");
+    fprintf(json, "    \"unresolved_filler_content\": %s,\n", redumpStyle ? "true" : "false");
     fprintf(json, "    \"filler_ranges\": [\n");
     if (redumpStyle)
     {
     fprintf(json, "    \"filler_ranges\": [\n");
     if (redumpStyle)
     {
@@ -2515,7 +2553,7 @@ static BOOL WriteXboxDvdMediaProfileFile(const char *isoFilename,
         fprintf(json, "      }\n");
     }
     fprintf(json, "    ],\n");
         fprintf(json, "      }\n");
     }
     fprintf(json, "    ],\n");
-    fprintf(json, "    \"note\": \"Padding/filler ranges are intentionally zero-filled placeholders until a drive/workflow capable of reading those regions is available.\"\n");
+    fprintf(json, "    \"note\": \"Pregame and postgame physical locations are verified by cache-aligned raw sector IDs, but their inaccessible contents remain zero-filled placeholders. The 32-sector game lead-in is drive-captured from unlocked LBA 0..31.\"\n");
     fprintf(json, "  },\n");
 
     fprintf(json, "  \"dvd_structures\": {\n");
     fprintf(json, "  },\n");
 
     fprintf(json, "  \"dvd_structures\": {\n");
@@ -2628,6 +2666,9 @@ static BOOL WriteXboxDvdSidecarFiles(const char *isoFilename,
         fprintf(json, "      \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
         fprintf(json, "      \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
         fprintf(json, "      \"game_output_start_lba\": %u,\n", gameOutputLba);
         fprintf(json, "      \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
         fprintf(json, "      \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
         fprintf(json, "      \"game_output_start_lba\": %u,\n", gameOutputLba);
+        fprintf(json, "      \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
+        fprintf(json, "      \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
+        fprintf(json, "      \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
         fprintf(json, "      \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
         fprintf(json, "      \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
         fprintf(json, "      \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
         fprintf(json, "      \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
         fprintf(json, "      \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
         fprintf(json, "      \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
@@ -2738,7 +2779,12 @@ static BOOL WriteXboxDvdSidecarFiles(const char *isoFilename,
     return ok;
 }
 
     return ok;
 }
 
-BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uint32_t totalDiscSectors, bool isDualLayer, bool EjectOnSuccess, xbox_ref_dump_result *result)
+BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat,
+                      uint32_t totalDiscSectors, bool isDualLayer,
+                      bool EjectOnSuccess,
+                      int (*cancel)(void *cancel_data),
+                      void *cancel_data,
+                      xbox_ref_dump_result *result)
 {
     HCRYPTPROV hProv = 0;
     HCRYPTHASH hHash = 0;
 {
     HCRYPTPROV hProv = 0;
     HCRYPTHASH hHash = 0;
@@ -2764,6 +2810,10 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
 
     memset(&sidecarCapture, 0, sizeof(sidecarCapture));
 
 
     memset(&sidecarCapture, 0, sizeof(sidecarCapture));
 
+    g_xbox_dump_cancel = cancel;
+    g_xbox_dump_cancel_data = cancel_data;
+    g_xbox_dump_cancel_result = result;
+
     if (result) {
         result->attempted = 1;
         result->mode = xisoFormat;
     if (result) {
         result->attempted = 1;
         result->mode = xisoFormat;
@@ -2778,15 +2828,18 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
     if (totalDiscSectors == 0)
         totalDiscSectors = REDUMP_SECTORS;
 
     if (totalDiscSectors == 0)
         totalDiscSectors = REDUMP_SECTORS;
 
+    if (XboxDumpCancelRequested())
+    {
+        printf("\n[CANCEL] User cancellation requested before Xbox output preparation.\n");
+        goto cleanup;
+    }
+
     rawTargetSectors = NormalizeRawIsoTargetSectors(totalDiscSectors, isDualLayer);
 
     if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
     rawTargetSectors = NormalizeRawIsoTargetSectors(totalDiscSectors, isDualLayer);
 
     if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
-        return FALSE;
+        goto cleanup;
     if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
     if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
-    {
-        CryptReleaseContext(hProv, 0);
-        return FALSE;
-    }
+        goto cleanup;
 
     EnsureDriveReady(hDevice, 30000);
     SetDriveSpeedMax(hDevice);
 
     EnsureDriveReady(hDevice, 30000);
     SetDriveSpeedMax(hDevice);
@@ -2865,7 +2918,7 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
             rawGameSectors = 0;
             CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
             rawSidecarAvailable = TRUE;
             rawGameSectors = 0;
             CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
             rawSidecarAvailable = TRUE;
-            dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, rawTargetSectors, 0, "RAW", FALSE);
+            dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, rawTargetSectors, 0, "RAW");
         }
         else if (rawTargetSectors == XGD1_FULL_REDUMP_SECTORS)
         {
         }
         else if (rawTargetSectors == XGD1_FULL_REDUMP_SECTORS)
         {
@@ -2906,7 +2959,7 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
             }
 
             printf("[RAW] Writing video L0 from locked source LBA 0..%u.\n", XGD1_VIDEO_L0_SECTORS - 1);
             }
 
             printf("[RAW] Writing video L0 from locked source LBA 0..%u.\n", XGD1_VIDEO_L0_SECTORS - 1);
-            if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, XGD1_VIDEO_L0_SECTORS, 0, "VIDEO-L0", FALSE))
+            if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, XGD1_VIDEO_L0_SECTORS, 0, "VIDEO-L0"))
             {
                 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
                 goto cleanup;
             {
                 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
                 goto cleanup;
@@ -2924,8 +2977,6 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
             Sleep(2000);
             EnsureDriveReady(hDevice, 30000);
             SetDriveSpeedMax(hDevice);
             Sleep(2000);
             EnsureDriveReady(hDevice, 30000);
             SetDriveSpeedMax(hDevice);
-            KickXboxMediaAuth(hDevice);
-            RecoveryKick(hDevice, TRUE);
             CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
             rawSidecarAvailable = TRUE;
 
             CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
             rawSidecarAvailable = TRUE;
 
@@ -2941,20 +2992,27 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
                 printf("[WARN] XDFS was detected at unlocked source LBA %u, not the expected XISO header LBA 32.\n", detectedXdfsLba);
             }
 
                 printf("[WARN] XDFS was detected at unlocked source LBA %u, not the expected XISO header LBA 32.\n", detectedXdfsLba);
             }
 
-            // Build the Redump-style XISO/game region using the same proven
-            // convention as option 2: the first 32 sectors are synthetic XISO
-            // lead-in/padding, and the real XDFS volume begins at source LBA 32.
-            // Do not read unlocked source LBA 0..31 here; on the 8050L path those
-            // LBAs are not the XDFS header sectors we want in the rebuilt image.
+            // FriiDump 0.5.3.13 cache-aligned raw-ID validation proved that
+            // unlocked source LBA 0..31 is the physical 32-sector game-region
+            // lead-in and that XDVDFS begins at source LBA 32. Preserve the
+            // drive-captured lead-in instead of synthesizing zero sectors.
             gameSourceLba = XGD1_GAME_SOURCE_START_LBA;
             gameSectors = REDUMP_SECTORS;
             rawVideoSectors = XGD1_GAME_OUTPUT_START_LBA;
             rawGameSourceLba = gameSourceLba;
             rawGameSectors = gameSectors;
 
             gameSourceLba = XGD1_GAME_SOURCE_START_LBA;
             gameSectors = REDUMP_SECTORS;
             rawVideoSectors = XGD1_GAME_OUTPUT_START_LBA;
             rawGameSourceLba = gameSourceLba;
             rawGameSectors = gameSectors;
 
-            printf("[RAW] Writing %u-sector XISO lead-in/padding at output LBA %u.\n",
-                   XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA);
-            if (!WriteZeroSectorsOutput(outFile, hHash, XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA, "GAME-XISO-LEADIN"))
+            printf("[RAW] Capturing %u-sector game lead-in from unlocked source LBA 0..%u at output LBA %u.\n",
+                   XGD1_XISO_LEADIN_SECTORS,
+                   XGD1_XISO_LEADIN_SECTORS - 1,
+                   XGD1_GAME_OUTPUT_START_LBA);
+            if (!DumpSectorRangeWithRetry(hDevice,
+                                          outFile,
+                                          hHash,
+                                          0,
+                                          XGD1_XISO_LEADIN_SECTORS,
+                                          XGD1_GAME_OUTPUT_START_LBA,
+                                          "GAME-XISO-LEADIN"))
             {
                 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
                 goto cleanup;
             {
                 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
                 goto cleanup;
@@ -2970,8 +3028,7 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
                                           XGD1_GAME_SOURCE_START_LBA,
                                           XGD1_GAME_SOURCE_SECTORS,
                                           XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS,
                                           XGD1_GAME_SOURCE_START_LBA,
                                           XGD1_GAME_SOURCE_SECTORS,
                                           XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS,
-                                          "GAME-XISO",
-                                          TRUE))
+                                          "GAME-XISO"))
             {
                 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
                 goto cleanup;
             {
                 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
                 goto cleanup;
@@ -2999,7 +3056,7 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
             rawVideoSectors = videoSectors;
 
             printf("[RAW] Dumping contiguous visible/video area first: source LBA 0..%u.\n", videoSectors - 1);
             rawVideoSectors = videoSectors;
 
             printf("[RAW] Dumping contiguous visible/video area first: source LBA 0..%u.\n", videoSectors - 1);
-            if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, videoSectors, 0, "VIDEO", FALSE))
+            if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, videoSectors, 0, "VIDEO"))
                 goto cleanup;
 
             printf("[RAW] Re-applying full Xbox handshake after media transition for hidden game/data area.\n");
                 goto cleanup;
 
             printf("[RAW] Re-applying full Xbox handshake after media transition for hidden game/data area.\n");
@@ -3008,8 +3065,6 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
             Sleep(2000);
             EnsureDriveReady(hDevice, 30000);
             SetDriveSpeedMax(hDevice);
             Sleep(2000);
             EnsureDriveReady(hDevice, 30000);
             SetDriveSpeedMax(hDevice);
-            KickXboxMediaAuth(hDevice);
-            RecoveryKick(hDevice, TRUE);
             CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
             rawSidecarAvailable = TRUE;
 
             CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
             rawSidecarAvailable = TRUE;
 
@@ -3025,7 +3080,7 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
             rawGameSectors = gameSectors;
             printf("[RAW] Appending hidden game/data area from unlocked source LBA %u for %u sectors.\n",
                    gameSourceLba, gameSectors);
             rawGameSectors = gameSectors;
             printf("[RAW] Appending hidden game/data area from unlocked source LBA %u for %u sectors.\n",
                    gameSourceLba, gameSectors);
-            dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, gameSourceLba, gameSectors, videoSectors, "GAME", TRUE);
+            dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, gameSourceLba, gameSectors, videoSectors, "GAME");
         }
     }
     else if (xisoFormat == '2')
         }
     }
     else if (xisoFormat == '2')
@@ -3041,8 +3096,6 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
             goto cleanup;
 
         SetDriveSpeedMax(hDevice);
             goto cleanup;
 
         SetDriveSpeedMax(hDevice);
-        KickXboxMediaAuth(hDevice);
-        RecoveryKick(hDevice, TRUE);
 
         vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
 
 
         vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
 
@@ -3104,12 +3157,18 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
         printf("Writing 64KB XISO lead-in padding...\n");
         for (int p = 0; p < 32; p++)
         {
         printf("Writing 64KB XISO lead-in padding...\n");
         for (int p = 0; p < 32; p++)
         {
+            if (XboxDumpCancelRequested())
+            {
+                printf("\n[CANCEL] User cancellation requested during XISO lead-in padding at output LBA %d.\n", p);
+                goto cleanup;
+            }
+
             if (!WriteOutputBytes(outFile, zeroSector, 2048, "XISO-PAD", 0, (uint32_t)p))
                 goto cleanup;
             CryptHashData(hHash, zeroSector, 2048, 0);
         }
 
             if (!WriteOutputBytes(outFile, zeroSector, 2048, "XISO-PAD", 0, (uint32_t)p))
                 goto cleanup;
             CryptHashData(hHash, zeroSector, 2048, 0);
         }
 
-        dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO", TRUE);
+        dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO");
     }
     else
     {
     }
     else
     {
@@ -3230,7 +3289,22 @@ BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uin
 
 cleanup:
     if (outFile)
 
 cleanup:
     if (outFile)
-        fclose(outFile);
+    {
+        if (result && result->cancelled)
+        {
+            if (!FlushAndCommitOutput(outFile, outputLabel))
+                printf("\n[WARN] Could not fully flush the controlled partial %s output before hashing.\n",
+                       outputLabel ? outputLabel : "Xbox");
+        }
+        if (fclose(outFile) != 0)
+        {
+            printf("\n[WARN] fclose failed for partial %s output.\n",
+                   outputLabel ? outputLabel : "Xbox");
+            if (errno)
+                printf("       errno: %d (%s)\n", errno, strerror(errno));
+        }
+        outFile = NULL;
+    }
     if (hHash)
         CryptDestroyHash(hHash);
     if (hProv)
     if (hHash)
         CryptDestroyHash(hHash);
     if (hProv)
@@ -3238,8 +3312,47 @@ cleanup:
     if (result) {
         result->dump_success = dumpOk ? 1 : 0;
         result->elapsed_seconds = (double)(GetTickCount() - operationStartTick) / 1000.0;
     if (result) {
         result->dump_success = dumpOk ? 1 : 0;
         result->elapsed_seconds = (double)(GetTickCount() - operationStartTick) / 1000.0;
-        if (dumpOk && result->output_size == 0)
+        if (result->output_size == 0 && filename && filename[0])
             result->output_size = GetFileSizeBytes64(filename);
             result->output_size = GetFileSizeBytes64(filename);
+        result->completed_sectors = (uint32_t)(result->output_size / 2048ULL);
+
+        if (result->cancelled &&
+            result->output_size > 0 &&
+            !result->hashes_complete)
+        {
+            printf("\n[HASH] Finalizing controlled partial Xbox output hashes...\n");
+            if (CalculateFileHashes(filename,
+                                    crc32String, sizeof(crc32String),
+                                    md5String, sizeof(md5String),
+                                    fileSha1String, sizeof(fileSha1String),
+                                    fileSha256String, sizeof(fileSha256String)))
+            {
+                strncpy(result->crc32, crc32String, sizeof(result->crc32) - 1);
+                result->crc32[sizeof(result->crc32) - 1] = '\0';
+                strncpy(result->md5, md5String, sizeof(result->md5) - 1);
+                result->md5[sizeof(result->md5) - 1] = '\0';
+                strncpy(result->sha1, fileSha1String, sizeof(result->sha1) - 1);
+                result->sha1[sizeof(result->sha1) - 1] = '\0';
+                strncpy(result->sha256, fileSha256String, sizeof(result->sha256) - 1);
+                result->sha256[sizeof(result->sha256) - 1] = '\0';
+                result->hashes_complete =
+                    result->crc32[0] &&
+                    result->md5[0] &&
+                    result->sha1[0] &&
+                    result->sha256[0];
+                if (result->hashes_complete)
+                    printf("[OK] Controlled partial Xbox output hashes captured.\n");
+            }
+            else
+            {
+                printf("[WARN] Controlled partial Xbox output hashing failed.\n");
+            }
+        }
     }
     }
+
+    g_xbox_dump_cancel = NULL;
+    g_xbox_dump_cancel_data = NULL;
+    g_xbox_dump_cancel_result = NULL;
+
     return dumpOk;
 }
     return dumpOk;
 }