2 #include "scsi_structs.h"
5 #include "xbox_ref_log.h"
19 #pragma comment(lib, "advapi32.lib")
22 #define PROV_RSA_AES 24
24 #ifndef ALG_SID_SHA_256
25 #define ALG_SID_SHA_256 12
28 #define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
31 #define printf xbox_ref_printf
33 #define GDR_8163B OL23
35 HANDLE OpenDrive(char driveLetter)
38 snprintf(devicePath, sizeof(devicePath), "\\\\.\\%c:", driveLetter);
40 HANDLE hDevice = CreateFileA(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
44 void CloseDrive(HANDLE hDevice)
46 if (hDevice && hDevice != INVALID_HANDLE_VALUE)
50 int IsDiscPresent(HANDLE hDevice)
53 return DeviceIoControl(hDevice, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, &bytesReturned, NULL);
56 void ControlTray(HANDLE hDevice, BOOL eject)
58 SCSI_PASS_THROUGH_DIRECT sptd;
60 memset(&sptd, 0, sizeof(sptd));
62 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
64 sptd.TimeOutValue = 10;
65 sptd.Cdb[0] = 0x1B; // START STOP UNIT
69 printf("Software Ejecting tray...\n");
70 sptd.Cdb[4] = 0x02; // Power Action: Eject
74 printf("Software Closing tray...\n");
75 sptd.Cdb[4] = 0x03; // Power Action: Load
77 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &returned, NULL))
79 printf("Tray %s successful.\n", eject ? "eject" : "close");
83 DWORD err = GetLastError();
84 printf("Failed to %s tray. Error: %lu\n", eject ? "eject" : "close", err);
86 if (err == ERROR_ACCESS_DENIED)
88 printf("Hint: Ensure no other program is locking the drive.\n");
93 BOOL TestUnitReady(HANDLE hDevice)
95 SCSI_PASS_THROUGH_DIRECT sptd;
97 memset(&sptd, 0, sizeof(sptd));
99 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
101 sptd.TimeOutValue = 10;
102 sptd.DataTransferLength = 0;
103 sptd.DataBuffer = NULL;
105 // TEST UNIT READY. This is our practical poll for "ready/spun up".
106 // Many drives do not expose a literal spindle-state bit to normal host software;
107 // after STOP UNIT, TEST UNIT READY should fail until the unit is ready again.
110 if (!DeviceIoControl(hDevice,
111 IOCTL_SCSI_PASS_THROUGH_DIRECT,
122 return (sptd.ScsiStatus == 0);
125 BOOL StartDriveUnit(HANDLE hDevice)
127 SCSI_PASS_THROUGH_DIRECT sptd;
129 memset(&sptd, 0, sizeof(sptd));
131 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
133 sptd.TimeOutValue = 30;
134 sptd.DataTransferLength = 0;
135 sptd.DataBuffer = NULL;
137 // START STOP UNIT, START=1, LOEJ=0.
138 // This requests spin-up/start without ejecting/loading the tray.
142 printf("Sending SCSI START UNIT / spin-up command...\n");
144 if (DeviceIoControl(hDevice,
145 IOCTL_SCSI_PASS_THROUGH_DIRECT,
153 printf("SCSI START UNIT / spin-up command accepted.\n");
158 DWORD err = GetLastError();
159 printf("[WARN] SCSI START UNIT / spin-up failed. Error: %lu\n", err);
164 BOOL EnsureDriveReady(HANDLE hDevice, DWORD timeoutMs)
166 DWORD startTick = GetTickCount();
167 BOOL startIssued = FALSE;
169 printf("Polling drive readiness with TEST UNIT READY...\n");
173 if (TestUnitReady(hDevice))
175 printf("Drive reports ready.\n");
181 printf("Drive is not ready/spun up yet; requesting START UNIT.\n");
182 StartDriveUnit(hDevice);
186 if ((GetTickCount() - startTick) >= timeoutMs)
188 printf("[WARN] Drive did not report ready within %lu ms.\n", (unsigned long)timeoutMs);
189 printf(" Continuing may fail if the unit is still spun down or still reading lead-in.\n");
197 BOOL StopDriveUnit(HANDLE hDevice)
199 SCSI_PASS_THROUGH_DIRECT sptd;
201 memset(&sptd, 0, sizeof(sptd));
203 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
205 sptd.TimeOutValue = 30;
206 sptd.DataTransferLength = 0;
207 sptd.DataBuffer = NULL;
209 // START STOP UNIT, START=0, LOEJ=0.
210 // This requests a normal stop/spin-down without ejecting or loading the tray.
214 printf("Sending SCSI STOP UNIT / spin-down command...\n");
216 if (DeviceIoControl(hDevice,
217 IOCTL_SCSI_PASS_THROUGH_DIRECT,
225 printf("SCSI STOP UNIT / spin-down successful.\n");
230 DWORD err = GetLastError();
231 printf("[WARN] SCSI STOP UNIT / spin-down failed. Error: %lu\n", err);
232 printf(" Dump output has already been finalized; this only affects drive spin state.\n");
237 void AutomateTrayCycle(HANDLE hDevice)
239 ControlTray(hDevice, TRUE);
240 Sleep(3000); // Give the tray time to fully extend
243 ControlTray(hDevice, FALSE);
244 printf("Waiting for disc spin-up/readiness after tray close...\n");
245 if (EnsureDriveReady(hDevice, 45000))
247 // Small settle period after readiness so the drive can finish lead-in/media-change bookkeeping.
252 // Preserve the old conservative behavior if TEST UNIT READY polling never succeeds.
253 printf("[WARN] Falling back to fixed 10s post-close settle delay.\n");
258 BOOL SetDriveSpeedMax(HANDLE hDevice)
260 SCSI_PASS_THROUGH_DIRECT sptd = {0};
261 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
265 sptd.CdbLength = 12; // 12-byte CDB for 0xBB
266 sptd.DataIn = SCSI_IOCTL_DATA_OUT;
267 sptd.TimeOutValue = 10;
268 sptd.DataBuffer = NULL;
269 sptd.DataTransferLength = 0;
271 // CDB 0xBB: [0] Opcode, [2-3] Read Speed, [4-5] Write Speed
273 sptd.Cdb[2] = 0xFF; // MSB
274 sptd.Cdb[3] = 0xFF; // LSB
275 sptd.Cdb[4] = 0xFF; // MSB
276 sptd.Cdb[5] = 0xFF; // LSB
279 return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT,
280 &sptd, sizeof(sptd), &sptd, sizeof(sptd),
284 void ForceMediaRefresh(HANDLE hDevice)
288 // Lock the volume so Windows stops background polling
289 DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
291 // Force the storage stack to re-read the Partition Table/Capacity
292 // without sending an Eject command to the hardware.
293 if (DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL))
295 printf("Windows Partition Stack refreshed silently.\n");
298 // Explicitly dismount to kill the "Video DVD" file system driver (UDFS/ISO9660)
299 DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
303 void HexDump(unsigned char *buffer, uint32_t size)
305 for (uint32_t i = 0; i < size; i++)
308 printf("\n%04X: ", i);
309 printf("%02X ", buffer[i]);
314 void outputdata(const uint8_t *buf, uint32_t lines)
316 for (uint32_t j = 0; j < lines; j++)
318 for (uint32_t k = 0; k < 16; k++)
320 uint32_t idx = j * 16 + k;
323 printf("%02X ", buf[idx]);
329 uint8_t chksum8(const unsigned char *buff, size_t len) {
330 unsigned int sum = 0;
331 for (sum = 0; len != 0; len--)
336 void FormatElapsedTime(DWORD dwMilliseconds, char *outStr)
338 uint32_t totalSeconds = dwMilliseconds / 1000;
339 uint32_t hours = totalSeconds / 3600;
340 uint32_t minutes = (totalSeconds % 3600) / 60;
341 uint32_t seconds = totalSeconds % 60;
343 sprintf(outStr, "%02u:%02u:%02u", hours, minutes, seconds);
346 void PrintFormattedCapacity(unsigned char *scsibuffer)
348 // The first 4 bytes are the Last Logical Block Address (Big Endian)
349 uint32_t maxLBA = (scsibuffer[0] << 24) | (scsibuffer[1] << 16) |
350 (scsibuffer[2] << 8) | scsibuffer[3];
352 // The next 4 bytes are the Block Length (Big Endian)
353 uint32_t blockLen = (scsibuffer[4] << 24) | (scsibuffer[5] << 16) |
354 (scsibuffer[6] << 8) | scsibuffer[7];
356 // Total bytes = (MaxLBA + 1) * BlockLen
357 // Use double for the math to avoid 32-bit integer overflow
358 double totalBytes = (double)(maxLBA + 1) * blockLen;
359 double totalGB = totalBytes / (1024.0 * 1024.0 * 1024.0);
361 printf("--------------------------------------------\n");
362 printf("Drive Capacity Details:\n");
363 printf(" Total Sectors: %u\n", maxLBA + 1);
364 printf(" Sector Size: %u bytes\n", blockLen);
365 printf(" Total Size: %.2f GB\n", totalGB);
366 printf("--------------------------------------------\n");
369 void ListOpticalDrives()
371 DWORD drives = GetLogicalDrives();
372 char rootPath[] = "A:\\";
373 char devicePath[] = "\\\\.\\A:";
376 printf("%-5s %-12s %-18s %-15s %s\n", "ID", "Vendor", "Model", "Volume Label", "Status");
377 printf("-------------------------------------------------------------------------------\n");
379 uint8_t driveCount = 0;
380 for (int i = 0; i < 26; i++)
382 if (drives & (1 << i))
384 rootPath[0] = 'A' + i;
386 if (GetDriveTypeA(rootPath) == DRIVE_CDROM)
388 devicePath[4] = 'A' + i;
390 // 1. Get Hardware Info (Vendor/Model)
391 char vendorStr[16] = "Generic";
392 char productStr[21] = "Unknown";
394 HANDLE h = CreateFileA(devicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
395 NULL, OPEN_EXISTING, 0, NULL);
397 if (h != INVALID_HANDLE_VALUE)
399 STORAGE_PROPERTY_QUERY query = {0};
400 query.PropertyId = StorageDeviceProperty;
401 query.QueryType = PropertyStandardQuery;
404 if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query),
405 buffer, sizeof(buffer), &bytes, NULL))
407 PSTORAGE_DEVICE_DESCRIPTOR desc = (PSTORAGE_DEVICE_DESCRIPTOR)buffer;
408 if (desc->VendorIdOffset)
409 strcpy(vendorStr, (char *)(buffer + desc->VendorIdOffset));
410 if (desc->ProductIdOffset)
411 strcpy(productStr, (char *)(buffer + desc->ProductIdOffset));
416 // 2. Get Volume Info (Disc Label)
417 char volumeName[MAX_PATH + 1] = {0};
418 char statusStr[20] = "No Disc";
420 if (GetVolumeInformationA(rootPath, volumeName, sizeof(volumeName),
421 NULL, NULL, NULL, NULL, 0))
423 if (strlen(volumeName) == 0)
424 strcpy(volumeName, "[No Label]");
425 strcpy(statusStr, "Ready");
428 printf(" %c: %-12.12s %-18.18s %-15.15s %s\n",
429 rootPath[0], vendorStr, productStr, volumeName, statusStr);
435 printf("No optical drives found.\n");
436 printf("-------------------------------------------------------------------------------\n");
437 printf("Total Optical Drives Found: %u\n", driveCount);
440 uint32_t GetTotalSectors(HANDLE hDevice)
442 typedef struct _SCSI_PASS_THROUGH_WITH_BUFFERS
444 SCSI_PASS_THROUGH spt;
445 unsigned char ucDataBuf[8]; // Buffer for the 8-byte READ CAPACITY result
446 } SCSI_PASS_THROUGH_WITH_BUFFERS;
448 SCSI_PASS_THROUGH_WITH_BUFFERS sptwb = {0};
450 sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
451 sptwb.spt.CdbLength = 10; // READ CAPACITY (10) is a 10-byte command
452 sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
453 sptwb.spt.DataTransferLength = 8;
454 sptwb.spt.TimeOutValue = 2; // 2 second timeout
455 sptwb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf);
457 // CDB 0x25 = READ CAPACITY (10)
458 sptwb.spt.Cdb[0] = 0x25;
461 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH,
462 &sptwb, sizeof(sptwb),
463 &sptwb, sizeof(sptwb),
464 &bytesReturned, NULL))
467 // Extract Max LBA (Big Endian) from the first 4 bytes
468 uint32_t maxLBA = (sptwb.ucDataBuf[0] << 24) |
469 (sptwb.ucDataBuf[1] << 16) |
470 (sptwb.ucDataBuf[2] << 8) |
476 return 0; // Return 0 on failure
479 uint32_t GetXboxPhysicalSectors(HANDLE hDevice)
481 SCSI_PASS_THROUGH_DIRECT sptd = {0};
482 unsigned char buffer[2048] = {0};
485 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
487 sptd.DataIn = SCSI_IOCTL_DATA_IN;
488 sptd.DataTransferLength = 2048;
489 sptd.TimeOutValue = 10;
490 sptd.DataBuffer = buffer;
492 // READ DVD STRUCTURE (0xAD)
494 sptd.Cdb[7] = 0x00; // Format: Physical Format Information
495 sptd.Cdb[8] = 0x08; // Allocation Length (MSB)
496 sptd.Cdb[9] = 0x00; // Allocation Length (LSB)
498 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
500 // Bytes 13-15 of the PFI contain the End LBA of the data area
501 uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15];
503 // For Xbox discs, we add 1 to the End LBA to get the total count
504 // and add the 32 sectors of lead-in padding we manually create.
508 // Fallback for Dual Layer if command fails
512 // Forces Windows to re-evaluate the drive without ejecting the tray
513 void RefreshVolume(HANDLE hDevice)
516 printf("Refreshing Volume Stack (Quiet Mode)...\n");
517 // Only update properties; do NOT dismount as it resets the GDR-8163B state.
518 DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL);
519 Sleep(1000); // Essential for the firmware to re-index after the OS check
522 void ListDirectoryRecursive(HANDLE hDevice, uint32_t lba, uint32_t size, int level)
524 if (size == 0 || level > 10)
525 return; // Prevent infinite recursion
527 uint32_t sectorsToRead = (size + 2047) / 2048;
528 unsigned char *dirBuffer = (unsigned char *)VirtualAlloc(NULL, sectorsToRead * 2048, MEM_COMMIT, PAGE_READWRITE);
532 if (ScsiReadSectors(hDevice, lba, (uint16_t)sectorsToRead, dirBuffer))
535 while (offset < size)
537 XDFS_DIR_ENTRY *entry = (XDFS_DIR_ENTRY *)&dirBuffer[offset];
539 // --- SANITY CHECK 1: End of Table ---
540 // If FileNameLength is 0 or 0xFF, we've hit the padding/end of the list.
541 if (entry->FileNameLength == 0 || entry->FileNameLength == 0xFF)
544 // --- SANITY CHECK 2: Buffer Overflow ---
545 // Ensure the entry doesn't claim to exist past our allocated buffer.
546 if (offset + 14 + entry->FileNameLength > size)
549 // --- SANITY CHECK 3: Character Validation ---
550 // If the first character isn't a printable ASCII, it's a glitch entry.
551 if (entry->FileName[0] < 32 || entry->FileName[0] > 126)
555 for (int i = 0; i < level; i++)
559 if (entry->Attributes & 0x10)
568 // Print Filename safely
569 for (int i = 0; i < entry->FileNameLength; i++)
571 char c = entry->FileName[i];
572 if (c >= 32 && c <= 126)
575 printf("?"); // Replace glitches with a placeholder
578 if (!(entry->Attributes & 0x10))
580 printf(" (%u bytes)", entry->FileSize);
584 // RECURSION: Only dive if it's a valid directory LBA
585 if ((entry->Attributes & 0x10) && entry->StartLBA > 0x100)
587 ListDirectoryRecursive(hDevice, entry->StartLBA, entry->FileSize, level + 1);
590 // Move to next entry (4-byte alignment)
591 uint32_t nextOffset = (14 + entry->FileNameLength + 3) & ~3;
593 // If the calculation gives us 0, we're stuck in an infinite loop; break.
596 offset += nextOffset;
600 VirtualFree(dirBuffer, 0, MEM_RELEASE);
603 void ReadXboxGameDir(HANDLE hDevice)
605 // Single buffer for the Volume Descriptor read
606 unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
610 // Read XDFS Volume Descriptor at Sector 0x20
611 if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer))
613 printf("Error: Could not read XDFS Volume Descriptor.\n");
614 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
618 // Map the descriptor and extract root location/size
619 XDFS_VOLUME_DESCRIPTOR *vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
620 uint32_t rootLba = vol->RootLBA;
621 uint32_t rootSize = vol->RootSize;
623 // We no longer need this buffer once we have the Root LBA/Size
624 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
626 // Draw the recursive tree
627 printf("\n--- XDFS FILE SYSTEM TREE ---\n");
631 ListDirectoryRecursive(hDevice, rootLba, rootSize, 0);
635 printf("Error: Invalid Root LBA.\n");
638 printf("------------------------------\n");
641 void SanitizeFilename(char *filename)
643 if (!filename || filename[0] == '\0')
648 int lastWasSpace = 1; // Using 1 for true to trim leading spaces
650 while (filename[readIndex] != '\0')
652 unsigned char c = (unsigned char)filename[readIndex];
654 // Whitelist: Only allow Letters (isalnum) and Spaces
655 // This strips ! ' ? : " / \ | * < > and non-printable characters
656 if (isalnum(c) || c == ' ')
659 // Collapse Multiple Spaces
664 filename[writeIndex++] = ' ';
670 // It's a letter or number, write it normally
671 filename[writeIndex++] = c;
678 // Null-terminate the new shorter string
679 filename[writeIndex] = '\0';
681 // Remove trailing space if one exists
682 if (writeIndex > 0 && filename[writeIndex - 1] == ' ')
684 filename[writeIndex - 1] = '\0';
688 BOOL ScsiReadSectors(HANDLE hDevice, uint32_t lba, uint16_t count, unsigned char *buffer)
690 SCSI_PASS_THROUGH_DIRECT sptd = {0};
693 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
696 sptd.DataTransferLength = count * 2048;
697 sptd.TimeOutValue = 30;
698 sptd.DataBuffer = buffer;
700 sptd.Cdb[0] = 0x28; // READ(10)
701 sptd.Cdb[2] = (lba >> 24) & 0xFF;
702 sptd.Cdb[3] = (lba >> 16) & 0xFF;
703 sptd.Cdb[4] = (lba >> 8) & 0xFF;
704 sptd.Cdb[5] = lba & 0xFF;
705 sptd.Cdb[7] = (unsigned char)((count >> 8) & 0xFF);
706 sptd.Cdb[8] = (unsigned char)(count & 0xFF);
708 return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL);
711 XboxGameInfo GetXboxGameInfo(HANDLE hDevice)
714 memset(&info, 0, sizeof(XboxGameInfo));
715 unsigned char sectorBuffer[2048];
717 // Get Volume Descriptor (LBA 0x20)
718 if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer))
721 // Verify XDFS Magic "XGD2" or "MICROSOFT*XBOX*MEDIA"
722 if (memcmp(sectorBuffer, "MICROSOFT", 9) != 0)
724 return (XboxGameInfo){.TitleName = "Not_XDFS"};
727 uint32_t rootLba = *(uint32_t *)§orBuffer[0x14];
728 uint32_t rootSize = *(uint32_t *)§orBuffer[0x18];
730 uint32_t rawVolumeSize = *(uint32_t *)§orBuffer[0x1C];
731 // Assign to the 64-bit member (cast to ensure no weird sign extension)
732 info.TotalSizeBytes = (uint64_t)rawVolumeSize;
734 // Read Root Directory (Scanning multiple sectors for default.xbe)
735 uint32_t sectorsToRead = (rootSize + 2047) / 2048;
736 for (uint32_t s = 0; s < sectorsToRead; s++)
738 unsigned char dirBuffer[2048];
739 if (!ScsiReadSectors(hDevice, rootLba + s, 1, dirBuffer))
743 while (offset < 2030)
745 uint16_t leftNode = *(uint16_t *)&dirBuffer[offset];
746 if (leftNode == 0xFFFF)
747 break; // End of directory
749 uint32_t startLba = *(uint32_t *)&dirBuffer[offset + 4];
750 uint8_t nameLen = dirBuffer[offset + 13];
751 char *name = (char *)&dirBuffer[offset + 14];
756 // Match "default.xbe"
757 if (nameLen == 11 && _strnicmp(name, "default.xbe", 11) == 0)
759 unsigned char xbeHeader[2048];
760 if (ScsiReadSectors(hDevice, startLba, 1, xbeHeader))
763 if (*(uint32_t *)xbeHeader != 0x48454258)
766 // 4. Locate Certificate
767 uint32_t baseVA = *(uint32_t *)&xbeHeader[0x104];
768 uint32_t certVA = *(uint32_t *)&xbeHeader[0x118];
769 uint32_t fileOffset = certVA - baseVA;
771 // Certificate might be in a later sector of the XBE file
772 uint32_t certSector = startLba + (fileOffset / 2048);
773 uint32_t innerOff = (fileOffset % 2048);
775 unsigned char certBuffer[2048];
776 if (ScsiReadSectors(hDevice, certSector, 1, certBuffer))
779 // Populate the Struct from the Certificate
780 info.TitleId = *(uint32_t *)&certBuffer[innerOff + 0x008];
781 info.AllowedMedia = *(uint32_t *)&certBuffer[innerOff + 0x09C];
782 info.GameRegion = *(uint32_t *)&certBuffer[innerOff + 0x0A0];
783 info.GameRatings = *(uint32_t *)&certBuffer[innerOff + 0x0A4];
784 info.DiscNumber = *(uint32_t *)&certBuffer[innerOff + 0x0A8];
785 info.Version = *(uint32_t *)&certBuffer[innerOff + 0x0AC];
787 // Convert UTF-16 Title Name (at 0x00C) to ASCII
788 for (int i = 0; i < 40; i++)
790 char c = certBuffer[innerOff + 0x00C + (i * 2)];
793 info.TitleName[i] = c;
801 offset += (14 + nameLen + 3) & ~3; // XDFS Alignment
805 return info; // Success will be 0 if we never found default.xbe or failed to read the cert
808 void DisplayXboxGameInfo(XboxGameInfo info)
812 printf("Error: Could not retrieve Xbox game information.\n");
816 printf("\n--- Xbox Game Information ---\n");
817 printf("Title Name: %s\n", info.TitleName);
818 printf("Title ID: 0x%08X\n", info.TitleId);
819 printf("Version: %u\n", info.Version);
820 printf("Disc Number: %u\n", info.DiscNumber);
824 if (info.GameRegion & XB_REGION_MANUFACTURING)
825 printf("[Manufacturing] ");
826 if (info.GameRegion & XB_REGION_US_CANADA)
827 printf("North America ");
828 if (info.GameRegion & XB_REGION_JAPAN)
830 if (info.GameRegion & XB_REGION_EUROPE_AU_NZ)
831 printf("Europe/AU ");
832 if (info.GameRegion & XB_REGION_REST_OF_WORLD)
833 printf("Rest of World ");
835 // If everything is set (0x7FFFFFFF or 0xFFFFFFFF), it's Region Free
836 if ((info.GameRegion & 0x7FFFFFFF) == 0x7FFFFFFF)
838 printf("(Region Free)");
840 else if (info.GameRegion == 0)
842 printf("None (Locked)");
846 // Decode Media Types
847 printf("Allowed Media: ");
848 if (info.AllowedMedia & XB_MEDIA_HARD_DRIVE)
850 if (info.AllowedMedia & XB_MEDIA_DVD_X2)
852 if (info.AllowedMedia & XB_MEDIA_DVD_5_RO)
854 if (info.AllowedMedia & XB_MEDIA_DVD_9_RO)
856 if (info.AllowedMedia & XB_MEDIA_CD)
858 if (info.AllowedMedia & XB_MEDIA_DONGLE)
859 printf("Memory_Unit ");
862 DisplayXboxRating(info.GameRatings);
864 printf("-----------------------------\n");
867 void DisplayXboxRating(uint32_t ratings)
869 // ESRB (North America) - Byte 0 (Bits 0-7)
870 uint8_t esrb = (uint8_t)(ratings & 0xFF);
871 if (esrb != 0 && esrb != 0xFF)
873 printf("ESRB Rating: ");
877 printf("EC (Early Childhood)\n");
880 printf("E (Everyone)\n");
883 printf("K-A (Kids to Adults)\n");
886 printf("T (Teen)\n");
889 printf("M (Mature)\n");
892 printf("AO (Adults Only)\n");
895 printf("RP (Rating Pending/Unrated)\n");
900 // PEGI (Europe) - Byte 1 (Bits 8-15)
901 uint8_t pegi = (uint8_t)((ratings >> 8) & 0xFF);
902 if (pegi != 0 && pegi != 0xFF)
904 printf("PEGI Rating: ");
923 printf("Other (0x%02X)\n", pegi);
928 // CERO (Japan) - Byte 2 (Bits 16-23)
929 uint8_t cero = (uint8_t)((ratings >> 16) & 0xFF);
930 if (cero != 0 && cero != 0xFF)
932 printf("CERO Rating: ");
936 printf("A (All Ages)\n");
948 printf("Z (18+ Only)\n");
951 printf("Other (0x%02X)\n", cero);
956 if ((ratings & 0x00FFFFFF) == 0)
958 printf("Rating: None/Unrated\n");
962 // --- POST-DUMP VERIFICATION ---
963 void PrintGamePartitionHash(const char *filename)
965 FILE *f = fopen(filename, "rb");
967 uint32_t startLba = START_LBA_MAGIC;
968 unsigned long long bytesRemaining = 0ULL;
969 unsigned long long totalBytesToHash = 0ULL;
970 unsigned long long bytesDone = 0ULL;
973 HCRYPTPROV hProv = 0;
974 HCRYPTHASH hHash = 0;
977 char finalHash[41] = {0};
979 DWORD lastPrintTick = 0;
983 char timeStr[12] = {0};
984 char etaStr[12] = {0};
989 if (_fseeki64(f, 0, SEEK_END) != 0)
994 fileBytes = _ftelli64(f);
1001 if ((unsigned long long)fileBytes == (unsigned long long)XGD1_FULL_REDUMP_SECTORS * 2048ULL)
1003 startLba = XGD1_GAME_OUTPUT_START_LBA;
1004 bytesRemaining = (unsigned long long)REDUMP_SECTORS * 2048ULL;
1005 printf("[HASH] Calculating Game/XISO-region SHA-1 (Redump-style output LBA %u, %u sectors)...\n",
1006 startLba, REDUMP_SECTORS);
1010 startLba = START_LBA_MAGIC;
1011 bytesRemaining = ((unsigned long long)fileBytes > (unsigned long long)startLba * 2048ULL)
1012 ? ((unsigned long long)fileBytes - (unsigned long long)startLba * 2048ULL)
1014 printf("[HASH] Calculating Game-Partition-Only SHA-1 (legacy contiguous output LBA %u)...\n", startLba);
1017 totalBytesToHash = bytesRemaining;
1018 if (bytesRemaining == 0)
1024 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
1029 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
1031 CryptReleaseContext(hProv, 0);
1036 _fseeki64(f, (__int64)startLba * 2048, SEEK_SET);
1038 vBuf = (unsigned char *)malloc(1024 * 1024); // 1MB buffer
1041 CryptDestroyHash(hHash);
1042 CryptReleaseContext(hProv, 0);
1047 startTick = GetTickCount();
1048 lastPrintTick = startTick;
1050 while (bytesRemaining > 0 && (read = fread(vBuf, 1, (bytesRemaining > 1024ULL * 1024ULL) ? 1024 * 1024 : (size_t)bytesRemaining, f)) > 0)
1052 CryptHashData(hHash, vBuf, (DWORD)read, 0);
1053 bytesRemaining -= read;
1054 bytesDone += (unsigned long long)read;
1056 nowTick = GetTickCount();
1057 if (bytesDone >= totalBytesToHash || (nowTick - lastPrintTick) >= 1000)
1059 double percent = ((double)bytesDone / (double)totalBytesToHash) * 100.0;
1060 double mbDone = (double)bytesDone / (1024.0 * 1024.0);
1062 elapsedMs = nowTick - startTick;
1064 speed = mbDone / ((double)elapsedMs / 1000.0);
1065 etaMs = (bytesDone > 0 && elapsedMs > 0)
1066 ? (DWORD)(((double)elapsedMs / (double)bytesDone) * (double)(totalBytesToHash - bytesDone))
1068 FormatElapsedTime(elapsedMs, timeStr);
1069 FormatElapsedTime(etaMs, etaStr);
1070 xbox_ref_console_printf("\r[HASH] Game/XISO-region: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ",
1077 lastPrintTick = nowTick;
1081 CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0);
1082 for (int i = 0; i < 20; i++)
1083 sprintf(&finalHash[i * 2], "%02x", rgbHash[i]);
1085 elapsedMs = GetTickCount() - startTick;
1086 FormatElapsedTime(elapsedMs, timeStr);
1087 if (totalBytesToHash > 0)
1088 xbox_ref_console_printf("\r[HASH] Game/XISO-region: 100.0%% | %.1f MB | Time: %s \n",
1089 (double)totalBytesToHash / (1024.0 * 1024.0),
1091 printf("Game/XISO-region SHA-1: %s\n", finalHash);
1092 printf("[OK] Game/XISO-region SHA-1 complete in %s.\n", timeStr);
1095 CryptDestroyHash(hHash);
1096 CryptReleaseContext(hProv, 0);
1100 void GetMediaID(HANDLE hDevice, char *outMediaId)
1102 SCSI_PASS_THROUGH_DIRECT sptd = {0};
1103 unsigned char buffer[2048] = {0};
1104 DWORD bytesReturned;
1106 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
1107 sptd.CdbLength = 12;
1108 sptd.DataIn = SCSI_IOCTL_DATA_IN;
1109 sptd.DataTransferLength = 2048;
1110 sptd.TimeOutValue = 5;
1111 sptd.DataBuffer = buffer;
1113 sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE
1114 sptd.Cdb[7] = 0x04; // Format: Disc Manufacturing Information (DMI)
1118 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
1120 // The Media ID is typically 32 bytes starting at offset 4 in the DMI
1121 // Offset 8 is where "MS11..." usually starts on Xbox discs
1122 // We'll grab 16 characters to be safe
1124 for (int i = 8; i < 24; i++)
1126 // Only add alphanumeric characters to keep the filename clean
1127 if (isalnum(buffer[i]))
1129 outMediaId[writePos++] = buffer[i];
1132 outMediaId[writePos] = '\0'; // Null terminate the string
1136 strcpy(outMediaId, "UNKNOWN_ID");
1140 void GetDiscMetadata(HANDLE hDevice, uint32_t *totalSectors, bool *isDualLayer, XboxGameInfo *gameInfo)
1142 SCSI_PASS_THROUGH_DIRECT sptd = {0};
1143 unsigned char buffer[2048] = {0};
1144 DWORD bytesReturned;
1146 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
1147 sptd.CdbLength = 12;
1148 sptd.DataIn = SCSI_IOCTL_DATA_IN;
1149 sptd.DataTransferLength = 2048;
1150 sptd.TimeOutValue = 10;
1151 sptd.DataBuffer = buffer;
1153 sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE
1154 sptd.Cdb[7] = 0x00; // Physical Format Information
1155 sptd.Cdb[8] = 0x08; // 2048 bytes
1158 if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL))
1161 // Byte 12: bits 5-6 (Number of Layers)
1162 // 0x20 = 00100000 (Two layers), 0x00 = 00000000 (One layer)
1163 unsigned char layerInfo = (buffer[12] >> 5) & 0x03;
1164 *isDualLayer = (layerInfo > 0);
1166 // Bytes 13-15: End LBA
1167 uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15];
1169 // XBOX SANITY CHECK
1170 // If the drive reports a value much larger than a standard Xbox Dual Layer (3.4M sectors)
1171 // it means the drive is reporting the raw DVD-9 limit. We must cap it.
1172 if (endLba > ((uint32_t)REDUMP_SECTORS - 1))
1174 printf("[!] Drive reported Raw DVD-9 geometry. Normalizing to Xbox Dual Layer...\n");
1175 if (gameInfo->TotalSizeBytes < LAYER_BREAK)
1177 printf("[!] Info: Game partition size is smaller than expected for a Dual Layer disc.\n");
1179 *totalSectors = REDUMP_SECTORS;
1180 *isDualLayer = true;
1184 printf("[!] Drive reported Raw DVD-5 geometry. Normalizing to Xbox Single Layer...\n");
1185 *totalSectors = endLba + 1;
1186 *isDualLayer = (endLba > (uint32_t)LAYER_THRESHOLD); // Standard threshold for SL vs DL
1192 *isDualLayer = true;
1193 *totalSectors = REDUMP_SECTORS;
1194 printf("Media Info: Could not read PFI. Defaulting to Dual Layer.\n");
1198 uint32_t GetGamePartitionSize(HANDLE hDevice, uint32_t totalDiscSectors, XDFS_VOLUME_DESCRIPTOR *vol)
1200 uint32_t sectorsToRead = 0;
1201 if (totalDiscSectors > 3300000)
1203 // DUAL LAYER (XGD2) Calculation:
1204 // LBA 1,913,920 is the physical end of the usable XDFS area on retail DVD-9s.
1205 uint32_t xgd2EndLba = 1913920;
1206 sectorsToRead = xgd2EndLba - vol->RootLBA;
1210 // SINGLE LAYER (XGD1 / Homebrew) Calculation:
1211 // On single layer discs, the header's VolumeSize is trustworthy.
1212 sectorsToRead = vol->VolumeSize / 2048;
1214 return sectorsToRead;
1217 static BOOL ProbeXboxVolumeAt(HANDLE hDevice, uint32_t lba)
1219 unsigned char sector[2048] = {0};
1220 return ScsiReadSectors(hDevice, lba, 1, sector) && memcmp(sector, "MICROSOFT", 9) == 0;
1223 static uint32_t DetectXboxVolumeStart(HANDLE hDevice)
1225 if (ProbeXboxVolumeAt(hDevice, START_LBA_MAGIC))
1226 return START_LBA_MAGIC;
1228 if (ProbeXboxVolumeAt(hDevice, 0x20))
1234 static void RecoveryKick(HANDLE hDevice, BOOL authRecovery)
1236 unsigned char dummy[2048] = {0};
1239 KickXboxMediaAuth(hDevice);
1241 SetDriveSpeedMax(hDevice);
1243 for (int i = 0; i < 10; i++)
1245 ScsiReadSectors(hDevice, 0, 1, dummy);
1251 static void GetDirectoryForPath(const char *filename, char *outDir, DWORD outDirSize)
1254 char fullPath[MAX_PATH];
1255 char *filePart = NULL;
1257 if (!outDir || outDirSize == 0)
1262 if (!filename || filename[0] == '\0')
1264 GetCurrentDirectoryA(outDirSize, outDir);
1268 len = GetFullPathNameA(filename, (DWORD)sizeof(fullPath), fullPath, &filePart);
1269 if (len == 0 || len >= sizeof(fullPath))
1271 GetCurrentDirectoryA(outDirSize, outDir);
1275 if (filePart && filePart > fullPath)
1277 size_t dirLen = (size_t)(filePart - fullPath);
1278 if (dirLen >= outDirSize)
1279 dirLen = outDirSize - 1;
1280 memcpy(outDir, fullPath, dirLen);
1281 outDir[dirLen] = '\0';
1285 GetCurrentDirectoryA(outDirSize, outDir);
1289 static BOOL FileExistsAndSize(const char *filename, unsigned long long *sizeOut)
1291 WIN32_FILE_ATTRIBUTE_DATA fad;
1296 if (!filename || !GetFileAttributesExA(filename, GetFileExInfoStandard, &fad))
1299 if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1304 ULARGE_INTEGER size;
1305 size.HighPart = fad.nFileSizeHigh;
1306 size.LowPart = fad.nFileSizeLow;
1307 *sizeOut = size.QuadPart;
1313 static BOOL CheckOutputFreeSpace(const char *filename, unsigned long long expectedBytes, const char *label)
1316 ULARGE_INTEGER freeToCaller;
1317 ULARGE_INTEGER totalBytes;
1318 ULARGE_INTEGER totalFree;
1319 unsigned long long existingBytes = 0ULL;
1320 unsigned long long effectiveFree;
1321 unsigned long long margin;
1323 if (expectedBytes == 0)
1326 GetDirectoryForPath(filename, dir, (DWORD)sizeof(dir));
1328 if (!GetDiskFreeSpaceExA(dir[0] ? dir : NULL, &freeToCaller, &totalBytes, &totalFree))
1330 DWORD err = GetLastError();
1331 printf("\n[WARN] Could not check free space for output path '%s' (GetDiskFreeSpaceEx error %lu).\n", filename, err);
1332 printf(" Continuing, but write errors will still be caught during the dump.\n");
1336 FileExistsAndSize(filename, &existingBytes);
1338 // If overwriting an existing output on the same volume, its current bytes can be
1339 // reclaimed by fopen(..., "wb"). This avoids rejecting a valid replacement run.
1340 effectiveFree = freeToCaller.QuadPart + existingBytes;
1342 // Add a small safety margin for sidecar/profile files and filesystem metadata.
1343 // Keep this modest so overwriting an existing full raw ISO still passes.
1344 margin = 64ULL * 1024ULL * 1024ULL;
1346 printf("[%s] Free-space preflight for '%s':\n", label ? label : "OUTPUT", filename);
1347 printf(" Required output bytes: %llu\n", expectedBytes);
1348 printf(" Safety margin: %llu\n", margin);
1349 printf(" Free to caller: %llu\n", (unsigned long long)freeToCaller.QuadPart);
1351 printf(" Existing output bytes: %llu (counted as reclaimable overwrite space)\n", existingBytes);
1352 printf(" Effective available: %llu\n", effectiveFree);
1354 if (effectiveFree < expectedBytes + margin)
1356 printf("\n[FATAL] Not enough free disk space for %s.\n", label ? label : "output");
1357 printf(" Required + margin: %llu bytes\n", expectedBytes + margin);
1358 printf(" Effective free: %llu bytes\n", effectiveFree);
1359 printf(" Free space can change while dumping; free extra space and rerun.\n");
1366 static BOOL WriteOutputBytes(FILE *outFile,
1368 size_t bytesToWrite,
1369 const char *phaseName,
1375 if (!outFile || !data || bytesToWrite == 0)
1376 return bytesToWrite == 0;
1378 written = fwrite(data, 1, bytesToWrite, outFile);
1379 if (written != bytesToWrite)
1381 printf("\n[FATAL] Output write failed during %s range.\n", phaseName ? phaseName : "dump");
1382 printf(" Source LBA: %u | Output LBA: %u\n", sourceLba, outputLba);
1383 printf(" Requested: %llu bytes\n", (unsigned long long)bytesToWrite);
1384 printf(" Written: %llu bytes\n", (unsigned long long)written);
1386 printf(" errno: %d (%s)\n", errno, strerror(errno));
1387 printf(" This commonly means another program consumed free space after preflight,\n");
1388 printf(" the destination volume filled up, or the destination became unavailable.\n");
1392 if (ferror(outFile))
1394 printf("\n[FATAL] Output stream error during %s range at output LBA %u.\n",
1395 phaseName ? phaseName : "dump", outputLba);
1397 printf(" errno: %d (%s)\n", errno, strerror(errno));
1404 static BOOL FlushAndCommitOutput(FILE *outFile, const char *label)
1411 if (fflush(outFile) != 0)
1413 printf("\n[FATAL] fflush failed for %s output.\n", label ? label : "dump");
1415 printf(" errno: %d (%s)\n", errno, strerror(errno));
1419 fd = _fileno(outFile);
1420 if (fd >= 0 && _commit(fd) != 0)
1422 printf("\n[FATAL] _commit failed for %s output. The OS may not have accepted all buffered data.\n",
1423 label ? label : "dump");
1425 printf(" errno: %d (%s)\n", errno, strerror(errno));
1432 static BOOL DumpSectorRangeWithRetry(HANDLE hDevice,
1435 uint32_t sourceStartLba,
1436 uint32_t sectorsToRead,
1437 uint32_t outputBaseLba,
1438 const char *phaseName,
1441 const uint32_t batchSize = 32;
1442 unsigned char *buffer = NULL;
1443 uint32_t sectorsDone = 0;
1444 DWORD startTime = GetTickCount();
1445 char timeStr[12] = {0};
1446 char etaStr[12] = {0};
1448 if (sectorsToRead == 0)
1451 buffer = (unsigned char *)VirtualAlloc(NULL, batchSize * 2048, MEM_COMMIT, PAGE_READWRITE);
1454 printf("\n[FATAL] Could not allocate dump buffer for %s range.\n", phaseName);
1458 printf("\n--- STARTING %s RANGE ---\n", phaseName);
1459 printf("Source LBA: %u | Output LBA: %u | Sectors: %u\n", sourceStartLba, outputBaseLba, sectorsToRead);
1461 RecoveryKick(hDevice, authRecovery);
1463 while (sectorsDone < sectorsToRead)
1465 uint32_t currentLba = sourceStartLba + sectorsDone;
1466 const char *currentLayerStr = "L0";
1467 uint32_t burstLimit = batchSize;
1469 BOOL success = FALSE;
1471 if ((outputBaseLba + sectorsDone) >= LAYER_BREAK)
1472 currentLayerStr = "L1";
1474 // Layer-boundary safety: do not let one READ(10) span the Xbox layer break.
1475 if (sectorsDone == 0 || (outputBaseLba + sectorsDone) == LAYER_BREAK)
1479 else if ((outputBaseLba + sectorsDone) < LAYER_BREAK &&
1480 (outputBaseLba + sectorsDone + batchSize) > LAYER_BREAK)
1482 burstLimit = LAYER_BREAK - (outputBaseLba + sectorsDone);
1485 toRead = (sectorsToRead - sectorsDone > burstLimit) ? burstLimit : (sectorsToRead - sectorsDone);
1487 if ((outputBaseLba + sectorsDone) == LAYER_BREAK)
1489 printf("\n[INFO] Redump/XGD1 output layer break at LBA %u. Reducing burst size to 1 sector for safety.\n", LAYER_BREAK);
1492 for (int retry = 0; retry <= MAX_RETRIES; retry++)
1494 if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, buffer))
1496 if (!WriteOutputBytes(outFile, buffer, (size_t)toRead * 2048U, phaseName, currentLba, outputBaseLba + sectorsDone))
1498 VirtualFree(buffer, 0, MEM_RELEASE);
1501 CryptHashData(hHash, buffer, toRead * 2048, 0);
1502 sectorsDone += toRead;
1507 RecoveryKick(hDevice, authRecovery);
1513 unsigned char *smallBuffer = NULL;
1514 printf("\n[!] Batch failed in %s range at source LBA %u. Recovering sectors individually.\n", phaseName, currentLba);
1516 smallBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
1519 printf("\n[FATAL] Could not allocate single-sector recovery buffer.\n");
1520 VirtualFree(buffer, 0, MEM_RELEASE);
1524 for (uint32_t i = 0; i < toRead; i++)
1526 BOOL sectorSuccess = FALSE;
1528 for (int sRetry = 0; sRetry <= MAX_RETRIES; sRetry++)
1530 if (ScsiReadSectors(hDevice, currentLba + i, 1, smallBuffer))
1532 if (!WriteOutputBytes(outFile, smallBuffer, 2048, phaseName, currentLba + i, outputBaseLba + sectorsDone))
1534 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1535 VirtualFree(buffer, 0, MEM_RELEASE);
1538 CryptHashData(hHash, smallBuffer, 2048, 0);
1540 sectorSuccess = TRUE;
1544 RecoveryKick(hDevice, authRecovery);
1550 printf("\n[FATAL] Unrecoverable %s sector at source LBA %u. Output hash is invalid.\n", phaseName, currentLba + i);
1551 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1552 VirtualFree(buffer, 0, MEM_RELEASE);
1557 VirtualFree(smallBuffer, 0, MEM_RELEASE);
1560 if (sectorsDone > 0)
1562 DWORD elapsedMs = GetTickCount() - startTime;
1563 uint32_t sectorsLeft = sectorsToRead - sectorsDone;
1564 DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft);
1565 float percent = ((float)sectorsDone / sectorsToRead) * 100.0f;
1566 float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024;
1567 float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f;
1569 FormatElapsedTime(elapsedMs, timeStr);
1570 FormatElapsedTime(etaMs, etaStr);
1572 xbox_ref_console_printf("\rProgress [%s/%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | sourceLba: %u | outputLba: %u | Time: %s | ETA: %s ",
1573 phaseName, currentLayerStr, percent, mbDone, speed, currentLba, outputBaseLba + sectorsDone, timeStr, etaStr);
1578 printf("\n[OK] Completed %s range.\n", phaseName);
1579 VirtualFree(buffer, 0, MEM_RELEASE);
1586 static BOOL WriteZeroSectorsOutput(FILE *outFile,
1588 uint32_t sectorCount,
1589 uint32_t outputBaseLba,
1590 const char *phaseName)
1592 const uint32_t batchSectors = 32;
1593 unsigned char *zeroBuffer = NULL;
1594 uint32_t sectorsDone = 0;
1595 DWORD startTime = GetTickCount();
1596 char timeStr[12] = {0};
1597 char etaStr[12] = {0};
1599 if (sectorCount == 0)
1602 zeroBuffer = (unsigned char *)VirtualAlloc(NULL, batchSectors * 2048, MEM_COMMIT, PAGE_READWRITE);
1605 printf("\n[FATAL] Could not allocate zero-fill buffer for %s range.\n", phaseName ? phaseName : "padding");
1608 memset(zeroBuffer, 0, batchSectors * 2048);
1610 printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "ZERO");
1611 printf("Output LBA: %u | Sectors: %u | Fill: synthetic zero-fill (not drive-captured)\n", outputBaseLba, sectorCount);
1613 while (sectorsDone < sectorCount)
1615 uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1616 uint32_t outputLba = outputBaseLba + sectorsDone;
1618 if (!WriteOutputBytes(outFile, zeroBuffer, (size_t)toWrite * 2048U, phaseName ? phaseName : "ZERO", 0, outputLba))
1620 VirtualFree(zeroBuffer, 0, MEM_RELEASE);
1624 CryptHashData(hHash, zeroBuffer, toWrite * 2048, 0);
1626 sectorsDone += toWrite;
1628 if (sectorsDone > 0)
1630 DWORD elapsedMs = GetTickCount() - startTime;
1631 uint32_t sectorsLeft = sectorCount - sectorsDone;
1632 DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft);
1633 float percent = ((float)sectorsDone / sectorCount) * 100.0f;
1634 float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024;
1635 float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f;
1637 FormatElapsedTime(elapsedMs, timeStr);
1638 FormatElapsedTime(etaMs, etaStr);
1639 xbox_ref_console_printf("\rProgress [%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | outputLba: %u | Time: %s | ETA: %s ",
1640 phaseName ? phaseName : "ZERO", percent, mbDone, speed, outputBaseLba + sectorsDone, timeStr, etaStr);
1645 printf("\n[OK] Completed %s range.\n", phaseName ? phaseName : "ZERO");
1646 VirtualFree(zeroBuffer, 0, MEM_RELEASE);
1650 static BOOL ReadSectorsToMemory(HANDLE hDevice,
1651 uint32_t sourceStartLba,
1652 uint32_t sectorCount,
1653 unsigned char *outBuffer,
1654 const char *phaseName)
1656 const uint32_t batchSectors = 32;
1657 uint32_t sectorsDone = 0;
1659 if (sectorCount == 0)
1664 printf("[RAW] Capturing %s to memory: source LBA %u..%u (%u sectors).\n",
1665 phaseName ? phaseName : "sector range",
1667 sourceStartLba + sectorCount - 1,
1670 while (sectorsDone < sectorCount)
1672 uint32_t toRead = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1673 uint32_t currentLba = sourceStartLba + sectorsDone;
1674 BOOL success = FALSE;
1676 for (int retry = 0; retry <= MAX_RETRIES; retry++)
1678 if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, outBuffer + ((size_t)sectorsDone * 2048U)))
1680 sectorsDone += toRead;
1689 printf("\n[FATAL] Could not capture %s at source LBA %u.\n", phaseName ? phaseName : "sector range", currentLba);
1697 static BOOL WriteMemorySectorsOutput(FILE *outFile,
1699 const unsigned char *buffer,
1700 uint32_t sectorCount,
1701 uint32_t outputBaseLba,
1702 const char *phaseName)
1704 const uint32_t batchSectors = 32;
1705 uint32_t sectorsDone = 0;
1707 if (sectorCount == 0)
1712 printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "MEMORY");
1713 printf("Output LBA: %u | Sectors: %u | Source: captured memory\n", outputBaseLba, sectorCount);
1715 while (sectorsDone < sectorCount)
1717 uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone);
1718 uint32_t outputLba = outputBaseLba + sectorsDone;
1719 const unsigned char *src = buffer + ((size_t)sectorsDone * 2048U);
1721 if (!WriteOutputBytes(outFile, src, (size_t)toWrite * 2048U, phaseName ? phaseName : "MEMORY", 0, outputLba))
1724 CryptHashData(hHash, src, toWrite * 2048, 0);
1726 sectorsDone += toWrite;
1729 printf("[OK] Completed %s range.\n", phaseName ? phaseName : "MEMORY");
1733 typedef struct _XboxDvdSidecarCapture
1735 BOOL hasLockedCapacity;
1736 BOOL hasLockedModeSense3E;
1737 BOOL hasUnlockedCapacity;
1738 BOOL hasUnlockedModeSense3E;
1743 unsigned char lockedCapacity[8];
1744 unsigned char lockedModeSense3E[28];
1745 unsigned char unlockedCapacity[8];
1746 unsigned char unlockedModeSense3E[28];
1748 unsigned char adC0[0x664];
1749 unsigned char pfi[2048];
1750 unsigned char dmi[2048];
1751 } XboxDvdSidecarCapture;
1753 static void StripKnownExtension(const char *filename, char *outBase, size_t outBaseSize)
1760 if (!outBase || outBaseSize == 0)
1767 strncpy(outBase, filename, outBaseSize - 1);
1768 outBase[outBaseSize - 1] = '\0';
1770 dot = strrchr(outBase, '.');
1771 slash1 = strrchr(outBase, '\\');
1772 slash2 = strrchr(outBase, '/');
1773 slash = slash1 > slash2 ? slash1 : slash2;
1775 if (dot && (!slash || dot > slash))
1779 static void MakeSidecarPath(const char *filename, const char *suffix, char *outPath, size_t outPathSize)
1781 char base[MAX_PATH];
1783 if (!outPath || outPathSize == 0)
1786 StripKnownExtension(filename, base, sizeof(base));
1787 snprintf(outPath, outPathSize, "%s%s", base, suffix);
1788 outPath[outPathSize - 1] = '\0';
1791 static BOOL WriteBinaryFile(const char *path, const unsigned char *data, size_t len)
1795 if (!path || !data || len == 0)
1798 f = fopen(path, "wb");
1802 if (fwrite(data, 1, len, f) != len)
1812 static void JsonWriteEscapedString(FILE *f, const char *s)
1819 unsigned char c = (unsigned char)*s++;
1820 if (c == '"' || c == '\\')
1839 fprintf(f, "\\u%04x", c);
1850 static void JsonWriteHexString(FILE *f, const unsigned char *data, size_t len)
1855 for (size_t i = 0; i < len; i++)
1856 fprintf(f, "%02X", data[i]);
1862 static const char *PathLeaf(const char *path)
1870 slash1 = strrchr(path, '\\');
1871 slash2 = strrchr(path, '/');
1873 if (slash1 && slash2)
1874 return (slash1 > slash2 ? slash1 : slash2) + 1;
1882 static void TrimTrailingSpaces(char *s)
1890 while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t'))
1897 static void CopyBounded(char *dst, size_t dstSize, const char *src, size_t srcLen)
1901 if (!dst || dstSize == 0)
1912 memcpy(dst, src, n);
1916 static void ExtractMediaProfileNames(const char *isoFilename,
1918 size_t titleHintSize,
1922 char base[MAX_PATH];
1924 const char *openBracket;
1925 const char *closeBracket;
1927 if (titleHint && titleHintSize > 0)
1928 titleHint[0] = '\0';
1929 if (mediaId && mediaIdSize > 0)
1935 StripKnownExtension(isoFilename, base, sizeof(base));
1936 leaf = PathLeaf(base);
1938 openBracket = strrchr(leaf, '[');
1939 closeBracket = openBracket ? strchr(openBracket, ']') : NULL;
1941 if (openBracket && closeBracket && closeBracket > openBracket)
1943 CopyBounded(titleHint, titleHintSize, leaf, (size_t)(openBracket - leaf));
1944 CopyBounded(mediaId, mediaIdSize, openBracket + 1, (size_t)(closeBracket - openBracket - 1));
1948 CopyBounded(titleHint, titleHintSize, leaf, strlen(leaf));
1951 TrimTrailingSpaces(titleHint);
1954 static void JsonWriteValidationWarnings(FILE *f, const XboxDvdSidecarCapture *cap, BOOL payloadFilesPresent, BOOL redumpStyleZeroFilledPadding)
1960 #define WRITE_WARNING(w) do { \
1961 if (wrote) fprintf(f, ", "); \
1962 JsonWriteEscapedString(f, (w)); \
1966 if (!cap || !cap->hasLockedCapacity)
1967 WRITE_WARNING("missing_locked_read_capacity_10");
1968 if (!cap || !cap->hasLockedModeSense3E)
1969 WRITE_WARNING("missing_locked_mode_sense_3e");
1970 if (!cap || !cap->hasUnlockedCapacity)
1971 WRITE_WARNING("missing_unlocked_read_capacity_10");
1972 if (!cap || !cap->hasUnlockedModeSense3E)
1973 WRITE_WARNING("missing_unlocked_mode_sense_3e");
1974 if (!cap || !cap->hasAdC0)
1975 WRITE_WARNING("missing_ad_c0_payload");
1976 if (!cap || !cap->hasPfi)
1977 WRITE_WARNING("missing_pfi_payload");
1978 if (!cap || !cap->hasDmi)
1979 WRITE_WARNING("missing_dmi_payload");
1980 if (!payloadFilesPresent)
1981 WRITE_WARNING("payload_files_not_fully_present");
1982 if (redumpStyleZeroFilledPadding)
1983 WRITE_WARNING("redump_style_padding_zero_filled_pending_hardware_capture");
1985 #undef WRITE_WARNING
1990 static BOOL ScsiDataInCommand(HANDLE hDevice,
1991 const unsigned char *cdb,
1994 unsigned char *buffer)
1996 SCSI_PASS_THROUGH_DIRECT sptd;
1997 DWORD bytesReturned = 0;
1999 if (!hDevice || !cdb || !buffer || dataLen == 0 || cdbLen == 0 || cdbLen > 16)
2002 memset(&sptd, 0, sizeof(sptd));
2003 memset(buffer, 0, dataLen);
2005 sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
2006 sptd.CdbLength = cdbLen;
2007 sptd.DataIn = SCSI_IOCTL_DATA_IN;
2008 sptd.DataTransferLength = dataLen;
2009 sptd.TimeOutValue = 30;
2010 sptd.DataBuffer = buffer;
2011 memcpy(sptd.Cdb, cdb, cdbLen);
2013 return DeviceIoControl(hDevice,
2014 IOCTL_SCSI_PASS_THROUGH_DIRECT,
2023 static BOOL CaptureReadCapacity10(HANDLE hDevice, unsigned char out8[8])
2025 static const unsigned char cdb[10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
2026 return ScsiDataInCommand(hDevice, cdb, 10, 8, out8);
2029 static BOOL CaptureModeSense3E(HANDLE hDevice, unsigned char out28[28])
2031 static const unsigned char cdb[10] = {0x5A, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00};
2032 return ScsiDataInCommand(hDevice, cdb, 10, 28, out28);
2035 static BOOL CaptureReadDvdStructureXboxC0(HANDLE hDevice, unsigned char out1664[0x664])
2037 static const unsigned char cdb[12] = {0xAD, 0x00, 0xFF, 0x02, 0xFD, 0xFF, 0xFE, 0x00, 0x06, 0x64, 0x00, 0xC0};
2038 return ScsiDataInCommand(hDevice, cdb, 12, 0x664, out1664);
2041 static BOOL CaptureReadDvdStructurePfi(HANDLE hDevice, unsigned char out2048[2048])
2043 static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00};
2044 return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048);
2047 static BOOL CaptureReadDvdStructureDmi(HANDLE hDevice, unsigned char out2048[2048])
2049 static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00};
2050 return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048);
2053 static void CaptureLockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap)
2058 cap->hasLockedCapacity = CaptureReadCapacity10(hDevice, cap->lockedCapacity);
2059 cap->hasLockedModeSense3E = CaptureModeSense3E(hDevice, cap->lockedModeSense3E);
2061 printf("[META] Locked READ CAPACITY: %s\n", cap->hasLockedCapacity ? "captured" : "failed");
2062 printf("[META] Locked MODE SENSE 0x3E: %s\n", cap->hasLockedModeSense3E ? "captured" : "failed");
2065 static void CaptureUnlockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap)
2070 cap->hasUnlockedCapacity = CaptureReadCapacity10(hDevice, cap->unlockedCapacity);
2071 cap->hasUnlockedModeSense3E = CaptureModeSense3E(hDevice, cap->unlockedModeSense3E);
2072 cap->hasAdC0 = CaptureReadDvdStructureXboxC0(hDevice, cap->adC0);
2073 cap->hasPfi = CaptureReadDvdStructurePfi(hDevice, cap->pfi);
2074 cap->hasDmi = CaptureReadDvdStructureDmi(hDevice, cap->dmi);
2076 printf("[META] Unlocked READ CAPACITY: %s\n", cap->hasUnlockedCapacity ? "captured" : "failed");
2077 printf("[META] Unlocked MODE SENSE 0x3E: %s\n", cap->hasUnlockedModeSense3E ? "captured" : "failed");
2078 printf("[META] READ DVD STRUCTURE Xbox C0 block: %s\n", cap->hasAdC0 ? "captured" : "failed");
2079 printf("[META] READ DVD STRUCTURE PFI: %s\n", cap->hasPfi ? "captured" : "failed");
2080 printf("[META] READ DVD STRUCTURE DMI: %s\n", cap->hasDmi ? "captured" : "failed");
2083 static uint32_t CapacitySectorsFromReadCapacity10(const unsigned char data[8])
2090 maxLba = ((uint32_t)data[0] << 24) |
2091 ((uint32_t)data[1] << 16) |
2092 ((uint32_t)data[2] << 8) |
2093 ((uint32_t)data[3]);
2098 static uint32_t NormalizeRawIsoTargetSectors(uint32_t reportedSectors, BOOL isDualLayer)
2100 // Option 1 targets a Redump-style reconstructed 2048-byte-sector image.
2101 // The GDR-8050L's unlocked READ CAPACITY reports the game/XISO view length
2102 // (3,431,264 sectors), while the full Original Xbox/XGD1 reconstructed image
2103 // is larger (3,820,880 sectors) because it also includes video L0/L1 and
2104 // padding around the game region.
2105 if (isDualLayer || reportedSectors > LAYER_THRESHOLD)
2106 return XGD1_FULL_REDUMP_SECTORS;
2108 return reportedSectors;
2111 static unsigned long long GetFileSizeBytes64(const char *filename)
2119 f = fopen(filename, "rb");
2123 if (_fseeki64(f, 0, SEEK_END) != 0)
2135 return (unsigned long long)pos;
2138 static BOOL VerifyOutputByteCount(const char *filename, unsigned long long expectedBytes, const char *label)
2140 unsigned long long actualBytes = GetFileSizeBytes64(filename);
2142 if (expectedBytes == 0)
2145 if (actualBytes != expectedBytes)
2147 printf("\n[FATAL] %s byte-count mismatch.\n", label ? label : "Output");
2148 printf(" Expected: %llu bytes\n", expectedBytes);
2149 printf(" Actual: %llu bytes\n", actualBytes);
2150 printf(" Refusing to mark this dump complete.\n");
2154 printf("[OK] %s byte count verified: %llu bytes.\n", label ? label : "Output", actualBytes);
2158 static void JsonWriteNull(FILE *f)
2163 static void HexBytesToString(const BYTE *bytes, DWORD byteCount, char *outHex, size_t outHexSize)
2167 if (!outHex || outHexSize == 0)
2171 if (!bytes || outHexSize < ((size_t)byteCount * 2U + 1U))
2174 for (i = 0; i < byteCount; i++)
2175 sprintf(&outHex[i * 2], "%02x", bytes[i]);
2178 static DWORD Crc32Update(DWORD crc, const unsigned char *buf, size_t len)
2180 static DWORD table[256];
2181 static BOOL tableReady = FALSE;
2187 for (n = 0; n < 256; n++)
2191 for (k = 0; k < 8; k++)
2192 c = (c & 1U) ? (0xEDB88320U ^ (c >> 1)) : (c >> 1);
2198 for (i = 0; i < len; i++)
2199 crc = table[(crc ^ buf[i]) & 0xFFU] ^ (crc >> 8);
2204 static BOOL CalculateFileHashes(const char *filename,
2206 size_t outCrc32Size,
2212 size_t outSha256Size)
2216 HCRYPTPROV hProv = 0;
2217 HCRYPTHASH hMd5 = 0;
2218 HCRYPTHASH hSha1 = 0;
2219 HCRYPTHASH hSha256 = 0;
2220 DWORD crc = 0xFFFFFFFFU;
2223 unsigned long long totalBytes = 0ULL;
2224 unsigned long long doneBytes = 0ULL;
2225 DWORD startTick = 0;
2226 DWORD lastPrintTick = 0;
2228 DWORD elapsedMs = 0;
2230 char timeStr[12] = {0};
2231 char etaStr[12] = {0};
2233 if (outCrc32 && outCrc32Size) outCrc32[0] = '\0';
2234 if (outMd5 && outMd5Size) outMd5[0] = '\0';
2235 if (outSha1 && outSha1Size) outSha1[0] = '\0';
2236 if (outSha256 && outSha256Size) outSha256[0] = '\0';
2241 totalBytes = GetFileSizeBytes64(filename);
2243 f = fopen(filename, "rb");
2247 buf = (unsigned char *)malloc(1024 * 1024);
2254 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) &&
2255 !CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
2257 if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hMd5))
2259 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hSha1))
2261 if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hSha256))
2264 startTick = GetTickCount();
2265 lastPrintTick = startTick;
2266 printf("[HASH] Calculating full-file CRC32/MD5/SHA-1/SHA-256 for %s (%llu bytes)...\n",
2270 while ((readBytes = fread(buf, 1, 1024 * 1024, f)) > 0)
2272 crc = Crc32Update(crc, buf, readBytes);
2273 if (!CryptHashData(hMd5, buf, (DWORD)readBytes, 0))
2275 if (!CryptHashData(hSha1, buf, (DWORD)readBytes, 0))
2277 if (!CryptHashData(hSha256, buf, (DWORD)readBytes, 0))
2280 doneBytes += (unsigned long long)readBytes;
2281 nowTick = GetTickCount();
2282 if (totalBytes > 0 && (doneBytes >= totalBytes || (nowTick - lastPrintTick) >= 1000))
2284 double percent = ((double)doneBytes / (double)totalBytes) * 100.0;
2285 double mbDone = (double)doneBytes / (1024.0 * 1024.0);
2287 unsigned long long bytesLeft = totalBytes - doneBytes;
2289 elapsedMs = nowTick - startTick;
2291 speed = mbDone / ((double)elapsedMs / 1000.0);
2292 etaMs = (doneBytes > 0 && elapsedMs > 0)
2293 ? (DWORD)(((double)elapsedMs / (double)doneBytes) * (double)bytesLeft)
2295 FormatElapsedTime(elapsedMs, timeStr);
2296 FormatElapsedTime(etaMs, etaStr);
2297 xbox_ref_console_printf("\r[HASH] Full-file: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ",
2304 lastPrintTick = nowTick;
2311 elapsedMs = GetTickCount() - startTick;
2312 FormatElapsedTime(elapsedMs, timeStr);
2314 xbox_ref_console_printf("\r[HASH] Full-file: 100.0%% | %.1f MB | Time: %s \n",
2315 (double)totalBytes / (1024.0 * 1024.0),
2317 printf("[OK] Full-file CRC32/MD5/SHA-1/SHA-256 complete in %s.\n", timeStr);
2320 if (outCrc32 && outCrc32Size >= 9)
2321 sprintf(outCrc32, "%08x", crc);
2323 if (outMd5 && outMd5Size >= 33)
2326 DWORD md5Len = sizeof(md5Bytes);
2327 if (!CryptGetHashParam(hMd5, HP_HASHVAL, md5Bytes, &md5Len, 0))
2329 HexBytesToString(md5Bytes, md5Len, outMd5, outMd5Size);
2332 if (outSha1 && outSha1Size >= 41)
2335 DWORD sha1Len = sizeof(sha1Bytes);
2336 if (!CryptGetHashParam(hSha1, HP_HASHVAL, sha1Bytes, &sha1Len, 0))
2338 HexBytesToString(sha1Bytes, sha1Len, outSha1, outSha1Size);
2341 if (outSha256 && outSha256Size >= 65)
2343 BYTE sha256Bytes[32];
2344 DWORD sha256Len = sizeof(sha256Bytes);
2345 if (!CryptGetHashParam(hSha256, HP_HASHVAL, sha256Bytes, &sha256Len, 0))
2347 HexBytesToString(sha256Bytes, sha256Len, outSha256, outSha256Size);
2353 if (!ok && startTick)
2355 elapsedMs = GetTickCount() - startTick;
2356 FormatElapsedTime(elapsedMs, timeStr);
2357 printf("\n[WARN] Full-file CRC32/MD5/SHA-1/SHA-256 calculation failed after %s.\n", timeStr);
2359 if (hSha256) CryptDestroyHash(hSha256);
2360 if (hSha1) CryptDestroyHash(hSha1);
2361 if (hMd5) CryptDestroyHash(hMd5);
2362 if (hProv) CryptReleaseContext(hProv, 0);
2368 static void WritePressedDvdRomWriteMediaState(FILE *json, const char *isoFilename, uint32_t totalDiscSectors)
2373 fprintf(json, " \"write_media_state\": {\n");
2374 fprintf(json, " \"media_class\": \"pressed_dvd_rom\",\n");
2375 fprintf(json, " \"writable\": false,\n");
2376 fprintf(json, " \"erasable\": false,\n");
2377 fprintf(json, " \"finalized\": true,\n");
2379 fprintf(json, " \"backing_image\": {\n");
2380 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename ? isoFilename : ""); fprintf(json, ",\n");
2381 fprintf(json, " \"sector_size\": 2048,\n");
2382 fprintf(json, " \"initial_sector_count\": %u,\n", totalDiscSectors);
2383 fprintf(json, " \"max_sector_count\": %u,\n", totalDiscSectors);
2384 fprintf(json, " \"growth_policy\": \"fixed_read_only\"\n");
2385 fprintf(json, " },\n");
2387 fprintf(json, " \"sessions\": [\n");
2388 fprintf(json, " {\n");
2389 fprintf(json, " \"session_number\": 1,\n");
2390 fprintf(json, " \"state\": \"closed\",\n");
2391 fprintf(json, " \"first_track_number\": 1,\n");
2392 fprintf(json, " \"last_track_number\": 1\n");
2393 fprintf(json, " }\n");
2394 fprintf(json, " ],\n");
2396 fprintf(json, " \"tracks\": [\n");
2397 fprintf(json, " {\n");
2398 fprintf(json, " \"track_number\": 1,\n");
2399 fprintf(json, " \"state\": \"complete\",\n");
2400 fprintf(json, " \"mode\": \"data\",\n");
2401 fprintf(json, " \"packet_or_track_mode\": \"pressed_read_only\",\n");
2402 fprintf(json, " \"start_lba\": 0,\n");
2403 fprintf(json, " \"next_writable_lba\": "); JsonWriteNull(json); fprintf(json, ",\n");
2404 fprintf(json, " \"free_blocks\": 0,\n");
2405 fprintf(json, " \"written_blocks\": %u\n", totalDiscSectors);
2406 fprintf(json, " }\n");
2407 fprintf(json, " ],\n");
2409 fprintf(json, " \"unwritten_read_policy\": \"not_applicable_read_only_media\",\n");
2410 fprintf(json, " \"flush_policy\": \"read_only_noop\"\n");
2411 fprintf(json, " },\n");
2414 static BOOL WriteXboxDvdMediaProfileFile(const char *isoFilename,
2415 const XboxDvdSidecarCapture *cap,
2416 uint32_t totalDiscSectors,
2418 uint32_t videoSectors,
2419 uint32_t gameSourceLba,
2420 uint32_t gameSectors,
2421 const char *isoSha1,
2423 const char *isoCrc32,
2424 const char *isoSha256,
2425 const char *adC0Path,
2426 const char *pfiPath,
2427 const char *dmiPath,
2428 BOOL payloadFilesPresent)
2430 char profilePath[MAX_PATH];
2431 char titleHint[256];
2436 if (!isoFilename || !cap)
2439 MakeSidecarPath(isoFilename, ".media.json", profilePath, sizeof(profilePath));
2440 ExtractMediaProfileNames(isoFilename, titleHint, sizeof(titleHint), mediaId, sizeof(mediaId));
2441 redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS);
2443 json = fopen(profilePath, "wb");
2447 fprintf(json, "{\n");
2448 fprintf(json, " \"format\": \"xdvd-media-profile\",\n");
2449 fprintf(json, " \"version\": 1,\n");
2450 fprintf(json, " \"media_id\": "); JsonWriteEscapedString(json, mediaId); fprintf(json, ",\n");
2451 fprintf(json, " \"title_hint\": "); JsonWriteEscapedString(json, titleHint); fprintf(json, ",\n");
2452 fprintf(json, " \"image\": {\n");
2453 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n");
2454 fprintf(json, " \"sector_size\": 2048,\n");
2455 fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors);
2456 fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2457 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2458 fprintf(json, " \"hashes\": {\n");
2459 fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n");
2460 fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n");
2461 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2462 fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n");
2463 fprintf(json, " }\n");
2464 fprintf(json, " },\n");
2467 uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors;
2469 fprintf(json, " \"layout\": {\n");
2470 fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n");
2471 fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U);
2472 fprintf(json, " \"layer_break_lba\": %u,\n", isDualLayer ? LAYER_BREAK : 0U);
2473 fprintf(json, " \"video_l0_start_lba\": 0,\n");
2474 fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors);
2475 fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
2476 fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
2477 fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
2478 fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
2479 fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
2480 fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2481 fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n");
2482 fprintf(json, " \"game_sector_count\": %u,\n", gameSectors);
2483 fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U);
2484 fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U);
2485 fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U);
2486 fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U);
2487 fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", videoSectors);
2488 fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2489 fprintf(json, " \"drive_reported_unlocked_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2490 fprintf(json, " \"reconstructed_output_sector_count\": %u\n", totalDiscSectors);
2491 fprintf(json, " },\n");
2494 fprintf(json, " \"reconstruction\": {\n");
2495 fprintf(json, " \"is_reconstructed_layout\": %s,\n", redumpStyle ? "true" : "false");
2496 fprintf(json, " \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "zero_fill_until_drive_can_read_filler" : "not_applicable"); fprintf(json, ",\n");
2497 fprintf(json, " \"filler_verified_from_disc\": false,\n");
2498 fprintf(json, " \"filler_byte_value\": %s,\n", redumpStyle ? "0" : "null");
2499 fprintf(json, " \"pending_hardware_capture\": %s,\n", redumpStyle ? "true" : "false");
2500 fprintf(json, " \"filler_ranges\": [\n");
2503 fprintf(json, " {\n");
2504 fprintf(json, " \"name\": \"pregame_padding\",\n");
2505 fprintf(json, " \"start_lba\": %u,\n", XGD1_VIDEO_L0_SECTORS);
2506 fprintf(json, " \"sector_count\": %u,\n", XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS);
2507 fprintf(json, " \"source\": \"synthetic_zero_fill\"\n");
2508 fprintf(json, " },\n");
2509 fprintf(json, " {\n");
2510 fprintf(json, " \"name\": \"postgame_padding\",\n");
2511 fprintf(json, " \"start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS);
2512 fprintf(json, " \"sector_count\": %u,\n", XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS));
2513 fprintf(json, " \"source\": \"synthetic_zero_fill\"\n");
2514 fprintf(json, " }\n");
2516 fprintf(json, " ],\n");
2517 fprintf(json, " \"note\": \"Padding/filler ranges are intentionally zero-filled placeholders until a drive/workflow capable of reading those regions is available.\"\n");
2518 fprintf(json, " },\n");
2520 fprintf(json, " \"dvd_structures\": {\n");
2521 fprintf(json, " \"ad_c0\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n");
2522 fprintf(json, " \"pfi\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n");
2523 fprintf(json, " \"dmi\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, "\n");
2524 fprintf(json, " },\n");
2526 fprintf(json, " \"non_lba_physical_metadata\": {\n");
2527 fprintf(json, " \"pfi_storage\": \"sidecar_bin\",\n");
2528 fprintf(json, " \"dmi_storage\": \"sidecar_bin\",\n");
2529 fprintf(json, " \"lead_in_storage\": \"not_in_iso_stream\",\n");
2530 fprintf(json, " \"lead_out_storage\": \"not_in_iso_stream\",\n");
2531 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");
2532 fprintf(json, " },\n");
2534 fprintf(json, " \"drive_state_observations\": {\n");
2535 fprintf(json, " \"locked\": {\n");
2536 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n");
2537 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2538 fprintf(json, " },\n");
2539 fprintf(json, " \"unlocked\": {\n");
2540 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n");
2541 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2542 fprintf(json, " }\n");
2543 fprintf(json, " },\n");
2545 WritePressedDvdRomWriteMediaState(json, isoFilename, totalDiscSectors);
2547 fprintf(json, " \"validation\": {\n");
2548 fprintf(json, " \"byte_count_matches_sector_count\": true,\n");
2549 fprintf(json, " \"payload_files_present\": %s,\n", payloadFilesPresent ? "true" : "false");
2550 fprintf(json, " \"warnings\": "); JsonWriteValidationWarnings(json, cap, payloadFilesPresent, redumpStyle); fprintf(json, "\n");
2551 fprintf(json, " }\n");
2552 fprintf(json, "}\n");
2555 printf("[META] Wrote media profile: %s\n", profilePath);
2559 static BOOL WriteXboxDvdSidecarFiles(const char *isoFilename,
2560 const XboxDvdSidecarCapture *cap,
2561 uint32_t totalDiscSectors,
2563 uint32_t videoSectors,
2564 uint32_t gameSourceLba,
2565 uint32_t gameSectors,
2566 const char *isoSha1,
2568 const char *isoCrc32,
2569 const char *isoSha256)
2571 char jsonPath[MAX_PATH];
2572 char adC0Path[MAX_PATH];
2573 char pfiPath[MAX_PATH];
2574 char dmiPath[MAX_PATH];
2577 BOOL payloadFilesPresent = FALSE;
2578 BOOL redumpStyle = FALSE;
2580 if (!isoFilename || !cap)
2583 MakeSidecarPath(isoFilename, ".xdvd.json", jsonPath, sizeof(jsonPath));
2584 MakeSidecarPath(isoFilename, ".ad_c0.bin", adC0Path, sizeof(adC0Path));
2585 MakeSidecarPath(isoFilename, ".pfi.bin", pfiPath, sizeof(pfiPath));
2586 MakeSidecarPath(isoFilename, ".dmi.bin", dmiPath, sizeof(dmiPath));
2588 if (cap->hasAdC0 && !WriteBinaryFile(adC0Path, cap->adC0, 0x664))
2590 if (cap->hasPfi && !WriteBinaryFile(pfiPath, cap->pfi, 2048))
2592 if (cap->hasDmi && !WriteBinaryFile(dmiPath, cap->dmi, 2048))
2595 payloadFilesPresent = cap->hasAdC0 && cap->hasPfi && cap->hasDmi && ok;
2596 redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS);
2598 json = fopen(jsonPath, "wb");
2602 fprintf(json, "{\n");
2603 fprintf(json, " \"format\": \"xdvd-sidecar\",\n");
2604 fprintf(json, " \"version\": 1,\n");
2605 fprintf(json, " \"image\": {\n");
2606 fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n");
2607 fprintf(json, " \"sector_size\": 2048,\n");
2608 fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors);
2609 fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2610 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2611 fprintf(json, " \"hashes\": {\n");
2612 fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n");
2613 fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n");
2614 fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n");
2615 fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n");
2616 fprintf(json, " },\n");
2618 uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors;
2620 fprintf(json, " \"layout\": {\n");
2621 fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n");
2622 fprintf(json, " \"legacy_contiguous_visible_start_lba\": 0,\n");
2623 fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors);
2624 fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2625 fprintf(json, " \"video_l0_start_lba\": 0,\n");
2626 fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors);
2627 fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U);
2628 fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U);
2629 fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba);
2630 fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba);
2631 fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors);
2632 fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U);
2633 fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n");
2634 fprintf(json, " \"game_sector_count\": %u,\n", gameSectors);
2635 fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U);
2636 fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U);
2637 fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U);
2638 fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U);
2639 fprintf(json, " \"layer_break_lba\": %u\n", LAYER_BREAK);
2640 fprintf(json, " }\n");
2642 fprintf(json, " },\n");
2644 fprintf(json, " \"disc\": {\n");
2645 fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U);
2646 fprintf(json, " \"reconstructed_output_sectors\": %u,\n", totalDiscSectors);
2647 fprintf(json, " \"reconstructed_output_byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL);
2648 fprintf(json, " \"drive_reported_unlocked_sectors\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2649 fprintf(json, " \"drive_reported_locked_sectors\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2650 fprintf(json, " \"unlocked_game_view_sectors\": %u\n", gameSectors);
2651 fprintf(json, " },\n");
2653 fprintf(json, " \"drive_states\": {\n");
2654 fprintf(json, " \"locked\": {\n");
2655 fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n");
2656 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n");
2657 fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0);
2658 fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n");
2659 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2660 fprintf(json, " },\n");
2661 fprintf(json, " \"unlocked\": {\n");
2662 fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n");
2663 fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n");
2664 fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0);
2665 fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n");
2666 fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n");
2667 fprintf(json, " }\n");
2668 fprintf(json, " },\n");
2670 fprintf(json, " \"scsi_responses\": [\n");
2671 fprintf(json, " {\n");
2672 fprintf(json, " \"name\": \"read_dvd_structure_xbox_control_block\",\n");
2673 fprintf(json, " \"cdb_hex\": \"AD00FF02FDFFFE00066400C0\",\n");
2674 fprintf(json, " \"data_in\": true,\n");
2675 fprintf(json, " \"data_len\": 1636,\n");
2676 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n");
2677 fprintf(json, " \"captured\": %s\n", cap->hasAdC0 ? "true" : "false");
2678 fprintf(json, " },\n");
2679 fprintf(json, " {\n");
2680 fprintf(json, " \"name\": \"read_dvd_structure_pfi\",\n");
2681 fprintf(json, " \"cdb_hex\": \"AD0000000000000008000000\",\n");
2682 fprintf(json, " \"data_in\": true,\n");
2683 fprintf(json, " \"data_len\": 2048,\n");
2684 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n");
2685 fprintf(json, " \"captured\": %s\n", cap->hasPfi ? "true" : "false");
2686 fprintf(json, " },\n");
2687 fprintf(json, " {\n");
2688 fprintf(json, " \"name\": \"read_dvd_structure_dmi\",\n");
2689 fprintf(json, " \"cdb_hex\": \"AD0000000000000408000000\",\n");
2690 fprintf(json, " \"data_in\": true,\n");
2691 fprintf(json, " \"data_len\": 2048,\n");
2692 fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, ",\n");
2693 fprintf(json, " \"captured\": %s\n", cap->hasDmi ? "true" : "false");
2694 fprintf(json, " }\n");
2695 fprintf(json, " ],\n");
2697 fprintf(json, " \"auth\": {\n");
2698 fprintf(json, " \"requires_media_transition\": true,\n");
2699 fprintf(json, " \"mode_page\": \"0x3E\",\n");
2700 fprintf(json, " \"challenge_table_source\": \"read_dvd_structure_xbox_control_block\",\n");
2701 fprintf(json, " \"challenge_table_response_offset\": 774,\n");
2702 fprintf(json, " \"challenge_table_hash_offset\": 1187,\n");
2703 fprintf(json, " \"challenge_table_hash_length\": 44\n");
2704 fprintf(json, " }\n");
2705 fprintf(json, "}\n");
2709 if (!WriteXboxDvdMediaProfileFile(isoFilename,
2723 payloadFilesPresent))
2726 printf("[WARN] Failed to write XDVD media profile.\n");
2729 printf("[META] Wrote XDVD sidecar: %s\n", jsonPath);
2730 if (cap->hasAdC0) printf("[META] Wrote Xbox control block: %s\n", adC0Path);
2731 if (cap->hasPfi) printf("[META] Wrote PFI: %s\n", pfiPath);
2732 if (cap->hasDmi) printf("[META] Wrote DMI: %s\n", dmiPath);
2737 void DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uint32_t totalDiscSectors, bool isDualLayer, bool EjectOnSuccess)
2739 HCRYPTPROV hProv = 0;
2740 HCRYPTHASH hHash = 0;
2741 FILE *outFile = NULL;
2743 char sha1String[41] = {0};
2744 char md5String[33] = {0};
2745 char crc32String[9] = {0};
2746 char fileSha1String[41] = {0};
2747 char fileSha256String[65] = {0};
2748 BOOL dumpOk = FALSE;
2749 XboxDvdSidecarCapture sidecarCapture;
2750 BOOL rawSidecarAvailable = FALSE;
2751 uint32_t rawVideoSectors = START_LBA_MAGIC;
2752 uint32_t rawGameSourceLba = 0xFFFFFFFFu;
2753 uint32_t rawGameSectors = 0;
2754 uint32_t rawTargetSectors = 0;
2755 unsigned long long expectedOutputBytes = 0ULL;
2756 const char *outputLabel = "Output";
2757 DWORD operationStartTick = GetTickCount();
2759 char operationTimeStr[12] = {0};
2761 memset(&sidecarCapture, 0, sizeof(sidecarCapture));
2763 if (totalDiscSectors == 0)
2764 totalDiscSectors = GetTotalSectors(hDevice);
2765 if (totalDiscSectors == 0)
2766 totalDiscSectors = REDUMP_SECTORS;
2768 rawTargetSectors = NormalizeRawIsoTargetSectors(totalDiscSectors, isDualLayer);
2770 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
2772 if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
2774 CryptReleaseContext(hProv, 0);
2778 EnsureDriveReady(hDevice, 30000);
2779 SetDriveSpeedMax(hDevice);
2781 if (xisoFormat == '1')
2783 DWORD bytesReturned;
2784 uint32_t gameSourceLba = 0xFFFFFFFFu;
2785 uint32_t videoSectors = START_LBA_MAGIC;
2786 uint32_t gameSectors = 0;
2787 BOOL lockedViewIsAlreadyXdfs = FALSE;
2789 outputLabel = "RAW ISO";
2790 expectedOutputBytes = (unsigned long long)rawTargetSectors * 2048ULL;
2792 if (rawTargetSectors != totalDiscSectors)
2794 printf("[RAW] Drive-reported unlocked sectors %u normalized to Redump-style output target %u.\n",
2795 totalDiscSectors, rawTargetSectors);
2798 printf("[RAW] Full-disc target: %u sectors (%llu bytes).\n",
2800 expectedOutputBytes);
2801 if (rawTargetSectors > LAYER_BREAK)
2803 printf("[RAW] Expected Redump/XGD1 layer break at output LBA %u.\n", LAYER_BREAK);
2805 printf("[RAW] Media-transition-preserving mode is enabled.\n");
2807 if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel))
2810 outFile = fopen(filename, "wb");
2813 printf("\n[FATAL] Could not create output file '%s'.\n", filename);
2815 printf(" errno: %d (%s)\n", errno, strerror(errno));
2819 // Raw mode must capture the visible/video view first. The caller normally reaches this
2820 // point after the drive has already been authenticated for metadata, so reset the drive
2821 // state with a real media transition before reading LBA 0.
2822 DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
2824 printf("[RAW] Cycling tray to restore locked/video view before dumping sector 0.\n");
2825 AutomateTrayCycle(hDevice);
2826 RefreshVolume(hDevice);
2827 SetDriveSpeedMax(hDevice);
2829 DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL);
2831 CaptureLockedSidecarState(hDevice, &sidecarCapture);
2833 lockedViewIsAlreadyXdfs = ProbeXboxVolumeAt(hDevice, 0x20);
2835 if (lockedViewIsAlreadyXdfs && rawTargetSectors > START_LBA_MAGIC)
2837 printf("\n[FATAL] Option 1 requires a full raw/video-front source image.\n");
2838 printf(" This source exposes XDFS at LBA 0x20 after the media-reset step,\n");
2839 printf(" which looks like an XISO/game-partition view, not a full raw disc view.\n");
2840 printf(" Use option 2 for this source, or mount/create a 7.29 GiB Redump-style option-1 ISO.\n");
2844 if (rawTargetSectors <= START_LBA_MAGIC)
2846 printf("[RAW] Non-retail-sized source; dumping visible LBA 0..%u directly.\n",
2847 rawTargetSectors - 1);
2848 rawVideoSectors = rawTargetSectors;
2849 rawGameSourceLba = 0;
2851 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
2852 rawSidecarAvailable = TRUE;
2853 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, rawTargetSectors, 0, "RAW", FALSE);
2855 else if (rawTargetSectors == XGD1_FULL_REDUMP_SECTORS)
2857 unsigned char *videoL1Buffer = NULL;
2858 uint32_t detectedXdfsLba;
2859 uint32_t pregamePaddingSectors = XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS;
2860 uint32_t postgamePaddingSectors = XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS);
2862 printf("[RAW] Using Original Xbox/XGD1 Redump-style 2048-byte-sector layout.\n");
2863 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",
2864 XGD1_VIDEO_L0_SECTORS,
2865 pregamePaddingSectors,
2867 postgamePaddingSectors,
2868 XGD1_VIDEO_L1_SECTORS);
2869 printf("[RAW] Note: filler/padding ranges are synthetic zero-fill placeholders until readable from hardware.\n");
2870 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");
2872 videoL1Buffer = (unsigned char *)VirtualAlloc(NULL, XGD1_VIDEO_L1_SECTORS * 2048U, MEM_COMMIT, PAGE_READWRITE);
2875 printf("\n[FATAL] Could not allocate VIDEO_L1 capture buffer.\n");
2879 // The locked-visible Xbox video ISO is 6,992 sectors. In the Redump-style
2880 // image, its L0 portion is placed at the beginning and its L1 tail is placed
2881 // at the end of the reconstructed image. Capture the L1 tail while the drive
2882 // is still in the locked/video state, before authenticating for the game view.
2883 if (!ReadSectorsToMemory(hDevice,
2884 XGD1_VIDEO_L0_SECTORS,
2885 XGD1_VIDEO_L1_SECTORS,
2889 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2893 printf("[RAW] Writing video L0 from locked source LBA 0..%u.\n", XGD1_VIDEO_L0_SECTORS - 1);
2894 if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, XGD1_VIDEO_L0_SECTORS, 0, "VIDEO-L0", FALSE))
2896 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2900 if (!WriteZeroSectorsOutput(outFile, hHash, pregamePaddingSectors, XGD1_VIDEO_L0_SECTORS, "PREGAME-PAD"))
2902 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2906 printf("[RAW] Re-applying full Xbox handshake for unlocked game/XISO view.\n");
2907 UnlockDrive(hDevice);
2908 RefreshVolume(hDevice);
2910 EnsureDriveReady(hDevice, 30000);
2911 SetDriveSpeedMax(hDevice);
2912 KickXboxMediaAuth(hDevice);
2913 RecoveryKick(hDevice, TRUE);
2914 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
2915 rawSidecarAvailable = TRUE;
2917 detectedXdfsLba = DetectXboxVolumeStart(hDevice);
2918 if (detectedXdfsLba == 0xFFFFFFFFu)
2920 printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC);
2921 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2924 if (detectedXdfsLba != 0x20)
2926 printf("[WARN] XDFS was detected at unlocked source LBA %u, not the expected XISO header LBA 32.\n", detectedXdfsLba);
2929 // Build the Redump-style XISO/game region using the same proven
2930 // convention as option 2: the first 32 sectors are synthetic XISO
2931 // lead-in/padding, and the real XDFS volume begins at source LBA 32.
2932 // Do not read unlocked source LBA 0..31 here; on the 8050L path those
2933 // LBAs are not the XDFS header sectors we want in the rebuilt image.
2934 gameSourceLba = XGD1_GAME_SOURCE_START_LBA;
2935 gameSectors = REDUMP_SECTORS;
2936 rawVideoSectors = XGD1_GAME_OUTPUT_START_LBA;
2937 rawGameSourceLba = gameSourceLba;
2938 rawGameSectors = gameSectors;
2940 printf("[RAW] Writing %u-sector XISO lead-in/padding at output LBA %u.\n",
2941 XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA);
2942 if (!WriteZeroSectorsOutput(outFile, hHash, XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA, "GAME-XISO-LEADIN"))
2944 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2948 printf("[RAW] Writing unlocked XDFS/game data from source LBA %u for %u sectors at output LBA %u.\n",
2949 XGD1_GAME_SOURCE_START_LBA,
2950 XGD1_GAME_SOURCE_SECTORS,
2951 XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS);
2952 if (!DumpSectorRangeWithRetry(hDevice,
2955 XGD1_GAME_SOURCE_START_LBA,
2956 XGD1_GAME_SOURCE_SECTORS,
2957 XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS,
2961 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2965 if (!WriteZeroSectorsOutput(outFile, hHash, postgamePaddingSectors, XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS, "POSTGAME-PAD"))
2967 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2971 if (!WriteMemorySectorsOutput(outFile, hHash, videoL1Buffer, XGD1_VIDEO_L1_SECTORS, XGD1_VIDEO_L1_OUTPUT_START_LBA, "VIDEO-L1"))
2973 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2977 VirtualFree(videoL1Buffer, 0, MEM_RELEASE);
2982 if (videoSectors > rawTargetSectors)
2983 videoSectors = rawTargetSectors;
2984 rawVideoSectors = videoSectors;
2986 printf("[RAW] Dumping contiguous visible/video area first: source LBA 0..%u.\n", videoSectors - 1);
2987 if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, videoSectors, 0, "VIDEO", FALSE))
2990 printf("[RAW] Re-applying full Xbox handshake after media transition for hidden game/data area.\n");
2991 UnlockDrive(hDevice);
2992 RefreshVolume(hDevice);
2994 EnsureDriveReady(hDevice, 30000);
2995 SetDriveSpeedMax(hDevice);
2996 KickXboxMediaAuth(hDevice);
2997 RecoveryKick(hDevice, TRUE);
2998 CaptureUnlockedSidecarState(hDevice, &sidecarCapture);
2999 rawSidecarAvailable = TRUE;
3001 gameSourceLba = DetectXboxVolumeStart(hDevice);
3002 if (gameSourceLba == 0xFFFFFFFFu)
3004 printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC);
3008 gameSectors = rawTargetSectors - videoSectors;
3009 rawGameSourceLba = gameSourceLba;
3010 rawGameSectors = gameSectors;
3011 printf("[RAW] Appending hidden game/data area from unlocked source LBA %u for %u sectors.\n",
3012 gameSourceLba, gameSectors);
3013 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, gameSourceLba, gameSectors, videoSectors, "GAME", TRUE);
3016 else if (xisoFormat == '2')
3018 uint32_t startLba = 0;
3019 uint32_t sectorsToRead = 0;
3020 unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE);
3021 XDFS_VOLUME_DESCRIPTOR *vol = NULL;
3022 uint32_t xgd2EndLba = 1913920;
3023 unsigned char zeroSector[2048] = {0};
3028 SetDriveSpeedMax(hDevice);
3029 KickXboxMediaAuth(hDevice);
3030 RecoveryKick(hDevice, TRUE);
3032 vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer;
3034 if (ScsiReadSectors(hDevice, START_LBA_MAGIC, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0)
3036 startLba = START_LBA_MAGIC;
3037 printf("[INFO] XGD2 Game Partition identified at LBA %u\n", startLba);
3039 else if (ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0)
3042 printf("[INFO] Standard Game Partition identified at LBA 32\n");
3046 printf("[ERROR] No Xbox Game Partition found. Disc may be non-standard.\n");
3047 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3051 if (isDualLayer || totalDiscSectors > 3300000)
3053 sectorsToRead = xgd2EndLba - startLba;
3054 printf("[INFO] Dual Layer disc detected. Calculating span across layers...\n");
3058 sectorsToRead = vol->VolumeSize / 2048;
3059 printf("[INFO] Single Layer disc detected. Using header-reported size.\n");
3062 outputLabel = "XISO";
3063 expectedOutputBytes = ((unsigned long long)sectorsToRead + 32ULL) * 2048ULL;
3065 printf("[SUCCESS] Final XISO target: %u sectors plus 32-sector lead-in (%llu bytes).\n",
3067 expectedOutputBytes);
3069 if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel))
3071 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3075 outFile = fopen(filename, "wb");
3078 printf("\n[FATAL] Could not create output file '%s'.\n", filename);
3080 printf(" errno: %d (%s)\n", errno, strerror(errno));
3081 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3085 VirtualFree(sectorBuffer, 0, MEM_RELEASE);
3087 printf("Writing 64KB XISO lead-in padding...\n");
3088 for (int p = 0; p < 32; p++)
3090 if (!WriteOutputBytes(outFile, zeroSector, 2048, "XISO-PAD", 0, (uint32_t)p))
3092 CryptHashData(hHash, zeroSector, 2048, 0);
3095 dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO", TRUE);
3099 printf("[ERROR] Unsupported dump mode '%c'.\n", xisoFormat);
3106 if (!FlushAndCommitOutput(outFile, outputLabel))
3112 if (fclose(outFile) != 0)
3114 printf("\n[FATAL] fclose failed for %s output.\n", outputLabel ? outputLabel : "dump");
3116 printf(" errno: %d (%s)\n", errno, strerror(errno));
3123 if (!VerifyOutputByteCount(filename, expectedOutputBytes, outputLabel))
3129 FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr);
3130 printf("\nDump/write phase complete at elapsed %s. Finalizing hashes...\n", operationTimeStr);
3132 if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
3134 HexBytesToString(rgbHash, cbHash, sha1String, sizeof(sha1String));
3137 if (CalculateFileHashes(filename, crc32String, sizeof(crc32String), md5String, sizeof(md5String), fileSha1String, sizeof(fileSha1String), fileSha256String, sizeof(fileSha256String)))
3139 if (fileSha1String[0] && sha1String[0] && strcmp(fileSha1String, sha1String) != 0)
3141 printf("\n[WARN] Streaming SHA-1 differs from file SHA-1. Using file SHA-1 in metadata.\n");
3142 printf(" Streaming SHA-1: %s\n", sha1String);
3143 printf(" File SHA-1: %s\n", fileSha1String);
3145 if (fileSha1String[0])
3146 strcpy(sha1String, fileSha1String);
3150 printf("\n[WARN] Could not calculate CRC32/MD5/SHA-1/SHA-256 from finalized output file.\n");
3153 if (xisoFormat == '1')
3155 printf("Final RAW ISO Sector Count: %u\n", rawTargetSectors);
3156 printf("Final RAW ISO Byte Count: %llu\n", (unsigned long long)rawTargetSectors * 2048ULL);
3157 printf("CRC32: %s\n", crc32String);
3158 printf("MD5: %s\n", md5String);
3159 printf("SHA-1: %s\n", sha1String);
3160 printf("SHA-256: %s\n", fileSha256String);
3161 PrintGamePartitionHash(filename);
3162 if (rawSidecarAvailable)
3164 if (!WriteXboxDvdSidecarFiles(filename,
3176 printf("[WARN] Failed to write one or more XDVD sidecar metadata files.\n");
3181 printf("[WARN] XDVD sidecar metadata was not captured for this raw dump.\n");
3186 printf("Final XISO Byte Count: see progress target above.\n");
3187 printf("CRC32: %s\n", crc32String);
3188 printf("MD5: %s\n", md5String);
3189 printf("SHA-1: %s\n", sha1String);
3190 printf("SHA-256: %s\n", fileSha256String);
3192 FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr);
3193 printf("\nOperation Complete! Total elapsed: %s\n", operationTimeStr);
3195 StopDriveUnit(hDevice);
3198 ControlTray(hDevice, TRUE);
3204 CryptDestroyHash(hHash);
3206 CryptReleaseContext(hProv, 0);