+#include "xbox_ref_bridge.h"
+
+#include <string.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#include <winioctl.h>
+#include <ntddscsi.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <io.h>
+
+#ifndef SCSI_IOCTL_DATA_UNSPECIFIED
+#define SCSI_IOCTL_DATA_UNSPECIFIED 2
+#endif
+
+#include "xbox_ref/utils.h"
+#include "xbox_ref/sha1.h"
+#include "xbox_ref/unlock.h"
+#include "xbox_ref/xbox_ref_log.h"
+
+#define printf xbox_ref_printf
+
+#define XGD1_RAW_ID_PROBE_SCHEMA_VERSION 2
+#define XGD1_RAW_ID_CACHE_BASE 0x80000000U
+#define XGD1_RAW_SECTOR_BYTES 2064U
+#define XGD1_LOGICAL_SECTOR_BYTES 2048U
+#define XGD1_CACHE_FILL_SECTORS 16U
+#define XGD1_RAW_ID_MAX_SAMPLES 16U
+
+typedef struct xgd1_sptd_with_sense_s
+{
+ SCSI_PASS_THROUGH_DIRECT sptd;
+ ULONG filler;
+ UCHAR sense[32];
+} xgd1_sptd_with_sense;
+
+typedef struct xgd1_cmd_result_s
+{
+ int ok;
+ DWORD win32_error;
+ UCHAR scsi_status;
+ UCHAR sense_key;
+ UCHAR asc;
+ UCHAR ascq;
+} xgd1_cmd_result;
+
+typedef struct xgd1_raw_id_sample_s
+{
+ uint32_t lba;
+ const char *label;
+ uint32_t cache_fill_lba;
+ uint32_t cache_index;
+ uint32_t cache_address;
+ xgd1_cmd_result cache_flush;
+ xgd1_cmd_result cache_fill;
+ xgd1_cmd_result memdump_header;
+ xgd1_cmd_result memdump_edc;
+ int raw_header_valid;
+ UCHAR sector_information;
+ uint32_t layer_number;
+ uint32_t physical_sector_number;
+ uint32_t normalized_physical_sector_number;
+ uint32_t output_lba;
+ uint32_t expected_layer_number;
+ uint32_t expected_physical_sector_number;
+ uint32_t expected_normalized_physical_sector_number;
+ int layer_matches_expected;
+ int psn_matches_expected;
+ int geometry_matches_expected;
+ char logical_sha1[41];
+ unsigned char logical[XGD1_LOGICAL_SECTOR_BYTES];
+ unsigned char raw_header[12];
+ unsigned char raw_edc[4];
+} xgd1_raw_id_sample;
+
+typedef struct xgd1_raw_id_state_s
+{
+ const char *name;
+ int locked_view;
+ uint32_t capacity;
+ int capacity_valid;
+ int state_verified;
+ uint32_t sample_count;
+ uint32_t geometry_validation_failures;
+ int geometry_verified;
+ xgd1_raw_id_sample samples[XGD1_RAW_ID_MAX_SAMPLES];
+} xgd1_raw_id_state;
+
+typedef struct xgd1_probe_point_s
+{
+ uint32_t lba;
+ const char *label;
+} xgd1_probe_point;
+
+static const xgd1_probe_point xgd1_locked_points[] = {
+ {0U, "locked_logical_start"},
+ {31U, "locked_early_lba_31"},
+ {32U, "locked_early_lba_32"},
+ {XGD1_VIDEO_L0_SECTORS - 1U, "video_l0_last"},
+ {XGD1_VIDEO_L0_SECTORS, "video_l1_first"},
+ {XGD1_VIDEO_TOTAL_SECTORS - 1U, "locked_video_last"}
+};
+
+static const xgd1_probe_point xgd1_unlocked_points[] = {
+ {0U, "unlocked_source_start"},
+ {XGD1_XISO_LEADIN_SECTORS - 1U, "unlocked_leadin_last"},
+ {XGD1_GAME_SOURCE_START_LBA, "xdfs_source_start"},
+ {XGD1_GAME_SOURCE_START_LBA + 1U, "xdfs_source_next"},
+ {(XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA) - 1U, "mapped_layer_break_source_minus_one"},
+ {XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA, "mapped_layer_break_source"},
+ {REDUMP_SECTORS - 1U, "unlocked_source_last"}
+};
+
+static char xgd1_drive_letter_from_device(const char *device)
+{
+ if (!device || !device[0]) return 0;
+ if (device[0] && device[1] == ':') return device[0];
+ if (device[0] == '\\' && device[1] == '\\' &&
+ device[2] == '.' && device[3] == '\\' &&
+ device[4] && device[5] == ':')
+ return device[4];
+ return device[0];
+}
+
+static int xgd1_capacity_is_locked(uint32_t sectors)
+{
+ return sectors == XGD1_VIDEO_TOTAL_SECTORS;
+}
+
+static int xgd1_capacity_is_game(uint32_t sectors)
+{
+ return sectors == REDUMP_SECTORS;
+}
+
+static uint32_t xgd1_refresh_ready_capacity(HANDLE hDevice)
+{
+ RefreshVolume(hDevice);
+ Sleep(2000);
+ EnsureDriveReady(hDevice, 30000);
+ return GetTotalSectors(hDevice);
+}
+
+static void xgd1_parse_sense(const UCHAR *sense,
+ UCHAR *sense_key,
+ UCHAR *asc,
+ UCHAR *ascq)
+{
+ UCHAR response;
+
+ if (sense_key) *sense_key = 0;
+ if (asc) *asc = 0;
+ if (ascq) *ascq = 0;
+ if (!sense)
+ return;
+
+ response = (UCHAR)(sense[0] & 0x7f);
+ if (response == 0x70 || response == 0x71)
+ {
+ if (sense_key) *sense_key = (UCHAR)(sense[2] & 0x0f);
+ if (asc) *asc = sense[12];
+ if (ascq) *ascq = sense[13];
+ }
+ else if (response == 0x72 || response == 0x73)
+ {
+ if (sense_key) *sense_key = (UCHAR)(sense[1] & 0x0f);
+ if (asc) *asc = sense[2];
+ if (ascq) *ascq = sense[3];
+ }
+}
+
+static void xgd1_sha1(const unsigned char *data,
+ uint32_t data_size,
+ char out_sha1[41])
+{
+ SHA1_CTX ctx;
+ unsigned char digest[20];
+ uint32_t i;
+
+ if (!out_sha1)
+ return;
+ out_sha1[0] = '\0';
+ if (!data || data_size == 0)
+ return;
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, data, data_size);
+ SHA1_Final(digest, &ctx);
+
+ for (i = 0; i < 20U; ++i)
+ sprintf(out_sha1 + (i * 2U), "%02x", digest[i]);
+ out_sha1[40] = '\0';
+}
+
+static void xgd1_cmd_result_init(xgd1_cmd_result *result)
+{
+ if (result)
+ memset(result, 0, sizeof(*result));
+}
+
+static void xgd1_execute_data_in(HANDLE hDevice,
+ const UCHAR *cdb,
+ UCHAR cdb_length,
+ unsigned char *buffer,
+ DWORD buffer_length,
+ DWORD timeout_seconds,
+ xgd1_cmd_result *result)
+{
+ xgd1_sptd_with_sense packet;
+ DWORD bytes_returned;
+ BOOL ioctl_ok;
+
+ xgd1_cmd_result_init(result);
+ if (!result || !cdb || !buffer || buffer_length == 0U)
+ return;
+
+ memset(buffer, 0, buffer_length);
+ memset(&packet, 0, sizeof(packet));
+
+ packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ packet.sptd.CdbLength = cdb_length;
+ packet.sptd.SenseInfoLength = sizeof(packet.sense);
+ packet.sptd.DataIn = SCSI_IOCTL_DATA_IN;
+ packet.sptd.DataTransferLength = buffer_length;
+ packet.sptd.TimeOutValue = timeout_seconds;
+ packet.sptd.DataBuffer = buffer;
+ packet.sptd.SenseInfoOffset = offsetof(xgd1_sptd_with_sense, sense);
+ memcpy(packet.sptd.Cdb, cdb, cdb_length);
+
+ bytes_returned = 0;
+ ioctl_ok = DeviceIoControl(hDevice,
+ IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &packet,
+ sizeof(packet),
+ &packet,
+ sizeof(packet),
+ &bytes_returned,
+ NULL);
+
+ result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
+ result->scsi_status = packet.sptd.ScsiStatus;
+ xgd1_parse_sense(packet.sense,
+ &result->sense_key,
+ &result->asc,
+ &result->ascq);
+ result->ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
+}
+
+static void xgd1_execute_no_data(HANDLE hDevice,
+ const UCHAR *cdb,
+ UCHAR cdb_length,
+ DWORD timeout_seconds,
+ xgd1_cmd_result *result)
+{
+ xgd1_sptd_with_sense packet;
+ DWORD bytes_returned;
+ BOOL ioctl_ok;
+
+ xgd1_cmd_result_init(result);
+ if (!result || !cdb)
+ return;
+
+ memset(&packet, 0, sizeof(packet));
+ packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
+ packet.sptd.CdbLength = cdb_length;
+ packet.sptd.SenseInfoLength = sizeof(packet.sense);
+ packet.sptd.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ packet.sptd.DataTransferLength = 0;
+ packet.sptd.TimeOutValue = timeout_seconds;
+ packet.sptd.DataBuffer = NULL;
+ packet.sptd.SenseInfoOffset = offsetof(xgd1_sptd_with_sense, sense);
+ memcpy(packet.sptd.Cdb, cdb, cdb_length);
+
+ bytes_returned = 0;
+ ioctl_ok = DeviceIoControl(hDevice,
+ IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &packet,
+ sizeof(packet),
+ &packet,
+ sizeof(packet),
+ &bytes_returned,
+ NULL);
+
+ result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
+ result->scsi_status = packet.sptd.ScsiStatus;
+ xgd1_parse_sense(packet.sense,
+ &result->sense_key,
+ &result->asc,
+ &result->ascq);
+ result->ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
+}
+
+static void xgd1_set_lba(UCHAR *cdb, uint32_t lba)
+{
+ cdb[2] = (UCHAR)((lba >> 24) & 0xff);
+ cdb[3] = (UCHAR)((lba >> 16) & 0xff);
+ cdb[4] = (UCHAR)((lba >> 8) & 0xff);
+ cdb[5] = (UCHAR)(lba & 0xff);
+}
+
+static void xgd1_build_memdump_cdb(UCHAR cdb[12],
+ uint32_t address,
+ uint32_t length)
+{
+ memset(cdb, 0, 12);
+ cdb[0] = 0xE7;
+ cdb[1] = 0x48;
+ cdb[2] = 0x49;
+ cdb[3] = 0x54;
+ cdb[4] = 0x01;
+ cdb[6] = (UCHAR)((address >> 24) & 0xff);
+ cdb[7] = (UCHAR)((address >> 16) & 0xff);
+ cdb[8] = (UCHAR)((address >> 8) & 0xff);
+ cdb[9] = (UCHAR)(address & 0xff);
+ cdb[10] = (UCHAR)((length >> 8) & 0xff);
+ cdb[11] = (UCHAR)(length & 0xff);
+}
+
+static void xgd1_set_expected_geometry(int locked_view,
+ xgd1_raw_id_sample *sample)
+{
+ uint32_t output_lba;
+ uint32_t layer_zero_last_psn;
+ uint32_t normalized_psn;
+ uint32_t expected_normalized_psn;
+ uint32_t expected_layer;
+ uint32_t expected_raw_psn;
+
+ if (!sample)
+ return;
+
+ if (locked_view)
+ {
+ if (sample->lba < XGD1_VIDEO_L0_SECTORS)
+ output_lba = sample->lba;
+ else
+ output_lba = XGD1_VIDEO_L1_OUTPUT_START_LBA +
+ (sample->lba - XGD1_VIDEO_L0_SECTORS);
+ }
+ else
+ {
+ output_lba = XGD1_GAME_OUTPUT_START_LBA + sample->lba;
+ }
+
+ layer_zero_last_psn = 0x30000U + XGD1_REDUMP_LAYER_BREAK_LBA - 1U;
+ if (output_lba < XGD1_REDUMP_LAYER_BREAK_LBA)
+ {
+ expected_layer = 0U;
+ expected_normalized_psn = 0x30000U + output_lba;
+ expected_raw_psn = expected_normalized_psn;
+ }
+ else
+ {
+ expected_layer = 1U;
+ expected_normalized_psn = layer_zero_last_psn -
+ (output_lba - XGD1_REDUMP_LAYER_BREAK_LBA);
+ expected_raw_psn = (~expected_normalized_psn) & 0x00ffffffU;
+ }
+
+ normalized_psn = (sample->layer_number != 0U)
+ ? ((~sample->physical_sector_number) & 0x00ffffffU)
+ : sample->physical_sector_number;
+
+ sample->output_lba = output_lba;
+ sample->normalized_physical_sector_number = normalized_psn;
+ sample->expected_layer_number = expected_layer;
+ sample->expected_physical_sector_number = expected_raw_psn;
+ sample->expected_normalized_physical_sector_number = expected_normalized_psn;
+ sample->layer_matches_expected =
+ (sample->layer_number == expected_layer) ? 1 : 0;
+ sample->psn_matches_expected =
+ (sample->physical_sector_number == expected_raw_psn) ? 1 : 0;
+ sample->geometry_matches_expected =
+ (sample->layer_matches_expected && sample->psn_matches_expected) ? 1 : 0;
+}
+
+
+static void xgd1_capture_sample(HANDLE hDevice,
+ uint32_t capacity,
+ int locked_view,
+ const xgd1_probe_point *point,
+ xgd1_raw_id_sample *sample)
+{
+ unsigned char *read_buffer;
+ UCHAR flush_cdb[12];
+ UCHAR read_cdb[12];
+ UCHAR e7_cdb[12];
+ uint32_t fill_lba;
+ uint32_t index;
+ uint32_t address;
+
+ if (!sample || !point)
+ return;
+
+ memset(sample, 0, sizeof(*sample));
+ sample->lba = point->lba;
+ sample->label = point->label;
+
+ if (capacity < XGD1_CACHE_FILL_SECTORS || point->lba >= capacity)
+ {
+ sample->cache_flush.win32_error = ERROR_INVALID_PARAMETER;
+ return;
+ }
+
+ fill_lba = point->lba & ~(XGD1_CACHE_FILL_SECTORS - 1U);
+ if (fill_lba > capacity - XGD1_CACHE_FILL_SECTORS)
+ fill_lba = capacity - XGD1_CACHE_FILL_SECTORS;
+ index = point->lba - fill_lba;
+ address = XGD1_RAW_ID_CACHE_BASE + index * XGD1_RAW_SECTOR_BYTES;
+
+ sample->cache_fill_lba = fill_lba;
+ sample->cache_index = index;
+ sample->cache_address = address;
+
+ memset(flush_cdb, 0, sizeof(flush_cdb));
+ flush_cdb[0] = 0xA8;
+ flush_cdb[1] = 0x08;
+ xgd1_set_lba(flush_cdb, fill_lba);
+ xgd1_execute_no_data(hDevice,
+ flush_cdb,
+ 12,
+ 15,
+ &sample->cache_flush);
+ if (!sample->cache_flush.ok)
+ return;
+
+ read_buffer = (unsigned char *)VirtualAlloc(
+ NULL,
+ XGD1_CACHE_FILL_SECTORS * XGD1_LOGICAL_SECTOR_BYTES,
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_READWRITE);
+ if (!read_buffer)
+ {
+ sample->cache_fill.win32_error = ERROR_NOT_ENOUGH_MEMORY;
+ return;
+ }
+
+ memset(read_cdb, 0, sizeof(read_cdb));
+ read_cdb[0] = 0xA8;
+ xgd1_set_lba(read_cdb, fill_lba);
+ read_cdb[9] = (UCHAR)XGD1_CACHE_FILL_SECTORS;
+ read_cdb[10] = 0x80;
+
+ xgd1_execute_data_in(hDevice,
+ read_cdb,
+ 12,
+ read_buffer,
+ XGD1_CACHE_FILL_SECTORS * XGD1_LOGICAL_SECTOR_BYTES,
+ 15,
+ &sample->cache_fill);
+
+ if (sample->cache_fill.ok)
+ {
+ memcpy(sample->logical,
+ read_buffer + index * XGD1_LOGICAL_SECTOR_BYTES,
+ XGD1_LOGICAL_SECTOR_BYTES);
+ xgd1_sha1(sample->logical,
+ XGD1_LOGICAL_SECTOR_BYTES,
+ sample->logical_sha1);
+
+ xgd1_build_memdump_cdb(e7_cdb, address, 12U);
+ xgd1_execute_data_in(hDevice,
+ e7_cdb,
+ 12,
+ sample->raw_header,
+ 12U,
+ 15,
+ &sample->memdump_header);
+
+ xgd1_build_memdump_cdb(e7_cdb, address + 2060U, 4U);
+ xgd1_execute_data_in(hDevice,
+ e7_cdb,
+ 12,
+ sample->raw_edc,
+ 4U,
+ 15,
+ &sample->memdump_edc);
+
+ if (sample->memdump_header.ok && sample->memdump_edc.ok)
+ {
+ sample->raw_header_valid = 1;
+ sample->sector_information = sample->raw_header[0];
+ sample->layer_number = (uint32_t)(sample->sector_information & 1U);
+ sample->physical_sector_number =
+ ((uint32_t)sample->raw_header[1] << 16) |
+ ((uint32_t)sample->raw_header[2] << 8) |
+ (uint32_t)sample->raw_header[3];
+ xgd1_set_expected_geometry(locked_view, sample);
+ }
+ }
+
+ VirtualFree(read_buffer, 0, MEM_RELEASE);
+}
+
+static void xgd1_capture_state(HANDLE hDevice,
+ xgd1_raw_id_state *state,
+ const xgd1_probe_point *points,
+ uint32_t point_count)
+{
+ uint32_t i;
+
+ if (!state || !points || !state->state_verified)
+ return;
+ if (point_count > XGD1_RAW_ID_MAX_SAMPLES)
+ point_count = XGD1_RAW_ID_MAX_SAMPLES;
+
+ state->sample_count = point_count;
+ state->geometry_validation_failures = 0U;
+ for (i = 0; i < point_count; ++i)
+ {
+ printf("[XGD1-RAW-ID] %s: cache-flush, aligned READ(12), and HIT 0xE7 header/EDC capture for LBA %u (%s).\n",
+ state->name ? state->name : "state",
+ points[i].lba,
+ points[i].label ? points[i].label : "unlabeled");
+ xgd1_capture_sample(hDevice,
+ state->capacity,
+ state->locked_view,
+ &points[i],
+ &state->samples[i]);
+ if (!state->samples[i].cache_flush.ok ||
+ !state->samples[i].cache_fill.ok ||
+ !state->samples[i].raw_header_valid ||
+ !state->samples[i].geometry_matches_expected)
+ state->geometry_validation_failures++;
+ }
+
+ state->geometry_verified =
+ (state->state_verified &&
+ state->sample_count > 0U &&
+ state->geometry_validation_failures == 0U) ? 1 : 0;
+}
+
+static void xgd1_json_string(FILE *f, const char *s)
+{
+ const unsigned char *p;
+
+ fputc('"', f);
+ if (s)
+ {
+ for (p = (const unsigned char *)s; *p; ++p)
+ {
+ switch (*p)
+ {
+ case '\\': fputs("\\\\", f); break;
+ case '"': fputs("\\\"", f); break;
+ case '\b': fputs("\\b", f); break;
+ case '\f': fputs("\\f", f); break;
+ case '\n': fputs("\\n", f); break;
+ case '\r': fputs("\\r", f); break;
+ case '\t': fputs("\\t", f); break;
+ default:
+ if (*p < 0x20)
+ fprintf(f, "\\u%04x", (unsigned int)*p);
+ else
+ fputc(*p, f);
+ break;
+ }
+ }
+ }
+ fputc('"', f);
+}
+
+static void xgd1_json_hex(FILE *f,
+ const unsigned char *data,
+ uint32_t size)
+{
+ uint32_t i;
+
+ fputc('"', f);
+ for (i = 0; i < size; ++i)
+ fprintf(f, "%02x", data[i]);
+ fputc('"', f);
+}
+
+static void xgd1_write_cmd_result(FILE *f,
+ const xgd1_cmd_result *r)
+{
+ fprintf(f, "{\"ok\":%s,\"win32_error\":%lu,\"scsi_status\":%u,"
+ "\"sense_key\":%u,\"asc\":%u,\"ascq\":%u}",
+ (r && r->ok) ? "true" : "false",
+ (unsigned long)(r ? r->win32_error : 0),
+ (unsigned int)(r ? r->scsi_status : 0),
+ (unsigned int)(r ? r->sense_key : 0),
+ (unsigned int)(r ? r->asc : 0),
+ (unsigned int)(r ? r->ascq : 0));
+}
+
+static void xgd1_write_sample(FILE *f,
+ const xgd1_raw_id_sample *s,
+ int comma)
+{
+ fprintf(f, " {\n");
+ fprintf(f, " \"lba\": %u,\n", s->lba);
+ fprintf(f, " \"label\": ");
+ xgd1_json_string(f, s->label ? s->label : "");
+ fprintf(f, ",\n");
+ fprintf(f, " \"cache_fill_lba\": %u,\n", s->cache_fill_lba);
+ fprintf(f, " \"cache_index\": %u,\n", s->cache_index);
+ fprintf(f, " \"cache_address\": \"0x%08x\",\n", s->cache_address);
+ fprintf(f, " \"cache_flush\": ");
+ xgd1_write_cmd_result(f, &s->cache_flush);
+ fprintf(f, ",\n");
+ fprintf(f, " \"cache_fill\": ");
+ xgd1_write_cmd_result(f, &s->cache_fill);
+ fprintf(f, ",\n");
+ fprintf(f, " \"memdump_header\": ");
+ xgd1_write_cmd_result(f, &s->memdump_header);
+ fprintf(f, ",\n");
+ fprintf(f, " \"memdump_edc\": ");
+ xgd1_write_cmd_result(f, &s->memdump_edc);
+ fprintf(f, ",\n");
+ fprintf(f, " \"raw_header_valid\": %s,\n",
+ s->raw_header_valid ? "true" : "false");
+ if (s->raw_header_valid)
+ {
+ fprintf(f, " \"sector_information\": %u,\n",
+ (unsigned int)s->sector_information);
+ fprintf(f, " \"layer_number\": %u,\n", s->layer_number);
+ fprintf(f, " \"physical_sector_number\": %u,\n",
+ s->physical_sector_number);
+ fprintf(f, " \"normalized_physical_sector_number\": %u,\n",
+ s->normalized_physical_sector_number);
+ fprintf(f, " \"output_lba\": %u,\n", s->output_lba);
+ fprintf(f, " \"expected_layer_number\": %u,\n",
+ s->expected_layer_number);
+ fprintf(f, " \"expected_physical_sector_number\": %u,\n",
+ s->expected_physical_sector_number);
+ fprintf(f, " \"expected_normalized_physical_sector_number\": %u,\n",
+ s->expected_normalized_physical_sector_number);
+ fprintf(f, " \"layer_matches_expected\": %s,\n",
+ s->layer_matches_expected ? "true" : "false");
+ fprintf(f, " \"psn_matches_expected\": %s,\n",
+ s->psn_matches_expected ? "true" : "false");
+ fprintf(f, " \"geometry_matches_expected\": %s,\n",
+ s->geometry_matches_expected ? "true" : "false");
+ fprintf(f, " \"logical_sha1\": \"%s\",\n", s->logical_sha1);
+ fprintf(f, " \"raw_header_hex\": ");
+ xgd1_json_hex(f, s->raw_header, 12U);
+ fprintf(f, ",\n");
+ fprintf(f, " \"raw_edc_hex\": ");
+ xgd1_json_hex(f, s->raw_edc, 4U);
+ fprintf(f, ",\n");
+ fprintf(f, " \"logical_data_hex\": ");
+ xgd1_json_hex(f, s->logical, XGD1_LOGICAL_SECTOR_BYTES);
+ fprintf(f, "\n");
+ }
+ else
+ {
+ fprintf(f, " \"sector_information\": null,\n");
+ fprintf(f, " \"layer_number\": null,\n");
+ fprintf(f, " \"physical_sector_number\": null,\n");
+ fprintf(f, " \"normalized_physical_sector_number\": null,\n");
+ fprintf(f, " \"output_lba\": null,\n");
+ fprintf(f, " \"expected_layer_number\": null,\n");
+ fprintf(f, " \"expected_physical_sector_number\": null,\n");
+ fprintf(f, " \"expected_normalized_physical_sector_number\": null,\n");
+ fprintf(f, " \"layer_matches_expected\": false,\n");
+ fprintf(f, " \"psn_matches_expected\": false,\n");
+ fprintf(f, " \"geometry_matches_expected\": false,\n");
+ fprintf(f, " \"logical_sha1\": null,\n");
+ fprintf(f, " \"raw_header_hex\": \"\",\n");
+ fprintf(f, " \"raw_edc_hex\": \"\",\n");
+ fprintf(f, " \"logical_data_hex\": \"\"\n");
+ }
+ fprintf(f, " }%s\n", comma ? "," : "");
+}
+
+static void xgd1_write_state(FILE *f,
+ const xgd1_raw_id_state *state,
+ int comma)
+{
+ uint32_t i;
+
+ fprintf(f, " {\n");
+ fprintf(f, " \"name\": ");
+ xgd1_json_string(f, state->name ? state->name : "");
+ fprintf(f, ",\n");
+ fprintf(f, " \"capacity_valid\": %s,\n",
+ state->capacity_valid ? "true" : "false");
+ fprintf(f, " \"capacity_sectors\": %u,\n", state->capacity);
+ fprintf(f, " \"state_verified\": %s,\n",
+ state->state_verified ? "true" : "false");
+ fprintf(f, " \"geometry_validation_failures\": %u,\n",
+ state->geometry_validation_failures);
+ fprintf(f, " \"geometry_verified\": %s,\n",
+ state->geometry_verified ? "true" : "false");
+ fprintf(f, " \"samples\": [\n");
+ for (i = 0; i < state->sample_count; ++i)
+ xgd1_write_sample(f,
+ &state->samples[i],
+ (i + 1U < state->sample_count));
+ fprintf(f, " ]\n");
+ fprintf(f, " }%s\n", comma ? "," : "");
+}
+
+static int xgd1_write_report(const char *report_path,
+ const char *device,
+ uint32_t entry_capacity,
+ int entry_game_view,
+ int volume_lock_ever,
+ const xgd1_raw_id_state *locked_state,
+ const xgd1_raw_id_state *unlocked_state,
+ int restore_attempted,
+ uint32_t restored_capacity,
+ int restore_verified)
+{
+ const char *final_path;
+ char tmp_path[MAX_PATH * 4];
+ FILE *f;
+ int fd;
+ BOOL moved;
+ uint32_t validation_failures;
+ int geometry_resolved;
+
+ final_path = (report_path && report_path[0])
+ ? report_path
+ : "xgd1_raw_id_probe.json";
+
+ validation_failures =
+ (locked_state ? locked_state->geometry_validation_failures : 0U) +
+ (unlocked_state ? unlocked_state->geometry_validation_failures : 0U);
+ geometry_resolved =
+ (locked_state && locked_state->geometry_verified &&
+ unlocked_state && unlocked_state->geometry_verified) ? 1 : 0;
+
+ if (strlen(final_path) + 32U >= sizeof(tmp_path))
+ {
+ printf("[XGD1-RAW-ID] Fatal: report path is too long.\n");
+ return 0;
+ }
+
+ sprintf(tmp_path,
+ "%s.tmp.%lu.%lu",
+ final_path,
+ (unsigned long)GetCurrentProcessId(),
+ (unsigned long)GetTickCount());
+
+ f = fopen(tmp_path, "wb");
+ if (!f)
+ {
+ printf("[XGD1-RAW-ID] Fatal: could not create temporary report %s.\n",
+ tmp_path);
+ return 0;
+ }
+
+ fprintf(f, "{\n");
+ fprintf(f, " \"schema_version\": %u,\n",
+ XGD1_RAW_ID_PROBE_SCHEMA_VERSION);
+ fprintf(f, " \"producer\": \"friidump-0.5.3.13\",\n");
+ fprintf(f, " \"probe\": \"original_xbox_xgd1_cache_aligned_raw_id_geometry\",\n");
+ fprintf(f, " \"device\": ");
+ xgd1_json_string(f, device ? device : "");
+ fprintf(f, ",\n");
+ fprintf(f, " \"cache\": {\n");
+ fprintf(f, " \"memdump_command\": \"E7 48 49 54 01\",\n");
+ fprintf(f, " \"base_address\": \"0x%08x\",\n",
+ XGD1_RAW_ID_CACHE_BASE);
+ fprintf(f, " \"raw_sector_stride\": %u,\n",
+ XGD1_RAW_SECTOR_BYTES);
+ fprintf(f, " \"cache_flush_command\": \"READ(12), byte1=0x08, zero transfer length\",\n");
+ fprintf(f, " \"cache_fill_command\": \"READ(12) streaming, 16 sectors, block-aligned LBA\",\n");
+ fprintf(f, " \"memdump_regions\": \"12-byte ID/IED/CPR_MAI header plus 4-byte EDC only\",\n");
+ fprintf(f, " \"raw_user_data_compared_to_logical\": false,\n");
+ fprintf(f, " \"raw_user_data_note\": \"The proven Method 8 path replaces the drive-cache user field with READ(12) data before unscrambling; equality is not expected and is not a geometry test.\"\n");
+ fprintf(f, " },\n");
+ fprintf(f, " \"geometry_model\": {\n");
+ fprintf(f, " \"dvd_start_psn\": 196608,\n");
+ fprintf(f, " \"game_output_start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA);
+ fprintf(f, " \"layer_break_lba\": %u,\n", XGD1_REDUMP_LAYER_BREAK_LBA);
+ fprintf(f, " \"layer_zero_last_normalized_psn\": %u,\n",
+ 0x30000U + XGD1_REDUMP_LAYER_BREAK_LBA - 1U);
+ fprintf(f, " \"layer_one_raw_psn_is_24bit_complement\": true\n");
+ fprintf(f, " },\n");
+ fprintf(f, " \"safety\": {\n");
+ fprintf(f, " \"read_only_sector_commands\": true,\n");
+ fprintf(f, " \"authentication_handshake_used\": true,\n");
+ fprintf(f, " \"tray_cycle_used\": true,\n");
+ fprintf(f, " \"firmware_write_used\": false,\n");
+ fprintf(f, " \"flash_command_used\": false\n");
+ fprintf(f, " },\n");
+ fprintf(f, " \"entry\": {\n");
+ fprintf(f, " \"capacity_sectors\": %u,\n", entry_capacity);
+ fprintf(f, " \"game_view\": %s,\n",
+ entry_game_view ? "true" : "false");
+ fprintf(f, " \"classification\": \"%s\"\n",
+ entry_game_view ? "unlocked_game" : "locked_video_or_unknown");
+ fprintf(f, " },\n");
+ fprintf(f, " \"volume_lock_acquired_at_least_once\": %s,\n",
+ volume_lock_ever ? "true" : "false");
+ fprintf(f, " \"required_states_verified\": %s,\n",
+ (locked_state && locked_state->state_verified &&
+ unlocked_state && unlocked_state->state_verified)
+ ? "true" : "false");
+ fprintf(f, " \"geometry_validation_failures\": %u,\n",
+ validation_failures);
+ fprintf(f, " \"physical_geometry_resolved\": %s,\n",
+ geometry_resolved ? "true" : "false");
+ fprintf(f, " \"states\": [\n");
+ xgd1_write_state(f, locked_state, 1);
+ xgd1_write_state(f, unlocked_state, 0);
+ fprintf(f, " ],\n");
+ fprintf(f, " \"restoration\": {\n");
+ fprintf(f, " \"attempted\": %s,\n",
+ restore_attempted ? "true" : "false");
+ fprintf(f, " \"capacity_sectors\": %u,\n", restored_capacity);
+ fprintf(f, " \"verified\": %s\n",
+ restore_verified ? "true" : "false");
+ fprintf(f, " },\n");
+ fprintf(f, " \"interpretation_boundary\": {\n");
+ fprintf(f, " \"physical_geometry_resolved\": %s,\n",
+ geometry_resolved ? "true" : "false");
+ fprintf(f, " \"pregame_filler_content_resolved\": false,\n");
+ fprintf(f, " \"postgame_filler_content_resolved\": false,\n");
+ fprintf(f, " \"note\": \"A complete cache-aligned raw-ID match can validate the logical-to-physical placement used by the current reconstruction. It does not make inaccessible filler sectors readable and does not establish exact filler bytes.\"\n");
+ fprintf(f, " }\n");
+ fprintf(f, "}\n");
+
+ if (fflush(f) != 0)
+ {
+ fclose(f);
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ fd = _fileno(f);
+ if (fd < 0 || _commit(fd) != 0)
+ {
+ fclose(f);
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ if (fclose(f) != 0)
+ {
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ moved = MoveFileExA(tmp_path,
+ final_path,
+ MOVEFILE_REPLACE_EXISTING |
+ MOVEFILE_WRITE_THROUGH);
+ if (!moved)
+ {
+ printf("[XGD1-RAW-ID] Fatal: could not atomically publish report %s (error %lu).\n",
+ final_path,
+ (unsigned long)GetLastError());
+ DeleteFileA(tmp_path);
+ return 0;
+ }
+
+ printf("[XGD1-RAW-ID] Atomic report written: %s\n", final_path);
+ return 1;
+}
+
+static int xgd1_raw_id_probe_core(HANDLE hDevice,
+ const char *device,
+ const char *report_path,
+ BOOL owns_handle)
+{
+ xgd1_raw_id_state locked_state;
+ xgd1_raw_id_state unlocked_state;
+ uint32_t entry_capacity;
+ uint32_t restored_capacity;
+ int entry_game_view;
+ int volume_lock_acquired;
+ int volume_lock_ever;
+ int restore_attempted;
+ int restore_verified;
+ int states_verified;
+ DWORD bytes_returned;
+ int report_ok;
+
+ memset(&locked_state, 0, sizeof(locked_state));
+ memset(&unlocked_state, 0, sizeof(unlocked_state));
+ locked_state.name = "locked_video";
+ locked_state.locked_view = 1;
+ unlocked_state.name = "unlocked_game";
+ unlocked_state.locked_view = 0;
+ entry_capacity = 0;
+ restored_capacity = 0;
+ entry_game_view = 0;
+ volume_lock_acquired = 0;
+ volume_lock_ever = 0;
+ restore_attempted = 0;
+ restore_verified = 0;
+ states_verified = 0;
+ bytes_returned = 0;
+
+ xbox_ref_log_open_for_target(report_path,
+ xgd1_drive_letter_from_device(device));
+ printf("[XGD1-RAW-ID] Starting cache-aligned raw-sector ID geometry probe.\n");
+ printf("[XGD1-RAW-ID] Requires modified GDR-8050L firmware with HIT 0xE7 memdump support.\n");
+ printf("[XGD1-RAW-ID] No ISO or firmware-write command will be issued.\n");
+
+ if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
+ {
+ printf("[XGD1-RAW-ID] Fatal: invalid drive handle.\n");
+ if (owns_handle)
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ if (!EnsureDriveReady(hDevice, 30000))
+ {
+ printf("[XGD1-RAW-ID] Fatal: media did not become ready.\n");
+ if (owns_handle)
+ xbox_ref_log_close();
+ return 1;
+ }
+
+ entry_capacity = GetTotalSectors(hDevice);
+ entry_game_view = xgd1_capacity_is_game(entry_capacity) ? 1 : 0;
+ printf("[XGD1-RAW-ID] Entry READ CAPACITY: %u sectors (%s).\n",
+ entry_capacity,
+ entry_game_view ? "game view" : "locked/video or unknown view");
+
+ DeviceIoControl(hDevice,
+ FSCTL_UNLOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL);
+
+ printf("[XGD1-RAW-ID] Cycling tray to establish locked/video view.\n");
+ AutomateTrayCycle(hDevice);
+ locked_state.capacity = xgd1_refresh_ready_capacity(hDevice);
+ locked_state.capacity_valid = locked_state.capacity != 0U;
+ locked_state.state_verified =
+ (locked_state.capacity_valid &&
+ xgd1_capacity_is_locked(locked_state.capacity)) ? 1 : 0;
+ printf("[XGD1-RAW-ID] Locked/video READ CAPACITY: %u sectors; verified=%s.\n",
+ locked_state.capacity,
+ locked_state.state_verified ? "yes" : "no");
+
+ if (DeviceIoControl(hDevice,
+ FSCTL_LOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL))
+ {
+ volume_lock_acquired = 1;
+ volume_lock_ever = 1;
+ printf("[XGD1-RAW-ID] Windows volume lock acquired.\n");
+ }
+ else
+ {
+ printf("[XGD1-RAW-ID][WARN] Windows volume lock was not acquired.\n");
+ }
+
+ SetDriveSpeedMax(hDevice);
+ xgd1_capture_state(hDevice,
+ &locked_state,
+ xgd1_locked_points,
+ (uint32_t)(sizeof(xgd1_locked_points) /
+ sizeof(xgd1_locked_points[0])));
+
+ printf("[XGD1-RAW-ID] Applying the full Xbox handshake to establish game view.\n");
+ UnlockDrive(hDevice);
+ unlocked_state.capacity = xgd1_refresh_ready_capacity(hDevice);
+ unlocked_state.capacity_valid = unlocked_state.capacity != 0U;
+ unlocked_state.state_verified =
+ (unlocked_state.capacity_valid &&
+ xgd1_capacity_is_game(unlocked_state.capacity)) ? 1 : 0;
+ printf("[XGD1-RAW-ID] Unlocked/game READ CAPACITY: %u sectors; verified=%s.\n",
+ unlocked_state.capacity,
+ unlocked_state.state_verified ? "yes" : "no");
+
+ xgd1_capture_state(hDevice,
+ &unlocked_state,
+ xgd1_unlocked_points,
+ (uint32_t)(sizeof(xgd1_unlocked_points) /
+ sizeof(xgd1_unlocked_points[0])));
+
+ if (volume_lock_acquired)
+ {
+ DeviceIoControl(hDevice,
+ FSCTL_UNLOCK_VOLUME,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &bytes_returned,
+ NULL);
+ volume_lock_acquired = 0;
+ }
+
+ if (!entry_game_view && entry_capacity != 0U)
+ {
+ restore_attempted = 1;
+ printf("[XGD1-RAW-ID] Restoring entry locked/video state with a final tray cycle.\n");
+ AutomateTrayCycle(hDevice);
+ restored_capacity = xgd1_refresh_ready_capacity(hDevice);
+ restore_verified =
+ (restored_capacity != 0U &&
+ xgd1_capacity_is_locked(restored_capacity)) ? 1 : 0;
+ }
+ else
+ {
+ restored_capacity = GetTotalSectors(hDevice);
+ restore_verified =
+ (entry_game_view &&
+ xgd1_capacity_is_game(restored_capacity)) ? 1 : 0;
+ }
+
+ states_verified =
+ (locked_state.state_verified &&
+ unlocked_state.state_verified) ? 1 : 0;
+
+ report_ok = xgd1_write_report(report_path,
+ device,
+ entry_capacity,
+ entry_game_view,
+ volume_lock_ever,
+ &locked_state,
+ &unlocked_state,
+ restore_attempted,
+ restored_capacity,
+ restore_verified);
+
+ if (owns_handle)
+ {
+ printf("[XGD1-RAW-ID] Issuing STOP UNIT / spin-down after probe... ");
+ printf("%s\n", StopDriveUnit(hDevice) ? "OK" : "FAILED");
+ CloseDrive(hDevice);
+ xbox_ref_log_close();
+ }
+
+ if (!report_ok)
+ return 1;
+ if (!states_verified)
+ {
+ printf("[XGD1-RAW-ID] Probe report is partial because one or more states were not verified.\n");
+ return 1;
+ }
+ if (!locked_state.geometry_verified || !unlocked_state.geometry_verified)
+ {
+ printf("[XGD1-RAW-ID] Geometry validation failed: locked failures=%u, unlocked failures=%u.\n",
+ locked_state.geometry_validation_failures,
+ unlocked_state.geometry_validation_failures);
+ return 1;
+ }
+
+ printf("[XGD1-RAW-ID] Probe complete. All selected raw IDs match the current XGD1 output geometry.\n");
+ return 0;
+}
+
+int xbox_ref_xgd1_raw_id_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path)
+{
+ return xgd1_raw_id_probe_core((HANDLE)native_handle,
+ device,
+ report_path,
+ FALSE);
+}
+
+int xbox_ref_xgd1_raw_id_probe(const char *device,
+ const char *report_path)
+{
+ char drive_letter;
+ HANDLE hDevice;
+
+ drive_letter = xgd1_drive_letter_from_device(device);
+ if (!drive_letter)
+ {
+ printf("[XGD1-RAW-ID] Fatal: could not parse drive letter from device.\n");
+ return 1;
+ }
+
+ hDevice = OpenDrive(drive_letter);
+ if (hDevice == INVALID_HANDLE_VALUE)
+ {
+ printf("[XGD1-RAW-ID] Fatal: cannot open drive %c:. Run as Administrator.\n",
+ drive_letter);
+ return 1;
+ }
+
+ return xgd1_raw_id_probe_core(hDevice,
+ device,
+ report_path,
+ TRUE);
+}
+
+#else
+
+int xbox_ref_xgd1_raw_id_probe_with_handle(void *native_handle,
+ const char *device,
+ const char *report_path)
+{
+ (void)native_handle;
+ (void)device;
+ (void)report_path;
+ return 1;
+}
+
+int xbox_ref_xgd1_raw_id_probe(const char *device,
+ const char *report_path)
+{
+ (void)device;
+ (void)report_path;
+ return 1;
+}
+
+#endif