2 #include "scsi_structs.h"
5 #include "xbox_ref_log.h"
6 #include "../xbox_ref_bridge.h"
20 #pragma comment(lib, "advapi32.lib")
23 #define PROV_RSA_AES 24
25 #ifndef ALG_SID_SHA_256
26 #define ALG_SID_SHA_256 12
29 #define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
32 #define printf xbox_ref_printf
34 #define GDR_8163B OL23
36 /* The copied Xbox dumper is single-threaded. FriiDump installs one
37 * per-invocation cancellation callback before entering the copied path and
38 * clears it during cleanup. */
39 static int (*g_xbox_dump_cancel)(void *cancel_data) = NULL;
40 static void *g_xbox_dump_cancel_data = NULL;
41 static xbox_ref_dump_result *g_xbox_dump_cancel_result = NULL;
43 static BOOL XboxDumpCancelRequested(void)
45 if (!g_xbox_dump_cancel || !g_xbox_dump_cancel(g_xbox_dump_cancel_data))
48 if (g_xbox_dump_cancel_result)
49 g_xbox_dump_cancel_result->cancelled = 1;
54 HANDLE OpenDrive(char driveLetter)
57 snprintf(devicePath, sizeof(devicePath), "\\\\.\\%c:", driveLetter);
59 HANDLE hDevice = CreateFileA(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
63 void CloseDrive(HANDLE hDevice)
65 if (hDevice && hDevice != INVALID_HANDLE_VALUE)
69 int IsDiscPresent(HANDLE hDevice)
72 return DeviceIoControl(hDevice, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, &bytesReturned, NULL);
75 void ControlTray(HANDLE hDevice, BOOL eject)
77 SCSI_PASS_THROUGH_DIRECT sptd;
79 memset(&sptd, 0, sizeof(sptd));
81 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
83 sptd.TimeOutValue = 10;
84 sptd.Cdb[0] = 0x1B; // START STOP UNIT
88 printf("Software Ejecting tray...\n");
89 sptd.Cdb[4] = 0x02; // Power Action: Eject
93 printf("Software Closing tray...\n");
94 sptd.Cdb[4] = 0x03; // Power Action: Load
96 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &returned, NULL))
98 printf("Tray %s successful.\n", eject ? "eject" : "close");
102 DWORD err = GetLastError();
103 printf("Failed to %s tray. Error: %lu\n", eject ? "eject" : "close", err);
105 if (err == ERROR_ACCESS_DENIED)
107 printf("Hint: Ensure no other program is locking the drive.\n");
112 BOOL TestUnitReady(HANDLE hDevice)
114 SCSI_PASS_THROUGH_DIRECT sptd;
116 memset(&sptd, 0, sizeof(sptd));
118 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
120 sptd.TimeOutValue = 10;
121 sptd.DataTransferLength = 0;
122 sptd.DataBuffer = NULL;
124 // TEST UNIT READY. This is our practical poll for "ready/spun up".
125 // Many drives do not expose a literal spindle-state bit to normal host software;
126 // after STOP UNIT, TEST UNIT READY should fail until the unit is ready again.
129 if (!DeviceIoControl(hDevice,
130 IOCTL_SCSI_PASS_THROUGH_DIRECT,
141 return (sptd.ScsiStatus == 0);
144 BOOL StartDriveUnit(HANDLE hDevice)
146 SCSI_PASS_THROUGH_DIRECT sptd;
148 memset(&sptd, 0, sizeof(sptd));
150 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
152 sptd.TimeOutValue = 30;
153 sptd.DataTransferLength = 0;
154 sptd.DataBuffer = NULL;
156 // START STOP UNIT, START=1, LOEJ=0.
157 // This requests spin-up/start without ejecting/loading the tray.
161 printf("Sending SCSI START UNIT / spin-up command...\n");
163 if (DeviceIoControl(hDevice,
164 IOCTL_SCSI_PASS_THROUGH_DIRECT,
172 printf("SCSI START UNIT / spin-up command accepted.\n");
177 DWORD err = GetLastError();
178 printf("[WARN] SCSI START UNIT / spin-up failed. Error: %lu\n", err);
183 BOOL EnsureDriveReady(HANDLE hDevice, DWORD timeoutMs)
185 DWORD startTick = GetTickCount();
186 BOOL startIssued = FALSE;
188 printf("Polling drive readiness with TEST UNIT READY...\n");
192 if (TestUnitReady(hDevice))
194 printf("Drive reports ready.\n");
200 printf("Drive is not ready/spun up yet; requesting START UNIT.\n");
201 StartDriveUnit(hDevice);
205 if ((GetTickCount() - startTick) >= timeoutMs)
207 printf("[WARN] Drive did not report ready within %lu ms.\n", (unsigned long)timeoutMs);
208 printf(" Continuing may fail if the unit is still spun down or still reading lead-in.\n");
216 BOOL StopDriveUnit(HANDLE hDevice)
218 SCSI_PASS_THROUGH_DIRECT sptd;
220 memset(&sptd, 0, sizeof(sptd));
222 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
224 sptd.TimeOutValue = 30;
225 sptd.DataTransferLength = 0;
226 sptd.DataBuffer = NULL;
228 // START STOP UNIT, START=0, LOEJ=0.
229 // This requests a normal stop/spin-down without ejecting or loading the tray.
233 printf("Sending SCSI STOP UNIT / spin-down command...\n");
235 if (DeviceIoControl(hDevice,
236 IOCTL_SCSI_PASS_THROUGH_DIRECT,
244 printf("SCSI STOP UNIT / spin-down successful.\n");
249 DWORD err = GetLastError();
250 printf("[WARN] SCSI STOP UNIT / spin-down failed. Error: %lu\n", err);
251 printf(" Dump output has already been finalized; this only affects drive spin state.\n");
256 void AutomateTrayCycle(HANDLE hDevice)
258 ControlTray(hDevice, TRUE);
259 Sleep(3000); // Give the tray time to fully extend
262 ControlTray(hDevice, FALSE);
263 printf("Waiting for disc spin-up/readiness after tray close...\n");
264 if (EnsureDriveReady(hDevice, 45000))
266 // Small settle period after readiness so the drive can finish lead-in/media-change bookkeeping.
271 // Preserve the old conservative behavior if TEST UNIT READY polling never succeeds.
272 printf("[WARN] Falling back to fixed 10s post-close settle delay.\n");
277 BOOL SetDriveSpeedMax(HANDLE hDevice)
279 SCSI_PASS_THROUGH_DIRECT sptd = {0};
280 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
284 sptd.CdbLength = 12; // 12-byte CDB for 0xBB
285 sptd.DataIn = SCSI_IOCTL_DATA_OUT;
286 sptd.TimeOutValue = 10;
287 sptd.DataBuffer = NULL;
288 sptd.DataTransferLength = 0;
290 // CDB 0xBB: [0] Opcode, [2-3] Read Speed, [4-5] Write Speed
292 sptd.Cdb[2] = 0xFF; // MSB
293 sptd.Cdb[3] = 0xFF; // LSB
294 sptd.Cdb[4] = 0xFF; // MSB
295 sptd.Cdb[5] = 0xFF; // LSB
298 return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT,
299 &sptd, sizeof(sptd), &sptd, sizeof(sptd),
303 void ForceMediaRefresh(HANDLE hDevice)
307 // Lock the volume so Windows stops background polling
308 DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
310 // Force the storage stack to re-read the Partition Table/Capacity
311 // without sending an Eject command to the hardware.
312 if (DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL))
314 printf("Windows Partition Stack refreshed silently.\n");
317 // Explicitly dismount to kill the "Video DVD" file system driver (UDFS/ISO9660)
318 DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
322 void HexDump(unsigned char *buffer, uint32_t size)
324 for (uint32_t i = 0; i < size; i++)
327 printf("\n%04X: ", i);
328 printf("%02X ", buffer[i]);
333 void outputdata(const uint8_t *buf, uint32_t lines)
335 for (uint32_t j = 0; j < lines; j++)
337 for (uint32_t k = 0; k < 16; k++)
339 uint32_t idx = j * 16 + k;
342 printf("%02X ", buf[idx]);
348 uint8_t chksum8(const unsigned char *buff, size_t len) {
349 unsigned int sum = 0;
350 for (sum = 0; len != 0; len--)
355 void FormatElapsedTime(DWORD dwMilliseconds, char *outStr)
357 uint32_t totalSeconds = dwMilliseconds / 1000;
358 uint32_t hours = totalSeconds / 3600;
359 uint32_t minutes = (totalSeconds % 3600) / 60;
360 uint32_t seconds = totalSeconds % 60;
362 sprintf(outStr, "%02u:%02u:%02u", hours, minutes, seconds);
365 void PrintFormattedCapacity(unsigned char *scsibuffer)
367 // The first 4 bytes are the Last Logical Block Address (Big Endian)
368 uint32_t maxLBA = (scsibuffer[0] << 24) | (scsibuffer[1] << 16) |
369 (scsibuffer[2] << 8) | scsibuffer[3];
371 // The next 4 bytes are the Block Length (Big Endian)
372 uint32_t blockLen = (scsibuffer[4] << 24) | (scsibuffer[5] << 16) |
373 (scsibuffer[6] << 8) | scsibuffer[7];
375 // Total bytes = (MaxLBA + 1) * BlockLen
376 // Use double for the math to avoid 32-bit integer overflow
377 double totalBytes = (double)(maxLBA + 1) * blockLen;
378 double totalGB = totalBytes / (1024.0 * 1024.0 * 1024.0);
380 printf("--------------------------------------------\n");
381 printf("Drive Capacity Details:\n");
382 printf(" Total Sectors: %u\n", maxLBA + 1);
383 printf(" Sector Size: %u bytes\n", blockLen);
384 printf(" Total Size: %.2f GB\n", totalGB);
385 printf("--------------------------------------------\n");
388 void ListOpticalDrives()
390 DWORD drives = GetLogicalDrives();
391 char rootPath[] = "A:\\";
392 char devicePath[] = "\\\\.\\A:";
395 printf("%-5s %-12s %-18s %-15s %s\n", "ID", "Vendor", "Model", "Volume Label", "Status");
396 printf("-------------------------------------------------------------------------------\n");
398 uint8_t driveCount = 0;
399 for (int i = 0; i < 26; i++)
401 if (drives & (1 << i))
403 rootPath[0] = 'A' + i;
405 if (GetDriveTypeA(rootPath) == DRIVE_CDROM)
407 devicePath[4] = 'A' + i;
409 // 1. Get Hardware Info (Vendor/Model)
410 char vendorStr[16] = "Generic";
411 char productStr[21] = "Unknown";
413 HANDLE h = CreateFileA(devicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
414 NULL, OPEN_EXISTING, 0, NULL);
416 if (h != INVALID_HANDLE_VALUE)
418 STORAGE_PROPERTY_QUERY query = {0};
419 query.PropertyId = StorageDeviceProperty;
420 query.QueryType = PropertyStandardQuery;
423 if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query),
424 buffer, sizeof(buffer), &bytes, NULL))
426 PSTORAGE_DEVICE_DESCRIPTOR desc = (PSTORAGE_DEVICE_DESCRIPTOR)buffer;
427 if (desc->VendorIdOffset)
428 strcpy(vendorStr, (char *)(buffer + desc->VendorIdOffset));
429 if (desc->ProductIdOffset)
430 strcpy(productStr, (char *)(buffer + desc->ProductIdOffset));
435 // 2. Get Volume Info (Disc Label)
436 char volumeName[MAX_PATH + 1] = {0};
437 char statusStr[20] = "No Disc";
439 if (GetVolumeInformationA(rootPath, volumeName, sizeof(volumeName),
440 NULL, NULL, NULL, NULL, 0))
442 if (strlen(volumeName) == 0)
443 strcpy(volumeName, "[No Label]");
444 strcpy(statusStr, "Ready");
447 printf(" %c: %-12.12s %-18.18s %-15.15s %s\n",
448 rootPath[0], vendorStr, productStr, volumeName, statusStr);
454 printf("No optical drives found.\n");
455 printf("-------------------------------------------------------------------------------\n");
456 printf("Total Optical Drives Found: %u\n", driveCount);
459 uint32_t GetTotalSectors(HANDLE hDevice)
461 typedef struct _SCSI_PASS_THROUGH_WITH_BUFFERS
463 SCSI_PASS_THROUGH spt;
464 unsigned char ucDataBuf[8]; // Buffer for the 8-byte READ CAPACITY result
465 } SCSI_PASS_THROUGH_WITH_BUFFERS;
467 SCSI_PASS_THROUGH_WITH_BUFFERS sptwb = {0};
469 sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
470 sptwb.spt.CdbLength = 10; // READ CAPACITY (10) is a 10-byte command
471 sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
472 sptwb.spt.DataTransferLength = 8;
473 sptwb.spt.TimeOutValue = 2; // 2 second timeout
474 sptwb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf);
476 // CDB 0x25 = READ CAPACITY (10)
477 sptwb.spt.Cdb[0] = 0x25;
480 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH,
481 &sptwb, sizeof(sptwb),
482 &sptwb, sizeof(sptwb),
483 &bytesReturned, NULL))
486 // Extract Max LBA (Big Endian) from the first 4 bytes
487 uint32_t maxLBA = (sptwb.ucDataBuf[0] << 24) |
488 (sptwb.ucDataBuf[1] << 16) |
489 (sptwb.ucDataBuf[2] << 8) |
495 return 0; // Return 0 on failure
498 uint32_t GetXboxPhysicalSectors(HANDLE hDevice)
500 SCSI_PASS_THROUGH_DIRECT sptd = {0};
501 unsigned char buffer[2048] = {0};
504 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
506 sptd.DataIn = SCSI_IOCTL_DATA_IN;
507 sptd.DataTransferLength = 2048;
508 sptd.TimeOutValue = 10;
509 sptd.DataBuffer = buffer;
511 // READ DVD STRUCTURE (0xAD)
513 sptd.Cdb[7] = 0x00; // Format: Physical Format Information
514 sptd.Cdb[8] = 0x08; // Allocation Length (MSB)
515 sptd.Cdb[9] = 0x00; // Allocation Length (LSB)
517 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
519 // Bytes 13-15 of the PFI contain the End LBA of the data area
520 uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15];
522 // For Xbox discs, we add 1 to the End LBA to get the total count
523 // and add the 32 sectors of lead-in padding we manually create.
527 // Fallback for Dual Layer if command fails
531 // Forces Windows to re-evaluate the drive without ejecting the tray
532 void RefreshVolume(HANDLE hDevice)
535 printf("Refreshing Volume Stack (Quiet Mode)...\n");
536 // Only update properties; do NOT dismount as it resets the GDR-8163B state.
537 DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL);
538 Sleep(1000); // Essential for the firmware to re-index after the OS check
541 void ListDirectoryRecursive(HANDLE hDevice, uint32_t lba, uint32_t size, int level)
543 if (size == 0 || level > 10)
544 return; // Prevent infinite recursion
546 uint32_t sectorsToRead = (size + 2047) / 2048;
547 unsigned char *dirBuffer = (unsigned char *)VirtualAlloc(NULL, sectorsToRead * 2048, MEM_COMMIT, PAGE_READWRITE);
551 if (ScsiReadSectors(hDevice, lba, (uint16_t)sectorsToRead, dirBuffer))
554 while (offset < size)
556 XDFS_DIR_ENTRY *entry = (XDFS_DIR_ENTRY *)&dirBuffer[offset];
558 // --- SANITY CHECK 1: End of Table ---
559 // If FileNameLength is 0 or 0xFF, we've hit the padding/end of the list.
560 if (entry->FileNameLength == 0 || entry->FileNameLength == 0xFF)
563 // --- SANITY CHECK 2: Buffer Overflow ---
564 // Ensure the entry doesn't claim to exist past our allocated buffer.
565 if (offset + 14 + entry->FileNameLength > size)
568 // --- SANITY CHECK 3: Character Validation ---
569 // If the first character isn't a printable ASCII, it's a glitch entry.
570 if (entry->FileName[0] < 32 || entry->FileName[0] > 126)
574 for (int i = 0; i < level; i++)
578 if (entry->Attributes & 0x10)
587 // Print Filename safely
588 for (int i = 0; i < entry->FileNameLength; i++)
590 char c = entry->FileName[i];
591 if (c >= 32 && c <= 126)
594 printf("?"); // Replace glitches with a placeholder
597 if (!(entry->Attributes & 0x10))
599 printf(" (%u bytes)", entry->FileSize);
603 // RECURSION: Only dive if it's a valid directory LBA
604 if ((entry->Attributes & 0x10) && entry->StartLBA > 0x100)
606 ListDirectoryRecursive(hDevice, entry->StartLBA, entry->FileSize, level + 1);
609 // Move to next entry (4-byte alignment)
610 uint32_t nextOffset = (14 + entry->FileNameLength + 3) & ~3;
612 // If the calculation gives us 0, we're stuck in an infinite loop; break.
615 offset += nextOffset;
619 VirtualFree(dirBuffer, 0, MEM_RELEASE);
622 void ReadXboxGameDir(HANDLE hDevice)
624 // Single buffer for the Volume Descriptor read
625 unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
629 // Read XDFS Volume Descriptor at Sector 0x20
630 if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer))
632 printf("Error: Could not read XDFS Volume Descriptor.\n");
633 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
637 // Map the descriptor and extract root location/size
638 XDFS_VOLUME_DESCRIPTOR *vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
639 uint32_t rootLba = vol->RootLBA;
640 uint32_t rootSize = vol->RootSize;
642 // We no longer need this buffer once we have the Root LBA/Size
643 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
645 // Draw the recursive tree
646 printf("\n--- XDFS FILE SYSTEM TREE ---\n");
650 ListDirectoryRecursive(hDevice, rootLba, rootSize, 0);
654 printf("Error: Invalid Root LBA.\n");
657 printf("------------------------------\n");
660 void SanitizeFilename(char *filename)
662 if (!filename || filename[0] == '\0')
667 int lastWasSpace = 1; // Using 1 for true to trim leading spaces
669 while (filename[readIndex] != '\0')
671 unsigned char c = (unsigned char)filename[readIndex];
673 // Whitelist: Only allow Letters (isalnum) and Spaces
674 // This strips ! ' ? : " / \ | * < > and non-printable characters
675 if (isalnum(c) || c == ' ')
678 // Collapse Multiple Spaces
683 filename[writeIndex++] = ' ';
689 // It's a letter or number, write it normally
690 filename[writeIndex++] = c;
697 // Null-terminate the new shorter string
698 filename[writeIndex] = '\0';
700 // Remove trailing space if one exists
701 if (writeIndex > 0 && filename[writeIndex - 1] == ' ')
703 filename[writeIndex - 1] = '\0';
707 BOOL ScsiReadSectors(HANDLE hDevice, uint32_t lba, uint16_t count, unsigned char *buffer)
709 SCSI_PASS_THROUGH_DIRECT sptd = {0};
712 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
715 sptd.DataTransferLength = count * 2048;
716 sptd.TimeOutValue = 30;
717 sptd.DataBuffer = buffer;
719 sptd.Cdb[0] = 0x28; // READ(10)
720 sptd.Cdb[2] = (lba >> 24) & 0xFF;
721 sptd.Cdb[3] = (lba >> 16) & 0xFF;
722 sptd.Cdb[4] = (lba >> 8) & 0xFF;
723 sptd.Cdb[5] = lba & 0xFF;
724 sptd.Cdb[7] = (unsigned char)((count >> 8) & 0xFF);
725 sptd.Cdb[8] = (unsigned char)(count & 0xFF);
727 return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL);
730 XboxGameInfo GetXboxGameInfo(HANDLE hDevice)
733 memset(&info, 0, sizeof(XboxGameInfo));
734 unsigned char sectorBuffer[2048];
736 // Get Volume Descriptor (LBA 0x20)
737 if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer))
740 // Verify XDFS Magic "XGD2" or "MICROSOFT*XBOX*MEDIA"
741 if (memcmp(sectorBuffer, "MICROSOFT", 9) != 0)
743 return (XboxGameInfo){.TitleName = "Not_XDFS"};
746 uint32_t rootLba = *(uint32_t *)§orBuffer[0x14];
747 uint32_t rootSize = *(uint32_t *)§orBuffer[0x18];
749 uint32_t rawVolumeSize = *(uint32_t *)§orBuffer[0x1C];
750 // Assign to the 64-bit member (cast to ensure no weird sign extension)
751 info.TotalSizeBytes = (uint64_t)rawVolumeSize;
753 // Read Root Directory (Scanning multiple sectors for default.xbe)
754 uint32_t sectorsToRead = (rootSize + 2047) / 2048;
755 for (uint32_t s = 0; s < sectorsToRead; s++)
757 unsigned char dirBuffer[2048];
758 if (!ScsiReadSectors(hDevice, rootLba + s, 1, dirBuffer))
762 while (offset < 2030)
764 uint16_t leftNode = *(uint16_t *)&dirBuffer[offset];
765 if (leftNode == 0xFFFF)
766 break; // End of directory
768 uint32_t startLba = *(uint32_t *)&dirBuffer[offset + 4];
769 uint8_t nameLen = dirBuffer[offset + 13];
770 char *name = (char *)&dirBuffer[offset + 14];
775 // Match "default.xbe"
776 if (nameLen == 11 && _strnicmp(name, "default.xbe", 11) == 0)
778 unsigned char xbeHeader[2048];
779 if (ScsiReadSectors(hDevice, startLba, 1, xbeHeader))
782 if (*(uint32_t *)xbeHeader != 0x48454258)
785 // 4. Locate Certificate
786 uint32_t baseVA = *(uint32_t *)&xbeHeader[0x104];
787 uint32_t certVA = *(uint32_t *)&xbeHeader[0x118];
788 uint32_t fileOffset = certVA - baseVA;
790 // Certificate might be in a later sector of the XBE file
791 uint32_t certSector = startLba + (fileOffset / 2048);
792 uint32_t innerOff = (fileOffset % 2048);
794 unsigned char certBuffer[2048];
795 if (ScsiReadSectors(hDevice, certSector, 1, certBuffer))
798 // Populate the Struct from the Certificate
799 info.TitleId = *(uint32_t *)&certBuffer[innerOff + 0x008];
800 info.AllowedMedia = *(uint32_t *)&certBuffer[innerOff + 0x09C];
801 info.GameRegion = *(uint32_t *)&certBuffer[innerOff + 0x0A0];
802 info.GameRatings = *(uint32_t *)&certBuffer[innerOff + 0x0A4];
803 info.DiscNumber = *(uint32_t *)&certBuffer[innerOff + 0x0A8];
804 info.Version = *(uint32_t *)&certBuffer[innerOff + 0x0AC];
806 // Convert UTF-16 Title Name (at 0x00C) to ASCII
807 for (int i = 0; i < 40; i++)
809 char c = certBuffer[innerOff + 0x00C + (i * 2)];
812 info.TitleName[i] = c;
820 offset += (14 + nameLen + 3) & ~3; // XDFS Alignment
824 return info; // Success will be 0 if we never found default.xbe or failed to read the cert
827 void DisplayXboxGameInfo(XboxGameInfo info)
831 printf("Error: Could not retrieve Xbox game information.\n");
835 printf("\n--- Xbox Game Information ---\n");
836 printf("Title Name: %s\n", info.TitleName);
837 printf("Title ID: 0x%08X\n", info.TitleId);
838 printf("Version: %u\n", info.Version);
839 printf("Disc Number: %u\n", info.DiscNumber);
843 if (info.GameRegion & XB_REGION_MANUFACTURING)
844 printf("[Manufacturing] ");
845 if (info.GameRegion & XB_REGION_US_CANADA)
846 printf("North America ");
847 if (info.GameRegion & XB_REGION_JAPAN)
849 if (info.GameRegion & XB_REGION_EUROPE_AU_NZ)
850 printf("Europe/AU ");
851 if (info.GameRegion & XB_REGION_REST_OF_WORLD)
852 printf("Rest of World ");
854 // If everything is set (0x7FFFFFFF or 0xFFFFFFFF), it's Region Free
855 if ((info.GameRegion & 0x7FFFFFFF) == 0x7FFFFFFF)
857 printf("(Region Free)");
859 else if (info.GameRegion == 0)
861 printf("None (Locked)");
865 // Decode Media Types
866 printf("Allowed Media: ");
867 if (info.AllowedMedia & XB_MEDIA_HARD_DRIVE)
869 if (info.AllowedMedia & XB_MEDIA_DVD_X2)
871 if (info.AllowedMedia & XB_MEDIA_DVD_5_RO)
873 if (info.AllowedMedia & XB_MEDIA_DVD_9_RO)
875 if (info.AllowedMedia & XB_MEDIA_CD)
877 if (info.AllowedMedia & XB_MEDIA_DONGLE)
878 printf("Memory_Unit ");
881 DisplayXboxRating(info.GameRatings);
883 printf("-----------------------------\n");
886 void DisplayXboxRating(uint32_t ratings)
888 // ESRB (North America) - Byte 0 (Bits 0-7)
889 uint8_t esrb = (uint8_t)(ratings & 0xFF);
890 if (esrb != 0 && esrb != 0xFF)
892 printf("ESRB Rating: ");
896 printf("EC (Early Childhood)\n");
899 printf("E (Everyone)\n");
902 printf("K-A (Kids to Adults)\n");
905 printf("T (Teen)\n");
908 printf("M (Mature)\n");
911 printf("AO (Adults Only)\n");
914 printf("RP (Rating Pending/Unrated)\n");
919 // PEGI (Europe) - Byte 1 (Bits 8-15)
920 uint8_t pegi = (uint8_t)((ratings >> 8) & 0xFF);
921 if (pegi != 0 && pegi != 0xFF)
923 printf("PEGI Rating: ");
942 printf("Other (0x%02X)\n", pegi);
947 // CERO (Japan) - Byte 2 (Bits 16-23)
948 uint8_t cero = (uint8_t)((ratings >> 16) & 0xFF);
949 if (cero != 0 && cero != 0xFF)
951 printf("CERO Rating: ");
955 printf("A (All Ages)\n");
967 printf("Z (18+ Only)\n");
970 printf("Other (0x%02X)\n", cero);
975 if ((ratings & 0x00FFFFFF) == 0)
977 printf("Rating: None/Unrated\n");
981 // --- POST-DUMP VERIFICATION ---
982 void PrintGamePartitionHash(const char *filename)
984 FILE *f = fopen(filename, "rb");
986 uint32_t startLba = START_LBA_MAGIC;
987 unsigned long long bytesRemaining = 0ULL;
988 unsigned long long totalBytesToHash = 0ULL;
989 unsigned long long bytesDone = 0ULL;
992 HCRYPTPROV hProv = 0;
993 HCRYPTHASH hHash = 0;
996 char finalHash[41] = {0};
998 DWORD lastPrintTick = 0;
1000 DWORD elapsedMs = 0;
1002 char timeStr[12] = {0};
1003 char etaStr[12] = {0};
1008 if (_fseeki64(f, 0, SEEK_END) != 0)
1013 fileBytes = _ftelli64(f);
1020 if ((unsigned long long)fileBytes == (unsigned long long)XGD1_FULL_REDUMP_SECTORS * 2048ULL)
1022 startLba = XGD1_GAME_OUTPUT_START_LBA;
1023 bytesRemaining = (unsigned long long)REDUMP_SECTORS * 2048ULL;
1024 printf("[HASH] Calculating Game/XISO-region SHA-1 (Redump-style output LBA %u, %u sectors)...\n",
1025 startLba, REDUMP_SECTORS);
1029 startLba = START_LBA_MAGIC;
1030 bytesRemaining = ((unsigned long long)fileBytes > (unsigned long long)startLba * 2048ULL)
1031 ? ((unsigned long long)fileBytes - (unsigned long long)startLba * 2048ULL)
1033 printf("[HASH] Calculating Game-Partition-Only SHA-1 (legacy contiguous output LBA %u)...\n", startLba);
1036 totalBytesToHash = bytesRemaining;
1037 if (bytesRemaining == 0)
1043 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
1048 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
1050 CryptReleaseContext(hProv, 0);
1055 _fseeki64(f, (__int64)startLba * 2048, SEEK_SET);
1057 vBuf = (unsigned char *)malloc(1024 * 1024); // 1MB buffer
1060 CryptDestroyHash(hHash);
1061 CryptReleaseContext(hProv, 0);
1066 startTick = GetTickCount();
1067 lastPrintTick = startTick;
1069 while (bytesRemaining > 0 && (read = fread(vBuf, 1, (bytesRemaining > 1024ULL * 1024ULL) ? 1024 * 1024 : (size_t)bytesRemaining, f)) > 0)
1071 CryptHashData(hHash, vBuf, (DWORD)read, 0);
1072 bytesRemaining -= read;
1073 bytesDone += (unsigned long long)read;
1075 nowTick = GetTickCount();
1076 if (bytesDone >= totalBytesToHash || (nowTick - lastPrintTick) >= 1000)
1078 double percent = ((double)bytesDone / (double)totalBytesToHash) * 100.0;
1079 double mbDone = (double)bytesDone / (1024.0 * 1024.0);
1081 elapsedMs = nowTick - startTick;
1083 speed = mbDone / ((double)elapsedMs / 1000.0);
1084 etaMs = (bytesDone > 0 && elapsedMs > 0)
1085 ? (DWORD)(((double)elapsedMs / (double)bytesDone) * (double)(totalBytesToHash - bytesDone))
1087 FormatElapsedTime(elapsedMs, timeStr);
1088 FormatElapsedTime(etaMs, etaStr);
1089 xbox_ref_console_printf("\r[HASH] Game/XISO-region: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ",
1096 lastPrintTick = nowTick;
1100 CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0);
1101 for (int i = 0; i < 20; i++)
1102 sprintf(&finalHash[i * 2], "%02x", rgbHash[i]);
1104 elapsedMs = GetTickCount() - startTick;
1105 FormatElapsedTime(elapsedMs, timeStr);
1106 if (totalBytesToHash > 0)
1107 xbox_ref_console_printf("\r[HASH] Game/XISO-region: 100.0%% | %.1f MB | Time: %s \n",
1108 (double)totalBytesToHash / (1024.0 * 1024.0),
1110 printf("Game/XISO-region SHA-1: %s\n", finalHash);
1111 printf("[OK] Game/XISO-region SHA-1 complete in %s.\n", timeStr);
1114 CryptDestroyHash(hHash);
1115 CryptReleaseContext(hProv, 0);
1119 void GetMediaID(HANDLE hDevice, char *outMediaId)
1121 SCSI_PASS_THROUGH_DIRECT sptd = {0};
1122 unsigned char buffer[2048] = {0};
1123 DWORD bytesReturned;
1125 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
1126 sptd.CdbLength = 12;
1127 sptd.DataIn = SCSI_IOCTL_DATA_IN;
1128 sptd.DataTransferLength = 2048;
1129 sptd.TimeOutValue = 5;
1130 sptd.DataBuffer = buffer;
1132 sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE
1133 sptd.Cdb[7] = 0x04; // Format: Disc Manufacturing Information (DMI)
1137 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
1139 // The Media ID is typically 32 bytes starting at offset 4 in the DMI
1140 // Offset 8 is where "MS11..." usually starts on Xbox discs
1141 // We'll grab 16 characters to be safe
1143 for (int i = 8; i < 24; i++)
1145 // Only add alphanumeric characters to keep the filename clean
1146 if (isalnum(buffer[i]))
1148 outMediaId[writePos++] = buffer[i];
1151 outMediaId[writePos] = '\0'; // Null terminate the string
1155 strcpy(outMediaId, "UNKNOWN_ID");
1159 void GetDiscMetadata(HANDLE hDevice, uint32_t *totalSectors, bool *isDualLayer, XboxGameInfo *gameInfo)
1161 SCSI_PASS_THROUGH_DIRECT sptd = {0};
1162 unsigned char buffer[2048] = {0};
1163 DWORD bytesReturned;
1165 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
1166 sptd.CdbLength = 12;
1167 sptd.DataIn = SCSI_IOCTL_DATA_IN;
1168 sptd.DataTransferLength = 2048;
1169 sptd.TimeOutValue = 10;
1170 sptd.DataBuffer = buffer;
1172 sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE
1173 sptd.Cdb[7] = 0x00; // Physical Format Information
1174 sptd.Cdb[8] = 0x08; // 2048 bytes
1177 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
1180 // Byte 12: bits 5-6 (Number of Layers)
1181 // 0x20 = 00100000 (Two layers), 0x00 = 00000000 (One layer)
1182 unsigned char layerInfo = (buffer[12] >> 5) & 0x03;
1183 *isDualLayer = (layerInfo > 0);
1185 // Bytes 13-15: End LBA
1186 uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15];
1188 // XBOX SANITY CHECK
1189 // If the drive reports a value much larger than a standard Xbox Dual Layer (3.4M sectors)
1190 // it means the drive is reporting the raw DVD-9 limit. We must cap it.
1191 if (endLba > ((uint32_t)REDUMP_SECTORS - 1))
1193 printf("[!] Drive reported Raw DVD-9 geometry. Normalizing to Xbox Dual Layer...\n");
1194 if (gameInfo->TotalSizeBytes < LAYER_BREAK)
1196 printf("[!] Info: Game partition size is smaller than expected for a Dual Layer disc.\n");
1198 *totalSectors = REDUMP_SECTORS;
1199 *isDualLayer = true;
1203 printf("[!] Drive reported Raw DVD-5 geometry. Normalizing to Xbox Single Layer...\n");
1204 *totalSectors = endLba + 1;
1205 *isDualLayer = (endLba > (uint32_t)LAYER_THRESHOLD); // Standard threshold for SL vs DL
1211 *isDualLayer = true;
1212 *totalSectors = REDUMP_SECTORS;
1213 printf("Media Info: Could not read PFI. Defaulting to Dual Layer.\n");
1217 uint32_t GetGamePartitionSize(HANDLE hDevice, uint32_t totalDiscSectors, XDFS_VOLUME_DESCRIPTOR *vol)
1219 uint32_t sectorsToRead = 0;
1220 if (totalDiscSectors > 3300000)
1222 // DUAL LAYER (XGD2) Calculation:
1223 // LBA 1,913,920 is the physical end of the usable XDFS area on retail DVD-9s.
1224 uint32_t xgd2EndLba = 1913920;
1225 sectorsToRead = xgd2EndLba - vol->RootLBA;
1229 // SINGLE LAYER (XGD1 / Homebrew) Calculation:
1230 // On single layer discs, the header's VolumeSize is trustworthy.
1231 sectorsToRead = vol->VolumeSize / 2048;
1233 return sectorsToRead;
1236 static BOOL ProbeXboxVolumeAt(HANDLE hDevice, uint32_t lba)
1238 unsigned char sector[2048] = {0};
1239 return ScsiReadSectors(hDevice, lba, 1, sector) && memcmp(sector, "MICROSOFT", 9) == 0;
1242 static uint32_t DetectXboxVolumeStart(HANDLE hDevice)
1244 if (ProbeXboxVolumeAt(hDevice, START_LBA_MAGIC))
1245 return START_LBA_MAGIC;
1247 if (ProbeXboxVolumeAt(hDevice, 0x20))
1253 static void GetDirectoryForPath(const char *filename, char *outDir, DWORD outDirSize)
1256 char fullPath[MAX_PATH];
1257 char *filePart = NULL;
1259 if (!outDir || outDirSize == 0)
1264 if (!filename || filename[0] == '\0')
1266 GetCurrentDirectoryA(outDirSize, outDir);
1270 len = GetFullPathNameA(filename, (DWORD)sizeof(fullPath), fullPath, &filePart);
1271 if (len == 0 || len >= sizeof(fullPath))
1273 GetCurrentDirectoryA(outDirSize, outDir);
1277 if (filePart && filePart > fullPath)
1279 size_t dirLen = (size_t)(filePart - fullPath);
1280 if (dirLen >= outDirSize)
1281 dirLen = outDirSize - 1;
1282 memcpy(outDir, fullPath, dirLen);
1283 outDir[dirLen] = '\0';
1287 GetCurrentDirectoryA(outDirSize, outDir);
1291 static BOOL FileExistsAndSize(const char *filename, unsigned long long *sizeOut)
1293 WIN32_FILE_ATTRIBUTE_DATA fad;
1298 if (!filename || !GetFileAttributesExA(filename, GetFileExInfoStandard, &fad))
1301 if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1306 ULARGE_INTEGER size;
1307 size.HighPart = fad.nFileSizeHigh;
1308 size.LowPart = fad.nFileSizeLow;
1309 *sizeOut = size.QuadPart;
1315 static BOOL CheckOutputFreeSpace(const char *filename, unsigned long long expectedBytes, const char *label)
1318 ULARGE_INTEGER freeToCaller;
1319 ULARGE_INTEGER totalBytes;
1320 ULARGE_INTEGER totalFree;
1321 unsigned long long existingBytes = 0ULL;
1322 unsigned long long effectiveFree;
1323 unsigned long long margin;
1325 if (expectedBytes == 0)
1328 GetDirectoryForPath(filename, dir, (DWORD)sizeof(dir));
1330 if (!GetDiskFreeSpaceExA(dir[0] ? dir : NULL, &freeToCaller, &totalBytes, &totalFree))
1332 DWORD err = GetLastError();
1333 printf("\n[WARN] Could not check free space for output path '%s' (GetDiskFreeSpaceEx error %lu).\n", filename, err);
1334 printf(" Continuing, but write errors will still be caught during the dump.\n");
1338 FileExistsAndSize(filename, &existingBytes);
1340 // If overwriting an existing output on the same volume, its current bytes can be
1341 // reclaimed by fopen(..., "wb"). This avoids rejecting a valid replacement run.
1342 effectiveFree = freeToCaller.QuadPart + existingBytes;
1344 // Add a small safety margin for sidecar/profile files and filesystem metadata.
1345 // Keep this modest so overwriting an existing full raw ISO still passes.
1346 margin = 64ULL * 1024ULL * 1024ULL;
1348 printf("[%s] Free-space preflight for '%s':\n", label ? label : "OUTPUT", filename);
1349 printf(" Required output bytes: %llu\n", expectedBytes);
1350 printf(" Safety margin: %llu\n", margin);
1351 printf(" Free to caller: %llu\n", (unsigned long long)freeToCaller.QuadPart);
1353 printf(" Existing output bytes: %llu (counted as reclaimable overwrite space)\n", existingBytes);
1354 printf(" Effective available: %llu\n", effectiveFree);
1356 if (effectiveFree < expectedBytes + margin)
1358 printf("\n[FATAL] Not enough free disk space for %s.\n", label ? label : "output");
1359 printf(" Required + margin: %llu bytes\n", expectedBytes + margin);
1360 printf(" Effective free: %llu bytes\n", effectiveFree);
1361 printf(" Free space can change while dumping; free extra space and rerun.\n");
1368 static BOOL WriteOutputBytes(FILE *outFile,
1370 size_t bytesToWrite,
1371 const char *phaseName,
1377 if (!outFile || !data || bytesToWrite == 0)
1378 return bytesToWrite == 0;
1380 written = fwrite(data, 1, bytesToWrite, outFile);
1381 if (written != bytesToWrite)
1383 printf("\n[FATAL] Output write failed during %s range.\n", phaseName ? phaseName : "dump");
1384 printf(" Source LBA: %u | Output LBA: %u\n", sourceLba, outputLba);
1385 printf(" Requested: %llu bytes\n", (unsigned long long)bytesToWrite);
1386 printf(" Written: %llu bytes\n", (unsigned long long)written);
1388 printf(" errno: %d (%s)\n", errno, strerror(errno));
1389 printf(" This commonly means another program consumed free space after preflight,\n");
1390 printf(" the destination volume filled up, or the destination became unavailable.\n");
1394 if (ferror(outFile))
1396 printf("\n[FATAL] Output stream error during %s range at output LBA %u.\n",
1397 phaseName ? phaseName : "dump", outputLba);
1399 printf(" errno: %d (%s)\n", errno, strerror(errno));
1406 static BOOL FlushAndCommitOutput(FILE *outFile, const char *label)
1413 if (fflush(outFile) != 0)
1415 printf("\n[FATAL] fflush failed for %s output.\n", label ? label : "dump");
1417 printf(" errno: %d (%s)\n", errno, strerror(errno));
1421 fd = _fileno(outFile);
1422 if (fd >= 0 && _commit(fd) != 0)
1424 printf("\n[FATAL] _commit failed for %s output. The OS may not have accepted all buffered data.\n",
1425 label ? label : "dump");
1427 printf(" errno: %d (%s)\n", errno, strerror(errno));
1434 static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
1437 uint32_t sourceStartLba,
1438 uint32_t sectorsToRead,
1439 uint32_t outputBaseLba,
1440 const char *phaseName)
1442 const uint32_t batchSize = 32;
1443 unsigned char *buffer = NULL;
1444 uint32_t sectorsDone = 0;
1445 DWORD startTime = GetTickCount();
1446 char timeStr[12] = {0};
1447 char etaStr[12] = {0};
1449 if (sectorsToRead == 0)
1452 buffer = (unsigned char *)VirtualAlloc(NULL, batchSize * 2048, MEM_COMMIT, PAGE_READWRITE);
1455 printf("\n[FATAL] Could not allocate dump buffer for %s range.\n", phaseName);
1459 printf("\n--- STARTING %s RANGE ---\n", phaseName);
1460 printf("Source LBA: %u | Output LBA: %u | Sectors: %u\n", sourceStartLba, outputBaseLba, sectorsToRead);
1462 while (sectorsDone < sectorsToRead)
1464 uint32_t currentLba;
1466 if (XboxDumpCancelRequested())
1468 printf("\n[CANCEL] User cancellation requested during %s at output LBA %u.\n",
1469 phaseName, outputBaseLba + sectorsDone);
1470 VirtualFree(buffer, 0, MEM_RELEASE);
1474 currentLba = sourceStartLba + sectorsDone;
1475 const char *currentLayerStr = "L0";
1476 uint32_t burstLimit = batchSize;
1478 BOOL success = FALSE;
1480 if ((outputBaseLba + sectorsDone) >= LAYER_BREAK)
1481 currentLayerStr = "L1";
1483 // Layer-boundary safety: do not let one READ(10) span the Xbox layer break.
1484 if (sectorsDone == 0 || (outputBaseLba + sectorsDone) == LAYER_BREAK)
1488 else if ((outputBaseLba + sectorsDone) < LAYER_BREAK &&
1489 (outputBaseLba + sectorsDone + batchSize) > LAYER_BREAK)
1491 burstLimit = LAYER_BREAK - (outputBaseLba + sectorsDone);
1494 toRead = (sectorsToRead - sectorsDone > burstLimit) ? burstLimit : (sectorsToRead - sectorsDone);
1496 if ((outputBaseLba + sectorsDone) == LAYER_BREAK)
1498 printf("\n[INFO] Redump/XGD1 output layer break at LBA %u. Reducing burst size to 1 sector for safety.\n", LAYER_BREAK);
1501 for (int retry = 0; retry <= MAX_RETRIES; retry++)
1503 if (XboxDumpCancelRequested())
1505 printf("\n[CANCEL] User cancellation requested during %s retry at output LBA %u.\n",
1506 phaseName, outputBaseLba + sectorsDone);
1507 VirtualFree(buffer, 0, MEM_RELEASE);
1511 if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, buffer))
1513 if (!WriteOutputBytes(outFile, buffer, (size_t)toRead * 2048U, phaseName, currentLba, outputBaseLba + sectorsDone))
1515 VirtualFree(buffer, 0, MEM_RELEASE);
1518 CryptHashData(hHash, buffer, toRead * 2048, 0);
1519 sectorsDone += toRead;
1529 unsigned char *smallBuffer = NULL;
1530 printf("\n[!] Batch failed in %s range at source LBA %u. Recovering sectors individually.\n", phaseName, currentLba);
1532 smallBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
1535 printf("\n[FATAL] Could not allocate single-sector recovery buffer.\n");
1536 VirtualFree(buffer, 0, MEM_RELEASE);
1540 for (uint32_t i = 0; i < toRead; i++)
1542 BOOL sectorSuccess = FALSE;
1544 for (int sRetry = 0; sRetry <= MAX_RETRIES; sRetry++)
1546 if (XboxDumpCancelRequested())
1548 printf("\n[CANCEL] User cancellation requested during %s sector recovery at output LBA %u.\n",
1549 phaseName, outputBaseLba + sectorsDone);
1550 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1551 VirtualFree(buffer, 0, MEM_RELEASE);
1555 if (ScsiReadSectors(hDevice, currentLba + i, 1, smallBuffer))
1557 if (!WriteOutputBytes(outFile, smallBuffer, 2048, phaseName, currentLba + i, outputBaseLba + sectorsDone))
1559 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1560 VirtualFree(buffer, 0, MEM_RELEASE);
1563 CryptHashData(hHash, smallBuffer, 2048, 0);
1565 sectorSuccess = TRUE;
1574 printf("\n[FATAL] Unrecoverable %s sector at source LBA %u. Output hash is invalid.\n", phaseName, currentLba + i);
1575 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1576 VirtualFree(buffer, 0, MEM_RELEASE);
1581 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1584 if (sectorsDone > 0)
1586 DWORD elapsedMs = GetTickCount() - startTime;
1587 uint32_t sectorsLeft = sectorsToRead - sectorsDone;
1588 DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft);
1589 float percent = ((float)sectorsDone / sectorsToRead) * 100.0f;
1590 float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024;
1591 float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f;
1593 FormatElapsedTime(elapsedMs, timeStr);
1594 FormatElapsedTime(etaMs, etaStr);
1596 xbox_ref_console_printf("\rProgress [%s/%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | sourceLba: %u | outputLba: %u | Time: %s | ETA: %s ",
1597 phaseName, currentLayerStr, percent, mbDone, speed, currentLba, outputBaseLba + sectorsDone, timeStr, etaStr);
1602 printf("\n[OK] Completed %s range.\n", phaseName);
1603 VirtualFree(buffer, 0, MEM_RELEASE);
1610 static BOOL WriteZeroSectorsOutput(FILE *outFile,
1612 uint32_t sectorCount,
1613 uint32_t outputBaseLba,
1614 const char *phaseName)
1616 const uint32_t batchSectors = 32;
1617 unsigned char *zeroBuffer = NULL;
1618 uint32_t sectorsDone = 0;
1619 DWORD startTime = GetTickCount();
1620 char timeStr[12] = {0};
1621 char etaStr[12] = {0};
1623 if (sectorCount == 0)
1626 zeroBuffer = (unsigned char *)VirtualAlloc(NULL, batchSectors * 2048, MEM_COMMIT, PAGE_READWRITE);
1629 printf("\n[FATAL] Could not allocate zero-fill buffer for %s range.\n", phaseName ? phaseName : "padding");
1632 memset(zeroBuffer, 0, batchSectors * 2048);
1634 printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "ZERO");
1635 printf("Output LBA: %u | Sectors: %u | Fill: synthetic zero-fill (not drive-captured)\n", outputBaseLba, sectorCount);
1637 while (sectorsDone < sectorCount)
1641 if (XboxDumpCancelRequested())
1643 printf("\n[CANCEL] User cancellation requested during %s at output LBA %u.\n",
1644 phaseName ? phaseName : "ZERO", outputBaseLba + sectorsDone);
1645 VirtualFree(zeroBuffer, 0, MEM_RELEASE);
1649 toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1650 uint32_t outputLba = outputBaseLba + sectorsDone;
1652 if (!WriteOutputBytes(outFile, zeroBuffer, (size_t)toWrite * 2048U, phaseName ? phaseName : "ZERO", 0, outputLba))
1654 VirtualFree(zeroBuffer, 0, MEM_RELEASE);
1658 CryptHashData(hHash, zeroBuffer, toWrite * 2048, 0);
1660 sectorsDone += toWrite;
1662 if (sectorsDone > 0)
1664 DWORD elapsedMs = GetTickCount() - startTime;
1665 uint32_t sectorsLeft = sectorCount - sectorsDone;
1666 DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft);
1667 float percent = ((float)sectorsDone / sectorCount) * 100.0f;
1668 float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024;
1669 float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f;
1671 FormatElapsedTime(elapsedMs, timeStr);
1672 FormatElapsedTime(etaMs, etaStr);
1673 xbox_ref_console_printf("\rProgress [%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | outputLba: %u | Time: %s | ETA: %s ",
1674 phaseName ? phaseName : "ZERO", percent, mbDone, speed, outputBaseLba + sectorsDone, timeStr, etaStr);
1679 printf("\n[OK] Completed %s range.\n", phaseName ? phaseName : "ZERO");
1680 VirtualFree(zeroBuffer, 0, MEM_RELEASE);
1684 static BOOL ReadSectorsToMemory(HANDLE hDevice,
1685 uint32_t sourceStartLba,
1686 uint32_t sectorCount,
1687 unsigned char *outBuffer,
1688 const char *phaseName)
1690 const uint32_t batchSectors = 32;
1691 uint32_t sectorsDone = 0;
1693 if (sectorCount == 0)
1698 printf("[RAW] Capturing %s to memory: source LBA %u..%u (%u sectors).\n",
1699 phaseName ? phaseName : "sector range",
1701 sourceStartLba + sectorCount - 1,
1704 while (sectorsDone < sectorCount)
1706 uint32_t toRead = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1707 uint32_t currentLba = sourceStartLba + sectorsDone;
1708 BOOL success = FALSE;
1710 for (int retry = 0; retry <= MAX_RETRIES; retry++)
1712 if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, outBuffer + ((size_t)sectorsDone * 2048U)))
1714 sectorsDone += toRead;
1723 printf("\n[FATAL] Could not capture %s at source LBA %u.\n", phaseName ? phaseName : "sector range", currentLba);
1731 static BOOL WriteMemorySectorsOutput(FILE *outFile,
1733 const unsigned char *buffer,
1734 uint32_t sectorCount,
1735 uint32_t outputBaseLba,
1736 const char *phaseName)
1738 const uint32_t batchSectors = 32;
1739 uint32_t sectorsDone = 0;
1741 if (sectorCount == 0)
1746 printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "MEMORY");
1747 printf("Output LBA: %u | Sectors: %u | Source: captured memory\n", outputBaseLba, sectorCount);
1749 while (sectorsDone < sectorCount)
1751 uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1752 uint32_t outputLba = outputBaseLba + sectorsDone;
1753 const unsigned char *src = buffer + ((size_t)sectorsDone * 2048U);
1755 if (!WriteOutputBytes(outFile, src, (size_t)toWrite * 2048U, phaseName ? phaseName : "MEMORY", 0, outputLba))
1758 CryptHashData(hHash, src, toWrite * 2048, 0);
1760 sectorsDone += toWrite;
1763 printf("[OK] Completed %s range.\n", phaseName ? phaseName : "MEMORY");
1767 typedef struct _XboxDvdSidecarCapture
1769 BOOL hasLockedCapacity;
1770 BOOL hasLockedModeSense3E;
1771 BOOL hasUnlockedCapacity;
1772 BOOL hasUnlockedModeSense3E;
1777 unsigned char lockedCapacity[8];
1778 unsigned char lockedModeSense3E[28];
1779 unsigned char unlockedCapacity[8];
1780 unsigned char unlockedModeSense3E[28];
1782 unsigned char adC0[0x664];
1783 unsigned char pfi[2048];
1784 unsigned char dmi[2048];
1785 } XboxDvdSidecarCapture;
1787 static void StripKnownExtension(const char *filename, char *outBase, size_t outBaseSize)
1794 if (!outBase || outBaseSize == 0)
1801 strncpy(outBase, filename, outBaseSize - 1);
1802 outBase[outBaseSize - 1] = '\0';
1804 dot = strrchr(outBase, '.');
1805 slash1 = strrchr(outBase, '\\');
1806 slash2 = strrchr(outBase, '/');
1807 slash = slash1 > slash2 ? slash1 : slash2;
1809 if (dot && (!slash || dot > slash))
1813 static void MakeSidecarPath(const char *filename, const char *suffix, char *outPath, size_t outPathSize)
1815 char base[MAX_PATH];
1817 if (!outPath || outPathSize == 0)
1820 StripKnownExtension(filename, base, sizeof(base));
1821 snprintf(outPath, outPathSize, "%s%s", base, suffix);
1822 outPath[outPathSize - 1] = '\0';
1825 static BOOL WriteBinaryFile(const char *path, const unsigned char *data, size_t len)
1829 if (!path || !data || len == 0)
1832 f = fopen(path, "wb");
1836 if (fwrite(data, 1, len, f) != len)
1846 static void JsonWriteEscapedString(FILE *f, const char *s)
1853 unsigned char c = (unsigned char)*s++;
1854 if (c == '"' || c == '\\')
1873 fprintf(f, "\\u%04x", c);
1884 static void JsonWriteHexString(FILE *f, const unsigned char *data, size_t len)
1889 for (size_t i = 0; i < len; i++)
1890 fprintf(f, "%02X", data[i]);
1896 static const char *PathLeaf(const char *path)
1904 slash1 = strrchr(path, '\\');
1905 slash2 = strrchr(path, '/');
1907 if (slash1 && slash2)
1908 return (slash1 > slash2 ? slash1 : slash2) + 1;
1916 static void TrimTrailingSpaces(char *s)
1924 while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t'))
1931 static void CopyBounded(char *dst, size_t dstSize, const char *src, size_t srcLen)
1935 if (!dst || dstSize == 0)
1946 memcpy(dst, src, n);
1950 static void ExtractMediaProfileNames(const char *isoFilename,
1952 size_t titleHintSize,
1956 char base[MAX_PATH];
1958 const char *openBracket;
1959 const char *closeBracket;
1961 if (titleHint && titleHintSize > 0)
1962 titleHint[0] = '\0';
1963 if (mediaId && mediaIdSize > 0)
1969 StripKnownExtension(isoFilename, base, sizeof(base));
1970 leaf = PathLeaf(base);
1972 openBracket = strrchr(leaf, '[');
1973 closeBracket = openBracket ? strchr(openBracket, ']') : NULL;
1975 if (openBracket && closeBracket && closeBracket > openBracket)
1977 CopyBounded(titleHint, titleHintSize, leaf, (size_t)(openBracket - leaf));
1978 CopyBounded(mediaId, mediaIdSize, openBracket + 1, (size_t)(closeBracket - openBracket - 1));
1982 CopyBounded(titleHint, titleHintSize, leaf, strlen(leaf));
1985 TrimTrailingSpaces(titleHint);
1988 static void JsonWriteValidationWarnings(FILE *f, const XboxDvdSidecarCapture *cap, BOOL payloadFilesPresent, BOOL redumpStyleZeroFilledPadding)
1994 #define WRITE_WARNING(w) do { \
1995 if (wrote) fprintf(f, ", "); \
1996 JsonWriteEscapedString(f, (w)); \
2000 if (!cap || !cap->hasLockedCapacity)
2001 WRITE_WARNING("missing_locked_read_capacity_10");
2002 if (!cap || !cap->hasLockedModeSense3E)
2003 WRITE_WARNING("missing_locked_mode_sense_3e");
2004 if (!cap || !cap->hasUnlockedCapacity)
2005 WRITE_WARNING("missing_unlocked_read_capacity_10");
2006 if (!cap || !cap->hasUnlockedModeSense3E)
2007 WRITE_WARNING("missing_unlocked_mode_sense_3e");
2008 if (!cap || !cap->hasAdC0)
2009 WRITE_WARNING("missing_ad_c0_payload");
2010 if (!cap || !cap->hasPfi)
2011 WRITE_WARNING("missing_pfi_payload");
2012 if (!cap || !cap->hasDmi)
2013 WRITE_WARNING("missing_dmi_payload");
2014 if (!payloadFilesPresent)
2015 WRITE_WARNING("payload_files_not_fully_present");
2016 if (redumpStyleZeroFilledPadding)
2017 WRITE_WARNING("redump_style_padding_zero_filled_unresolved_content");
2019 #undef WRITE_WARNING
2024 static BOOL ScsiDataInCommand(HANDLE hDevice,
2025 const unsigned char *cdb,
2028 unsigned char *buffer)
2030 SCSI_PASS_THROUGH_DIRECT sptd;
2031 DWORD bytesReturned = 0;
2033 if (!hDevice || !cdb || !buffer || dataLen == 0 || cdbLen == 0 || cdbLen > 16)
2036 memset(&sptd, 0, sizeof(sptd));
2037 memset(buffer, 0, dataLen);
2039 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
2040 sptd.CdbLength = cdbLen;
2041 sptd.DataIn = SCSI_IOCTL_DATA_IN;
2042 sptd.DataTransferLength = dataLen;
2043 sptd.TimeOutValue = 30;
2044 sptd.DataBuffer = buffer;
2045 memcpy(sptd.Cdb, cdb, cdbLen);
2047 return DeviceIoControl(hDevice,
2048 IOCTL_SCSI_PASS_THROUGH_DIRECT,
2057 static BOOL CaptureReadCapacity10(HANDLE hDevice, unsigned char out8[8])
2059 static const unsigned char cdb[10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
2060 return ScsiDataInCommand(hDevice, cdb, 10, 8, out8);
2063 static BOOL CaptureModeSense3E(HANDLE hDevice, unsigned char out28[28])
2065 static const unsigned char cdb[10] = {0x5A, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00};
2066 return ScsiDataInCommand(hDevice, cdb, 10, 28, out28);
2069 static BOOL CaptureReadDvdStructureXboxC0(HANDLE hDevice, unsigned char out1664[0x664])
2071 static const unsigned char cdb[12] = {0xAD, 0x00, 0xFF, 0x02, 0xFD, 0xFF, 0xFE, 0x00, 0x06, 0x64, 0x00, 0xC0};
2072 return ScsiDataInCommand(hDevice, cdb, 12, 0x664, out1664);
2075 static BOOL CaptureReadDvdStructurePfi(HANDLE hDevice, unsigned char out2048[2048])
2077 static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00};
2078 return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048);
2081 static BOOL CaptureReadDvdStructureDmi(HANDLE hDevice, unsigned char out2048[2048])
2083 static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00};
2084 return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048);
2087 static void CaptureLockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap)
2092 cap->hasLockedCapacity = CaptureReadCapacity10(hDevice, cap->lockedCapacity);
2093 cap->hasLockedModeSense3E = CaptureModeSense3E(hDevice, cap->lockedModeSense3E);
2095 printf("[META] Locked READ CAPACITY: %s\n", cap->hasLockedCapacity ? "captured" : "failed");
2096 printf("[META] Locked MODE SENSE 0x3E: %s\n", cap->hasLockedModeSense3E ? "captured" : "failed");
2099 static void CaptureUnlockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap)
2104 cap->hasUnlockedCapacity = CaptureReadCapacity10(hDevice, cap->unlockedCapacity);
2105 cap->hasUnlockedModeSense3E = CaptureModeSense3E(hDevice, cap->unlockedModeSense3E);
2106 cap->hasAdC0 = CaptureReadDvdStructureXboxC0(hDevice, cap->adC0);
2107 cap->hasPfi = CaptureReadDvdStructurePfi(hDevice, cap->pfi);
2108 cap->hasDmi = CaptureReadDvdStructureDmi(hDevice, cap->dmi);
2110 printf("[META] Unlocked READ CAPACITY: %s\n", cap->hasUnlockedCapacity ? "captured" : "failed");
2111 printf("[META] Unlocked MODE SENSE 0x3E: %s\n", cap->hasUnlockedModeSense3E ? "captured" : "failed");
2112 printf("[META] READ DVD STRUCTURE Xbox C0 block: %s\n", cap->hasAdC0 ? "captured" : "failed");
2113 printf("[META] READ DVD STRUCTURE PFI: %s\n", cap->hasPfi ? "captured" : "failed");
2114 printf("[META] READ DVD STRUCTURE DMI: %s\n", cap->hasDmi ? "captured" : "failed");
2117 static uint32_t CapacitySectorsFromReadCapacity10(const unsigned char data[8])
2124 maxLba = ((uint32_t)data[0] << 24) |
2125 ((uint32_t)data[1] << 16) |
2126 ((uint32_t)data[2] << 8) |
2127 ((uint32_t)data[3]);
2132 static uint32_t NormalizeRawIsoTargetSectors(uint32_t reportedSectors, BOOL isDualLayer)
2134 // Option 1 targets a Redump-style reconstructed 2048-byte-sector image.
2135 // The GDR-8050L's unlocked READ CAPACITY reports the game/XISO view length
2136 // (3,431,264 sectors), while the full Original Xbox/XGD1 reconstructed image
2137 // is larger (3,820,880 sectors) because it also includes video L0/L1 and
2138 // padding around the game region.
2139 if (isDualLayer || reportedSectors > LAYER_THRESHOLD)
2140 return XGD1_FULL_REDUMP_SECTORS;
2142 return reportedSectors;
2145 static unsigned long long GetFileSizeBytes64(const char *filename)
2153 f = fopen(filename, "rb");
2157 if (_fseeki64(f, 0, SEEK_END) != 0)
2169 return (unsigned long long)pos;
2172 static BOOL VerifyOutputByteCount(const char *filename, unsigned long long expectedBytes, const char *label)
2174 unsigned long long actualBytes = GetFileSizeBytes64(filename);
2176 if (expectedBytes == 0)
2179 if (actualBytes != expectedBytes)
2181 printf("\n[FATAL] %s byte-count mismatch.\n", label ? label : "Output");
2182 printf(" Expected: %llu bytes\n", expectedBytes);
2183 printf(" Actual: %llu bytes\n", actualBytes);
2184 printf(" Refusing to mark this dump complete.\n");
2188 printf("[OK] %s byte count verified: %llu bytes.\n", label ? label : "Output", actualBytes);
2192 static void JsonWriteNull(FILE *f)
2197 static void HexBytesToString(const BYTE *bytes, DWORD byteCount, char *outHex, size_t outHexSize)
2201 if (!outHex || outHexSize == 0)
2205 if (!bytes || outHexSize < ((size_t)byteCount * 2U + 1U))
2208 for (i = 0; i < byteCount; i++)
2209 sprintf(&outHex[i * 2], "%02x", bytes[i]);
2212 static DWORD Crc32Update(DWORD crc, const unsigned char *buf, size_t len)
2214 static DWORD table[256];
2215 static BOOL tableReady = FALSE;
2221 for (n = 0; n < 256; n++)
2225 for (k = 0; k < 8; k++)
2226 c = (c & 1U) ? (0xEDB88320U ^ (c >> 1)) : (c >> 1);
2232 for (i = 0; i < len; i++)
2233 crc = table[(crc ^ buf[i]) & 0xFFU] ^ (crc >> 8);
2238 static BOOL CalculateFileHashes(const char *filename,
2240 size_t outCrc32Size,
2246 size_t outSha256Size)
2250 HCRYPTPROV hProv = 0;
2251 HCRYPTHASH hMd5 = 0;
2252 HCRYPTHASH hSha1 = 0;
2253 HCRYPTHASH hSha256 = 0;
2254 DWORD crc = 0xFFFFFFFFU;
2257 unsigned long long totalBytes = 0ULL;
2258 unsigned long long doneBytes = 0ULL;
2259 DWORD startTick = 0;
2260 DWORD lastPrintTick = 0;
2262 DWORD elapsedMs = 0;
2264 char timeStr[12] = {0};
2265 char etaStr[12] = {0};
2267 if (outCrc32 && outCrc32Size) outCrc32[0] = '\0';
2268 if (outMd5 && outMd5Size) outMd5[0] = '\0';
2269 if (outSha1 && outSha1Size) outSha1[0] = '\0';
2270 if (outSha256 && outSha256Size) outSha256[0] = '\0';
2275 totalBytes = GetFileSizeBytes64(filename);
2277 f = fopen(filename, "rb");
2281 buf = (unsigned char *)malloc(1024 * 1024);
2288 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) &&
2289 !CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
2291 if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hMd5))
2293 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hSha1))
2295 if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hSha256))
2298 startTick = GetTickCount();
2299 lastPrintTick = startTick;
2300 printf("[HASH] Calculating full-file CRC32/MD5/SHA-1/SHA-256 for %s (%llu bytes)...\n",
2304 while ((readBytes = fread(buf, 1, 1024 * 1024, f)) > 0)
2306 crc = Crc32Update(crc, buf, readBytes);
2307 if (!CryptHashData(hMd5, buf, (DWORD)readBytes, 0))
2309 if (!CryptHashData(hSha1, buf, (DWORD)readBytes, 0))
2311 if (!CryptHashData(hSha256, buf, (DWORD)readBytes, 0))
2314 doneBytes += (unsigned long long)readBytes;
2315 nowTick = GetTickCount();
2316 if (totalBytes > 0 && (doneBytes >= totalBytes || (nowTick - lastPrintTick) >= 1000))
2318 double percent = ((double)doneBytes / (double)totalBytes) * 100.0;
2319 double mbDone = (double)doneBytes / (1024.0 * 1024.0);
2321 unsigned long long bytesLeft = totalBytes - doneBytes;
2323 elapsedMs = nowTick - startTick;
2325 speed = mbDone / ((double)elapsedMs / 1000.0);
2326 etaMs = (doneBytes > 0 && elapsedMs > 0)
2327 ? (DWORD)(((double)elapsedMs / (double)doneBytes) * (double)bytesLeft)
2329 FormatElapsedTime(elapsedMs, timeStr);
2330 FormatElapsedTime(etaMs, etaStr);
2331 xbox_ref_console_printf("\r[HASH] Full-file: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ",
2338 lastPrintTick = nowTick;
2345 elapsedMs = GetTickCount() - startTick;
2346 FormatElapsedTime(elapsedMs, timeStr);
2348 xbox_ref_console_printf("\r[HASH] Full-file: 100.0%% | %.1f MB | Time: %s \n",
2349 (double)totalBytes / (1024.0 * 1024.0),
2351 printf("[OK] Full-file CRC32/MD5/SHA-1/SHA-256 complete in %s.\n", timeStr);
2354 if (outCrc32 && outCrc32Size >= 9)
2355 sprintf(outCrc32, "%08x", crc);
2357 if (outMd5 && outMd5Size >= 33)
2360 DWORD md5Len = sizeof(md5Bytes);
2361 if (!CryptGetHashParam(hMd5, HP_HASHVAL, md5Bytes, &md5Len, 0))
2363 HexBytesToString(md5Bytes, md5Len, outMd5, outMd5Size);
2366 if (outSha1 && outSha1Size >= 41)
2369 DWORD sha1Len = sizeof(sha1Bytes);
2370 if (!CryptGetHashParam(hSha1, HP_HASHVAL, sha1Bytes, &sha1Len, 0))
2372 HexBytesToString(sha1Bytes, sha1Len, outSha1, outSha1Size);
2375 if (outSha256 && outSha256Size >= 65)
2377 BYTE sha256Bytes[32];
2378 DWORD sha256Len = sizeof(sha256Bytes);
2379 if (!CryptGetHashParam(hSha256, HP_HASHVAL, sha256Bytes, &sha256Len, 0))
2381 HexBytesToString(sha256Bytes, sha256Len, outSha256, outSha256Size);
2387 if (!ok && startTick)
2389 elapsedMs = GetTickCount() - startTick;
2390 FormatElapsedTime(elapsedMs, timeStr);
2391 printf("\n[WARN] Full-file CRC32/MD5/SHA-1/SHA-256 calculation failed after %s.\n", timeStr);
2393 if (hSha256) CryptDestroyHash(hSha256);
2394 if (hSha1) CryptDestroyHash(hSha1);
2395 if (hMd5) CryptDestroyHash(hMd5);
2396 if (hProv) CryptReleaseContext(hProv, 0);
2402 static void WritePressedDvdRomWriteMediaState(FILE *json, const char *isoFilename, uint32_t totalDiscSectors)
2407 fprintf(json, " \"write_media_state\": {\n");
2408 fprintf(json, " \"media_class\": \"pressed_dvd_rom\",\n");
2409 fprintf(json, " \"writable\": false,\n");
2410 fprintf(json, " \"erasable\": false,\n");
2411 fprintf(json, " \"finalized\": true,\n");
2413 fprintf(json, " \"backing_image\": {\n");
2414 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename ? isoFilename : ""); fprintf(json, ",\n");
2415 fprintf(json, " \"sector_size\": 2048,\n");
2416 fprintf(json, " \"initial_sector_count\": %u,\n", totalDiscSectors);
2417 fprintf(json, " \"max_sector_count\": %u,\n", totalDiscSectors);
2418 fprintf(json, " \"growth_policy\": \"fixed_read_only\"\n");
2419 fprintf(json, " },\n");
2421 fprintf(json, " \"sessions\": [\n");
2422 fprintf(json, " {\n");
2423 fprintf(json, " \"session_number\": 1,\n");
2424 fprintf(json, " \"state\": \"closed\",\n");
2425 fprintf(json, " \"first_track_number\": 1,\n");
2426 fprintf(json, " \"last_track_number\": 1\n");
2427 fprintf(json, " }\n");
2428 fprintf(json, " ],\n");
2430 fprintf(json, " \"tracks\": [\n");
2431 fprintf(json, " {\n");
2432 fprintf(json, " \"track_number\": 1,\n");
2433 fprintf(json, " \"state\": \"complete\",\n");
2434 fprintf(json, " \"mode\": \"data\",\n");
2435 fprintf(json, " \"packet_or_track_mode\": \"pressed_read_only\",\n");
2436 fprintf(json, " \"start_lba\": 0,\n");
2437 fprintf(json, " \"next_writable_lba\": "); JsonWriteNull(json); fprintf(json, ",\n");
2438 fprintf(json, " \"free_blocks\": 0,\n");
2439 fprintf(json, " \"written_blocks\": %u\n", totalDiscSectors);
2440 fprintf(json, " }\n");
2441 fprintf(json, " ],\n");
2443 fprintf(json, " \"unwritten_read_policy\": \"not_applicable_read_only_media\",\n");
2444 fprintf(json, " \"flush_policy\": \"read_only_noop\"\n");
2445 fprintf(json, " },\n");
2448 static BOOL WriteXboxDvdMediaProfileFile(const char *isoFilename,
2449 const XboxDvdSidecarCapture *cap,
2450 uint32_t totalDiscSectors,
2452 uint32_t videoSectors,
2453 uint32_t gameSourceLba,
2454 uint32_t gameSectors,
2455 const char *isoSha1,
2457 const char *isoCrc32,
2458 const char *isoSha256,
2459 const char *adC0Path,
2460 const char *pfiPath,
2461 const char *dmiPath,
2462 BOOL payloadFilesPresent)
2464 char profilePath[MAX_PATH];
2465 char titleHint[256];
2470 if (!isoFilename || !cap)
2473 MakeSidecarPath(isoFilename, ".media.json", profilePath, sizeof(profilePath));
2474 ExtractMediaProfileNames(isoFilename, titleHint, sizeof(titleHint), mediaId, sizeof(mediaId));
2475 redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS);
2477 json = fopen(profilePath, "wb");
2481 fprintf(json, "{\n");
2482 fprintf(json, " \"format\": \"xdvd-media-profile\",\n");
2483 fprintf(json, " \"version\": 1,\n");
2484 fprintf(json, " \"media_id\": "); JsonWriteEscapedString(json, mediaId); fprintf(json, ",\n");
2485 fprintf(json, " \"title_hint\": "); JsonWriteEscapedString(json, titleHint); fprintf(json, ",\n");
2486 fprintf(json, " \"image\": {\n");
2487 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n");
2488 fprintf(json, " \"sector_size\": 2048,\n");
2489 fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors);
2490 fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2491 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2492 fprintf(json, " \"hashes\": {\n");
2493 fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n");
2494 fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n");
2495 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2496 fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n");
2497 fprintf(json, " }\n");
2498 fprintf(json, " },\n");
2501 uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors;
2503 fprintf(json, " \"layout\": {\n");
2504 fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n");
2505 fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U);
2506 fprintf(json, " \"layer_break_lba\": %u,\n", isDualLayer ? LAYER_BREAK : 0U);
2507 fprintf(json, " \"video_l0_start_lba\": 0,\n");
2508 fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors);
2509 fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
2510 fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
2511 fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
2512 fprintf(json, " \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
2513 fprintf(json, " \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2514 fprintf(json, " \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
2515 fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
2516 fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
2517 fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2518 fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n");
2519 fprintf(json, " \"game_sector_count\": %u,\n", gameSectors);
2520 fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U);
2521 fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U);
2522 fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U);
2523 fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U);
2524 fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", videoSectors);
2525 fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2526 fprintf(json, " \"drive_reported_unlocked_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2527 fprintf(json, " \"reconstructed_output_sector_count\": %u\n", totalDiscSectors);
2528 fprintf(json, " },\n");
2531 fprintf(json, " \"reconstruction\": {\n");
2532 fprintf(json, " \"is_reconstructed_layout\": %s,\n", redumpStyle ? "true" : "false");
2533 fprintf(json, " \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "pregame_and_postgame_zero_fill_content_placeholder" : "not_applicable"); fprintf(json, ",\n");
2534 fprintf(json, " \"filler_geometry_verified\": %s,\n", redumpStyle ? "true" : "false");
2535 fprintf(json, " \"filler_verified_from_disc\": false,\n");
2536 fprintf(json, " \"filler_byte_value\": %s,\n", redumpStyle ? "0" : "null");
2537 fprintf(json, " \"pending_hardware_capture\": false,\n");
2538 fprintf(json, " \"unresolved_filler_content\": %s,\n", redumpStyle ? "true" : "false");
2539 fprintf(json, " \"filler_ranges\": [\n");
2542 fprintf(json, " {\n");
2543 fprintf(json, " \"name\": \"pregame_padding\",\n");
2544 fprintf(json, " \"start_lba\": %u,\n", XGD1_VIDEO_L0_SECTORS);
2545 fprintf(json, " \"sector_count\": %u,\n", XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS);
2546 fprintf(json, " \"source\": \"synthetic_zero_fill\"\n");
2547 fprintf(json, " },\n");
2548 fprintf(json, " {\n");
2549 fprintf(json, " \"name\": \"postgame_padding\",\n");
2550 fprintf(json, " \"start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS);
2551 fprintf(json, " \"sector_count\": %u,\n", XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS));
2552 fprintf(json, " \"source\": \"synthetic_zero_fill\"\n");
2553 fprintf(json, " }\n");
2555 fprintf(json, " ],\n");
2556 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");
2557 fprintf(json, " },\n");
2559 fprintf(json, " \"dvd_structures\": {\n");
2560 fprintf(json, " \"ad_c0\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n");
2561 fprintf(json, " \"pfi\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n");
2562 fprintf(json, " \"dmi\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, "\n");
2563 fprintf(json, " },\n");
2565 fprintf(json, " \"non_lba_physical_metadata\": {\n");
2566 fprintf(json, " \"pfi_storage\": \"sidecar_bin\",\n");
2567 fprintf(json, " \"dmi_storage\": \"sidecar_bin\",\n");
2568 fprintf(json, " \"lead_in_storage\": \"not_in_iso_stream\",\n");
2569 fprintf(json, " \"lead_out_storage\": \"not_in_iso_stream\",\n");
2570 fprintf(json, " \"note\": \"A standard 2048-byte-sector ISO contains READ(10) user-data sectors only. PFI/DMI/lead-in/lead-out are represented by sidecar/profile metadata.\"\n");
2571 fprintf(json, " },\n");
2573 fprintf(json, " \"drive_state_observations\": {\n");
2574 fprintf(json, " \"locked\": {\n");
2575 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n");
2576 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2577 fprintf(json, " },\n");
2578 fprintf(json, " \"unlocked\": {\n");
2579 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n");
2580 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2581 fprintf(json, " }\n");
2582 fprintf(json, " },\n");
2584 WritePressedDvdRomWriteMediaState(json, isoFilename, totalDiscSectors);
2586 fprintf(json, " \"validation\": {\n");
2587 fprintf(json, " \"byte_count_matches_sector_count\": true,\n");
2588 fprintf(json, " \"payload_files_present\": %s,\n", payloadFilesPresent ? "true" : "false");
2589 fprintf(json, " \"warnings\": "); JsonWriteValidationWarnings(json, cap, payloadFilesPresent, redumpStyle); fprintf(json, "\n");
2590 fprintf(json, " }\n");
2591 fprintf(json, "}\n");
2594 printf("[META] Wrote media profile: %s\n", profilePath);
2598 static BOOL WriteXboxDvdSidecarFiles(const char *isoFilename,
2599 const XboxDvdSidecarCapture *cap,
2600 uint32_t totalDiscSectors,
2602 uint32_t videoSectors,
2603 uint32_t gameSourceLba,
2604 uint32_t gameSectors,
2605 const char *isoSha1,
2607 const char *isoCrc32,
2608 const char *isoSha256)
2610 char jsonPath[MAX_PATH];
2611 char adC0Path[MAX_PATH];
2612 char pfiPath[MAX_PATH];
2613 char dmiPath[MAX_PATH];
2616 BOOL payloadFilesPresent = FALSE;
2617 BOOL redumpStyle = FALSE;
2619 if (!isoFilename || !cap)
2622 MakeSidecarPath(isoFilename, ".xdvd.json", jsonPath, sizeof(jsonPath));
2623 MakeSidecarPath(isoFilename, ".ad_c0.bin", adC0Path, sizeof(adC0Path));
2624 MakeSidecarPath(isoFilename, ".pfi.bin", pfiPath, sizeof(pfiPath));
2625 MakeSidecarPath(isoFilename, ".dmi.bin", dmiPath, sizeof(dmiPath));
2627 if (cap->hasAdC0 && !WriteBinaryFile(adC0Path, cap->adC0, 0x664))
2629 if (cap->hasPfi && !WriteBinaryFile(pfiPath, cap->pfi, 2048))
2631 if (cap->hasDmi && !WriteBinaryFile(dmiPath, cap->dmi, 2048))
2634 payloadFilesPresent = cap->hasAdC0 && cap->hasPfi && cap->hasDmi && ok;
2635 redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS);
2637 json = fopen(jsonPath, "wb");
2641 fprintf(json, "{\n");
2642 fprintf(json, " \"format\": \"xdvd-sidecar\",\n");
2643 fprintf(json, " \"version\": 1,\n");
2644 fprintf(json, " \"image\": {\n");
2645 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n");
2646 fprintf(json, " \"sector_size\": 2048,\n");
2647 fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors);
2648 fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2649 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2650 fprintf(json, " \"hashes\": {\n");
2651 fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n");
2652 fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n");
2653 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2654 fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n");
2655 fprintf(json, " },\n");
2657 uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors;
2659 fprintf(json, " \"layout\": {\n");
2660 fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n");
2661 fprintf(json, " \"legacy_contiguous_visible_start_lba\": 0,\n");
2662 fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors);
2663 fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2664 fprintf(json, " \"video_l0_start_lba\": 0,\n");
2665 fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors);
2666 fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
2667 fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
2668 fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
2669 fprintf(json, " \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
2670 fprintf(json, " \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2671 fprintf(json, " \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
2672 fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
2673 fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
2674 fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2675 fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n");
2676 fprintf(json, " \"game_sector_count\": %u,\n", gameSectors);
2677 fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U);
2678 fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U);
2679 fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U);
2680 fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U);
2681 fprintf(json, " \"layer_break_lba\": %u\n", LAYER_BREAK);
2682 fprintf(json, " }\n");
2684 fprintf(json, " },\n");
2686 fprintf(json, " \"disc\": {\n");
2687 fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U);
2688 fprintf(json, " \"reconstructed_output_sectors\": %u,\n", totalDiscSectors);
2689 fprintf(json, " \"reconstructed_output_byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2690 fprintf(json, " \"drive_reported_unlocked_sectors\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2691 fprintf(json, " \"drive_reported_locked_sectors\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2692 fprintf(json, " \"unlocked_game_view_sectors\": %u\n", gameSectors);
2693 fprintf(json, " },\n");
2695 fprintf(json, " \"drive_states\": {\n");
2696 fprintf(json, " \"locked\": {\n");
2697 fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n");
2698 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n");
2699 fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2700 fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n");
2701 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2702 fprintf(json, " },\n");
2703 fprintf(json, " \"unlocked\": {\n");
2704 fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n");
2705 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n");
2706 fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2707 fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n");
2708 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2709 fprintf(json, " }\n");
2710 fprintf(json, " },\n");
2712 fprintf(json, " \"scsi_responses\": [\n");
2713 fprintf(json, " {\n");
2714 fprintf(json, " \"name\": \"read_dvd_structure_xbox_control_block\",\n");
2715 fprintf(json, " \"cdb_hex\": \"AD00FF02FDFFFE00066400C0\",\n");
2716 fprintf(json, " \"data_in\": true,\n");
2717 fprintf(json, " \"data_len\": 1636,\n");
2718 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n");
2719 fprintf(json, " \"captured\": %s\n", cap->hasAdC0 ? "true" : "false");
2720 fprintf(json, " },\n");
2721 fprintf(json, " {\n");
2722 fprintf(json, " \"name\": \"read_dvd_structure_pfi\",\n");
2723 fprintf(json, " \"cdb_hex\": \"AD0000000000000008000000\",\n");
2724 fprintf(json, " \"data_in\": true,\n");
2725 fprintf(json, " \"data_len\": 2048,\n");
2726 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n");
2727 fprintf(json, " \"captured\": %s\n", cap->hasPfi ? "true" : "false");
2728 fprintf(json, " },\n");
2729 fprintf(json, " {\n");
2730 fprintf(json, " \"name\": \"read_dvd_structure_dmi\",\n");
2731 fprintf(json, " \"cdb_hex\": \"AD0000000000000408000000\",\n");
2732 fprintf(json, " \"data_in\": true,\n");
2733 fprintf(json, " \"data_len\": 2048,\n");
2734 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, ",\n");
2735 fprintf(json, " \"captured\": %s\n", cap->hasDmi ? "true" : "false");
2736 fprintf(json, " }\n");
2737 fprintf(json, " ],\n");
2739 fprintf(json, " \"auth\": {\n");
2740 fprintf(json, " \"requires_media_transition\": false,\n");
2741 fprintf(json, " \"unlock_requires_media_transition\": false,\n");
2742 fprintf(json, " \"locked_video_view_restore_requires_media_transition\": true,\n");
2743 fprintf(json, " \"state_detection\": \"READ CAPACITY (10) visible-sector count\",\n");
2744 fprintf(json, " \"mode_page\": \"0x3E\",\n");
2745 fprintf(json, " \"challenge_table_source\": \"read_dvd_structure_xbox_control_block\",\n");
2746 fprintf(json, " \"challenge_table_response_offset\": 774,\n");
2747 fprintf(json, " \"challenge_table_hash_offset\": 1187,\n");
2748 fprintf(json, " \"challenge_table_hash_length\": 44\n");
2749 fprintf(json, " }\n");
2750 fprintf(json, "}\n");
2754 if (!WriteXboxDvdMediaProfileFile(isoFilename,
2768 payloadFilesPresent))
2771 printf("[WARN] Failed to write XDVD media profile.\n");
2774 printf("[META] Wrote XDVD sidecar: %s\n", jsonPath);
2775 if (cap->hasAdC0) printf("[META] Wrote Xbox control block: %s\n", adC0Path);
2776 if (cap->hasPfi) printf("[META] Wrote PFI: %s\n", pfiPath);
2777 if (cap->hasDmi) printf("[META] Wrote DMI: %s\n", dmiPath);
2782 BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat,
2783 uint32_t totalDiscSectors, bool isDualLayer,
2784 bool EjectOnSuccess,
2785 int (*cancel)(void *cancel_data),
2787 xbox_ref_dump_result *result)
2789 HCRYPTPROV hProv = 0;
2790 HCRYPTHASH hHash = 0;
2791 FILE *outFile = NULL;
2793 char sha1String[41] = {0};
2794 char md5String[33] = {0};
2795 char crc32String[9] = {0};
2796 char fileSha1String[41] = {0};
2797 char fileSha256String[65] = {0};
2798 BOOL dumpOk = FALSE;
2799 XboxDvdSidecarCapture sidecarCapture;
2800 BOOL rawSidecarAvailable = FALSE;
2801 uint32_t rawVideoSectors = START_LBA_MAGIC;
2802 uint32_t rawGameSourceLba = 0xFFFFFFFFu;
2803 uint32_t rawGameSectors = 0;
2804 uint32_t rawTargetSectors = 0;
2805 unsigned long long expectedOutputBytes = 0ULL;
2806 const char *outputLabel = "Output";
2807 DWORD operationStartTick = GetTickCount();
2809 char operationTimeStr[12] = {0};
2811 memset(&sidecarCapture, 0, sizeof(sidecarCapture));
2813 g_xbox_dump_cancel = cancel;
2814 g_xbox_dump_cancel_data = cancel_data;
2815 g_xbox_dump_cancel_result = result;
2818 result->attempted = 1;
2819 result->mode = xisoFormat;
2820 if (filename && filename[0]) {
2821 strncpy(result->output_path, filename, sizeof(result->output_path) - 1);
2822 result->output_path[sizeof(result->output_path) - 1] = '\0';
2826 if (totalDiscSectors == 0)
2827 totalDiscSectors = GetTotalSectors(hDevice);
2828 if (totalDiscSectors == 0)
2829 totalDiscSectors = REDUMP_SECTORS;
2831 if (XboxDumpCancelRequested())
2833 printf("\n[CANCEL] User cancellation requested before Xbox output preparation.\n");
2837 rawTargetSectors = NormalizeRawIsoTargetSectors(totalDiscSectors, isDualLayer);
2839 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
2841 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
2844 EnsureDriveReady(hDevice, 30000);
2845 SetDriveSpeedMax(hDevice);
2847 if (xisoFormat == '1')
2849 DWORD bytesReturned;
2850 uint32_t gameSourceLba = 0xFFFFFFFFu;
2851 uint32_t videoSectors = START_LBA_MAGIC;
2852 uint32_t gameSectors = 0;
2853 BOOL lockedViewIsAlreadyXdfs = FALSE;
2855 outputLabel = "RAW ISO";
2856 expectedOutputBytes = (unsigned long long)rawTargetSectors * 2048ULL;
2858 result->output_sectors = rawTargetSectors;
2860 if (rawTargetSectors != totalDiscSectors)
2862 printf("[RAW] Drive-reported unlocked sectors %u normalized to Redump-style output target %u.\n",
2863 totalDiscSectors, rawTargetSectors);
2866 printf("[RAW] Full-disc target: %u sectors (%llu bytes).\n",
2868 expectedOutputBytes);
2869 if (rawTargetSectors > LAYER_BREAK)
2871 printf("[RAW] Expected Redump/XGD1 layer break at output LBA %u.\n", LAYER_BREAK);
2873 printf("[RAW] Media-transition-preserving mode is enabled.\n");
2875 if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel))
2878 outFile = fopen(filename, "wb");
2881 printf("\n[FATAL] Could not create output file '%s'.\n", filename);
2883 printf(" errno: %d (%s)\n", errno, strerror(errno));
2887 // Raw mode must capture the visible/video view first. The caller normally reaches this
2888 // point after the drive has already been authenticated for metadata, so reset the drive
2889 // state with a real media transition before reading LBA 0.
2890 DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
2892 printf("[RAW] Cycling tray to restore locked/video view before dumping sector 0.\n");
2893 AutomateTrayCycle(hDevice);
2894 RefreshVolume(hDevice);
2895 SetDriveSpeedMax(hDevice);
2897 DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
2899 CaptureLockedSidecarState(hDevice, &sidecarCapture);
2901 lockedViewIsAlreadyXdfs = ProbeXboxVolumeAt(hDevice, 0x20);
2903 if (lockedViewIsAlreadyXdfs && rawTargetSectors > START_LBA_MAGIC)
2905 printf("\n[FATAL] Option 1 requires a full raw/video-front source image.\n");
2906 printf(" This source exposes XDFS at LBA 0x20 after the media-reset step,\n");
2907 printf(" which looks like an XISO/game-partition view, not a full raw disc view.\n");
2908 printf(" Use option 2 for this source, or mount/create a 7.29 GiB Redump-style option-1 ISO.\n");
2912 if (rawTargetSectors <= START_LBA_MAGIC)
2914 printf("[RAW] Non-retail-sized source; dumping visible LBA 0..%u directly.\n",
2915 rawTargetSectors - 1);
2916 rawVideoSectors = rawTargetSectors;
2917 rawGameSourceLba = 0;
2919 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
2920 rawSidecarAvailable = TRUE;
2921 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, rawTargetSectors, 0, "RAW");
2923 else if (rawTargetSectors == XGD1_FULL_REDUMP_SECTORS)
2925 unsigned char *videoL1Buffer = NULL;
2926 uint32_t detectedXdfsLba;
2927 uint32_t pregamePaddingSectors = XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS;
2928 uint32_t postgamePaddingSectors = XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS);
2930 printf("[RAW] Using Original Xbox/XGD1 Redump-style 2048-byte-sector layout.\n");
2931 printf("[RAW] Layout: VIDEO_L0=%u sectors, pregame zero-fill padding=%u sectors, game/XISO=%u sectors, postgame zero-fill padding=%u sectors, VIDEO_L1=%u sectors.\n",
2932 XGD1_VIDEO_L0_SECTORS,
2933 pregamePaddingSectors,
2935 postgamePaddingSectors,
2936 XGD1_VIDEO_L1_SECTORS);
2937 printf("[RAW] Note: filler/padding ranges are synthetic zero-fill placeholders until readable from hardware.\n");
2938 printf("[RAW] Note: PFI/DMI/lead-in/lead-out are MMC/physical metadata, not READ(10) user-data sectors; they are emitted in sidecar/profile files.\n");
2940 videoL1Buffer = (unsigned char *)VirtualAlloc(NULL, XGD1_VIDEO_L1_SECTORS * 2048U, MEM_COMMIT, PAGE_READWRITE);
2943 printf("\n[FATAL] Could not allocate VIDEO_L1 capture buffer.\n");
2947 // The locked-visible Xbox video ISO is 6,992 sectors. In the Redump-style
2948 // image, its L0 portion is placed at the beginning and its L1 tail is placed
2949 // at the end of the reconstructed image. Capture the L1 tail while the drive
2950 // is still in the locked/video state, before authenticating for the game view.
2951 if (!ReadSectorsToMemory(hDevice,
2952 XGD1_VIDEO_L0_SECTORS,
2953 XGD1_VIDEO_L1_SECTORS,
2957 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2961 printf("[RAW] Writing video L0 from locked source LBA 0..%u.\n", XGD1_VIDEO_L0_SECTORS - 1);
2962 if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, XGD1_VIDEO_L0_SECTORS, 0, "VIDEO-L0"))
2964 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2968 if (!WriteZeroSectorsOutput(outFile, hHash, pregamePaddingSectors, XGD1_VIDEO_L0_SECTORS, "PREGAME-PAD"))
2970 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2974 printf("[RAW] Re-applying full Xbox handshake for unlocked game/XISO view.\n");
2975 UnlockDrive(hDevice);
2976 RefreshVolume(hDevice);
2978 EnsureDriveReady(hDevice, 30000);
2979 SetDriveSpeedMax(hDevice);
2980 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
2981 rawSidecarAvailable = TRUE;
2983 detectedXdfsLba = DetectXboxVolumeStart(hDevice);
2984 if (detectedXdfsLba == 0xFFFFFFFFu)
2986 printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC);
2987 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2990 if (detectedXdfsLba != 0x20)
2992 printf("[WARN] XDFS was detected at unlocked source LBA %u, not the expected XISO header LBA 32.\n", detectedXdfsLba);
2995 // FriiDump 0.5.3.13 cache-aligned raw-ID validation proved that
2996 // unlocked source LBA 0..31 is the physical 32-sector game-region
2997 // lead-in and that XDVDFS begins at source LBA 32. Preserve the
2998 // drive-captured lead-in instead of synthesizing zero sectors.
2999 gameSourceLba = XGD1_GAME_SOURCE_START_LBA;
3000 gameSectors = REDUMP_SECTORS;
3001 rawVideoSectors = XGD1_GAME_OUTPUT_START_LBA;
3002 rawGameSourceLba = gameSourceLba;
3003 rawGameSectors = gameSectors;
3005 printf("[RAW] Capturing %u-sector game lead-in from unlocked source LBA 0..%u at output LBA %u.\n",
3006 XGD1_XISO_LEADIN_SECTORS,
3007 XGD1_XISO_LEADIN_SECTORS - 1,
3008 XGD1_GAME_OUTPUT_START_LBA);
3009 if (!DumpSectorRangeWithRetry(hDevice,
3013 XGD1_XISO_LEADIN_SECTORS,
3014 XGD1_GAME_OUTPUT_START_LBA,
3015 "GAME-XISO-LEADIN"))
3017 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3021 printf("[RAW] Writing unlocked XDFS/game data from source LBA %u for %u sectors at output LBA %u.\n",
3022 XGD1_GAME_SOURCE_START_LBA,
3023 XGD1_GAME_SOURCE_SECTORS,
3024 XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS);
3025 if (!DumpSectorRangeWithRetry(hDevice,
3028 XGD1_GAME_SOURCE_START_LBA,
3029 XGD1_GAME_SOURCE_SECTORS,
3030 XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS,
3033 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3037 if (!WriteZeroSectorsOutput(outFile, hHash, postgamePaddingSectors, XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS, "POSTGAME-PAD"))
3039 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3043 if (!WriteMemorySectorsOutput(outFile, hHash, videoL1Buffer, XGD1_VIDEO_L1_SECTORS, XGD1_VIDEO_L1_OUTPUT_START_LBA, "VIDEO-L1"))
3045 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3049 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3054 if (videoSectors > rawTargetSectors)
3055 videoSectors = rawTargetSectors;
3056 rawVideoSectors = videoSectors;
3058 printf("[RAW] Dumping contiguous visible/video area first: source LBA 0..%u.\n", videoSectors - 1);
3059 if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, videoSectors, 0, "VIDEO"))
3062 printf("[RAW] Re-applying full Xbox handshake after media transition for hidden game/data area.\n");
3063 UnlockDrive(hDevice);
3064 RefreshVolume(hDevice);
3066 EnsureDriveReady(hDevice, 30000);
3067 SetDriveSpeedMax(hDevice);
3068 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
3069 rawSidecarAvailable = TRUE;
3071 gameSourceLba = DetectXboxVolumeStart(hDevice);
3072 if (gameSourceLba == 0xFFFFFFFFu)
3074 printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC);
3078 gameSectors = rawTargetSectors - videoSectors;
3079 rawGameSourceLba = gameSourceLba;
3080 rawGameSectors = gameSectors;
3081 printf("[RAW] Appending hidden game/data area from unlocked source LBA %u for %u sectors.\n",
3082 gameSourceLba, gameSectors);
3083 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, gameSourceLba, gameSectors, videoSectors, "GAME");
3086 else if (xisoFormat == '2')
3088 uint32_t startLba = 0;
3089 uint32_t sectorsToRead = 0;
3090 unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
3091 XDFS_VOLUME_DESCRIPTOR *vol = NULL;
3092 uint32_t xgd2EndLba = 1913920;
3093 unsigned char zeroSector[2048] = {0};
3098 SetDriveSpeedMax(hDevice);
3100 vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
3102 if (ScsiReadSectors(hDevice, START_LBA_MAGIC, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0)
3104 startLba = START_LBA_MAGIC;
3105 printf("[INFO] XGD2 Game Partition identified at LBA %u\n", startLba);
3107 else if (ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0)
3110 printf("[INFO] Standard Game Partition identified at LBA 32\n");
3114 printf("[ERROR] No Xbox Game Partition found. Disc may be non-standard.\n");
3115 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3119 if (isDualLayer || totalDiscSectors > 3300000)
3121 sectorsToRead = xgd2EndLba - startLba;
3122 printf("[INFO] Dual Layer disc detected. Calculating span across layers...\n");
3126 sectorsToRead = vol->VolumeSize / 2048;
3127 printf("[INFO] Single Layer disc detected. Using header-reported size.\n");
3130 outputLabel = "XISO";
3131 expectedOutputBytes = ((unsigned long long)sectorsToRead + 32ULL) * 2048ULL;
3133 result->output_sectors = sectorsToRead + 32U;
3135 printf("[SUCCESS] Final XISO target: %u sectors plus 32-sector lead-in (%llu bytes).\n",
3137 expectedOutputBytes);
3139 if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel))
3141 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3145 outFile = fopen(filename, "wb");
3148 printf("\n[FATAL] Could not create output file '%s'.\n", filename);
3150 printf(" errno: %d (%s)\n", errno, strerror(errno));
3151 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3155 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3157 printf("Writing 64KB XISO lead-in padding...\n");
3158 for (int p = 0; p < 32; p++)
3160 if (XboxDumpCancelRequested())
3162 printf("\n[CANCEL] User cancellation requested during XISO lead-in padding at output LBA %d.\n", p);
3166 if (!WriteOutputBytes(outFile, zeroSector, 2048, "XISO-PAD", 0, (uint32_t)p))
3168 CryptHashData(hHash, zeroSector, 2048, 0);
3171 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO");
3175 printf("[ERROR] Unsupported dump mode '%c'.\n", xisoFormat);
3182 if (!FlushAndCommitOutput(outFile, outputLabel))
3188 if (fclose(outFile) != 0)
3190 printf("\n[FATAL] fclose failed for %s output.\n", outputLabel ? outputLabel : "dump");
3192 printf(" errno: %d (%s)\n", errno, strerror(errno));
3199 if (!VerifyOutputByteCount(filename, expectedOutputBytes, outputLabel))
3205 FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr);
3206 printf("\nDump/write phase complete at elapsed %s. Finalizing hashes...\n", operationTimeStr);
3208 if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
3210 HexBytesToString(rgbHash, cbHash, sha1String, sizeof(sha1String));
3213 if (CalculateFileHashes(filename, crc32String, sizeof(crc32String), md5String, sizeof(md5String), fileSha1String, sizeof(fileSha1String), fileSha256String, sizeof(fileSha256String)))
3215 if (fileSha1String[0] && sha1String[0] && strcmp(fileSha1String, sha1String) != 0)
3217 printf("\n[WARN] Streaming SHA-1 differs from file SHA-1. Using file SHA-1 in metadata.\n");
3218 printf(" Streaming SHA-1: %s\n", sha1String);
3219 printf(" File SHA-1: %s\n", fileSha1String);
3221 if (fileSha1String[0])
3222 strcpy(sha1String, fileSha1String);
3226 printf("\n[WARN] Could not calculate CRC32/MD5/SHA-1/SHA-256 from finalized output file.\n");
3230 result->output_size = GetFileSizeBytes64(filename);
3231 strncpy(result->crc32, crc32String, sizeof(result->crc32) - 1);
3232 result->crc32[sizeof(result->crc32) - 1] = '\0';
3233 strncpy(result->md5, md5String, sizeof(result->md5) - 1);
3234 result->md5[sizeof(result->md5) - 1] = '\0';
3235 strncpy(result->sha1, sha1String, sizeof(result->sha1) - 1);
3236 result->sha1[sizeof(result->sha1) - 1] = '\0';
3237 strncpy(result->sha256, fileSha256String, sizeof(result->sha256) - 1);
3238 result->sha256[sizeof(result->sha256) - 1] = '\0';
3239 result->hashes_complete = result->crc32[0] && result->md5[0] && result->sha1[0] && result->sha256[0];
3242 if (xisoFormat == '1')
3244 printf("Final RAW ISO Sector Count: %u\n", rawTargetSectors);
3245 printf("Final RAW ISO Byte Count: %llu\n", (unsigned long long)rawTargetSectors * 2048ULL);
3246 printf("CRC32: %s\n", crc32String);
3247 printf("MD5: %s\n", md5String);
3248 printf("SHA-1: %s\n", sha1String);
3249 printf("SHA-256: %s\n", fileSha256String);
3250 PrintGamePartitionHash(filename);
3251 if (rawSidecarAvailable)
3253 if (!WriteXboxDvdSidecarFiles(filename,
3265 printf("[WARN] Failed to write one or more XDVD sidecar metadata files.\n");
3270 printf("[WARN] XDVD sidecar metadata was not captured for this raw dump.\n");
3275 printf("Final XISO Byte Count: see progress target above.\n");
3276 printf("CRC32: %s\n", crc32String);
3277 printf("MD5: %s\n", md5String);
3278 printf("SHA-1: %s\n", sha1String);
3279 printf("SHA-256: %s\n", fileSha256String);
3281 FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr);
3282 printf("\nOperation Complete! Total elapsed: %s\n", operationTimeStr);
3284 /* The caller owns final drive cleanup. Embedded FriiDump runs issue one
3285 * STOP UNIT after Redump verification; standalone bridge runs stop the
3286 * drive in xbox_ref_gdr8050l_dump_core() before closing the handle. */
3288 ControlTray(hDevice, TRUE);
3293 if (result && result->cancelled)
3295 if (!FlushAndCommitOutput(outFile, outputLabel))
3296 printf("\n[WARN] Could not fully flush the controlled partial %s output before hashing.\n",
3297 outputLabel ? outputLabel : "Xbox");
3299 if (fclose(outFile) != 0)
3301 printf("\n[WARN] fclose failed for partial %s output.\n",
3302 outputLabel ? outputLabel : "Xbox");
3304 printf(" errno: %d (%s)\n", errno, strerror(errno));
3309 CryptDestroyHash(hHash);
3311 CryptReleaseContext(hProv, 0);
3313 result->dump_success = dumpOk ? 1 : 0;
3314 result->elapsed_seconds = (double)(GetTickCount() - operationStartTick) / 1000.0;
3315 if (result->output_size == 0 && filename && filename[0])
3316 result->output_size = GetFileSizeBytes64(filename);
3317 result->completed_sectors = (uint32_t)(result->output_size / 2048ULL);
3319 if (result->cancelled &&
3320 result->output_size > 0 &&
3321 !result->hashes_complete)
3323 printf("\n[HASH] Finalizing controlled partial Xbox output hashes...\n");
3324 if (CalculateFileHashes(filename,
3325 crc32String, sizeof(crc32String),
3326 md5String, sizeof(md5String),
3327 fileSha1String, sizeof(fileSha1String),
3328 fileSha256String, sizeof(fileSha256String)))
3330 strncpy(result->crc32, crc32String, sizeof(result->crc32) - 1);
3331 result->crc32[sizeof(result->crc32) - 1] = '\0';
3332 strncpy(result->md5, md5String, sizeof(result->md5) - 1);
3333 result->md5[sizeof(result->md5) - 1] = '\0';
3334 strncpy(result->sha1, fileSha1String, sizeof(result->sha1) - 1);
3335 result->sha1[sizeof(result->sha1) - 1] = '\0';
3336 strncpy(result->sha256, fileSha256String, sizeof(result->sha256) - 1);
3337 result->sha256[sizeof(result->sha256) - 1] = '\0';
3338 result->hashes_complete =
3343 if (result->hashes_complete)
3344 printf("[OK] Controlled partial Xbox output hashes captured.\n");
3348 printf("[WARN] Controlled partial Xbox output hashing failed.\n");
3353 g_xbox_dump_cancel = NULL;
3354 g_xbox_dump_cancel_data = NULL;
3355 g_xbox_dump_cancel_result = NULL;