X-Git-Url: https://git.jtryba.com/browse/friidump.git/blobdiff_plain/6e43cd3fd4f7324a177331a01d653b4c19c4bca7..HEAD:/libfriidump/xbox_ref_bridge.c?ds=sidebyside diff --git a/libfriidump/xbox_ref_bridge.c b/libfriidump/xbox_ref_bridge.c index 663805d..25e317a 100644 --- a/libfriidump/xbox_ref_bridge.c +++ b/libfriidump/xbox_ref_bridge.c @@ -1,4 +1,5 @@ #include "xbox_ref_bridge.h" +#include "xbox_region.h" #include @@ -13,8 +14,12 @@ void xbox_ref_dump_result_init(xbox_ref_dump_result *result) #define _WIN32_WINNT 0x0500 #include #include +#include #include +#include +#include #include "xbox_ref/utils.h" +#include "xbox_ref/sha1.h" #include "xbox_ref/unlock.h" #include "xbox_ref/xbox_ref_log.h" @@ -27,6 +32,16 @@ static BOOL xbox_ref_capacity_is_game_view(uint32_t sectors) return sectors > XBOX_REF_LOCKED_VIDEO_VIEW_MAX_SECTORS; } +static BOOL xbox_ref_capacity_is_xgd1_locked_view(uint32_t sectors) +{ + return sectors == XGD1_VIDEO_TOTAL_SECTORS; +} + +static BOOL xbox_ref_capacity_is_xgd1_game_view(uint32_t sectors) +{ + return sectors == REDUMP_SECTORS; +} + static uint32_t xbox_ref_refresh_ready_capacity(HANDLE hDevice) { RefreshVolume(hDevice); @@ -43,7 +58,752 @@ static char xbox_ref_drive_letter_from_device(const char *device) return device[0]; } -static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, const char *filename, char mode, BOOL ownsHandle, xbox_ref_dump_result *result) +#define XGD1_LAYOUT_PROBE_SCHEMA_VERSION 1 +#define XGD1_LAYOUT_PROBE_SECTOR_BYTES 2048U +#define XGD1_LAYOUT_PROBE_MAX_SAMPLES 20U + +typedef struct xbox_ref_probe_read_s +{ + int read_ok; + DWORD win32_error; + UCHAR scsi_status; + UCHAR sense_key; + UCHAR asc; + UCHAR ascq; + uint32_t nonzero_bytes; + int all_zero; + char sha1[41]; + unsigned char data[XGD1_LAYOUT_PROBE_SECTOR_BYTES]; +} xbox_ref_probe_read; + +typedef struct xbox_ref_probe_sample_s +{ + uint32_t lba; + const char *label; + xbox_ref_probe_read read10; + xbox_ref_probe_read read12; +} xbox_ref_probe_sample; + +typedef struct xbox_ref_probe_state_s +{ + const char *name; + uint32_t capacity; + int capacity_valid; + int state_verified; + uint32_t sample_count; + xbox_ref_probe_sample samples[XGD1_LAYOUT_PROBE_MAX_SAMPLES]; +} xbox_ref_probe_state; + +typedef struct xbox_ref_probe_point_s +{ + uint32_t lba; + const char *label; +} xbox_ref_probe_point; + +typedef struct xbox_ref_sptd_with_sense_s +{ + SCSI_PASS_THROUGH_DIRECT sptd; + ULONG filler; + UCHAR sense[32]; +} xbox_ref_sptd_with_sense; + +static const xbox_ref_probe_point xbox_ref_locked_probe_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, "locked_video_l1_first"}, + {XGD1_VIDEO_TOTAL_SECTORS - 1U, "locked_video_last"}, + {XGD1_VIDEO_TOTAL_SECTORS, "locked_capacity_plus_one"}, + {XGD1_GAME_OUTPUT_START_LBA - 1U, "pregame_output_last"}, + {XGD1_GAME_OUTPUT_START_LBA, "game_output_start"}, + {XGD1_REDUMP_LAYER_BREAK_LBA - 1U, "output_layer_break_minus_one"}, + {XGD1_REDUMP_LAYER_BREAK_LBA, "output_layer_break"}, + {XGD1_VIDEO_L1_OUTPUT_START_LBA - 1U, "postgame_output_last"}, + {XGD1_VIDEO_L1_OUTPUT_START_LBA, "video_l1_output_start"}, + {XGD1_FULL_REDUMP_SECTORS - 1U, "full_output_last"} +}; + +static const xbox_ref_probe_point xbox_ref_unlocked_probe_points[] = { + {0U, "unlocked_source_start"}, + {XGD1_XISO_LEADIN_SECTORS - 1U, "unlocked_source_leadin_last"}, + {XGD1_GAME_SOURCE_START_LBA, "xdfs_source_start"}, + {XGD1_GAME_SOURCE_START_LBA + 1U, "xdfs_source_next"}, + {(XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) - 1U, "synthetic_gap_length_minus_one"}, + {XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS, "synthetic_gap_length"}, + {XGD1_GAME_OUTPUT_START_LBA - 1U, "pregame_output_last_as_unlocked_source"}, + {XGD1_GAME_OUTPUT_START_LBA, "game_output_start_as_unlocked_source"}, + {(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"}, + {XGD1_GAME_SOURCE_START_LBA + XGD1_GAME_SOURCE_SECTORS - 1U, "unlocked_source_last"}, + {REDUMP_SECTORS, "unlocked_capacity_plus_one"}, + {XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS, "postgame_output_start_as_unlocked_source"}, + {XGD1_VIDEO_L1_OUTPUT_START_LBA, "video_l1_output_start_as_unlocked_source"} +}; + +static void xbox_ref_probe_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 xbox_ref_probe_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 xbox_ref_probe_read_one(HANDLE hDevice, + UCHAR opcode, + uint32_t lba, + xbox_ref_probe_read *result) +{ + xbox_ref_sptd_with_sense packet; + unsigned char *buffer; + DWORD bytes_returned; + BOOL ioctl_ok; + uint32_t i; + + if (!result) + return; + memset(result, 0, sizeof(*result)); + + buffer = (unsigned char *)VirtualAlloc(NULL, + XGD1_LAYOUT_PROBE_SECTOR_BYTES, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); + if (!buffer) + { + result->win32_error = ERROR_NOT_ENOUGH_MEMORY; + return; + } + + memset(buffer, 0, XGD1_LAYOUT_PROBE_SECTOR_BYTES); + memset(&packet, 0, sizeof(packet)); + + packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + packet.sptd.CdbLength = (opcode == 0xA8) ? 12 : 10; + packet.sptd.SenseInfoLength = sizeof(packet.sense); + packet.sptd.DataIn = SCSI_IOCTL_DATA_IN; + packet.sptd.DataTransferLength = XGD1_LAYOUT_PROBE_SECTOR_BYTES; + packet.sptd.TimeOutValue = 10; + packet.sptd.DataBuffer = buffer; + packet.sptd.SenseInfoOffset = offsetof(xbox_ref_sptd_with_sense, sense); + packet.sptd.Cdb[0] = opcode; + packet.sptd.Cdb[2] = (UCHAR)((lba >> 24) & 0xff); + packet.sptd.Cdb[3] = (UCHAR)((lba >> 16) & 0xff); + packet.sptd.Cdb[4] = (UCHAR)((lba >> 8) & 0xff); + packet.sptd.Cdb[5] = (UCHAR)(lba & 0xff); + + if (opcode == 0xA8) + { + packet.sptd.Cdb[9] = 1; + } + else + { + packet.sptd.Cdb[8] = 1; + } + + 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; + xbox_ref_probe_parse_sense(packet.sense, + &result->sense_key, + &result->asc, + &result->ascq); + result->read_ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0; + + if (result->read_ok) + { + memcpy(result->data, buffer, XGD1_LAYOUT_PROBE_SECTOR_BYTES); + result->nonzero_bytes = 0; + for (i = 0; i < XGD1_LAYOUT_PROBE_SECTOR_BYTES; ++i) + { + if (buffer[i] != 0) + result->nonzero_bytes++; + } + result->all_zero = (result->nonzero_bytes == 0U) ? 1 : 0; + xbox_ref_probe_sha1(buffer, + XGD1_LAYOUT_PROBE_SECTOR_BYTES, + result->sha1); + } + + VirtualFree(buffer, 0, MEM_RELEASE); +} + +static void xbox_ref_probe_capture_state(HANDLE hDevice, + xbox_ref_probe_state *state, + const xbox_ref_probe_point *points, + uint32_t point_count) +{ + uint32_t i; + + if (!state || !points) + return; + + if (point_count > XGD1_LAYOUT_PROBE_MAX_SAMPLES) + point_count = XGD1_LAYOUT_PROBE_MAX_SAMPLES; + + state->sample_count = point_count; + for (i = 0; i < point_count; ++i) + { + state->samples[i].lba = points[i].lba; + state->samples[i].label = points[i].label; + + printf("[XGD1-PROBE] %s: probing LBA %u (%s) with READ(10) and READ(12).\n", + state->name ? state->name : "state", + points[i].lba, + points[i].label ? points[i].label : "unlabeled"); + + xbox_ref_probe_read_one(hDevice, + 0x28, + points[i].lba, + &state->samples[i].read10); + xbox_ref_probe_read_one(hDevice, + 0xA8, + points[i].lba, + &state->samples[i].read12); + } +} + +static void xbox_ref_probe_json_string(FILE *json, const char *value) +{ + const unsigned char *p; + + if (!json) + return; + if (!value) + value = ""; + + fputc('"', json); + p = (const unsigned char *)value; + while (*p) + { + switch (*p) + { + case '\\': fputs("\\\\", json); break; + case '"': fputs("\\\"", json); break; + case '\b': fputs("\\b", json); break; + case '\f': fputs("\\f", json); break; + case '\n': fputs("\\n", json); break; + case '\r': fputs("\\r", json); break; + case '\t': fputs("\\t", json); break; + default: + if (*p < 0x20) + fprintf(json, "\\u%04x", (unsigned int)*p); + else + fputc(*p, json); + break; + } + ++p; + } + fputc('"', json); +} + +static void xbox_ref_probe_json_hex(FILE *json, + const unsigned char *data, + uint32_t data_size) +{ + uint32_t i; + + if (!json) + return; + fputc('"', json); + if (data) + { + for (i = 0; i < data_size; ++i) + fprintf(json, "%02x", data[i]); + } + fputc('"', json); +} + +static void xbox_ref_probe_write_cdb(FILE *json, + UCHAR opcode, + uint32_t lba) +{ + if (!json) + return; + + if (opcode == 0xA8) + { + fprintf(json, + "\"a800%08x000000010000\"", + lba); + } + else + { + fprintf(json, + "\"2800%08x00000100\"", + lba); + } +} + +static void xbox_ref_probe_write_read(FILE *json, + const char *indent, + const char *name, + const xbox_ref_probe_read *read) +{ + if (!json || !read) + return; + + fprintf(json, "%s", indent); + xbox_ref_probe_json_string(json, name); + fprintf(json, ": {\n"); + fprintf(json, "%s \"read_ok\": %s,\n", indent, read->read_ok ? "true" : "false"); + fprintf(json, "%s \"win32_error\": %lu,\n", indent, (unsigned long)read->win32_error); + fprintf(json, "%s \"scsi_status\": %u,\n", indent, (unsigned int)read->scsi_status); + fprintf(json, "%s \"sense_key\": %u,\n", indent, (unsigned int)read->sense_key); + fprintf(json, "%s \"asc\": %u,\n", indent, (unsigned int)read->asc); + fprintf(json, "%s \"ascq\": %u,\n", indent, (unsigned int)read->ascq); + if (read->read_ok) + { + fprintf(json, "%s \"nonzero_bytes\": %u,\n", indent, read->nonzero_bytes); + fprintf(json, "%s \"all_zero\": %s,\n", indent, read->all_zero ? "true" : "false"); + } + else + { + fprintf(json, "%s \"nonzero_bytes\": null,\n", indent); + fprintf(json, "%s \"all_zero\": null,\n", indent); + } + fprintf(json, "%s \"sha1\": ", indent); + if (read->read_ok) + xbox_ref_probe_json_string(json, read->sha1); + else + fprintf(json, "null"); + fprintf(json, ",\n"); + fprintf(json, "%s \"data_hex\": ", indent); + if (read->read_ok) + xbox_ref_probe_json_hex(json, read->data, XGD1_LAYOUT_PROBE_SECTOR_BYTES); + else + xbox_ref_probe_json_string(json, ""); + fprintf(json, "\n%s}", indent); +} + +static void xbox_ref_probe_write_state(FILE *json, + const xbox_ref_probe_state *state, + int trailing_comma) +{ + uint32_t i; + + fprintf(json, " {\n"); + fprintf(json, " \"name\": "); + xbox_ref_probe_json_string(json, state->name); + fprintf(json, ",\n"); + fprintf(json, " \"capacity_valid\": %s,\n", state->capacity_valid ? "true" : "false"); + fprintf(json, " \"capacity_sectors\": %u,\n", state->capacity); + fprintf(json, " \"state_verified\": %s,\n", state->state_verified ? "true" : "false"); + fprintf(json, " \"samples\": [\n"); + + for (i = 0; i < state->sample_count; ++i) + { + const xbox_ref_probe_sample *sample = &state->samples[i]; + int identical = 0; + + if (sample->read10.read_ok && sample->read12.read_ok) + { + identical = (memcmp(sample->read10.data, + sample->read12.data, + XGD1_LAYOUT_PROBE_SECTOR_BYTES) == 0) ? 1 : 0; + } + + fprintf(json, " {\n"); + fprintf(json, " \"lba\": %u,\n", sample->lba); + fprintf(json, " \"label\": "); + xbox_ref_probe_json_string(json, sample->label); + fprintf(json, ",\n"); + fprintf(json, " \"read10_cdb_hex\": "); + xbox_ref_probe_write_cdb(json, 0x28, sample->lba); + fprintf(json, ",\n"); + fprintf(json, " \"read12_cdb_hex\": "); + xbox_ref_probe_write_cdb(json, 0xA8, sample->lba); + fprintf(json, ",\n"); + xbox_ref_probe_write_read(json, " ", "read10", &sample->read10); + fprintf(json, ",\n"); + xbox_ref_probe_write_read(json, " ", "read12", &sample->read12); + fprintf(json, ",\n"); + fprintf(json, + " \"read10_read12_comparable\": %s,\n", + (sample->read10.read_ok && sample->read12.read_ok) ? "true" : "false"); + fprintf(json, " \"read10_read12_identical\": "); + if (sample->read10.read_ok && sample->read12.read_ok) + fprintf(json, "%s\n", identical ? "true" : "false"); + else + fprintf(json, "null\n"); + fprintf(json, " }%s\n", (i + 1U < state->sample_count) ? "," : ""); + } + + fprintf(json, " ]\n"); + fprintf(json, " }%s\n", trailing_comma ? "," : ""); +} + +static BOOL xbox_ref_probe_write_report(const char *report_path, + const char *device, + uint32_t entry_capacity, + int entry_game_view, + int volume_lock_acquired, + const xbox_ref_probe_state *locked_state, + const xbox_ref_probe_state *unlocked_state, + int restore_attempted, + uint32_t restored_capacity, + int restore_verified) +{ + char tmp_path[XBOX_REF_RESULT_PATH_MAX]; + const char *final_path; + FILE *json; + int fd; + BOOL moved; + + final_path = (report_path && report_path[0]) ? report_path : "xgd1_layout_probe.json"; + { + int path_length = snprintf(tmp_path, + sizeof(tmp_path), + "%s.tmp.%lu", + final_path, + (unsigned long)GetCurrentProcessId()); + if (path_length < 0 || (size_t)path_length >= sizeof(tmp_path)) + { + printf("[XGD1-PROBE] Fatal: report path is too long.\n"); + return FALSE; + } + } + + json = fopen(tmp_path, "wb"); + if (!json) + { + printf("[XGD1-PROBE] Fatal: could not create temporary report %s.\n", tmp_path); + return FALSE; + } + + fprintf(json, "{\n"); + fprintf(json, " \"schema_version\": %d,\n", XGD1_LAYOUT_PROBE_SCHEMA_VERSION); + fprintf(json, " \"producer\": \"friidump-0.5.3.11\",\n"); + fprintf(json, " \"probe\": \"original_xbox_xgd1_logical_layout_boundary\",\n"); + fprintf(json, " \"device\": "); + xbox_ref_probe_json_string(json, device ? device : ""); + fprintf(json, ",\n"); + fprintf(json, " \"safety\": {\n"); + fprintf(json, " \"read_only_sector_commands\": true,\n"); + fprintf(json, " \"authentication_handshake_used\": true,\n"); + fprintf(json, " \"tray_cycle_used\": true,\n"); + fprintf(json, " \"firmware_write_used\": false,\n"); + fprintf(json, " \"flash_command_used\": false\n"); + fprintf(json, " },\n"); + fprintf(json, " \"constants\": {\n"); + fprintf(json, " \"full_output_sectors\": %u,\n", XGD1_FULL_REDUMP_SECTORS); + fprintf(json, " \"game_output_start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA); + fprintf(json, " \"game_view_sectors\": %u,\n", REDUMP_SECTORS); + fprintf(json, " \"video_total_sectors\": %u,\n", XGD1_VIDEO_TOTAL_SECTORS); + fprintf(json, " \"video_l0_sectors\": %u,\n", XGD1_VIDEO_L0_SECTORS); + fprintf(json, " \"video_l1_sectors\": %u,\n", XGD1_VIDEO_L1_SECTORS); + fprintf(json, " \"video_l1_output_start_lba\": %u,\n", XGD1_VIDEO_L1_OUTPUT_START_LBA); + fprintf(json, " \"output_layer_break_lba\": %u\n", XGD1_REDUMP_LAYER_BREAK_LBA); + fprintf(json, " },\n"); + fprintf(json, " \"entry\": {\n"); + fprintf(json, " \"capacity_sectors\": %u,\n", entry_capacity); + fprintf(json, " \"game_view\": %s,\n", entry_game_view ? "true" : "false"); + fprintf(json, " \"classification\": "); + if (xbox_ref_capacity_is_xgd1_game_view(entry_capacity)) + xbox_ref_probe_json_string(json, "unlocked_game"); + else if (xbox_ref_capacity_is_xgd1_locked_view(entry_capacity)) + xbox_ref_probe_json_string(json, "locked_video"); + else + xbox_ref_probe_json_string(json, "other_or_unknown"); + fprintf(json, "\n"); + fprintf(json, " },\n"); + fprintf(json, " \"volume_lock_acquired_at_least_once\": %s,\n", volume_lock_acquired ? "true" : "false"); + fprintf(json, + " \"required_states_verified\": %s,\n", + (locked_state->state_verified && unlocked_state->state_verified) ? "true" : "false"); + fprintf(json, " \"states\": [\n"); + xbox_ref_probe_write_state(json, locked_state, 1); + xbox_ref_probe_write_state(json, unlocked_state, 0); + fprintf(json, " ],\n"); + fprintf(json, " \"restoration\": {\n"); + fprintf(json, " \"attempted\": %s,\n", restore_attempted ? "true" : "false"); + fprintf(json, " \"capacity_sectors\": %u,\n", restored_capacity); + fprintf(json, " \"verified\": %s\n", restore_verified ? "true" : "false"); + fprintf(json, " },\n"); + fprintf(json, " \"interpretation_boundary\": {\n"); + fprintf(json, " \"pregame_padding_resolved\": false,\n"); + fprintf(json, " \"postgame_padding_resolved\": false,\n"); + fprintf(json, " \"game_leadin_resolved\": false,\n"); + fprintf(json, " \"note\": \"This report records ordinary logical READ(10)/READ(12) behavior in locked/video and unlocked/game states. It does not claim that inaccessible physical filler sectors are zero or that the current reconstructed image is Redump-exact.\"\n"); + fprintf(json, " }\n"); + fprintf(json, "}\n"); + + if (fflush(json) != 0) + { + fclose(json); + DeleteFileA(tmp_path); + return FALSE; + } + + fd = _fileno(json); + if (fd < 0 || _commit(fd) != 0) + { + fclose(json); + DeleteFileA(tmp_path); + return FALSE; + } + + if (fclose(json) != 0) + { + DeleteFileA(tmp_path); + return FALSE; + } + + moved = MoveFileExA(tmp_path, + final_path, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); + if (!moved) + { + printf("[XGD1-PROBE] Fatal: could not atomically publish report %s (error %lu).\n", + final_path, + (unsigned long)GetLastError()); + DeleteFileA(tmp_path); + return FALSE; + } + + printf("[XGD1-PROBE] Atomic report written: %s\n", final_path); + return TRUE; +} + +static int xbox_ref_xgd1_layout_probe_core(HANDLE hDevice, + const char *device, + const char *report_path, + BOOL owns_handle) +{ + xbox_ref_probe_state locked_state; + xbox_ref_probe_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; + BOOL report_ok; + + memset(&locked_state, 0, sizeof(locked_state)); + memset(&unlocked_state, 0, sizeof(unlocked_state)); + locked_state.name = "locked_video"; + unlocked_state.name = "unlocked_game"; + 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, xbox_ref_drive_letter_from_device(device)); + printf("[XGD1-PROBE] Starting read-only logical layout boundary probe.\n"); + printf("[XGD1-PROBE] The existing XGD1 output layout will not be modified.\n"); + + if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) + { + printf("[XGD1-PROBE] Fatal: invalid drive handle.\n"); + if (owns_handle) + xbox_ref_log_close(); + return 1; + } + + if (!EnsureDriveReady(hDevice, 30000)) + { + printf("[XGD1-PROBE] Fatal: media did not become ready.\n"); + if (owns_handle) + xbox_ref_log_close(); + return 1; + } + + entry_capacity = GetTotalSectors(hDevice); + entry_game_view = xbox_ref_capacity_is_xgd1_game_view(entry_capacity) ? 1 : 0; + printf("[XGD1-PROBE] 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-PROBE] Cycling tray to establish locked/video view.\n"); + AutomateTrayCycle(hDevice); + locked_state.capacity = xbox_ref_refresh_ready_capacity(hDevice); + locked_state.capacity_valid = locked_state.capacity != 0U; + locked_state.state_verified = (locked_state.capacity_valid && + xbox_ref_capacity_is_xgd1_locked_view(locked_state.capacity)) ? 1 : 0; + printf("[XGD1-PROBE] 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-PROBE] Windows volume lock acquired.\n"); + } + else + { + printf("[XGD1-PROBE][WARN] Windows volume lock was not acquired; close AutoPlay and Explorer dialogs if results are unstable.\n"); + } + + SetDriveSpeedMax(hDevice); + xbox_ref_probe_capture_state(hDevice, + &locked_state, + xbox_ref_locked_probe_points, + (uint32_t)(sizeof(xbox_ref_locked_probe_points) / + sizeof(xbox_ref_locked_probe_points[0]))); + + printf("[XGD1-PROBE] Applying the full Xbox handshake to establish game view.\n"); + UnlockDrive(hDevice); + unlocked_state.capacity = xbox_ref_refresh_ready_capacity(hDevice); + unlocked_state.capacity_valid = unlocked_state.capacity != 0U; + unlocked_state.state_verified = (unlocked_state.capacity_valid && + xbox_ref_capacity_is_xgd1_game_view(unlocked_state.capacity)) ? 1 : 0; + printf("[XGD1-PROBE] Unlocked/game READ CAPACITY: %u sectors; verified=%s.\n", + unlocked_state.capacity, + unlocked_state.state_verified ? "yes" : "no"); + + xbox_ref_probe_capture_state(hDevice, + &unlocked_state, + xbox_ref_unlocked_probe_points, + (uint32_t)(sizeof(xbox_ref_unlocked_probe_points) / + sizeof(xbox_ref_unlocked_probe_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-PROBE] Restoring the entry locked/video state with a final tray cycle.\n"); + AutomateTrayCycle(hDevice); + restored_capacity = xbox_ref_refresh_ready_capacity(hDevice); + restore_verified = (restored_capacity != 0U && + xbox_ref_capacity_is_xgd1_locked_view(restored_capacity)) ? 1 : 0; + } + else + { + restored_capacity = GetTotalSectors(hDevice); + restore_verified = (entry_game_view && + xbox_ref_capacity_is_xgd1_game_view(restored_capacity)) ? 1 : 0; + } + + states_verified = (locked_state.state_verified && + unlocked_state.state_verified) ? 1 : 0; + + report_ok = xbox_ref_probe_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-PROBE] 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-PROBE] Probe report is partial because one or more drive states were not verified.\n"); + return 1; + } + + printf("[XGD1-PROBE] Probe complete. Synthetic ranges remain unresolved until the captured evidence supports a source mapping.\n"); + return 0; +} + + +static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, + const char *filename, char mode, + BOOL ownsHandle, + xbox_ref_cancel_func cancel, + void *cancel_data, + xbox_ref_dump_result *result) { char driveLetter; XboxGameInfo game; @@ -164,6 +924,13 @@ static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, const strncpy(result->media_id, mediaId, sizeof(result->media_id) - 1); result->media_id[sizeof(result->media_id) - 1] = '\0'; } + if (game.Success) { + result->have_game_region = 1; + result->game_region = game.GameRegion; + xbox_region_format(game.GameRegion, + result->region, + sizeof(result->region)); + } } if (game.Success) { @@ -218,7 +985,7 @@ static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, const } printf("Starting dump with copied DumpXboxGameDisc() mode %c...\n", mode); - ok = DumpXboxGameDisc(hDevice, finalFilename, mode, totalSectors, isDualLayerBool, FALSE, result); + ok = DumpXboxGameDisc(hDevice, finalFilename, mode, totalSectors, isDualLayerBool, FALSE, cancel, cancel_data, result); cleanup: if (volumeLocked) @@ -236,14 +1003,61 @@ cleanup: } else if (ok) { printf("[XBOX-REF] Returning finalized output evidence to FriiDump's shared Redump verifier.\n"); } else { - printf("[XBOX-REF] No completed output evidence is available for shared Redump verification.\n"); + printf("[XBOX-REF] No completed full-output evidence is available for shared Redump verification.\n"); } return ok ? 0 : 1; } -int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result) +int xbox_ref_xgd1_layout_probe_with_handle(void *native_handle, + const char *device, + const char *report_path) { - return xbox_ref_gdr8050l_dump_core((HANDLE)native_handle, device, filename, mode, FALSE, result); + return xbox_ref_xgd1_layout_probe_core((HANDLE)native_handle, + device, + report_path, + FALSE); +} + +int xbox_ref_xgd1_layout_probe(const char *device, + const char *report_path) +{ + char drive_letter; + HANDLE hDevice; + + drive_letter = xbox_ref_drive_letter_from_device(device); + if (!drive_letter) + { + xbox_ref_log_open_for_target(report_path, 0); + printf("[XGD1-PROBE] Fatal: could not parse drive letter from device '%s'.\n", + device ? device : "(null)"); + xbox_ref_log_close(); + return 1; + } + + hDevice = OpenDrive(drive_letter); + if (hDevice == INVALID_HANDLE_VALUE) + { + xbox_ref_log_open_for_target(report_path, drive_letter); + printf("[XGD1-PROBE] Fatal: cannot open drive %c:. Run as Administrator.\n", + drive_letter); + xbox_ref_log_close(); + return 1; + } + + return xbox_ref_xgd1_layout_probe_core(hDevice, + device, + report_path, + TRUE); +} + +int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, + const char *filename, char mode, + xbox_ref_cancel_func cancel, + void *cancel_data, + xbox_ref_dump_result *result) +{ + return xbox_ref_gdr8050l_dump_core((HANDLE)native_handle, device, filename, + mode, FALSE, cancel, cancel_data, result); } int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode, xbox_ref_dump_result *result) @@ -267,12 +1081,37 @@ int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode, return 1; } - return xbox_ref_gdr8050l_dump_core(hDevice, device, filename, mode, TRUE, result); + return xbox_ref_gdr8050l_dump_core(hDevice, device, filename, mode, TRUE, NULL, NULL, result); } #else -int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result) +int xbox_ref_xgd1_layout_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_layout_probe(const char *device, + const char *report_path) +{ + (void)device; + (void)report_path; + return 1; +} + +int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, + const char *filename, char mode, + xbox_ref_cancel_func cancel, + void *cancel_data, + xbox_ref_dump_result *result) { - (void)native_handle; (void)device; (void)filename; (void)mode; xbox_ref_dump_result_init(result); return 1; + (void)native_handle; (void)device; (void)filename; (void)mode; + (void)cancel; (void)cancel_data; + xbox_ref_dump_result_init(result); + return 1; } int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode, xbox_ref_dump_result *result)