#include "xbe_cert.h"
#include "unlock.h"
#include "xbox_ref_log.h"
+#include "../xbox_ref_bridge.h"
#include <time.h>
#include <windows.h>
#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];
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;
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;
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)
{
- 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;
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))
break;
}
- RecoveryKick(hDevice, authRecovery);
Sleep(500);
}
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))
break;
}
- RecoveryKick(hDevice, authRecovery);
Sleep(500);
}
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))
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
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, " \"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, " \"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, " }\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, " \"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, " ],\n");
fprintf(json, " \"auth\": {\n");
- fprintf(json, " \"requires_media_transition\": true,\n");
+ fprintf(json, " \"requires_media_transition\": false,\n");
+ fprintf(json, " \"unlock_requires_media_transition\": false,\n");
+ fprintf(json, " \"locked_video_view_restore_requires_media_transition\": true,\n");
+ fprintf(json, " \"state_detection\": \"READ CAPACITY (10) visible-sector count\",\n");
fprintf(json, " \"mode_page\": \"0x3E\",\n");
fprintf(json, " \"challenge_table_source\": \"read_dvd_structure_xbox_control_block\",\n");
fprintf(json, " \"challenge_table_response_offset\": 774,\n");
return ok;
}
-void DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uint32_t totalDiscSectors, bool isDualLayer, bool EjectOnSuccess)
+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;
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 (filename && filename[0]) {
+ strncpy(result->output_path, filename, sizeof(result->output_path) - 1);
+ result->output_path[sizeof(result->output_path) - 1] = '\0';
+ }
+ }
+
if (totalDiscSectors == 0)
totalDiscSectors = GetTotalSectors(hDevice);
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))
- return;
+ goto cleanup;
if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
- {
- CryptReleaseContext(hProv, 0);
- return;
- }
+ goto cleanup;
EnsureDriveReady(hDevice, 30000);
SetDriveSpeedMax(hDevice);
outputLabel = "RAW ISO";
expectedOutputBytes = (unsigned long long)rawTargetSectors * 2048ULL;
+ if (result)
+ result->output_sectors = rawTargetSectors;
if (rawTargetSectors != totalDiscSectors)
{
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)
{
}
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;
Sleep(2000);
EnsureDriveReady(hDevice, 30000);
SetDriveSpeedMax(hDevice);
- KickXboxMediaAuth(hDevice);
- RecoveryKick(hDevice, TRUE);
CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
rawSidecarAvailable = TRUE;
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;
- 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;
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;
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");
Sleep(2000);
EnsureDriveReady(hDevice, 30000);
SetDriveSpeedMax(hDevice);
- KickXboxMediaAuth(hDevice);
- RecoveryKick(hDevice, TRUE);
CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
rawSidecarAvailable = TRUE;
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')
goto cleanup;
SetDriveSpeedMax(hDevice);
- KickXboxMediaAuth(hDevice);
- RecoveryKick(hDevice, TRUE);
vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
outputLabel = "XISO";
expectedOutputBytes = ((unsigned long long)sectorsToRead + 32ULL) * 2048ULL;
+ if (result)
+ result->output_sectors = sectorsToRead + 32U;
printf("[SUCCESS] Final XISO target: %u sectors plus 32-sector lead-in (%llu bytes).\n",
sectorsToRead,
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);
}
- dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO", TRUE);
+ dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO");
}
else
{
printf("\n[WARN] Could not calculate CRC32/MD5/SHA-1/SHA-256 from finalized output file.\n");
}
+ if (result) {
+ result->output_size = GetFileSizeBytes64(filename);
+ 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, sha1String, 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 (xisoFormat == '1')
{
printf("Final RAW ISO Sector Count: %u\n", rawTargetSectors);
FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr);
printf("\nOperation Complete! Total elapsed: %s\n", operationTimeStr);
- StopDriveUnit(hDevice);
-
+ /* The caller owns final drive cleanup. Embedded FriiDump runs issue one
+ * STOP UNIT after Redump verification; standalone bridge runs stop the
+ * drive in xbox_ref_gdr8050l_dump_core() before closing the handle. */
if (EjectOnSuccess)
ControlTray(hDevice, TRUE);
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)
CryptReleaseContext(hProv, 0);
+ if (result) {
+ result->dump_success = dumpOk ? 1 : 0;
+ result->elapsed_seconds = (double)(GetTickCount() - operationStartTick) / 1000.0;
+ if (result->output_size == 0 && filename && filename[0])
+ 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;
}