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 HANDLE OpenDrive(char driveLetter)
39 snprintf(devicePath, sizeof(devicePath), "\\\\.\\%c:", driveLetter);
41 HANDLE hDevice = CreateFileA(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
45 void CloseDrive(HANDLE hDevice)
47 if (hDevice && hDevice != INVALID_HANDLE_VALUE)
51 int IsDiscPresent(HANDLE hDevice)
54 return DeviceIoControl(hDevice, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, &bytesReturned, NULL);
57 void ControlTray(HANDLE hDevice, BOOL eject)
59 SCSI_PASS_THROUGH_DIRECT sptd;
61 memset(&sptd, 0, sizeof(sptd));
63 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
65 sptd.TimeOutValue = 10;
66 sptd.Cdb[0] = 0x1B; // START STOP UNIT
70 printf("Software Ejecting tray...\n");
71 sptd.Cdb[4] = 0x02; // Power Action: Eject
75 printf("Software Closing tray...\n");
76 sptd.Cdb[4] = 0x03; // Power Action: Load
78 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &returned, NULL))
80 printf("Tray %s successful.\n", eject ? "eject" : "close");
84 DWORD err = GetLastError();
85 printf("Failed to %s tray. Error: %lu\n", eject ? "eject" : "close", err);
87 if (err == ERROR_ACCESS_DENIED)
89 printf("Hint: Ensure no other program is locking the drive.\n");
94 BOOL TestUnitReady(HANDLE hDevice)
96 SCSI_PASS_THROUGH_DIRECT sptd;
98 memset(&sptd, 0, sizeof(sptd));
100 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
102 sptd.TimeOutValue = 10;
103 sptd.DataTransferLength = 0;
104 sptd.DataBuffer = NULL;
106 // TEST UNIT READY. This is our practical poll for "ready/spun up".
107 // Many drives do not expose a literal spindle-state bit to normal host software;
108 // after STOP UNIT, TEST UNIT READY should fail until the unit is ready again.
111 if (!DeviceIoControl(hDevice,
112 IOCTL_SCSI_PASS_THROUGH_DIRECT,
123 return (sptd.ScsiStatus == 0);
126 BOOL StartDriveUnit(HANDLE hDevice)
128 SCSI_PASS_THROUGH_DIRECT sptd;
130 memset(&sptd, 0, sizeof(sptd));
132 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
134 sptd.TimeOutValue = 30;
135 sptd.DataTransferLength = 0;
136 sptd.DataBuffer = NULL;
138 // START STOP UNIT, START=1, LOEJ=0.
139 // This requests spin-up/start without ejecting/loading the tray.
143 printf("Sending SCSI START UNIT / spin-up command...\n");
145 if (DeviceIoControl(hDevice,
146 IOCTL_SCSI_PASS_THROUGH_DIRECT,
154 printf("SCSI START UNIT / spin-up command accepted.\n");
159 DWORD err = GetLastError();
160 printf("[WARN] SCSI START UNIT / spin-up failed. Error: %lu\n", err);
165 BOOL EnsureDriveReady(HANDLE hDevice, DWORD timeoutMs)
167 DWORD startTick = GetTickCount();
168 BOOL startIssued = FALSE;
170 printf("Polling drive readiness with TEST UNIT READY...\n");
174 if (TestUnitReady(hDevice))
176 printf("Drive reports ready.\n");
182 printf("Drive is not ready/spun up yet; requesting START UNIT.\n");
183 StartDriveUnit(hDevice);
187 if ((GetTickCount() - startTick) >= timeoutMs)
189 printf("[WARN] Drive did not report ready within %lu ms.\n", (unsigned long)timeoutMs);
190 printf(" Continuing may fail if the unit is still spun down or still reading lead-in.\n");
198 BOOL StopDriveUnit(HANDLE hDevice)
200 SCSI_PASS_THROUGH_DIRECT sptd;
202 memset(&sptd, 0, sizeof(sptd));
204 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
206 sptd.TimeOutValue = 30;
207 sptd.DataTransferLength = 0;
208 sptd.DataBuffer = NULL;
210 // START STOP UNIT, START=0, LOEJ=0.
211 // This requests a normal stop/spin-down without ejecting or loading the tray.
215 printf("Sending SCSI STOP UNIT / spin-down command...\n");
217 if (DeviceIoControl(hDevice,
218 IOCTL_SCSI_PASS_THROUGH_DIRECT,
226 printf("SCSI STOP UNIT / spin-down successful.\n");
231 DWORD err = GetLastError();
232 printf("[WARN] SCSI STOP UNIT / spin-down failed. Error: %lu\n", err);
233 printf(" Dump output has already been finalized; this only affects drive spin state.\n");
238 void AutomateTrayCycle(HANDLE hDevice)
240 ControlTray(hDevice, TRUE);
241 Sleep(3000); // Give the tray time to fully extend
244 ControlTray(hDevice, FALSE);
245 printf("Waiting for disc spin-up/readiness after tray close...\n");
246 if (EnsureDriveReady(hDevice, 45000))
248 // Small settle period after readiness so the drive can finish lead-in/media-change bookkeeping.
253 // Preserve the old conservative behavior if TEST UNIT READY polling never succeeds.
254 printf("[WARN] Falling back to fixed 10s post-close settle delay.\n");
259 BOOL SetDriveSpeedMax(HANDLE hDevice)
261 SCSI_PASS_THROUGH_DIRECT sptd = {0};
262 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
266 sptd.CdbLength = 12; // 12-byte CDB for 0xBB
267 sptd.DataIn = SCSI_IOCTL_DATA_OUT;
268 sptd.TimeOutValue = 10;
269 sptd.DataBuffer = NULL;
270 sptd.DataTransferLength = 0;
272 // CDB 0xBB: [0] Opcode, [2-3] Read Speed, [4-5] Write Speed
274 sptd.Cdb[2] = 0xFF; // MSB
275 sptd.Cdb[3] = 0xFF; // LSB
276 sptd.Cdb[4] = 0xFF; // MSB
277 sptd.Cdb[5] = 0xFF; // LSB
280 return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT,
281 &sptd, sizeof(sptd), &sptd, sizeof(sptd),
285 void ForceMediaRefresh(HANDLE hDevice)
289 // Lock the volume so Windows stops background polling
290 DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
292 // Force the storage stack to re-read the Partition Table/Capacity
293 // without sending an Eject command to the hardware.
294 if (DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL))
296 printf("Windows Partition Stack refreshed silently.\n");
299 // Explicitly dismount to kill the "Video DVD" file system driver (UDFS/ISO9660)
300 DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
304 void HexDump(unsigned char *buffer, uint32_t size)
306 for (uint32_t i = 0; i < size; i++)
309 printf("\n%04X: ", i);
310 printf("%02X ", buffer[i]);
315 void outputdata(const uint8_t *buf, uint32_t lines)
317 for (uint32_t j = 0; j < lines; j++)
319 for (uint32_t k = 0; k < 16; k++)
321 uint32_t idx = j * 16 + k;
324 printf("%02X ", buf[idx]);
330 uint8_t chksum8(const unsigned char *buff, size_t len) {
331 unsigned int sum = 0;
332 for (sum = 0; len != 0; len--)
337 void FormatElapsedTime(DWORD dwMilliseconds, char *outStr)
339 uint32_t totalSeconds = dwMilliseconds / 1000;
340 uint32_t hours = totalSeconds / 3600;
341 uint32_t minutes = (totalSeconds % 3600) / 60;
342 uint32_t seconds = totalSeconds % 60;
344 sprintf(outStr, "%02u:%02u:%02u", hours, minutes, seconds);
347 void PrintFormattedCapacity(unsigned char *scsibuffer)
349 // The first 4 bytes are the Last Logical Block Address (Big Endian)
350 uint32_t maxLBA = (scsibuffer[0] << 24) | (scsibuffer[1] << 16) |
351 (scsibuffer[2] << 8) | scsibuffer[3];
353 // The next 4 bytes are the Block Length (Big Endian)
354 uint32_t blockLen = (scsibuffer[4] << 24) | (scsibuffer[5] << 16) |
355 (scsibuffer[6] << 8) | scsibuffer[7];
357 // Total bytes = (MaxLBA + 1) * BlockLen
358 // Use double for the math to avoid 32-bit integer overflow
359 double totalBytes = (double)(maxLBA + 1) * blockLen;
360 double totalGB = totalBytes / (1024.0 * 1024.0 * 1024.0);
362 printf("--------------------------------------------\n");
363 printf("Drive Capacity Details:\n");
364 printf(" Total Sectors: %u\n", maxLBA + 1);
365 printf(" Sector Size: %u bytes\n", blockLen);
366 printf(" Total Size: %.2f GB\n", totalGB);
367 printf("--------------------------------------------\n");
370 void ListOpticalDrives()
372 DWORD drives = GetLogicalDrives();
373 char rootPath[] = "A:\\";
374 char devicePath[] = "\\\\.\\A:";
377 printf("%-5s %-12s %-18s %-15s %s\n", "ID", "Vendor", "Model", "Volume Label", "Status");
378 printf("-------------------------------------------------------------------------------\n");
380 uint8_t driveCount = 0;
381 for (int i = 0; i < 26; i++)
383 if (drives & (1 << i))
385 rootPath[0] = 'A' + i;
387 if (GetDriveTypeA(rootPath) == DRIVE_CDROM)
389 devicePath[4] = 'A' + i;
391 // 1. Get Hardware Info (Vendor/Model)
392 char vendorStr[16] = "Generic";
393 char productStr[21] = "Unknown";
395 HANDLE h = CreateFileA(devicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
396 NULL, OPEN_EXISTING, 0, NULL);
398 if (h != INVALID_HANDLE_VALUE)
400 STORAGE_PROPERTY_QUERY query = {0};
401 query.PropertyId = StorageDeviceProperty;
402 query.QueryType = PropertyStandardQuery;
405 if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query),
406 buffer, sizeof(buffer), &bytes, NULL))
408 PSTORAGE_DEVICE_DESCRIPTOR desc = (PSTORAGE_DEVICE_DESCRIPTOR)buffer;
409 if (desc->VendorIdOffset)
410 strcpy(vendorStr, (char *)(buffer + desc->VendorIdOffset));
411 if (desc->ProductIdOffset)
412 strcpy(productStr, (char *)(buffer + desc->ProductIdOffset));
417 // 2. Get Volume Info (Disc Label)
418 char volumeName[MAX_PATH + 1] = {0};
419 char statusStr[20] = "No Disc";
421 if (GetVolumeInformationA(rootPath, volumeName, sizeof(volumeName),
422 NULL, NULL, NULL, NULL, 0))
424 if (strlen(volumeName) == 0)
425 strcpy(volumeName, "[No Label]");
426 strcpy(statusStr, "Ready");
429 printf(" %c: %-12.12s %-18.18s %-15.15s %s\n",
430 rootPath[0], vendorStr, productStr, volumeName, statusStr);
436 printf("No optical drives found.\n");
437 printf("-------------------------------------------------------------------------------\n");
438 printf("Total Optical Drives Found: %u\n", driveCount);
441 uint32_t GetTotalSectors(HANDLE hDevice)
443 typedef struct _SCSI_PASS_THROUGH_WITH_BUFFERS
445 SCSI_PASS_THROUGH spt;
446 unsigned char ucDataBuf[8]; // Buffer for the 8-byte READ CAPACITY result
447 } SCSI_PASS_THROUGH_WITH_BUFFERS;
449 SCSI_PASS_THROUGH_WITH_BUFFERS sptwb = {0};
451 sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
452 sptwb.spt.CdbLength = 10; // READ CAPACITY (10) is a 10-byte command
453 sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
454 sptwb.spt.DataTransferLength = 8;
455 sptwb.spt.TimeOutValue = 2; // 2 second timeout
456 sptwb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf);
458 // CDB 0x25 = READ CAPACITY (10)
459 sptwb.spt.Cdb[0] = 0x25;
462 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH,
463 &sptwb, sizeof(sptwb),
464 &sptwb, sizeof(sptwb),
465 &bytesReturned, NULL))
468 // Extract Max LBA (Big Endian) from the first 4 bytes
469 uint32_t maxLBA = (sptwb.ucDataBuf[0] << 24) |
470 (sptwb.ucDataBuf[1] << 16) |
471 (sptwb.ucDataBuf[2] << 8) |
477 return 0; // Return 0 on failure
480 uint32_t GetXboxPhysicalSectors(HANDLE hDevice)
482 SCSI_PASS_THROUGH_DIRECT sptd = {0};
483 unsigned char buffer[2048] = {0};
486 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
488 sptd.DataIn = SCSI_IOCTL_DATA_IN;
489 sptd.DataTransferLength = 2048;
490 sptd.TimeOutValue = 10;
491 sptd.DataBuffer = buffer;
493 // READ DVD STRUCTURE (0xAD)
495 sptd.Cdb[7] = 0x00; // Format: Physical Format Information
496 sptd.Cdb[8] = 0x08; // Allocation Length (MSB)
497 sptd.Cdb[9] = 0x00; // Allocation Length (LSB)
499 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
501 // Bytes 13-15 of the PFI contain the End LBA of the data area
502 uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15];
504 // For Xbox discs, we add 1 to the End LBA to get the total count
505 // and add the 32 sectors of lead-in padding we manually create.
509 // Fallback for Dual Layer if command fails
513 // Forces Windows to re-evaluate the drive without ejecting the tray
514 void RefreshVolume(HANDLE hDevice)
517 printf("Refreshing Volume Stack (Quiet Mode)...\n");
518 // Only update properties; do NOT dismount as it resets the GDR-8163B state.
519 DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL);
520 Sleep(1000); // Essential for the firmware to re-index after the OS check
523 void ListDirectoryRecursive(HANDLE hDevice, uint32_t lba, uint32_t size, int level)
525 if (size == 0 || level > 10)
526 return; // Prevent infinite recursion
528 uint32_t sectorsToRead = (size + 2047) / 2048;
529 unsigned char *dirBuffer = (unsigned char *)VirtualAlloc(NULL, sectorsToRead * 2048, MEM_COMMIT, PAGE_READWRITE);
533 if (ScsiReadSectors(hDevice, lba, (uint16_t)sectorsToRead, dirBuffer))
536 while (offset < size)
538 XDFS_DIR_ENTRY *entry = (XDFS_DIR_ENTRY *)&dirBuffer[offset];
540 // --- SANITY CHECK 1: End of Table ---
541 // If FileNameLength is 0 or 0xFF, we've hit the padding/end of the list.
542 if (entry->FileNameLength == 0 || entry->FileNameLength == 0xFF)
545 // --- SANITY CHECK 2: Buffer Overflow ---
546 // Ensure the entry doesn't claim to exist past our allocated buffer.
547 if (offset + 14 + entry->FileNameLength > size)
550 // --- SANITY CHECK 3: Character Validation ---
551 // If the first character isn't a printable ASCII, it's a glitch entry.
552 if (entry->FileName[0] < 32 || entry->FileName[0] > 126)
556 for (int i = 0; i < level; i++)
560 if (entry->Attributes & 0x10)
569 // Print Filename safely
570 for (int i = 0; i < entry->FileNameLength; i++)
572 char c = entry->FileName[i];
573 if (c >= 32 && c <= 126)
576 printf("?"); // Replace glitches with a placeholder
579 if (!(entry->Attributes & 0x10))
581 printf(" (%u bytes)", entry->FileSize);
585 // RECURSION: Only dive if it's a valid directory LBA
586 if ((entry->Attributes & 0x10) && entry->StartLBA > 0x100)
588 ListDirectoryRecursive(hDevice, entry->StartLBA, entry->FileSize, level + 1);
591 // Move to next entry (4-byte alignment)
592 uint32_t nextOffset = (14 + entry->FileNameLength + 3) & ~3;
594 // If the calculation gives us 0, we're stuck in an infinite loop; break.
597 offset += nextOffset;
601 VirtualFree(dirBuffer, 0, MEM_RELEASE);
604 void ReadXboxGameDir(HANDLE hDevice)
606 // Single buffer for the Volume Descriptor read
607 unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
611 // Read XDFS Volume Descriptor at Sector 0x20
612 if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer))
614 printf("Error: Could not read XDFS Volume Descriptor.\n");
615 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
619 // Map the descriptor and extract root location/size
620 XDFS_VOLUME_DESCRIPTOR *vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
621 uint32_t rootLba = vol->RootLBA;
622 uint32_t rootSize = vol->RootSize;
624 // We no longer need this buffer once we have the Root LBA/Size
625 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
627 // Draw the recursive tree
628 printf("\n--- XDFS FILE SYSTEM TREE ---\n");
632 ListDirectoryRecursive(hDevice, rootLba, rootSize, 0);
636 printf("Error: Invalid Root LBA.\n");
639 printf("------------------------------\n");
642 void SanitizeFilename(char *filename)
644 if (!filename || filename[0] == '\0')
649 int lastWasSpace = 1; // Using 1 for true to trim leading spaces
651 while (filename[readIndex] != '\0')
653 unsigned char c = (unsigned char)filename[readIndex];
655 // Whitelist: Only allow Letters (isalnum) and Spaces
656 // This strips ! ' ? : " / \ | * < > and non-printable characters
657 if (isalnum(c) || c == ' ')
660 // Collapse Multiple Spaces
665 filename[writeIndex++] = ' ';
671 // It's a letter or number, write it normally
672 filename[writeIndex++] = c;
679 // Null-terminate the new shorter string
680 filename[writeIndex] = '\0';
682 // Remove trailing space if one exists
683 if (writeIndex > 0 && filename[writeIndex - 1] == ' ')
685 filename[writeIndex - 1] = '\0';
689 BOOL ScsiReadSectors(HANDLE hDevice, uint32_t lba, uint16_t count, unsigned char *buffer)
691 SCSI_PASS_THROUGH_DIRECT sptd = {0};
694 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
697 sptd.DataTransferLength = count * 2048;
698 sptd.TimeOutValue = 30;
699 sptd.DataBuffer = buffer;
701 sptd.Cdb[0] = 0x28; // READ(10)
702 sptd.Cdb[2] = (lba >> 24) & 0xFF;
703 sptd.Cdb[3] = (lba >> 16) & 0xFF;
704 sptd.Cdb[4] = (lba >> 8) & 0xFF;
705 sptd.Cdb[5] = lba & 0xFF;
706 sptd.Cdb[7] = (unsigned char)((count >> 8) & 0xFF);
707 sptd.Cdb[8] = (unsigned char)(count & 0xFF);
709 return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL);
712 XboxGameInfo GetXboxGameInfo(HANDLE hDevice)
715 memset(&info, 0, sizeof(XboxGameInfo));
716 unsigned char sectorBuffer[2048];
718 // Get Volume Descriptor (LBA 0x20)
719 if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer))
722 // Verify XDFS Magic "XGD2" or "MICROSOFT*XBOX*MEDIA"
723 if (memcmp(sectorBuffer, "MICROSOFT", 9) != 0)
725 return (XboxGameInfo){.TitleName = "Not_XDFS"};
728 uint32_t rootLba = *(uint32_t *)§orBuffer[0x14];
729 uint32_t rootSize = *(uint32_t *)§orBuffer[0x18];
731 uint32_t rawVolumeSize = *(uint32_t *)§orBuffer[0x1C];
732 // Assign to the 64-bit member (cast to ensure no weird sign extension)
733 info.TotalSizeBytes = (uint64_t)rawVolumeSize;
735 // Read Root Directory (Scanning multiple sectors for default.xbe)
736 uint32_t sectorsToRead = (rootSize + 2047) / 2048;
737 for (uint32_t s = 0; s < sectorsToRead; s++)
739 unsigned char dirBuffer[2048];
740 if (!ScsiReadSectors(hDevice, rootLba + s, 1, dirBuffer))
744 while (offset < 2030)
746 uint16_t leftNode = *(uint16_t *)&dirBuffer[offset];
747 if (leftNode == 0xFFFF)
748 break; // End of directory
750 uint32_t startLba = *(uint32_t *)&dirBuffer[offset + 4];
751 uint8_t nameLen = dirBuffer[offset + 13];
752 char *name = (char *)&dirBuffer[offset + 14];
757 // Match "default.xbe"
758 if (nameLen == 11 && _strnicmp(name, "default.xbe", 11) == 0)
760 unsigned char xbeHeader[2048];
761 if (ScsiReadSectors(hDevice, startLba, 1, xbeHeader))
764 if (*(uint32_t *)xbeHeader != 0x48454258)
767 // 4. Locate Certificate
768 uint32_t baseVA = *(uint32_t *)&xbeHeader[0x104];
769 uint32_t certVA = *(uint32_t *)&xbeHeader[0x118];
770 uint32_t fileOffset = certVA - baseVA;
772 // Certificate might be in a later sector of the XBE file
773 uint32_t certSector = startLba + (fileOffset / 2048);
774 uint32_t innerOff = (fileOffset % 2048);
776 unsigned char certBuffer[2048];
777 if (ScsiReadSectors(hDevice, certSector, 1, certBuffer))
780 // Populate the Struct from the Certificate
781 info.TitleId = *(uint32_t *)&certBuffer[innerOff + 0x008];
782 info.AllowedMedia = *(uint32_t *)&certBuffer[innerOff + 0x09C];
783 info.GameRegion = *(uint32_t *)&certBuffer[innerOff + 0x0A0];
784 info.GameRatings = *(uint32_t *)&certBuffer[innerOff + 0x0A4];
785 info.DiscNumber = *(uint32_t *)&certBuffer[innerOff + 0x0A8];
786 info.Version = *(uint32_t *)&certBuffer[innerOff + 0x0AC];
788 // Convert UTF-16 Title Name (at 0x00C) to ASCII
789 for (int i = 0; i < 40; i++)
791 char c = certBuffer[innerOff + 0x00C + (i * 2)];
794 info.TitleName[i] = c;
802 offset += (14 + nameLen + 3) & ~3; // XDFS Alignment
806 return info; // Success will be 0 if we never found default.xbe or failed to read the cert
809 void DisplayXboxGameInfo(XboxGameInfo info)
813 printf("Error: Could not retrieve Xbox game information.\n");
817 printf("\n--- Xbox Game Information ---\n");
818 printf("Title Name: %s\n", info.TitleName);
819 printf("Title ID: 0x%08X\n", info.TitleId);
820 printf("Version: %u\n", info.Version);
821 printf("Disc Number: %u\n", info.DiscNumber);
825 if (info.GameRegion & XB_REGION_MANUFACTURING)
826 printf("[Manufacturing] ");
827 if (info.GameRegion & XB_REGION_US_CANADA)
828 printf("North America ");
829 if (info.GameRegion & XB_REGION_JAPAN)
831 if (info.GameRegion & XB_REGION_EUROPE_AU_NZ)
832 printf("Europe/AU ");
833 if (info.GameRegion & XB_REGION_REST_OF_WORLD)
834 printf("Rest of World ");
836 // If everything is set (0x7FFFFFFF or 0xFFFFFFFF), it's Region Free
837 if ((info.GameRegion & 0x7FFFFFFF) == 0x7FFFFFFF)
839 printf("(Region Free)");
841 else if (info.GameRegion == 0)
843 printf("None (Locked)");
847 // Decode Media Types
848 printf("Allowed Media: ");
849 if (info.AllowedMedia & XB_MEDIA_HARD_DRIVE)
851 if (info.AllowedMedia & XB_MEDIA_DVD_X2)
853 if (info.AllowedMedia & XB_MEDIA_DVD_5_RO)
855 if (info.AllowedMedia & XB_MEDIA_DVD_9_RO)
857 if (info.AllowedMedia & XB_MEDIA_CD)
859 if (info.AllowedMedia & XB_MEDIA_DONGLE)
860 printf("Memory_Unit ");
863 DisplayXboxRating(info.GameRatings);
865 printf("-----------------------------\n");
868 void DisplayXboxRating(uint32_t ratings)
870 // ESRB (North America) - Byte 0 (Bits 0-7)
871 uint8_t esrb = (uint8_t)(ratings & 0xFF);
872 if (esrb != 0 && esrb != 0xFF)
874 printf("ESRB Rating: ");
878 printf("EC (Early Childhood)\n");
881 printf("E (Everyone)\n");
884 printf("K-A (Kids to Adults)\n");
887 printf("T (Teen)\n");
890 printf("M (Mature)\n");
893 printf("AO (Adults Only)\n");
896 printf("RP (Rating Pending/Unrated)\n");
901 // PEGI (Europe) - Byte 1 (Bits 8-15)
902 uint8_t pegi = (uint8_t)((ratings >> 8) & 0xFF);
903 if (pegi != 0 && pegi != 0xFF)
905 printf("PEGI Rating: ");
924 printf("Other (0x%02X)\n", pegi);
929 // CERO (Japan) - Byte 2 (Bits 16-23)
930 uint8_t cero = (uint8_t)((ratings >> 16) & 0xFF);
931 if (cero != 0 && cero != 0xFF)
933 printf("CERO Rating: ");
937 printf("A (All Ages)\n");
949 printf("Z (18+ Only)\n");
952 printf("Other (0x%02X)\n", cero);
957 if ((ratings & 0x00FFFFFF) == 0)
959 printf("Rating: None/Unrated\n");
963 // --- POST-DUMP VERIFICATION ---
964 void PrintGamePartitionHash(const char *filename)
966 FILE *f = fopen(filename, "rb");
968 uint32_t startLba = START_LBA_MAGIC;
969 unsigned long long bytesRemaining = 0ULL;
970 unsigned long long totalBytesToHash = 0ULL;
971 unsigned long long bytesDone = 0ULL;
974 HCRYPTPROV hProv = 0;
975 HCRYPTHASH hHash = 0;
978 char finalHash[41] = {0};
980 DWORD lastPrintTick = 0;
984 char timeStr[12] = {0};
985 char etaStr[12] = {0};
990 if (_fseeki64(f, 0, SEEK_END) != 0)
995 fileBytes = _ftelli64(f);
1002 if ((unsigned long long)fileBytes == (unsigned long long)XGD1_FULL_REDUMP_SECTORS * 2048ULL)
1004 startLba = XGD1_GAME_OUTPUT_START_LBA;
1005 bytesRemaining = (unsigned long long)REDUMP_SECTORS * 2048ULL;
1006 printf("[HASH] Calculating Game/XISO-region SHA-1 (Redump-style output LBA %u, %u sectors)...\n",
1007 startLba, REDUMP_SECTORS);
1011 startLba = START_LBA_MAGIC;
1012 bytesRemaining = ((unsigned long long)fileBytes > (unsigned long long)startLba * 2048ULL)
1013 ? ((unsigned long long)fileBytes - (unsigned long long)startLba * 2048ULL)
1015 printf("[HASH] Calculating Game-Partition-Only SHA-1 (legacy contiguous output LBA %u)...\n", startLba);
1018 totalBytesToHash = bytesRemaining;
1019 if (bytesRemaining == 0)
1025 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
1030 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
1032 CryptReleaseContext(hProv, 0);
1037 _fseeki64(f, (__int64)startLba * 2048, SEEK_SET);
1039 vBuf = (unsigned char *)malloc(1024 * 1024); // 1MB buffer
1042 CryptDestroyHash(hHash);
1043 CryptReleaseContext(hProv, 0);
1048 startTick = GetTickCount();
1049 lastPrintTick = startTick;
1051 while (bytesRemaining > 0 && (read = fread(vBuf, 1, (bytesRemaining > 1024ULL * 1024ULL) ? 1024 * 1024 : (size_t)bytesRemaining, f)) > 0)
1053 CryptHashData(hHash, vBuf, (DWORD)read, 0);
1054 bytesRemaining -= read;
1055 bytesDone += (unsigned long long)read;
1057 nowTick = GetTickCount();
1058 if (bytesDone >= totalBytesToHash || (nowTick - lastPrintTick) >= 1000)
1060 double percent = ((double)bytesDone / (double)totalBytesToHash) * 100.0;
1061 double mbDone = (double)bytesDone / (1024.0 * 1024.0);
1063 elapsedMs = nowTick - startTick;
1065 speed = mbDone / ((double)elapsedMs / 1000.0);
1066 etaMs = (bytesDone > 0 && elapsedMs > 0)
1067 ? (DWORD)(((double)elapsedMs / (double)bytesDone) * (double)(totalBytesToHash - bytesDone))
1069 FormatElapsedTime(elapsedMs, timeStr);
1070 FormatElapsedTime(etaMs, etaStr);
1071 xbox_ref_console_printf("\r[HASH] Game/XISO-region: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ",
1078 lastPrintTick = nowTick;
1082 CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0);
1083 for (int i = 0; i < 20; i++)
1084 sprintf(&finalHash[i * 2], "%02x", rgbHash[i]);
1086 elapsedMs = GetTickCount() - startTick;
1087 FormatElapsedTime(elapsedMs, timeStr);
1088 if (totalBytesToHash > 0)
1089 xbox_ref_console_printf("\r[HASH] Game/XISO-region: 100.0%% | %.1f MB | Time: %s \n",
1090 (double)totalBytesToHash / (1024.0 * 1024.0),
1092 printf("Game/XISO-region SHA-1: %s\n", finalHash);
1093 printf("[OK] Game/XISO-region SHA-1 complete in %s.\n", timeStr);
1096 CryptDestroyHash(hHash);
1097 CryptReleaseContext(hProv, 0);
1101 void GetMediaID(HANDLE hDevice, char *outMediaId)
1103 SCSI_PASS_THROUGH_DIRECT sptd = {0};
1104 unsigned char buffer[2048] = {0};
1105 DWORD bytesReturned;
1107 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
1108 sptd.CdbLength = 12;
1109 sptd.DataIn = SCSI_IOCTL_DATA_IN;
1110 sptd.DataTransferLength = 2048;
1111 sptd.TimeOutValue = 5;
1112 sptd.DataBuffer = buffer;
1114 sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE
1115 sptd.Cdb[7] = 0x04; // Format: Disc Manufacturing Information (DMI)
1119 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
1121 // The Media ID is typically 32 bytes starting at offset 4 in the DMI
1122 // Offset 8 is where "MS11..." usually starts on Xbox discs
1123 // We'll grab 16 characters to be safe
1125 for (int i = 8; i < 24; i++)
1127 // Only add alphanumeric characters to keep the filename clean
1128 if (isalnum(buffer[i]))
1130 outMediaId[writePos++] = buffer[i];
1133 outMediaId[writePos] = '\0'; // Null terminate the string
1137 strcpy(outMediaId, "UNKNOWN_ID");
1141 void GetDiscMetadata(HANDLE hDevice, uint32_t *totalSectors, bool *isDualLayer, XboxGameInfo *gameInfo)
1143 SCSI_PASS_THROUGH_DIRECT sptd = {0};
1144 unsigned char buffer[2048] = {0};
1145 DWORD bytesReturned;
1147 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
1148 sptd.CdbLength = 12;
1149 sptd.DataIn = SCSI_IOCTL_DATA_IN;
1150 sptd.DataTransferLength = 2048;
1151 sptd.TimeOutValue = 10;
1152 sptd.DataBuffer = buffer;
1154 sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE
1155 sptd.Cdb[7] = 0x00; // Physical Format Information
1156 sptd.Cdb[8] = 0x08; // 2048 bytes
1159 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
1162 // Byte 12: bits 5-6 (Number of Layers)
1163 // 0x20 = 00100000 (Two layers), 0x00 = 00000000 (One layer)
1164 unsigned char layerInfo = (buffer[12] >> 5) & 0x03;
1165 *isDualLayer = (layerInfo > 0);
1167 // Bytes 13-15: End LBA
1168 uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15];
1170 // XBOX SANITY CHECK
1171 // If the drive reports a value much larger than a standard Xbox Dual Layer (3.4M sectors)
1172 // it means the drive is reporting the raw DVD-9 limit. We must cap it.
1173 if (endLba > ((uint32_t)REDUMP_SECTORS - 1))
1175 printf("[!] Drive reported Raw DVD-9 geometry. Normalizing to Xbox Dual Layer...\n");
1176 if (gameInfo->TotalSizeBytes < LAYER_BREAK)
1178 printf("[!] Info: Game partition size is smaller than expected for a Dual Layer disc.\n");
1180 *totalSectors = REDUMP_SECTORS;
1181 *isDualLayer = true;
1185 printf("[!] Drive reported Raw DVD-5 geometry. Normalizing to Xbox Single Layer...\n");
1186 *totalSectors = endLba + 1;
1187 *isDualLayer = (endLba > (uint32_t)LAYER_THRESHOLD); // Standard threshold for SL vs DL
1193 *isDualLayer = true;
1194 *totalSectors = REDUMP_SECTORS;
1195 printf("Media Info: Could not read PFI. Defaulting to Dual Layer.\n");
1199 uint32_t GetGamePartitionSize(HANDLE hDevice, uint32_t totalDiscSectors, XDFS_VOLUME_DESCRIPTOR *vol)
1201 uint32_t sectorsToRead = 0;
1202 if (totalDiscSectors > 3300000)
1204 // DUAL LAYER (XGD2) Calculation:
1205 // LBA 1,913,920 is the physical end of the usable XDFS area on retail DVD-9s.
1206 uint32_t xgd2EndLba = 1913920;
1207 sectorsToRead = xgd2EndLba - vol->RootLBA;
1211 // SINGLE LAYER (XGD1 / Homebrew) Calculation:
1212 // On single layer discs, the header's VolumeSize is trustworthy.
1213 sectorsToRead = vol->VolumeSize / 2048;
1215 return sectorsToRead;
1218 static BOOL ProbeXboxVolumeAt(HANDLE hDevice, uint32_t lba)
1220 unsigned char sector[2048] = {0};
1221 return ScsiReadSectors(hDevice, lba, 1, sector) && memcmp(sector, "MICROSOFT", 9) == 0;
1224 static uint32_t DetectXboxVolumeStart(HANDLE hDevice)
1226 if (ProbeXboxVolumeAt(hDevice, START_LBA_MAGIC))
1227 return START_LBA_MAGIC;
1229 if (ProbeXboxVolumeAt(hDevice, 0x20))
1235 static void RecoveryKick(HANDLE hDevice, BOOL authRecovery)
1237 unsigned char dummy[2048] = {0};
1240 KickXboxMediaAuth(hDevice);
1242 SetDriveSpeedMax(hDevice);
1244 for (int i = 0; i < 10; i++)
1246 ScsiReadSectors(hDevice, 0, 1, dummy);
1252 static void GetDirectoryForPath(const char *filename, char *outDir, DWORD outDirSize)
1255 char fullPath[MAX_PATH];
1256 char *filePart = NULL;
1258 if (!outDir || outDirSize == 0)
1263 if (!filename || filename[0] == '\0')
1265 GetCurrentDirectoryA(outDirSize, outDir);
1269 len = GetFullPathNameA(filename, (DWORD)sizeof(fullPath), fullPath, &filePart);
1270 if (len == 0 || len >= sizeof(fullPath))
1272 GetCurrentDirectoryA(outDirSize, outDir);
1276 if (filePart && filePart > fullPath)
1278 size_t dirLen = (size_t)(filePart - fullPath);
1279 if (dirLen >= outDirSize)
1280 dirLen = outDirSize - 1;
1281 memcpy(outDir, fullPath, dirLen);
1282 outDir[dirLen] = '\0';
1286 GetCurrentDirectoryA(outDirSize, outDir);
1290 static BOOL FileExistsAndSize(const char *filename, unsigned long long *sizeOut)
1292 WIN32_FILE_ATTRIBUTE_DATA fad;
1297 if (!filename || !GetFileAttributesExA(filename, GetFileExInfoStandard, &fad))
1300 if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1305 ULARGE_INTEGER size;
1306 size.HighPart = fad.nFileSizeHigh;
1307 size.LowPart = fad.nFileSizeLow;
1308 *sizeOut = size.QuadPart;
1314 static BOOL CheckOutputFreeSpace(const char *filename, unsigned long long expectedBytes, const char *label)
1317 ULARGE_INTEGER freeToCaller;
1318 ULARGE_INTEGER totalBytes;
1319 ULARGE_INTEGER totalFree;
1320 unsigned long long existingBytes = 0ULL;
1321 unsigned long long effectiveFree;
1322 unsigned long long margin;
1324 if (expectedBytes == 0)
1327 GetDirectoryForPath(filename, dir, (DWORD)sizeof(dir));
1329 if (!GetDiskFreeSpaceExA(dir[0] ? dir : NULL, &freeToCaller, &totalBytes, &totalFree))
1331 DWORD err = GetLastError();
1332 printf("\n[WARN] Could not check free space for output path '%s' (GetDiskFreeSpaceEx error %lu).\n", filename, err);
1333 printf(" Continuing, but write errors will still be caught during the dump.\n");
1337 FileExistsAndSize(filename, &existingBytes);
1339 // If overwriting an existing output on the same volume, its current bytes can be
1340 // reclaimed by fopen(..., "wb"). This avoids rejecting a valid replacement run.
1341 effectiveFree = freeToCaller.QuadPart + existingBytes;
1343 // Add a small safety margin for sidecar/profile files and filesystem metadata.
1344 // Keep this modest so overwriting an existing full raw ISO still passes.
1345 margin = 64ULL * 1024ULL * 1024ULL;
1347 printf("[%s] Free-space preflight for '%s':\n", label ? label : "OUTPUT", filename);
1348 printf(" Required output bytes: %llu\n", expectedBytes);
1349 printf(" Safety margin: %llu\n", margin);
1350 printf(" Free to caller: %llu\n", (unsigned long long)freeToCaller.QuadPart);
1352 printf(" Existing output bytes: %llu (counted as reclaimable overwrite space)\n", existingBytes);
1353 printf(" Effective available: %llu\n", effectiveFree);
1355 if (effectiveFree < expectedBytes + margin)
1357 printf("\n[FATAL] Not enough free disk space for %s.\n", label ? label : "output");
1358 printf(" Required + margin: %llu bytes\n", expectedBytes + margin);
1359 printf(" Effective free: %llu bytes\n", effectiveFree);
1360 printf(" Free space can change while dumping; free extra space and rerun.\n");
1367 static BOOL WriteOutputBytes(FILE *outFile,
1369 size_t bytesToWrite,
1370 const char *phaseName,
1376 if (!outFile || !data || bytesToWrite == 0)
1377 return bytesToWrite == 0;
1379 written = fwrite(data, 1, bytesToWrite, outFile);
1380 if (written != bytesToWrite)
1382 printf("\n[FATAL] Output write failed during %s range.\n", phaseName ? phaseName : "dump");
1383 printf(" Source LBA: %u | Output LBA: %u\n", sourceLba, outputLba);
1384 printf(" Requested: %llu bytes\n", (unsigned long long)bytesToWrite);
1385 printf(" Written: %llu bytes\n", (unsigned long long)written);
1387 printf(" errno: %d (%s)\n", errno, strerror(errno));
1388 printf(" This commonly means another program consumed free space after preflight,\n");
1389 printf(" the destination volume filled up, or the destination became unavailable.\n");
1393 if (ferror(outFile))
1395 printf("\n[FATAL] Output stream error during %s range at output LBA %u.\n",
1396 phaseName ? phaseName : "dump", outputLba);
1398 printf(" errno: %d (%s)\n", errno, strerror(errno));
1405 static BOOL FlushAndCommitOutput(FILE *outFile, const char *label)
1412 if (fflush(outFile) != 0)
1414 printf("\n[FATAL] fflush failed for %s output.\n", label ? label : "dump");
1416 printf(" errno: %d (%s)\n", errno, strerror(errno));
1420 fd = _fileno(outFile);
1421 if (fd >= 0 && _commit(fd) != 0)
1423 printf("\n[FATAL] _commit failed for %s output. The OS may not have accepted all buffered data.\n",
1424 label ? label : "dump");
1426 printf(" errno: %d (%s)\n", errno, strerror(errno));
1433 static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
1436 uint32_t sourceStartLba,
1437 uint32_t sectorsToRead,
1438 uint32_t outputBaseLba,
1439 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 RecoveryKick(hDevice, authRecovery);
1464 while (sectorsDone < sectorsToRead)
1466 uint32_t currentLba = sourceStartLba + sectorsDone;
1467 const char *currentLayerStr = "L0";
1468 uint32_t burstLimit = batchSize;
1470 BOOL success = FALSE;
1472 if ((outputBaseLba + sectorsDone) >= LAYER_BREAK)
1473 currentLayerStr = "L1";
1475 // Layer-boundary safety: do not let one READ(10) span the Xbox layer break.
1476 if (sectorsDone == 0 || (outputBaseLba + sectorsDone) == LAYER_BREAK)
1480 else if ((outputBaseLba + sectorsDone) < LAYER_BREAK &&
1481 (outputBaseLba + sectorsDone + batchSize) > LAYER_BREAK)
1483 burstLimit = LAYER_BREAK - (outputBaseLba + sectorsDone);
1486 toRead = (sectorsToRead - sectorsDone > burstLimit) ? burstLimit : (sectorsToRead - sectorsDone);
1488 if ((outputBaseLba + sectorsDone) == LAYER_BREAK)
1490 printf("\n[INFO] Redump/XGD1 output layer break at LBA %u. Reducing burst size to 1 sector for safety.\n", LAYER_BREAK);
1493 for (int retry = 0; retry <= MAX_RETRIES; retry++)
1495 if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, buffer))
1497 if (!WriteOutputBytes(outFile, buffer, (size_t)toRead * 2048U, phaseName, currentLba, outputBaseLba + sectorsDone))
1499 VirtualFree(buffer, 0, MEM_RELEASE);
1502 CryptHashData(hHash, buffer, toRead * 2048, 0);
1503 sectorsDone += toRead;
1508 RecoveryKick(hDevice, authRecovery);
1514 unsigned char *smallBuffer = NULL;
1515 printf("\n[!] Batch failed in %s range at source LBA %u. Recovering sectors individually.\n", phaseName, currentLba);
1517 smallBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
1520 printf("\n[FATAL] Could not allocate single-sector recovery buffer.\n");
1521 VirtualFree(buffer, 0, MEM_RELEASE);
1525 for (uint32_t i = 0; i < toRead; i++)
1527 BOOL sectorSuccess = FALSE;
1529 for (int sRetry = 0; sRetry <= MAX_RETRIES; sRetry++)
1531 if (ScsiReadSectors(hDevice, currentLba + i, 1, smallBuffer))
1533 if (!WriteOutputBytes(outFile, smallBuffer, 2048, phaseName, currentLba + i, outputBaseLba + sectorsDone))
1535 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1536 VirtualFree(buffer, 0, MEM_RELEASE);
1539 CryptHashData(hHash, smallBuffer, 2048, 0);
1541 sectorSuccess = TRUE;
1545 RecoveryKick(hDevice, authRecovery);
1551 printf("\n[FATAL] Unrecoverable %s sector at source LBA %u. Output hash is invalid.\n", phaseName, currentLba + i);
1552 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1553 VirtualFree(buffer, 0, MEM_RELEASE);
1558 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1561 if (sectorsDone > 0)
1563 DWORD elapsedMs = GetTickCount() - startTime;
1564 uint32_t sectorsLeft = sectorsToRead - sectorsDone;
1565 DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft);
1566 float percent = ((float)sectorsDone / sectorsToRead) * 100.0f;
1567 float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024;
1568 float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f;
1570 FormatElapsedTime(elapsedMs, timeStr);
1571 FormatElapsedTime(etaMs, etaStr);
1573 xbox_ref_console_printf("\rProgress [%s/%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | sourceLba: %u | outputLba: %u | Time: %s | ETA: %s ",
1574 phaseName, currentLayerStr, percent, mbDone, speed, currentLba, outputBaseLba + sectorsDone, timeStr, etaStr);
1579 printf("\n[OK] Completed %s range.\n", phaseName);
1580 VirtualFree(buffer, 0, MEM_RELEASE);
1587 static BOOL WriteZeroSectorsOutput(FILE *outFile,
1589 uint32_t sectorCount,
1590 uint32_t outputBaseLba,
1591 const char *phaseName)
1593 const uint32_t batchSectors = 32;
1594 unsigned char *zeroBuffer = NULL;
1595 uint32_t sectorsDone = 0;
1596 DWORD startTime = GetTickCount();
1597 char timeStr[12] = {0};
1598 char etaStr[12] = {0};
1600 if (sectorCount == 0)
1603 zeroBuffer = (unsigned char *)VirtualAlloc(NULL, batchSectors * 2048, MEM_COMMIT, PAGE_READWRITE);
1606 printf("\n[FATAL] Could not allocate zero-fill buffer for %s range.\n", phaseName ? phaseName : "padding");
1609 memset(zeroBuffer, 0, batchSectors * 2048);
1611 printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "ZERO");
1612 printf("Output LBA: %u | Sectors: %u | Fill: synthetic zero-fill (not drive-captured)\n", outputBaseLba, sectorCount);
1614 while (sectorsDone < sectorCount)
1616 uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1617 uint32_t outputLba = outputBaseLba + sectorsDone;
1619 if (!WriteOutputBytes(outFile, zeroBuffer, (size_t)toWrite * 2048U, phaseName ? phaseName : "ZERO", 0, outputLba))
1621 VirtualFree(zeroBuffer, 0, MEM_RELEASE);
1625 CryptHashData(hHash, zeroBuffer, toWrite * 2048, 0);
1627 sectorsDone += toWrite;
1629 if (sectorsDone > 0)
1631 DWORD elapsedMs = GetTickCount() - startTime;
1632 uint32_t sectorsLeft = sectorCount - sectorsDone;
1633 DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft);
1634 float percent = ((float)sectorsDone / sectorCount) * 100.0f;
1635 float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024;
1636 float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f;
1638 FormatElapsedTime(elapsedMs, timeStr);
1639 FormatElapsedTime(etaMs, etaStr);
1640 xbox_ref_console_printf("\rProgress [%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | outputLba: %u | Time: %s | ETA: %s ",
1641 phaseName ? phaseName : "ZERO", percent, mbDone, speed, outputBaseLba + sectorsDone, timeStr, etaStr);
1646 printf("\n[OK] Completed %s range.\n", phaseName ? phaseName : "ZERO");
1647 VirtualFree(zeroBuffer, 0, MEM_RELEASE);
1651 static BOOL ReadSectorsToMemory(HANDLE hDevice,
1652 uint32_t sourceStartLba,
1653 uint32_t sectorCount,
1654 unsigned char *outBuffer,
1655 const char *phaseName)
1657 const uint32_t batchSectors = 32;
1658 uint32_t sectorsDone = 0;
1660 if (sectorCount == 0)
1665 printf("[RAW] Capturing %s to memory: source LBA %u..%u (%u sectors).\n",
1666 phaseName ? phaseName : "sector range",
1668 sourceStartLba + sectorCount - 1,
1671 while (sectorsDone < sectorCount)
1673 uint32_t toRead = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1674 uint32_t currentLba = sourceStartLba + sectorsDone;
1675 BOOL success = FALSE;
1677 for (int retry = 0; retry <= MAX_RETRIES; retry++)
1679 if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, outBuffer + ((size_t)sectorsDone * 2048U)))
1681 sectorsDone += toRead;
1690 printf("\n[FATAL] Could not capture %s at source LBA %u.\n", phaseName ? phaseName : "sector range", currentLba);
1698 static BOOL WriteMemorySectorsOutput(FILE *outFile,
1700 const unsigned char *buffer,
1701 uint32_t sectorCount,
1702 uint32_t outputBaseLba,
1703 const char *phaseName)
1705 const uint32_t batchSectors = 32;
1706 uint32_t sectorsDone = 0;
1708 if (sectorCount == 0)
1713 printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "MEMORY");
1714 printf("Output LBA: %u | Sectors: %u | Source: captured memory\n", outputBaseLba, sectorCount);
1716 while (sectorsDone < sectorCount)
1718 uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1719 uint32_t outputLba = outputBaseLba + sectorsDone;
1720 const unsigned char *src = buffer + ((size_t)sectorsDone * 2048U);
1722 if (!WriteOutputBytes(outFile, src, (size_t)toWrite * 2048U, phaseName ? phaseName : "MEMORY", 0, outputLba))
1725 CryptHashData(hHash, src, toWrite * 2048, 0);
1727 sectorsDone += toWrite;
1730 printf("[OK] Completed %s range.\n", phaseName ? phaseName : "MEMORY");
1734 typedef struct _XboxDvdSidecarCapture
1736 BOOL hasLockedCapacity;
1737 BOOL hasLockedModeSense3E;
1738 BOOL hasUnlockedCapacity;
1739 BOOL hasUnlockedModeSense3E;
1744 unsigned char lockedCapacity[8];
1745 unsigned char lockedModeSense3E[28];
1746 unsigned char unlockedCapacity[8];
1747 unsigned char unlockedModeSense3E[28];
1749 unsigned char adC0[0x664];
1750 unsigned char pfi[2048];
1751 unsigned char dmi[2048];
1752 } XboxDvdSidecarCapture;
1754 static void StripKnownExtension(const char *filename, char *outBase, size_t outBaseSize)
1761 if (!outBase || outBaseSize == 0)
1768 strncpy(outBase, filename, outBaseSize - 1);
1769 outBase[outBaseSize - 1] = '\0';
1771 dot = strrchr(outBase, '.');
1772 slash1 = strrchr(outBase, '\\');
1773 slash2 = strrchr(outBase, '/');
1774 slash = slash1 > slash2 ? slash1 : slash2;
1776 if (dot && (!slash || dot > slash))
1780 static void MakeSidecarPath(const char *filename, const char *suffix, char *outPath, size_t outPathSize)
1782 char base[MAX_PATH];
1784 if (!outPath || outPathSize == 0)
1787 StripKnownExtension(filename, base, sizeof(base));
1788 snprintf(outPath, outPathSize, "%s%s", base, suffix);
1789 outPath[outPathSize - 1] = '\0';
1792 static BOOL WriteBinaryFile(const char *path, const unsigned char *data, size_t len)
1796 if (!path || !data || len == 0)
1799 f = fopen(path, "wb");
1803 if (fwrite(data, 1, len, f) != len)
1813 static void JsonWriteEscapedString(FILE *f, const char *s)
1820 unsigned char c = (unsigned char)*s++;
1821 if (c == '"' || c == '\\')
1840 fprintf(f, "\\u%04x", c);
1851 static void JsonWriteHexString(FILE *f, const unsigned char *data, size_t len)
1856 for (size_t i = 0; i < len; i++)
1857 fprintf(f, "%02X", data[i]);
1863 static const char *PathLeaf(const char *path)
1871 slash1 = strrchr(path, '\\');
1872 slash2 = strrchr(path, '/');
1874 if (slash1 && slash2)
1875 return (slash1 > slash2 ? slash1 : slash2) + 1;
1883 static void TrimTrailingSpaces(char *s)
1891 while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t'))
1898 static void CopyBounded(char *dst, size_t dstSize, const char *src, size_t srcLen)
1902 if (!dst || dstSize == 0)
1913 memcpy(dst, src, n);
1917 static void ExtractMediaProfileNames(const char *isoFilename,
1919 size_t titleHintSize,
1923 char base[MAX_PATH];
1925 const char *openBracket;
1926 const char *closeBracket;
1928 if (titleHint && titleHintSize > 0)
1929 titleHint[0] = '\0';
1930 if (mediaId && mediaIdSize > 0)
1936 StripKnownExtension(isoFilename, base, sizeof(base));
1937 leaf = PathLeaf(base);
1939 openBracket = strrchr(leaf, '[');
1940 closeBracket = openBracket ? strchr(openBracket, ']') : NULL;
1942 if (openBracket && closeBracket && closeBracket > openBracket)
1944 CopyBounded(titleHint, titleHintSize, leaf, (size_t)(openBracket - leaf));
1945 CopyBounded(mediaId, mediaIdSize, openBracket + 1, (size_t)(closeBracket - openBracket - 1));
1949 CopyBounded(titleHint, titleHintSize, leaf, strlen(leaf));
1952 TrimTrailingSpaces(titleHint);
1955 static void JsonWriteValidationWarnings(FILE *f, const XboxDvdSidecarCapture *cap, BOOL payloadFilesPresent, BOOL redumpStyleZeroFilledPadding)
1961 #define WRITE_WARNING(w) do { \
1962 if (wrote) fprintf(f, ", "); \
1963 JsonWriteEscapedString(f, (w)); \
1967 if (!cap || !cap->hasLockedCapacity)
1968 WRITE_WARNING("missing_locked_read_capacity_10");
1969 if (!cap || !cap->hasLockedModeSense3E)
1970 WRITE_WARNING("missing_locked_mode_sense_3e");
1971 if (!cap || !cap->hasUnlockedCapacity)
1972 WRITE_WARNING("missing_unlocked_read_capacity_10");
1973 if (!cap || !cap->hasUnlockedModeSense3E)
1974 WRITE_WARNING("missing_unlocked_mode_sense_3e");
1975 if (!cap || !cap->hasAdC0)
1976 WRITE_WARNING("missing_ad_c0_payload");
1977 if (!cap || !cap->hasPfi)
1978 WRITE_WARNING("missing_pfi_payload");
1979 if (!cap || !cap->hasDmi)
1980 WRITE_WARNING("missing_dmi_payload");
1981 if (!payloadFilesPresent)
1982 WRITE_WARNING("payload_files_not_fully_present");
1983 if (redumpStyleZeroFilledPadding)
1984 WRITE_WARNING("redump_style_padding_zero_filled_unresolved_content");
1986 #undef WRITE_WARNING
1991 static BOOL ScsiDataInCommand(HANDLE hDevice,
1992 const unsigned char *cdb,
1995 unsigned char *buffer)
1997 SCSI_PASS_THROUGH_DIRECT sptd;
1998 DWORD bytesReturned = 0;
2000 if (!hDevice || !cdb || !buffer || dataLen == 0 || cdbLen == 0 || cdbLen > 16)
2003 memset(&sptd, 0, sizeof(sptd));
2004 memset(buffer, 0, dataLen);
2006 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
2007 sptd.CdbLength = cdbLen;
2008 sptd.DataIn = SCSI_IOCTL_DATA_IN;
2009 sptd.DataTransferLength = dataLen;
2010 sptd.TimeOutValue = 30;
2011 sptd.DataBuffer = buffer;
2012 memcpy(sptd.Cdb, cdb, cdbLen);
2014 return DeviceIoControl(hDevice,
2015 IOCTL_SCSI_PASS_THROUGH_DIRECT,
2024 static BOOL CaptureReadCapacity10(HANDLE hDevice, unsigned char out8[8])
2026 static const unsigned char cdb[10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
2027 return ScsiDataInCommand(hDevice, cdb, 10, 8, out8);
2030 static BOOL CaptureModeSense3E(HANDLE hDevice, unsigned char out28[28])
2032 static const unsigned char cdb[10] = {0x5A, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00};
2033 return ScsiDataInCommand(hDevice, cdb, 10, 28, out28);
2036 static BOOL CaptureReadDvdStructureXboxC0(HANDLE hDevice, unsigned char out1664[0x664])
2038 static const unsigned char cdb[12] = {0xAD, 0x00, 0xFF, 0x02, 0xFD, 0xFF, 0xFE, 0x00, 0x06, 0x64, 0x00, 0xC0};
2039 return ScsiDataInCommand(hDevice, cdb, 12, 0x664, out1664);
2042 static BOOL CaptureReadDvdStructurePfi(HANDLE hDevice, unsigned char out2048[2048])
2044 static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00};
2045 return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048);
2048 static BOOL CaptureReadDvdStructureDmi(HANDLE hDevice, unsigned char out2048[2048])
2050 static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00};
2051 return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048);
2054 static void CaptureLockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap)
2059 cap->hasLockedCapacity = CaptureReadCapacity10(hDevice, cap->lockedCapacity);
2060 cap->hasLockedModeSense3E = CaptureModeSense3E(hDevice, cap->lockedModeSense3E);
2062 printf("[META] Locked READ CAPACITY: %s\n", cap->hasLockedCapacity ? "captured" : "failed");
2063 printf("[META] Locked MODE SENSE 0x3E: %s\n", cap->hasLockedModeSense3E ? "captured" : "failed");
2066 static void CaptureUnlockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap)
2071 cap->hasUnlockedCapacity = CaptureReadCapacity10(hDevice, cap->unlockedCapacity);
2072 cap->hasUnlockedModeSense3E = CaptureModeSense3E(hDevice, cap->unlockedModeSense3E);
2073 cap->hasAdC0 = CaptureReadDvdStructureXboxC0(hDevice, cap->adC0);
2074 cap->hasPfi = CaptureReadDvdStructurePfi(hDevice, cap->pfi);
2075 cap->hasDmi = CaptureReadDvdStructureDmi(hDevice, cap->dmi);
2077 printf("[META] Unlocked READ CAPACITY: %s\n", cap->hasUnlockedCapacity ? "captured" : "failed");
2078 printf("[META] Unlocked MODE SENSE 0x3E: %s\n", cap->hasUnlockedModeSense3E ? "captured" : "failed");
2079 printf("[META] READ DVD STRUCTURE Xbox C0 block: %s\n", cap->hasAdC0 ? "captured" : "failed");
2080 printf("[META] READ DVD STRUCTURE PFI: %s\n", cap->hasPfi ? "captured" : "failed");
2081 printf("[META] READ DVD STRUCTURE DMI: %s\n", cap->hasDmi ? "captured" : "failed");
2084 static uint32_t CapacitySectorsFromReadCapacity10(const unsigned char data[8])
2091 maxLba = ((uint32_t)data[0] << 24) |
2092 ((uint32_t)data[1] << 16) |
2093 ((uint32_t)data[2] << 8) |
2094 ((uint32_t)data[3]);
2099 static uint32_t NormalizeRawIsoTargetSectors(uint32_t reportedSectors, BOOL isDualLayer)
2101 // Option 1 targets a Redump-style reconstructed 2048-byte-sector image.
2102 // The GDR-8050L's unlocked READ CAPACITY reports the game/XISO view length
2103 // (3,431,264 sectors), while the full Original Xbox/XGD1 reconstructed image
2104 // is larger (3,820,880 sectors) because it also includes video L0/L1 and
2105 // padding around the game region.
2106 if (isDualLayer || reportedSectors > LAYER_THRESHOLD)
2107 return XGD1_FULL_REDUMP_SECTORS;
2109 return reportedSectors;
2112 static unsigned long long GetFileSizeBytes64(const char *filename)
2120 f = fopen(filename, "rb");
2124 if (_fseeki64(f, 0, SEEK_END) != 0)
2136 return (unsigned long long)pos;
2139 static BOOL VerifyOutputByteCount(const char *filename, unsigned long long expectedBytes, const char *label)
2141 unsigned long long actualBytes = GetFileSizeBytes64(filename);
2143 if (expectedBytes == 0)
2146 if (actualBytes != expectedBytes)
2148 printf("\n[FATAL] %s byte-count mismatch.\n", label ? label : "Output");
2149 printf(" Expected: %llu bytes\n", expectedBytes);
2150 printf(" Actual: %llu bytes\n", actualBytes);
2151 printf(" Refusing to mark this dump complete.\n");
2155 printf("[OK] %s byte count verified: %llu bytes.\n", label ? label : "Output", actualBytes);
2159 static void JsonWriteNull(FILE *f)
2164 static void HexBytesToString(const BYTE *bytes, DWORD byteCount, char *outHex, size_t outHexSize)
2168 if (!outHex || outHexSize == 0)
2172 if (!bytes || outHexSize < ((size_t)byteCount * 2U + 1U))
2175 for (i = 0; i < byteCount; i++)
2176 sprintf(&outHex[i * 2], "%02x", bytes[i]);
2179 static DWORD Crc32Update(DWORD crc, const unsigned char *buf, size_t len)
2181 static DWORD table[256];
2182 static BOOL tableReady = FALSE;
2188 for (n = 0; n < 256; n++)
2192 for (k = 0; k < 8; k++)
2193 c = (c & 1U) ? (0xEDB88320U ^ (c >> 1)) : (c >> 1);
2199 for (i = 0; i < len; i++)
2200 crc = table[(crc ^ buf[i]) & 0xFFU] ^ (crc >> 8);
2205 static BOOL CalculateFileHashes(const char *filename,
2207 size_t outCrc32Size,
2213 size_t outSha256Size)
2217 HCRYPTPROV hProv = 0;
2218 HCRYPTHASH hMd5 = 0;
2219 HCRYPTHASH hSha1 = 0;
2220 HCRYPTHASH hSha256 = 0;
2221 DWORD crc = 0xFFFFFFFFU;
2224 unsigned long long totalBytes = 0ULL;
2225 unsigned long long doneBytes = 0ULL;
2226 DWORD startTick = 0;
2227 DWORD lastPrintTick = 0;
2229 DWORD elapsedMs = 0;
2231 char timeStr[12] = {0};
2232 char etaStr[12] = {0};
2234 if (outCrc32 && outCrc32Size) outCrc32[0] = '\0';
2235 if (outMd5 && outMd5Size) outMd5[0] = '\0';
2236 if (outSha1 && outSha1Size) outSha1[0] = '\0';
2237 if (outSha256 && outSha256Size) outSha256[0] = '\0';
2242 totalBytes = GetFileSizeBytes64(filename);
2244 f = fopen(filename, "rb");
2248 buf = (unsigned char *)malloc(1024 * 1024);
2255 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) &&
2256 !CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
2258 if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hMd5))
2260 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hSha1))
2262 if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hSha256))
2265 startTick = GetTickCount();
2266 lastPrintTick = startTick;
2267 printf("[HASH] Calculating full-file CRC32/MD5/SHA-1/SHA-256 for %s (%llu bytes)...\n",
2271 while ((readBytes = fread(buf, 1, 1024 * 1024, f)) > 0)
2273 crc = Crc32Update(crc, buf, readBytes);
2274 if (!CryptHashData(hMd5, buf, (DWORD)readBytes, 0))
2276 if (!CryptHashData(hSha1, buf, (DWORD)readBytes, 0))
2278 if (!CryptHashData(hSha256, buf, (DWORD)readBytes, 0))
2281 doneBytes += (unsigned long long)readBytes;
2282 nowTick = GetTickCount();
2283 if (totalBytes > 0 && (doneBytes >= totalBytes || (nowTick - lastPrintTick) >= 1000))
2285 double percent = ((double)doneBytes / (double)totalBytes) * 100.0;
2286 double mbDone = (double)doneBytes / (1024.0 * 1024.0);
2288 unsigned long long bytesLeft = totalBytes - doneBytes;
2290 elapsedMs = nowTick - startTick;
2292 speed = mbDone / ((double)elapsedMs / 1000.0);
2293 etaMs = (doneBytes > 0 && elapsedMs > 0)
2294 ? (DWORD)(((double)elapsedMs / (double)doneBytes) * (double)bytesLeft)
2296 FormatElapsedTime(elapsedMs, timeStr);
2297 FormatElapsedTime(etaMs, etaStr);
2298 xbox_ref_console_printf("\r[HASH] Full-file: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ",
2305 lastPrintTick = nowTick;
2312 elapsedMs = GetTickCount() - startTick;
2313 FormatElapsedTime(elapsedMs, timeStr);
2315 xbox_ref_console_printf("\r[HASH] Full-file: 100.0%% | %.1f MB | Time: %s \n",
2316 (double)totalBytes / (1024.0 * 1024.0),
2318 printf("[OK] Full-file CRC32/MD5/SHA-1/SHA-256 complete in %s.\n", timeStr);
2321 if (outCrc32 && outCrc32Size >= 9)
2322 sprintf(outCrc32, "%08x", crc);
2324 if (outMd5 && outMd5Size >= 33)
2327 DWORD md5Len = sizeof(md5Bytes);
2328 if (!CryptGetHashParam(hMd5, HP_HASHVAL, md5Bytes, &md5Len, 0))
2330 HexBytesToString(md5Bytes, md5Len, outMd5, outMd5Size);
2333 if (outSha1 && outSha1Size >= 41)
2336 DWORD sha1Len = sizeof(sha1Bytes);
2337 if (!CryptGetHashParam(hSha1, HP_HASHVAL, sha1Bytes, &sha1Len, 0))
2339 HexBytesToString(sha1Bytes, sha1Len, outSha1, outSha1Size);
2342 if (outSha256 && outSha256Size >= 65)
2344 BYTE sha256Bytes[32];
2345 DWORD sha256Len = sizeof(sha256Bytes);
2346 if (!CryptGetHashParam(hSha256, HP_HASHVAL, sha256Bytes, &sha256Len, 0))
2348 HexBytesToString(sha256Bytes, sha256Len, outSha256, outSha256Size);
2354 if (!ok && startTick)
2356 elapsedMs = GetTickCount() - startTick;
2357 FormatElapsedTime(elapsedMs, timeStr);
2358 printf("\n[WARN] Full-file CRC32/MD5/SHA-1/SHA-256 calculation failed after %s.\n", timeStr);
2360 if (hSha256) CryptDestroyHash(hSha256);
2361 if (hSha1) CryptDestroyHash(hSha1);
2362 if (hMd5) CryptDestroyHash(hMd5);
2363 if (hProv) CryptReleaseContext(hProv, 0);
2369 static void WritePressedDvdRomWriteMediaState(FILE *json, const char *isoFilename, uint32_t totalDiscSectors)
2374 fprintf(json, " \"write_media_state\": {\n");
2375 fprintf(json, " \"media_class\": \"pressed_dvd_rom\",\n");
2376 fprintf(json, " \"writable\": false,\n");
2377 fprintf(json, " \"erasable\": false,\n");
2378 fprintf(json, " \"finalized\": true,\n");
2380 fprintf(json, " \"backing_image\": {\n");
2381 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename ? isoFilename : ""); fprintf(json, ",\n");
2382 fprintf(json, " \"sector_size\": 2048,\n");
2383 fprintf(json, " \"initial_sector_count\": %u,\n", totalDiscSectors);
2384 fprintf(json, " \"max_sector_count\": %u,\n", totalDiscSectors);
2385 fprintf(json, " \"growth_policy\": \"fixed_read_only\"\n");
2386 fprintf(json, " },\n");
2388 fprintf(json, " \"sessions\": [\n");
2389 fprintf(json, " {\n");
2390 fprintf(json, " \"session_number\": 1,\n");
2391 fprintf(json, " \"state\": \"closed\",\n");
2392 fprintf(json, " \"first_track_number\": 1,\n");
2393 fprintf(json, " \"last_track_number\": 1\n");
2394 fprintf(json, " }\n");
2395 fprintf(json, " ],\n");
2397 fprintf(json, " \"tracks\": [\n");
2398 fprintf(json, " {\n");
2399 fprintf(json, " \"track_number\": 1,\n");
2400 fprintf(json, " \"state\": \"complete\",\n");
2401 fprintf(json, " \"mode\": \"data\",\n");
2402 fprintf(json, " \"packet_or_track_mode\": \"pressed_read_only\",\n");
2403 fprintf(json, " \"start_lba\": 0,\n");
2404 fprintf(json, " \"next_writable_lba\": "); JsonWriteNull(json); fprintf(json, ",\n");
2405 fprintf(json, " \"free_blocks\": 0,\n");
2406 fprintf(json, " \"written_blocks\": %u\n", totalDiscSectors);
2407 fprintf(json, " }\n");
2408 fprintf(json, " ],\n");
2410 fprintf(json, " \"unwritten_read_policy\": \"not_applicable_read_only_media\",\n");
2411 fprintf(json, " \"flush_policy\": \"read_only_noop\"\n");
2412 fprintf(json, " },\n");
2415 static BOOL WriteXboxDvdMediaProfileFile(const char *isoFilename,
2416 const XboxDvdSidecarCapture *cap,
2417 uint32_t totalDiscSectors,
2419 uint32_t videoSectors,
2420 uint32_t gameSourceLba,
2421 uint32_t gameSectors,
2422 const char *isoSha1,
2424 const char *isoCrc32,
2425 const char *isoSha256,
2426 const char *adC0Path,
2427 const char *pfiPath,
2428 const char *dmiPath,
2429 BOOL payloadFilesPresent)
2431 char profilePath[MAX_PATH];
2432 char titleHint[256];
2437 if (!isoFilename || !cap)
2440 MakeSidecarPath(isoFilename, ".media.json", profilePath, sizeof(profilePath));
2441 ExtractMediaProfileNames(isoFilename, titleHint, sizeof(titleHint), mediaId, sizeof(mediaId));
2442 redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS);
2444 json = fopen(profilePath, "wb");
2448 fprintf(json, "{\n");
2449 fprintf(json, " \"format\": \"xdvd-media-profile\",\n");
2450 fprintf(json, " \"version\": 1,\n");
2451 fprintf(json, " \"media_id\": "); JsonWriteEscapedString(json, mediaId); fprintf(json, ",\n");
2452 fprintf(json, " \"title_hint\": "); JsonWriteEscapedString(json, titleHint); fprintf(json, ",\n");
2453 fprintf(json, " \"image\": {\n");
2454 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n");
2455 fprintf(json, " \"sector_size\": 2048,\n");
2456 fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors);
2457 fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2458 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2459 fprintf(json, " \"hashes\": {\n");
2460 fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n");
2461 fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n");
2462 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2463 fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n");
2464 fprintf(json, " }\n");
2465 fprintf(json, " },\n");
2468 uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors;
2470 fprintf(json, " \"layout\": {\n");
2471 fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n");
2472 fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U);
2473 fprintf(json, " \"layer_break_lba\": %u,\n", isDualLayer ? LAYER_BREAK : 0U);
2474 fprintf(json, " \"video_l0_start_lba\": 0,\n");
2475 fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors);
2476 fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
2477 fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
2478 fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
2479 fprintf(json, " \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
2480 fprintf(json, " \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2481 fprintf(json, " \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
2482 fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
2483 fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
2484 fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2485 fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n");
2486 fprintf(json, " \"game_sector_count\": %u,\n", gameSectors);
2487 fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U);
2488 fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U);
2489 fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U);
2490 fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U);
2491 fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", videoSectors);
2492 fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2493 fprintf(json, " \"drive_reported_unlocked_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2494 fprintf(json, " \"reconstructed_output_sector_count\": %u\n", totalDiscSectors);
2495 fprintf(json, " },\n");
2498 fprintf(json, " \"reconstruction\": {\n");
2499 fprintf(json, " \"is_reconstructed_layout\": %s,\n", redumpStyle ? "true" : "false");
2500 fprintf(json, " \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "pregame_and_postgame_zero_fill_content_placeholder" : "not_applicable"); fprintf(json, ",\n");
2501 fprintf(json, " \"filler_geometry_verified\": %s,\n", redumpStyle ? "true" : "false");
2502 fprintf(json, " \"filler_verified_from_disc\": false,\n");
2503 fprintf(json, " \"filler_byte_value\": %s,\n", redumpStyle ? "0" : "null");
2504 fprintf(json, " \"pending_hardware_capture\": false,\n");
2505 fprintf(json, " \"unresolved_filler_content\": %s,\n", redumpStyle ? "true" : "false");
2506 fprintf(json, " \"filler_ranges\": [\n");
2509 fprintf(json, " {\n");
2510 fprintf(json, " \"name\": \"pregame_padding\",\n");
2511 fprintf(json, " \"start_lba\": %u,\n", XGD1_VIDEO_L0_SECTORS);
2512 fprintf(json, " \"sector_count\": %u,\n", XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS);
2513 fprintf(json, " \"source\": \"synthetic_zero_fill\"\n");
2514 fprintf(json, " },\n");
2515 fprintf(json, " {\n");
2516 fprintf(json, " \"name\": \"postgame_padding\",\n");
2517 fprintf(json, " \"start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS);
2518 fprintf(json, " \"sector_count\": %u,\n", XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS));
2519 fprintf(json, " \"source\": \"synthetic_zero_fill\"\n");
2520 fprintf(json, " }\n");
2522 fprintf(json, " ],\n");
2523 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");
2524 fprintf(json, " },\n");
2526 fprintf(json, " \"dvd_structures\": {\n");
2527 fprintf(json, " \"ad_c0\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n");
2528 fprintf(json, " \"pfi\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n");
2529 fprintf(json, " \"dmi\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, "\n");
2530 fprintf(json, " },\n");
2532 fprintf(json, " \"non_lba_physical_metadata\": {\n");
2533 fprintf(json, " \"pfi_storage\": \"sidecar_bin\",\n");
2534 fprintf(json, " \"dmi_storage\": \"sidecar_bin\",\n");
2535 fprintf(json, " \"lead_in_storage\": \"not_in_iso_stream\",\n");
2536 fprintf(json, " \"lead_out_storage\": \"not_in_iso_stream\",\n");
2537 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");
2538 fprintf(json, " },\n");
2540 fprintf(json, " \"drive_state_observations\": {\n");
2541 fprintf(json, " \"locked\": {\n");
2542 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n");
2543 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2544 fprintf(json, " },\n");
2545 fprintf(json, " \"unlocked\": {\n");
2546 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n");
2547 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2548 fprintf(json, " }\n");
2549 fprintf(json, " },\n");
2551 WritePressedDvdRomWriteMediaState(json, isoFilename, totalDiscSectors);
2553 fprintf(json, " \"validation\": {\n");
2554 fprintf(json, " \"byte_count_matches_sector_count\": true,\n");
2555 fprintf(json, " \"payload_files_present\": %s,\n", payloadFilesPresent ? "true" : "false");
2556 fprintf(json, " \"warnings\": "); JsonWriteValidationWarnings(json, cap, payloadFilesPresent, redumpStyle); fprintf(json, "\n");
2557 fprintf(json, " }\n");
2558 fprintf(json, "}\n");
2561 printf("[META] Wrote media profile: %s\n", profilePath);
2565 static BOOL WriteXboxDvdSidecarFiles(const char *isoFilename,
2566 const XboxDvdSidecarCapture *cap,
2567 uint32_t totalDiscSectors,
2569 uint32_t videoSectors,
2570 uint32_t gameSourceLba,
2571 uint32_t gameSectors,
2572 const char *isoSha1,
2574 const char *isoCrc32,
2575 const char *isoSha256)
2577 char jsonPath[MAX_PATH];
2578 char adC0Path[MAX_PATH];
2579 char pfiPath[MAX_PATH];
2580 char dmiPath[MAX_PATH];
2583 BOOL payloadFilesPresent = FALSE;
2584 BOOL redumpStyle = FALSE;
2586 if (!isoFilename || !cap)
2589 MakeSidecarPath(isoFilename, ".xdvd.json", jsonPath, sizeof(jsonPath));
2590 MakeSidecarPath(isoFilename, ".ad_c0.bin", adC0Path, sizeof(adC0Path));
2591 MakeSidecarPath(isoFilename, ".pfi.bin", pfiPath, sizeof(pfiPath));
2592 MakeSidecarPath(isoFilename, ".dmi.bin", dmiPath, sizeof(dmiPath));
2594 if (cap->hasAdC0 && !WriteBinaryFile(adC0Path, cap->adC0, 0x664))
2596 if (cap->hasPfi && !WriteBinaryFile(pfiPath, cap->pfi, 2048))
2598 if (cap->hasDmi && !WriteBinaryFile(dmiPath, cap->dmi, 2048))
2601 payloadFilesPresent = cap->hasAdC0 && cap->hasPfi && cap->hasDmi && ok;
2602 redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS);
2604 json = fopen(jsonPath, "wb");
2608 fprintf(json, "{\n");
2609 fprintf(json, " \"format\": \"xdvd-sidecar\",\n");
2610 fprintf(json, " \"version\": 1,\n");
2611 fprintf(json, " \"image\": {\n");
2612 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n");
2613 fprintf(json, " \"sector_size\": 2048,\n");
2614 fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors);
2615 fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2616 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2617 fprintf(json, " \"hashes\": {\n");
2618 fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n");
2619 fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n");
2620 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2621 fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n");
2622 fprintf(json, " },\n");
2624 uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors;
2626 fprintf(json, " \"layout\": {\n");
2627 fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n");
2628 fprintf(json, " \"legacy_contiguous_visible_start_lba\": 0,\n");
2629 fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors);
2630 fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2631 fprintf(json, " \"video_l0_start_lba\": 0,\n");
2632 fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors);
2633 fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
2634 fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
2635 fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
2636 fprintf(json, " \"game_leadin_unlocked_source_start_lba\": %u,\n", redumpStyle ? 0U : gameSourceLba);
2637 fprintf(json, " \"game_leadin_source_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2638 fprintf(json, " \"game_leadin_source\": "); JsonWriteEscapedString(json, redumpStyle ? "drive_read10" : "not_applicable"); fprintf(json, ",\n");
2639 fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
2640 fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
2641 fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2642 fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n");
2643 fprintf(json, " \"game_sector_count\": %u,\n", gameSectors);
2644 fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U);
2645 fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U);
2646 fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U);
2647 fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U);
2648 fprintf(json, " \"layer_break_lba\": %u\n", LAYER_BREAK);
2649 fprintf(json, " }\n");
2651 fprintf(json, " },\n");
2653 fprintf(json, " \"disc\": {\n");
2654 fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U);
2655 fprintf(json, " \"reconstructed_output_sectors\": %u,\n", totalDiscSectors);
2656 fprintf(json, " \"reconstructed_output_byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2657 fprintf(json, " \"drive_reported_unlocked_sectors\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2658 fprintf(json, " \"drive_reported_locked_sectors\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2659 fprintf(json, " \"unlocked_game_view_sectors\": %u\n", gameSectors);
2660 fprintf(json, " },\n");
2662 fprintf(json, " \"drive_states\": {\n");
2663 fprintf(json, " \"locked\": {\n");
2664 fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n");
2665 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n");
2666 fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2667 fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n");
2668 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2669 fprintf(json, " },\n");
2670 fprintf(json, " \"unlocked\": {\n");
2671 fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n");
2672 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n");
2673 fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2674 fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n");
2675 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2676 fprintf(json, " }\n");
2677 fprintf(json, " },\n");
2679 fprintf(json, " \"scsi_responses\": [\n");
2680 fprintf(json, " {\n");
2681 fprintf(json, " \"name\": \"read_dvd_structure_xbox_control_block\",\n");
2682 fprintf(json, " \"cdb_hex\": \"AD00FF02FDFFFE00066400C0\",\n");
2683 fprintf(json, " \"data_in\": true,\n");
2684 fprintf(json, " \"data_len\": 1636,\n");
2685 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n");
2686 fprintf(json, " \"captured\": %s\n", cap->hasAdC0 ? "true" : "false");
2687 fprintf(json, " },\n");
2688 fprintf(json, " {\n");
2689 fprintf(json, " \"name\": \"read_dvd_structure_pfi\",\n");
2690 fprintf(json, " \"cdb_hex\": \"AD0000000000000008000000\",\n");
2691 fprintf(json, " \"data_in\": true,\n");
2692 fprintf(json, " \"data_len\": 2048,\n");
2693 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n");
2694 fprintf(json, " \"captured\": %s\n", cap->hasPfi ? "true" : "false");
2695 fprintf(json, " },\n");
2696 fprintf(json, " {\n");
2697 fprintf(json, " \"name\": \"read_dvd_structure_dmi\",\n");
2698 fprintf(json, " \"cdb_hex\": \"AD0000000000000408000000\",\n");
2699 fprintf(json, " \"data_in\": true,\n");
2700 fprintf(json, " \"data_len\": 2048,\n");
2701 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, ",\n");
2702 fprintf(json, " \"captured\": %s\n", cap->hasDmi ? "true" : "false");
2703 fprintf(json, " }\n");
2704 fprintf(json, " ],\n");
2706 fprintf(json, " \"auth\": {\n");
2707 fprintf(json, " \"requires_media_transition\": false,\n");
2708 fprintf(json, " \"unlock_requires_media_transition\": false,\n");
2709 fprintf(json, " \"locked_video_view_restore_requires_media_transition\": true,\n");
2710 fprintf(json, " \"state_detection\": \"READ CAPACITY (10) visible-sector count\",\n");
2711 fprintf(json, " \"mode_page\": \"0x3E\",\n");
2712 fprintf(json, " \"challenge_table_source\": \"read_dvd_structure_xbox_control_block\",\n");
2713 fprintf(json, " \"challenge_table_response_offset\": 774,\n");
2714 fprintf(json, " \"challenge_table_hash_offset\": 1187,\n");
2715 fprintf(json, " \"challenge_table_hash_length\": 44\n");
2716 fprintf(json, " }\n");
2717 fprintf(json, "}\n");
2721 if (!WriteXboxDvdMediaProfileFile(isoFilename,
2735 payloadFilesPresent))
2738 printf("[WARN] Failed to write XDVD media profile.\n");
2741 printf("[META] Wrote XDVD sidecar: %s\n", jsonPath);
2742 if (cap->hasAdC0) printf("[META] Wrote Xbox control block: %s\n", adC0Path);
2743 if (cap->hasPfi) printf("[META] Wrote PFI: %s\n", pfiPath);
2744 if (cap->hasDmi) printf("[META] Wrote DMI: %s\n", dmiPath);
2749 BOOL DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uint32_t totalDiscSectors, bool isDualLayer, bool EjectOnSuccess, xbox_ref_dump_result *result)
2751 HCRYPTPROV hProv = 0;
2752 HCRYPTHASH hHash = 0;
2753 FILE *outFile = NULL;
2755 char sha1String[41] = {0};
2756 char md5String[33] = {0};
2757 char crc32String[9] = {0};
2758 char fileSha1String[41] = {0};
2759 char fileSha256String[65] = {0};
2760 BOOL dumpOk = FALSE;
2761 XboxDvdSidecarCapture sidecarCapture;
2762 BOOL rawSidecarAvailable = FALSE;
2763 uint32_t rawVideoSectors = START_LBA_MAGIC;
2764 uint32_t rawGameSourceLba = 0xFFFFFFFFu;
2765 uint32_t rawGameSectors = 0;
2766 uint32_t rawTargetSectors = 0;
2767 unsigned long long expectedOutputBytes = 0ULL;
2768 const char *outputLabel = "Output";
2769 DWORD operationStartTick = GetTickCount();
2771 char operationTimeStr[12] = {0};
2773 memset(&sidecarCapture, 0, sizeof(sidecarCapture));
2776 result->attempted = 1;
2777 result->mode = xisoFormat;
2778 if (filename && filename[0]) {
2779 strncpy(result->output_path, filename, sizeof(result->output_path) - 1);
2780 result->output_path[sizeof(result->output_path) - 1] = '\0';
2784 if (totalDiscSectors == 0)
2785 totalDiscSectors = GetTotalSectors(hDevice);
2786 if (totalDiscSectors == 0)
2787 totalDiscSectors = REDUMP_SECTORS;
2789 rawTargetSectors = NormalizeRawIsoTargetSectors(totalDiscSectors, isDualLayer);
2791 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
2793 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
2795 CryptReleaseContext(hProv, 0);
2799 EnsureDriveReady(hDevice, 30000);
2800 SetDriveSpeedMax(hDevice);
2802 if (xisoFormat == '1')
2804 DWORD bytesReturned;
2805 uint32_t gameSourceLba = 0xFFFFFFFFu;
2806 uint32_t videoSectors = START_LBA_MAGIC;
2807 uint32_t gameSectors = 0;
2808 BOOL lockedViewIsAlreadyXdfs = FALSE;
2810 outputLabel = "RAW ISO";
2811 expectedOutputBytes = (unsigned long long)rawTargetSectors * 2048ULL;
2813 result->output_sectors = rawTargetSectors;
2815 if (rawTargetSectors != totalDiscSectors)
2817 printf("[RAW] Drive-reported unlocked sectors %u normalized to Redump-style output target %u.\n",
2818 totalDiscSectors, rawTargetSectors);
2821 printf("[RAW] Full-disc target: %u sectors (%llu bytes).\n",
2823 expectedOutputBytes);
2824 if (rawTargetSectors > LAYER_BREAK)
2826 printf("[RAW] Expected Redump/XGD1 layer break at output LBA %u.\n", LAYER_BREAK);
2828 printf("[RAW] Media-transition-preserving mode is enabled.\n");
2830 if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel))
2833 outFile = fopen(filename, "wb");
2836 printf("\n[FATAL] Could not create output file '%s'.\n", filename);
2838 printf(" errno: %d (%s)\n", errno, strerror(errno));
2842 // Raw mode must capture the visible/video view first. The caller normally reaches this
2843 // point after the drive has already been authenticated for metadata, so reset the drive
2844 // state with a real media transition before reading LBA 0.
2845 DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
2847 printf("[RAW] Cycling tray to restore locked/video view before dumping sector 0.\n");
2848 AutomateTrayCycle(hDevice);
2849 RefreshVolume(hDevice);
2850 SetDriveSpeedMax(hDevice);
2852 DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
2854 CaptureLockedSidecarState(hDevice, &sidecarCapture);
2856 lockedViewIsAlreadyXdfs = ProbeXboxVolumeAt(hDevice, 0x20);
2858 if (lockedViewIsAlreadyXdfs && rawTargetSectors > START_LBA_MAGIC)
2860 printf("\n[FATAL] Option 1 requires a full raw/video-front source image.\n");
2861 printf(" This source exposes XDFS at LBA 0x20 after the media-reset step,\n");
2862 printf(" which looks like an XISO/game-partition view, not a full raw disc view.\n");
2863 printf(" Use option 2 for this source, or mount/create a 7.29 GiB Redump-style option-1 ISO.\n");
2867 if (rawTargetSectors <= START_LBA_MAGIC)
2869 printf("[RAW] Non-retail-sized source; dumping visible LBA 0..%u directly.\n",
2870 rawTargetSectors - 1);
2871 rawVideoSectors = rawTargetSectors;
2872 rawGameSourceLba = 0;
2874 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
2875 rawSidecarAvailable = TRUE;
2876 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, rawTargetSectors, 0, "RAW", FALSE);
2878 else if (rawTargetSectors == XGD1_FULL_REDUMP_SECTORS)
2880 unsigned char *videoL1Buffer = NULL;
2881 uint32_t detectedXdfsLba;
2882 uint32_t pregamePaddingSectors = XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS;
2883 uint32_t postgamePaddingSectors = XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS);
2885 printf("[RAW] Using Original Xbox/XGD1 Redump-style 2048-byte-sector layout.\n");
2886 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",
2887 XGD1_VIDEO_L0_SECTORS,
2888 pregamePaddingSectors,
2890 postgamePaddingSectors,
2891 XGD1_VIDEO_L1_SECTORS);
2892 printf("[RAW] Note: filler/padding ranges are synthetic zero-fill placeholders until readable from hardware.\n");
2893 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");
2895 videoL1Buffer = (unsigned char *)VirtualAlloc(NULL, XGD1_VIDEO_L1_SECTORS * 2048U, MEM_COMMIT, PAGE_READWRITE);
2898 printf("\n[FATAL] Could not allocate VIDEO_L1 capture buffer.\n");
2902 // The locked-visible Xbox video ISO is 6,992 sectors. In the Redump-style
2903 // image, its L0 portion is placed at the beginning and its L1 tail is placed
2904 // at the end of the reconstructed image. Capture the L1 tail while the drive
2905 // is still in the locked/video state, before authenticating for the game view.
2906 if (!ReadSectorsToMemory(hDevice,
2907 XGD1_VIDEO_L0_SECTORS,
2908 XGD1_VIDEO_L1_SECTORS,
2912 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2916 printf("[RAW] Writing video L0 from locked source LBA 0..%u.\n", XGD1_VIDEO_L0_SECTORS - 1);
2917 if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, XGD1_VIDEO_L0_SECTORS, 0, "VIDEO-L0", FALSE))
2919 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2923 if (!WriteZeroSectorsOutput(outFile, hHash, pregamePaddingSectors, XGD1_VIDEO_L0_SECTORS, "PREGAME-PAD"))
2925 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2929 printf("[RAW] Re-applying full Xbox handshake for unlocked game/XISO view.\n");
2930 UnlockDrive(hDevice);
2931 RefreshVolume(hDevice);
2933 EnsureDriveReady(hDevice, 30000);
2934 SetDriveSpeedMax(hDevice);
2935 KickXboxMediaAuth(hDevice);
2936 RecoveryKick(hDevice, TRUE);
2937 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
2938 rawSidecarAvailable = TRUE;
2940 detectedXdfsLba = DetectXboxVolumeStart(hDevice);
2941 if (detectedXdfsLba == 0xFFFFFFFFu)
2943 printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC);
2944 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2947 if (detectedXdfsLba != 0x20)
2949 printf("[WARN] XDFS was detected at unlocked source LBA %u, not the expected XISO header LBA 32.\n", detectedXdfsLba);
2952 // FriiDump 0.5.3.13 cache-aligned raw-ID validation proved that
2953 // unlocked source LBA 0..31 is the physical 32-sector game-region
2954 // lead-in and that XDVDFS begins at source LBA 32. Preserve the
2955 // drive-captured lead-in instead of synthesizing zero sectors.
2956 gameSourceLba = XGD1_GAME_SOURCE_START_LBA;
2957 gameSectors = REDUMP_SECTORS;
2958 rawVideoSectors = XGD1_GAME_OUTPUT_START_LBA;
2959 rawGameSourceLba = gameSourceLba;
2960 rawGameSectors = gameSectors;
2962 printf("[RAW] Capturing %u-sector game lead-in from unlocked source LBA 0..%u at output LBA %u.\n",
2963 XGD1_XISO_LEADIN_SECTORS,
2964 XGD1_XISO_LEADIN_SECTORS - 1,
2965 XGD1_GAME_OUTPUT_START_LBA);
2966 if (!DumpSectorRangeWithRetry(hDevice,
2970 XGD1_XISO_LEADIN_SECTORS,
2971 XGD1_GAME_OUTPUT_START_LBA,
2975 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2979 printf("[RAW] Writing unlocked XDFS/game data from source LBA %u for %u sectors at output LBA %u.\n",
2980 XGD1_GAME_SOURCE_START_LBA,
2981 XGD1_GAME_SOURCE_SECTORS,
2982 XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS);
2983 if (!DumpSectorRangeWithRetry(hDevice,
2986 XGD1_GAME_SOURCE_START_LBA,
2987 XGD1_GAME_SOURCE_SECTORS,
2988 XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS,
2992 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2996 if (!WriteZeroSectorsOutput(outFile, hHash, postgamePaddingSectors, XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS, "POSTGAME-PAD"))
2998 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3002 if (!WriteMemorySectorsOutput(outFile, hHash, videoL1Buffer, XGD1_VIDEO_L1_SECTORS, XGD1_VIDEO_L1_OUTPUT_START_LBA, "VIDEO-L1"))
3004 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3008 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
3013 if (videoSectors > rawTargetSectors)
3014 videoSectors = rawTargetSectors;
3015 rawVideoSectors = videoSectors;
3017 printf("[RAW] Dumping contiguous visible/video area first: source LBA 0..%u.\n", videoSectors - 1);
3018 if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, videoSectors, 0, "VIDEO", FALSE))
3021 printf("[RAW] Re-applying full Xbox handshake after media transition for hidden game/data area.\n");
3022 UnlockDrive(hDevice);
3023 RefreshVolume(hDevice);
3025 EnsureDriveReady(hDevice, 30000);
3026 SetDriveSpeedMax(hDevice);
3027 KickXboxMediaAuth(hDevice);
3028 RecoveryKick(hDevice, TRUE);
3029 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
3030 rawSidecarAvailable = TRUE;
3032 gameSourceLba = DetectXboxVolumeStart(hDevice);
3033 if (gameSourceLba == 0xFFFFFFFFu)
3035 printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC);
3039 gameSectors = rawTargetSectors - videoSectors;
3040 rawGameSourceLba = gameSourceLba;
3041 rawGameSectors = gameSectors;
3042 printf("[RAW] Appending hidden game/data area from unlocked source LBA %u for %u sectors.\n",
3043 gameSourceLba, gameSectors);
3044 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, gameSourceLba, gameSectors, videoSectors, "GAME", TRUE);
3047 else if (xisoFormat == '2')
3049 uint32_t startLba = 0;
3050 uint32_t sectorsToRead = 0;
3051 unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
3052 XDFS_VOLUME_DESCRIPTOR *vol = NULL;
3053 uint32_t xgd2EndLba = 1913920;
3054 unsigned char zeroSector[2048] = {0};
3059 SetDriveSpeedMax(hDevice);
3060 KickXboxMediaAuth(hDevice);
3061 RecoveryKick(hDevice, TRUE);
3063 vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
3065 if (ScsiReadSectors(hDevice, START_LBA_MAGIC, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0)
3067 startLba = START_LBA_MAGIC;
3068 printf("[INFO] XGD2 Game Partition identified at LBA %u\n", startLba);
3070 else if (ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0)
3073 printf("[INFO] Standard Game Partition identified at LBA 32\n");
3077 printf("[ERROR] No Xbox Game Partition found. Disc may be non-standard.\n");
3078 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3082 if (isDualLayer || totalDiscSectors > 3300000)
3084 sectorsToRead = xgd2EndLba - startLba;
3085 printf("[INFO] Dual Layer disc detected. Calculating span across layers...\n");
3089 sectorsToRead = vol->VolumeSize / 2048;
3090 printf("[INFO] Single Layer disc detected. Using header-reported size.\n");
3093 outputLabel = "XISO";
3094 expectedOutputBytes = ((unsigned long long)sectorsToRead + 32ULL) * 2048ULL;
3096 result->output_sectors = sectorsToRead + 32U;
3098 printf("[SUCCESS] Final XISO target: %u sectors plus 32-sector lead-in (%llu bytes).\n",
3100 expectedOutputBytes);
3102 if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel))
3104 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3108 outFile = fopen(filename, "wb");
3111 printf("\n[FATAL] Could not create output file '%s'.\n", filename);
3113 printf(" errno: %d (%s)\n", errno, strerror(errno));
3114 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3118 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3120 printf("Writing 64KB XISO lead-in padding...\n");
3121 for (int p = 0; p < 32; p++)
3123 if (!WriteOutputBytes(outFile, zeroSector, 2048, "XISO-PAD", 0, (uint32_t)p))
3125 CryptHashData(hHash, zeroSector, 2048, 0);
3128 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO", TRUE);
3132 printf("[ERROR] Unsupported dump mode '%c'.\n", xisoFormat);
3139 if (!FlushAndCommitOutput(outFile, outputLabel))
3145 if (fclose(outFile) != 0)
3147 printf("\n[FATAL] fclose failed for %s output.\n", outputLabel ? outputLabel : "dump");
3149 printf(" errno: %d (%s)\n", errno, strerror(errno));
3156 if (!VerifyOutputByteCount(filename, expectedOutputBytes, outputLabel))
3162 FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr);
3163 printf("\nDump/write phase complete at elapsed %s. Finalizing hashes...\n", operationTimeStr);
3165 if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
3167 HexBytesToString(rgbHash, cbHash, sha1String, sizeof(sha1String));
3170 if (CalculateFileHashes(filename, crc32String, sizeof(crc32String), md5String, sizeof(md5String), fileSha1String, sizeof(fileSha1String), fileSha256String, sizeof(fileSha256String)))
3172 if (fileSha1String[0] && sha1String[0] && strcmp(fileSha1String, sha1String) != 0)
3174 printf("\n[WARN] Streaming SHA-1 differs from file SHA-1. Using file SHA-1 in metadata.\n");
3175 printf(" Streaming SHA-1: %s\n", sha1String);
3176 printf(" File SHA-1: %s\n", fileSha1String);
3178 if (fileSha1String[0])
3179 strcpy(sha1String, fileSha1String);
3183 printf("\n[WARN] Could not calculate CRC32/MD5/SHA-1/SHA-256 from finalized output file.\n");
3187 result->output_size = GetFileSizeBytes64(filename);
3188 strncpy(result->crc32, crc32String, sizeof(result->crc32) - 1);
3189 result->crc32[sizeof(result->crc32) - 1] = '\0';
3190 strncpy(result->md5, md5String, sizeof(result->md5) - 1);
3191 result->md5[sizeof(result->md5) - 1] = '\0';
3192 strncpy(result->sha1, sha1String, sizeof(result->sha1) - 1);
3193 result->sha1[sizeof(result->sha1) - 1] = '\0';
3194 strncpy(result->sha256, fileSha256String, sizeof(result->sha256) - 1);
3195 result->sha256[sizeof(result->sha256) - 1] = '\0';
3196 result->hashes_complete = result->crc32[0] && result->md5[0] && result->sha1[0] && result->sha256[0];
3199 if (xisoFormat == '1')
3201 printf("Final RAW ISO Sector Count: %u\n", rawTargetSectors);
3202 printf("Final RAW ISO Byte Count: %llu\n", (unsigned long long)rawTargetSectors * 2048ULL);
3203 printf("CRC32: %s\n", crc32String);
3204 printf("MD5: %s\n", md5String);
3205 printf("SHA-1: %s\n", sha1String);
3206 printf("SHA-256: %s\n", fileSha256String);
3207 PrintGamePartitionHash(filename);
3208 if (rawSidecarAvailable)
3210 if (!WriteXboxDvdSidecarFiles(filename,
3222 printf("[WARN] Failed to write one or more XDVD sidecar metadata files.\n");
3227 printf("[WARN] XDVD sidecar metadata was not captured for this raw dump.\n");
3232 printf("Final XISO Byte Count: see progress target above.\n");
3233 printf("CRC32: %s\n", crc32String);
3234 printf("MD5: %s\n", md5String);
3235 printf("SHA-1: %s\n", sha1String);
3236 printf("SHA-256: %s\n", fileSha256String);
3238 FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr);
3239 printf("\nOperation Complete! Total elapsed: %s\n", operationTimeStr);
3241 /* The caller owns final drive cleanup. Embedded FriiDump runs issue one
3242 * STOP UNIT after Redump verification; standalone bridge runs stop the
3243 * drive in xbox_ref_gdr8050l_dump_core() before closing the handle. */
3245 ControlTray(hDevice, TRUE);
3251 CryptDestroyHash(hHash);
3253 CryptReleaseContext(hProv, 0);
3255 result->dump_success = dumpOk ? 1 : 0;
3256 result->elapsed_seconds = (double)(GetTickCount() - operationStartTick) / 1000.0;
3257 if (dumpOk && result->output_size == 0)
3258 result->output_size = GetFileSizeBytes64(filename);