]> FriiDump Source - friidump.git/blob - libfriidump/xbox_ref_bridge.c
FriiDump 0.5.3.15: close XGD1 geometry and lead-in capture
[friidump.git] / libfriidump / xbox_ref_bridge.c
1 #include "xbox_ref_bridge.h"
2
3 #include <string.h>
4
5 void xbox_ref_dump_result_init(xbox_ref_dump_result *result)
6 {
7     if (result)
8         memset(result, 0, sizeof(*result));
9 }
10
11 #ifdef WIN32
12 #define WIN32_LEAN_AND_MEAN
13 #define _WIN32_WINNT 0x0500
14 #include <windows.h>
15 #include <winioctl.h>
16 #include <ntddscsi.h>
17 #include <stdio.h>
18 #include <stddef.h>
19 #include <io.h>
20 #include "xbox_ref/utils.h"
21 #include "xbox_ref/sha1.h"
22 #include "xbox_ref/unlock.h"
23 #include "xbox_ref/xbox_ref_log.h"
24
25 #define printf xbox_ref_printf
26
27 #define XBOX_REF_LOCKED_VIDEO_VIEW_MAX_SECTORS 200000U
28
29 static BOOL xbox_ref_capacity_is_game_view(uint32_t sectors)
30 {
31     return sectors > XBOX_REF_LOCKED_VIDEO_VIEW_MAX_SECTORS;
32 }
33
34 static BOOL xbox_ref_capacity_is_xgd1_locked_view(uint32_t sectors)
35 {
36     return sectors == XGD1_VIDEO_TOTAL_SECTORS;
37 }
38
39 static BOOL xbox_ref_capacity_is_xgd1_game_view(uint32_t sectors)
40 {
41     return sectors == REDUMP_SECTORS;
42 }
43
44 static uint32_t xbox_ref_refresh_ready_capacity(HANDLE hDevice)
45 {
46     RefreshVolume(hDevice);
47     Sleep(2000);
48     EnsureDriveReady(hDevice, 30000);
49     return GetTotalSectors(hDevice);
50 }
51
52 static char xbox_ref_drive_letter_from_device(const char *device)
53 {
54     if (!device || !device[0]) return 0;
55     if (device[0] && device[1] == ':') return device[0];
56     if (device[0] == '\\' && device[1] == '\\' && device[2] == '.' && device[3] == '\\' && device[4] && device[5] == ':') return device[4];
57     return device[0];
58 }
59
60 #define XGD1_LAYOUT_PROBE_SCHEMA_VERSION 1
61 #define XGD1_LAYOUT_PROBE_SECTOR_BYTES 2048U
62 #define XGD1_LAYOUT_PROBE_MAX_SAMPLES 20U
63
64 typedef struct xbox_ref_probe_read_s
65 {
66     int read_ok;
67     DWORD win32_error;
68     UCHAR scsi_status;
69     UCHAR sense_key;
70     UCHAR asc;
71     UCHAR ascq;
72     uint32_t nonzero_bytes;
73     int all_zero;
74     char sha1[41];
75     unsigned char data[XGD1_LAYOUT_PROBE_SECTOR_BYTES];
76 } xbox_ref_probe_read;
77
78 typedef struct xbox_ref_probe_sample_s
79 {
80     uint32_t lba;
81     const char *label;
82     xbox_ref_probe_read read10;
83     xbox_ref_probe_read read12;
84 } xbox_ref_probe_sample;
85
86 typedef struct xbox_ref_probe_state_s
87 {
88     const char *name;
89     uint32_t capacity;
90     int capacity_valid;
91     int state_verified;
92     uint32_t sample_count;
93     xbox_ref_probe_sample samples[XGD1_LAYOUT_PROBE_MAX_SAMPLES];
94 } xbox_ref_probe_state;
95
96 typedef struct xbox_ref_probe_point_s
97 {
98     uint32_t lba;
99     const char *label;
100 } xbox_ref_probe_point;
101
102 typedef struct xbox_ref_sptd_with_sense_s
103 {
104     SCSI_PASS_THROUGH_DIRECT sptd;
105     ULONG filler;
106     UCHAR sense[32];
107 } xbox_ref_sptd_with_sense;
108
109 static const xbox_ref_probe_point xbox_ref_locked_probe_points[] = {
110     {0U, "locked_logical_start"},
111     {31U, "locked_early_lba_31"},
112     {32U, "locked_early_lba_32"},
113     {XGD1_VIDEO_L0_SECTORS - 1U, "video_l0_last"},
114     {XGD1_VIDEO_L0_SECTORS, "locked_video_l1_first"},
115     {XGD1_VIDEO_TOTAL_SECTORS - 1U, "locked_video_last"},
116     {XGD1_VIDEO_TOTAL_SECTORS, "locked_capacity_plus_one"},
117     {XGD1_GAME_OUTPUT_START_LBA - 1U, "pregame_output_last"},
118     {XGD1_GAME_OUTPUT_START_LBA, "game_output_start"},
119     {XGD1_REDUMP_LAYER_BREAK_LBA - 1U, "output_layer_break_minus_one"},
120     {XGD1_REDUMP_LAYER_BREAK_LBA, "output_layer_break"},
121     {XGD1_VIDEO_L1_OUTPUT_START_LBA - 1U, "postgame_output_last"},
122     {XGD1_VIDEO_L1_OUTPUT_START_LBA, "video_l1_output_start"},
123     {XGD1_FULL_REDUMP_SECTORS - 1U, "full_output_last"}
124 };
125
126 static const xbox_ref_probe_point xbox_ref_unlocked_probe_points[] = {
127     {0U, "unlocked_source_start"},
128     {XGD1_XISO_LEADIN_SECTORS - 1U, "unlocked_source_leadin_last"},
129     {XGD1_GAME_SOURCE_START_LBA, "xdfs_source_start"},
130     {XGD1_GAME_SOURCE_START_LBA + 1U, "xdfs_source_next"},
131     {(XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) - 1U, "synthetic_gap_length_minus_one"},
132     {XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS, "synthetic_gap_length"},
133     {XGD1_GAME_OUTPUT_START_LBA - 1U, "pregame_output_last_as_unlocked_source"},
134     {XGD1_GAME_OUTPUT_START_LBA, "game_output_start_as_unlocked_source"},
135     {(XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA) - 1U, "mapped_layer_break_source_minus_one"},
136     {XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA, "mapped_layer_break_source"},
137     {XGD1_GAME_SOURCE_START_LBA + XGD1_GAME_SOURCE_SECTORS - 1U, "unlocked_source_last"},
138     {REDUMP_SECTORS, "unlocked_capacity_plus_one"},
139     {XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS, "postgame_output_start_as_unlocked_source"},
140     {XGD1_VIDEO_L1_OUTPUT_START_LBA, "video_l1_output_start_as_unlocked_source"}
141 };
142
143 static void xbox_ref_probe_parse_sense(const UCHAR *sense,
144                                        UCHAR *sense_key,
145                                        UCHAR *asc,
146                                        UCHAR *ascq)
147 {
148     UCHAR response;
149
150     if (sense_key) *sense_key = 0;
151     if (asc) *asc = 0;
152     if (ascq) *ascq = 0;
153     if (!sense)
154         return;
155
156     response = (UCHAR)(sense[0] & 0x7f);
157     if (response == 0x70 || response == 0x71)
158     {
159         if (sense_key) *sense_key = (UCHAR)(sense[2] & 0x0f);
160         if (asc) *asc = sense[12];
161         if (ascq) *ascq = sense[13];
162     }
163     else if (response == 0x72 || response == 0x73)
164     {
165         if (sense_key) *sense_key = (UCHAR)(sense[1] & 0x0f);
166         if (asc) *asc = sense[2];
167         if (ascq) *ascq = sense[3];
168     }
169 }
170
171 static void xbox_ref_probe_sha1(const unsigned char *data,
172                                 uint32_t data_size,
173                                 char out_sha1[41])
174 {
175     SHA1_CTX ctx;
176     unsigned char digest[20];
177     uint32_t i;
178
179     if (!out_sha1)
180         return;
181     out_sha1[0] = '\0';
182     if (!data || data_size == 0)
183         return;
184
185     SHA1_Init(&ctx);
186     SHA1_Update(&ctx, data, data_size);
187     SHA1_Final(digest, &ctx);
188
189     for (i = 0; i < 20U; ++i)
190         sprintf(out_sha1 + (i * 2U), "%02x", digest[i]);
191     out_sha1[40] = '\0';
192 }
193
194 static void xbox_ref_probe_read_one(HANDLE hDevice,
195                                     UCHAR opcode,
196                                     uint32_t lba,
197                                     xbox_ref_probe_read *result)
198 {
199     xbox_ref_sptd_with_sense packet;
200     unsigned char *buffer;
201     DWORD bytes_returned;
202     BOOL ioctl_ok;
203     uint32_t i;
204
205     if (!result)
206         return;
207     memset(result, 0, sizeof(*result));
208
209     buffer = (unsigned char *)VirtualAlloc(NULL,
210                                            XGD1_LAYOUT_PROBE_SECTOR_BYTES,
211                                            MEM_COMMIT | MEM_RESERVE,
212                                            PAGE_READWRITE);
213     if (!buffer)
214     {
215         result->win32_error = ERROR_NOT_ENOUGH_MEMORY;
216         return;
217     }
218
219     memset(buffer, 0, XGD1_LAYOUT_PROBE_SECTOR_BYTES);
220     memset(&packet, 0, sizeof(packet));
221
222     packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
223     packet.sptd.CdbLength = (opcode == 0xA8) ? 12 : 10;
224     packet.sptd.SenseInfoLength = sizeof(packet.sense);
225     packet.sptd.DataIn = SCSI_IOCTL_DATA_IN;
226     packet.sptd.DataTransferLength = XGD1_LAYOUT_PROBE_SECTOR_BYTES;
227     packet.sptd.TimeOutValue = 10;
228     packet.sptd.DataBuffer = buffer;
229     packet.sptd.SenseInfoOffset = offsetof(xbox_ref_sptd_with_sense, sense);
230     packet.sptd.Cdb[0] = opcode;
231     packet.sptd.Cdb[2] = (UCHAR)((lba >> 24) & 0xff);
232     packet.sptd.Cdb[3] = (UCHAR)((lba >> 16) & 0xff);
233     packet.sptd.Cdb[4] = (UCHAR)((lba >> 8) & 0xff);
234     packet.sptd.Cdb[5] = (UCHAR)(lba & 0xff);
235
236     if (opcode == 0xA8)
237     {
238         packet.sptd.Cdb[9] = 1;
239     }
240     else
241     {
242         packet.sptd.Cdb[8] = 1;
243     }
244
245     bytes_returned = 0;
246     ioctl_ok = DeviceIoControl(hDevice,
247                                IOCTL_SCSI_PASS_THROUGH_DIRECT,
248                                &packet,
249                                sizeof(packet),
250                                &packet,
251                                sizeof(packet),
252                                &bytes_returned,
253                                NULL);
254
255     result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
256     result->scsi_status = packet.sptd.ScsiStatus;
257     xbox_ref_probe_parse_sense(packet.sense,
258                                &result->sense_key,
259                                &result->asc,
260                                &result->ascq);
261     result->read_ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
262
263     if (result->read_ok)
264     {
265         memcpy(result->data, buffer, XGD1_LAYOUT_PROBE_SECTOR_BYTES);
266         result->nonzero_bytes = 0;
267         for (i = 0; i < XGD1_LAYOUT_PROBE_SECTOR_BYTES; ++i)
268         {
269             if (buffer[i] != 0)
270                 result->nonzero_bytes++;
271         }
272         result->all_zero = (result->nonzero_bytes == 0U) ? 1 : 0;
273         xbox_ref_probe_sha1(buffer,
274                             XGD1_LAYOUT_PROBE_SECTOR_BYTES,
275                             result->sha1);
276     }
277
278     VirtualFree(buffer, 0, MEM_RELEASE);
279 }
280
281 static void xbox_ref_probe_capture_state(HANDLE hDevice,
282                                          xbox_ref_probe_state *state,
283                                          const xbox_ref_probe_point *points,
284                                          uint32_t point_count)
285 {
286     uint32_t i;
287
288     if (!state || !points)
289         return;
290
291     if (point_count > XGD1_LAYOUT_PROBE_MAX_SAMPLES)
292         point_count = XGD1_LAYOUT_PROBE_MAX_SAMPLES;
293
294     state->sample_count = point_count;
295     for (i = 0; i < point_count; ++i)
296     {
297         state->samples[i].lba = points[i].lba;
298         state->samples[i].label = points[i].label;
299
300         printf("[XGD1-PROBE] %s: probing LBA %u (%s) with READ(10) and READ(12).\n",
301                state->name ? state->name : "state",
302                points[i].lba,
303                points[i].label ? points[i].label : "unlabeled");
304
305         xbox_ref_probe_read_one(hDevice,
306                                 0x28,
307                                 points[i].lba,
308                                 &state->samples[i].read10);
309         xbox_ref_probe_read_one(hDevice,
310                                 0xA8,
311                                 points[i].lba,
312                                 &state->samples[i].read12);
313     }
314 }
315
316 static void xbox_ref_probe_json_string(FILE *json, const char *value)
317 {
318     const unsigned char *p;
319
320     if (!json)
321         return;
322     if (!value)
323         value = "";
324
325     fputc('"', json);
326     p = (const unsigned char *)value;
327     while (*p)
328     {
329         switch (*p)
330         {
331             case '\\': fputs("\\\\", json); break;
332             case '"': fputs("\\\"", json); break;
333             case '\b': fputs("\\b", json); break;
334             case '\f': fputs("\\f", json); break;
335             case '\n': fputs("\\n", json); break;
336             case '\r': fputs("\\r", json); break;
337             case '\t': fputs("\\t", json); break;
338             default:
339                 if (*p < 0x20)
340                     fprintf(json, "\\u%04x", (unsigned int)*p);
341                 else
342                     fputc(*p, json);
343                 break;
344         }
345         ++p;
346     }
347     fputc('"', json);
348 }
349
350 static void xbox_ref_probe_json_hex(FILE *json,
351                                     const unsigned char *data,
352                                     uint32_t data_size)
353 {
354     uint32_t i;
355
356     if (!json)
357         return;
358     fputc('"', json);
359     if (data)
360     {
361         for (i = 0; i < data_size; ++i)
362             fprintf(json, "%02x", data[i]);
363     }
364     fputc('"', json);
365 }
366
367 static void xbox_ref_probe_write_cdb(FILE *json,
368                                       UCHAR opcode,
369                                       uint32_t lba)
370 {
371     if (!json)
372         return;
373
374     if (opcode == 0xA8)
375     {
376         fprintf(json,
377                 "\"a800%08x000000010000\"",
378                 lba);
379     }
380     else
381     {
382         fprintf(json,
383                 "\"2800%08x00000100\"",
384                 lba);
385     }
386 }
387
388 static void xbox_ref_probe_write_read(FILE *json,
389                                       const char *indent,
390                                       const char *name,
391                                       const xbox_ref_probe_read *read)
392 {
393     if (!json || !read)
394         return;
395
396     fprintf(json, "%s", indent);
397     xbox_ref_probe_json_string(json, name);
398     fprintf(json, ": {\n");
399     fprintf(json, "%s  \"read_ok\": %s,\n", indent, read->read_ok ? "true" : "false");
400     fprintf(json, "%s  \"win32_error\": %lu,\n", indent, (unsigned long)read->win32_error);
401     fprintf(json, "%s  \"scsi_status\": %u,\n", indent, (unsigned int)read->scsi_status);
402     fprintf(json, "%s  \"sense_key\": %u,\n", indent, (unsigned int)read->sense_key);
403     fprintf(json, "%s  \"asc\": %u,\n", indent, (unsigned int)read->asc);
404     fprintf(json, "%s  \"ascq\": %u,\n", indent, (unsigned int)read->ascq);
405     if (read->read_ok)
406     {
407         fprintf(json, "%s  \"nonzero_bytes\": %u,\n", indent, read->nonzero_bytes);
408         fprintf(json, "%s  \"all_zero\": %s,\n", indent, read->all_zero ? "true" : "false");
409     }
410     else
411     {
412         fprintf(json, "%s  \"nonzero_bytes\": null,\n", indent);
413         fprintf(json, "%s  \"all_zero\": null,\n", indent);
414     }
415     fprintf(json, "%s  \"sha1\": ", indent);
416     if (read->read_ok)
417         xbox_ref_probe_json_string(json, read->sha1);
418     else
419         fprintf(json, "null");
420     fprintf(json, ",\n");
421     fprintf(json, "%s  \"data_hex\": ", indent);
422     if (read->read_ok)
423         xbox_ref_probe_json_hex(json, read->data, XGD1_LAYOUT_PROBE_SECTOR_BYTES);
424     else
425         xbox_ref_probe_json_string(json, "");
426     fprintf(json, "\n%s}", indent);
427 }
428
429 static void xbox_ref_probe_write_state(FILE *json,
430                                        const xbox_ref_probe_state *state,
431                                        int trailing_comma)
432 {
433     uint32_t i;
434
435     fprintf(json, "    {\n");
436     fprintf(json, "      \"name\": ");
437     xbox_ref_probe_json_string(json, state->name);
438     fprintf(json, ",\n");
439     fprintf(json, "      \"capacity_valid\": %s,\n", state->capacity_valid ? "true" : "false");
440     fprintf(json, "      \"capacity_sectors\": %u,\n", state->capacity);
441     fprintf(json, "      \"state_verified\": %s,\n", state->state_verified ? "true" : "false");
442     fprintf(json, "      \"samples\": [\n");
443
444     for (i = 0; i < state->sample_count; ++i)
445     {
446         const xbox_ref_probe_sample *sample = &state->samples[i];
447         int identical = 0;
448
449         if (sample->read10.read_ok && sample->read12.read_ok)
450         {
451             identical = (memcmp(sample->read10.data,
452                                 sample->read12.data,
453                                 XGD1_LAYOUT_PROBE_SECTOR_BYTES) == 0) ? 1 : 0;
454         }
455
456         fprintf(json, "        {\n");
457         fprintf(json, "          \"lba\": %u,\n", sample->lba);
458         fprintf(json, "          \"label\": ");
459         xbox_ref_probe_json_string(json, sample->label);
460         fprintf(json, ",\n");
461         fprintf(json, "          \"read10_cdb_hex\": ");
462         xbox_ref_probe_write_cdb(json, 0x28, sample->lba);
463         fprintf(json, ",\n");
464         fprintf(json, "          \"read12_cdb_hex\": ");
465         xbox_ref_probe_write_cdb(json, 0xA8, sample->lba);
466         fprintf(json, ",\n");
467         xbox_ref_probe_write_read(json, "          ", "read10", &sample->read10);
468         fprintf(json, ",\n");
469         xbox_ref_probe_write_read(json, "          ", "read12", &sample->read12);
470         fprintf(json, ",\n");
471         fprintf(json,
472                 "          \"read10_read12_comparable\": %s,\n",
473                 (sample->read10.read_ok && sample->read12.read_ok) ? "true" : "false");
474         fprintf(json, "          \"read10_read12_identical\": ");
475         if (sample->read10.read_ok && sample->read12.read_ok)
476             fprintf(json, "%s\n", identical ? "true" : "false");
477         else
478             fprintf(json, "null\n");
479         fprintf(json, "        }%s\n", (i + 1U < state->sample_count) ? "," : "");
480     }
481
482     fprintf(json, "      ]\n");
483     fprintf(json, "    }%s\n", trailing_comma ? "," : "");
484 }
485
486 static BOOL xbox_ref_probe_write_report(const char *report_path,
487                                         const char *device,
488                                         uint32_t entry_capacity,
489                                         int entry_game_view,
490                                         int volume_lock_acquired,
491                                         const xbox_ref_probe_state *locked_state,
492                                         const xbox_ref_probe_state *unlocked_state,
493                                         int restore_attempted,
494                                         uint32_t restored_capacity,
495                                         int restore_verified)
496 {
497     char tmp_path[XBOX_REF_RESULT_PATH_MAX];
498     const char *final_path;
499     FILE *json;
500     int fd;
501     BOOL moved;
502
503     final_path = (report_path && report_path[0]) ? report_path : "xgd1_layout_probe.json";
504     {
505         int path_length = snprintf(tmp_path,
506                                    sizeof(tmp_path),
507                                    "%s.tmp.%lu",
508                                    final_path,
509                                    (unsigned long)GetCurrentProcessId());
510         if (path_length < 0 || (size_t)path_length >= sizeof(tmp_path))
511         {
512             printf("[XGD1-PROBE] Fatal: report path is too long.\n");
513             return FALSE;
514         }
515     }
516
517     json = fopen(tmp_path, "wb");
518     if (!json)
519     {
520         printf("[XGD1-PROBE] Fatal: could not create temporary report %s.\n", tmp_path);
521         return FALSE;
522     }
523
524     fprintf(json, "{\n");
525     fprintf(json, "  \"schema_version\": %d,\n", XGD1_LAYOUT_PROBE_SCHEMA_VERSION);
526     fprintf(json, "  \"producer\": \"friidump-0.5.3.11\",\n");
527     fprintf(json, "  \"probe\": \"original_xbox_xgd1_logical_layout_boundary\",\n");
528     fprintf(json, "  \"device\": ");
529     xbox_ref_probe_json_string(json, device ? device : "");
530     fprintf(json, ",\n");
531     fprintf(json, "  \"safety\": {\n");
532     fprintf(json, "    \"read_only_sector_commands\": true,\n");
533     fprintf(json, "    \"authentication_handshake_used\": true,\n");
534     fprintf(json, "    \"tray_cycle_used\": true,\n");
535     fprintf(json, "    \"firmware_write_used\": false,\n");
536     fprintf(json, "    \"flash_command_used\": false\n");
537     fprintf(json, "  },\n");
538     fprintf(json, "  \"constants\": {\n");
539     fprintf(json, "    \"full_output_sectors\": %u,\n", XGD1_FULL_REDUMP_SECTORS);
540     fprintf(json, "    \"game_output_start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA);
541     fprintf(json, "    \"game_view_sectors\": %u,\n", REDUMP_SECTORS);
542     fprintf(json, "    \"video_total_sectors\": %u,\n", XGD1_VIDEO_TOTAL_SECTORS);
543     fprintf(json, "    \"video_l0_sectors\": %u,\n", XGD1_VIDEO_L0_SECTORS);
544     fprintf(json, "    \"video_l1_sectors\": %u,\n", XGD1_VIDEO_L1_SECTORS);
545     fprintf(json, "    \"video_l1_output_start_lba\": %u,\n", XGD1_VIDEO_L1_OUTPUT_START_LBA);
546     fprintf(json, "    \"output_layer_break_lba\": %u\n", XGD1_REDUMP_LAYER_BREAK_LBA);
547     fprintf(json, "  },\n");
548     fprintf(json, "  \"entry\": {\n");
549     fprintf(json, "    \"capacity_sectors\": %u,\n", entry_capacity);
550     fprintf(json, "    \"game_view\": %s,\n", entry_game_view ? "true" : "false");
551     fprintf(json, "    \"classification\": ");
552     if (xbox_ref_capacity_is_xgd1_game_view(entry_capacity))
553         xbox_ref_probe_json_string(json, "unlocked_game");
554     else if (xbox_ref_capacity_is_xgd1_locked_view(entry_capacity))
555         xbox_ref_probe_json_string(json, "locked_video");
556     else
557         xbox_ref_probe_json_string(json, "other_or_unknown");
558     fprintf(json, "\n");
559     fprintf(json, "  },\n");
560     fprintf(json, "  \"volume_lock_acquired_at_least_once\": %s,\n", volume_lock_acquired ? "true" : "false");
561     fprintf(json,
562             "  \"required_states_verified\": %s,\n",
563             (locked_state->state_verified && unlocked_state->state_verified) ? "true" : "false");
564     fprintf(json, "  \"states\": [\n");
565     xbox_ref_probe_write_state(json, locked_state, 1);
566     xbox_ref_probe_write_state(json, unlocked_state, 0);
567     fprintf(json, "  ],\n");
568     fprintf(json, "  \"restoration\": {\n");
569     fprintf(json, "    \"attempted\": %s,\n", restore_attempted ? "true" : "false");
570     fprintf(json, "    \"capacity_sectors\": %u,\n", restored_capacity);
571     fprintf(json, "    \"verified\": %s\n", restore_verified ? "true" : "false");
572     fprintf(json, "  },\n");
573     fprintf(json, "  \"interpretation_boundary\": {\n");
574     fprintf(json, "    \"pregame_padding_resolved\": false,\n");
575     fprintf(json, "    \"postgame_padding_resolved\": false,\n");
576     fprintf(json, "    \"game_leadin_resolved\": false,\n");
577     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");
578     fprintf(json, "  }\n");
579     fprintf(json, "}\n");
580
581     if (fflush(json) != 0)
582     {
583         fclose(json);
584         DeleteFileA(tmp_path);
585         return FALSE;
586     }
587
588     fd = _fileno(json);
589     if (fd < 0 || _commit(fd) != 0)
590     {
591         fclose(json);
592         DeleteFileA(tmp_path);
593         return FALSE;
594     }
595
596     if (fclose(json) != 0)
597     {
598         DeleteFileA(tmp_path);
599         return FALSE;
600     }
601
602     moved = MoveFileExA(tmp_path,
603                         final_path,
604                         MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
605     if (!moved)
606     {
607         printf("[XGD1-PROBE] Fatal: could not atomically publish report %s (error %lu).\n",
608                final_path,
609                (unsigned long)GetLastError());
610         DeleteFileA(tmp_path);
611         return FALSE;
612     }
613
614     printf("[XGD1-PROBE] Atomic report written: %s\n", final_path);
615     return TRUE;
616 }
617
618 static int xbox_ref_xgd1_layout_probe_core(HANDLE hDevice,
619                                             const char *device,
620                                             const char *report_path,
621                                             BOOL owns_handle)
622 {
623     xbox_ref_probe_state locked_state;
624     xbox_ref_probe_state unlocked_state;
625     uint32_t entry_capacity;
626     uint32_t restored_capacity;
627     int entry_game_view;
628     int volume_lock_acquired;
629     int volume_lock_ever;
630     int restore_attempted;
631     int restore_verified;
632     int states_verified;
633     DWORD bytes_returned;
634     BOOL report_ok;
635
636     memset(&locked_state, 0, sizeof(locked_state));
637     memset(&unlocked_state, 0, sizeof(unlocked_state));
638     locked_state.name = "locked_video";
639     unlocked_state.name = "unlocked_game";
640     entry_capacity = 0;
641     restored_capacity = 0;
642     entry_game_view = 0;
643     volume_lock_acquired = 0;
644     volume_lock_ever = 0;
645     restore_attempted = 0;
646     restore_verified = 0;
647     states_verified = 0;
648     bytes_returned = 0;
649
650     xbox_ref_log_open_for_target(report_path, xbox_ref_drive_letter_from_device(device));
651     printf("[XGD1-PROBE] Starting read-only logical layout boundary probe.\n");
652     printf("[XGD1-PROBE] The existing XGD1 output layout will not be modified.\n");
653
654     if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
655     {
656         printf("[XGD1-PROBE] Fatal: invalid drive handle.\n");
657         if (owns_handle)
658             xbox_ref_log_close();
659         return 1;
660     }
661
662     if (!EnsureDriveReady(hDevice, 30000))
663     {
664         printf("[XGD1-PROBE] Fatal: media did not become ready.\n");
665         if (owns_handle)
666             xbox_ref_log_close();
667         return 1;
668     }
669
670     entry_capacity = GetTotalSectors(hDevice);
671     entry_game_view = xbox_ref_capacity_is_xgd1_game_view(entry_capacity) ? 1 : 0;
672     printf("[XGD1-PROBE] Entry READ CAPACITY: %u sectors (%s).\n",
673            entry_capacity,
674            entry_game_view ? "game view" : "locked/video or unknown view");
675
676     DeviceIoControl(hDevice,
677                     FSCTL_UNLOCK_VOLUME,
678                     NULL,
679                     0,
680                     NULL,
681                     0,
682                     &bytes_returned,
683                     NULL);
684
685     printf("[XGD1-PROBE] Cycling tray to establish locked/video view.\n");
686     AutomateTrayCycle(hDevice);
687     locked_state.capacity = xbox_ref_refresh_ready_capacity(hDevice);
688     locked_state.capacity_valid = locked_state.capacity != 0U;
689     locked_state.state_verified = (locked_state.capacity_valid &&
690                                    xbox_ref_capacity_is_xgd1_locked_view(locked_state.capacity)) ? 1 : 0;
691     printf("[XGD1-PROBE] Locked/video READ CAPACITY: %u sectors; verified=%s.\n",
692            locked_state.capacity,
693            locked_state.state_verified ? "yes" : "no");
694
695     if (DeviceIoControl(hDevice,
696                         FSCTL_LOCK_VOLUME,
697                         NULL,
698                         0,
699                         NULL,
700                         0,
701                         &bytes_returned,
702                         NULL))
703     {
704         volume_lock_acquired = 1;
705         volume_lock_ever = 1;
706         printf("[XGD1-PROBE] Windows volume lock acquired.\n");
707     }
708     else
709     {
710         printf("[XGD1-PROBE][WARN] Windows volume lock was not acquired; close AutoPlay and Explorer dialogs if results are unstable.\n");
711     }
712
713     SetDriveSpeedMax(hDevice);
714     xbox_ref_probe_capture_state(hDevice,
715                                  &locked_state,
716                                  xbox_ref_locked_probe_points,
717                                  (uint32_t)(sizeof(xbox_ref_locked_probe_points) /
718                                             sizeof(xbox_ref_locked_probe_points[0])));
719
720     printf("[XGD1-PROBE] Applying the full Xbox handshake to establish game view.\n");
721     UnlockDrive(hDevice);
722     unlocked_state.capacity = xbox_ref_refresh_ready_capacity(hDevice);
723     unlocked_state.capacity_valid = unlocked_state.capacity != 0U;
724     unlocked_state.state_verified = (unlocked_state.capacity_valid &&
725                                      xbox_ref_capacity_is_xgd1_game_view(unlocked_state.capacity)) ? 1 : 0;
726     printf("[XGD1-PROBE] Unlocked/game READ CAPACITY: %u sectors; verified=%s.\n",
727            unlocked_state.capacity,
728            unlocked_state.state_verified ? "yes" : "no");
729
730     xbox_ref_probe_capture_state(hDevice,
731                                  &unlocked_state,
732                                  xbox_ref_unlocked_probe_points,
733                                  (uint32_t)(sizeof(xbox_ref_unlocked_probe_points) /
734                                             sizeof(xbox_ref_unlocked_probe_points[0])));
735
736     if (volume_lock_acquired)
737     {
738         DeviceIoControl(hDevice,
739                         FSCTL_UNLOCK_VOLUME,
740                         NULL,
741                         0,
742                         NULL,
743                         0,
744                         &bytes_returned,
745                         NULL);
746         volume_lock_acquired = 0;
747     }
748
749     if (!entry_game_view && entry_capacity != 0U)
750     {
751         restore_attempted = 1;
752         printf("[XGD1-PROBE] Restoring the entry locked/video state with a final tray cycle.\n");
753         AutomateTrayCycle(hDevice);
754         restored_capacity = xbox_ref_refresh_ready_capacity(hDevice);
755         restore_verified = (restored_capacity != 0U &&
756                             xbox_ref_capacity_is_xgd1_locked_view(restored_capacity)) ? 1 : 0;
757     }
758     else
759     {
760         restored_capacity = GetTotalSectors(hDevice);
761         restore_verified = (entry_game_view &&
762                             xbox_ref_capacity_is_xgd1_game_view(restored_capacity)) ? 1 : 0;
763     }
764
765     states_verified = (locked_state.state_verified &&
766                        unlocked_state.state_verified) ? 1 : 0;
767
768     report_ok = xbox_ref_probe_write_report(report_path,
769                                              device,
770                                              entry_capacity,
771                                              entry_game_view,
772                                              volume_lock_ever,
773                                              &locked_state,
774                                              &unlocked_state,
775                                              restore_attempted,
776                                              restored_capacity,
777                                              restore_verified);
778
779     if (owns_handle)
780     {
781         printf("[XGD1-PROBE] Issuing STOP UNIT / spin-down after probe... ");
782         printf("%s\n", StopDriveUnit(hDevice) ? "OK" : "FAILED");
783         CloseDrive(hDevice);
784         xbox_ref_log_close();
785     }
786
787     if (!report_ok)
788         return 1;
789     if (!states_verified)
790     {
791         printf("[XGD1-PROBE] Probe report is partial because one or more drive states were not verified.\n");
792         return 1;
793     }
794
795     printf("[XGD1-PROBE] Probe complete. Synthetic ranges remain unresolved until the captured evidence supports a source mapping.\n");
796     return 0;
797 }
798
799
800 static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, const char *filename, char mode, BOOL ownsHandle, xbox_ref_dump_result *result)
801 {
802     char driveLetter;
803     XboxGameInfo game;
804     BOOL isDualLayer = FALSE;
805     bool isDualLayerBool = false;
806     char mediaId[33] = {0};
807     char finalFilename[MAX_PATH] = {0};
808     BOOL filenameOverride;
809     uint32_t totalSectors = 0;
810     DWORD dwBytesReturned = 0;
811     BOOL volumeLocked = FALSE;
812     BOOL ok = FALSE;
813
814     xbox_ref_dump_result_init(result);
815     if (result) {
816         result->attempted = 1;
817         result->mode = mode;
818     }
819
820     filenameOverride = (filename && filename[0]);
821
822     if (mode != '1' && mode != '2') {
823         xbox_ref_log_open_for_target(filename, 0);
824         printf("[XBOX-REF] Fatal: unsupported Xbox reference dump mode '%c'.\n", mode);
825         if (ownsHandle)
826             xbox_ref_log_close();
827         return 1;
828     }
829
830     driveLetter = xbox_ref_drive_letter_from_device(device);
831     if (!driveLetter) {
832         xbox_ref_log_open_for_target(filename, 0);
833         printf("[XBOX-REF] Fatal: could not parse drive letter from device '%s'.\n", device ? device : "(null)");
834         if (ownsHandle)
835             xbox_ref_log_close();
836         return 1;
837     }
838
839     xbox_ref_log_open_for_target(filename, driveLetter);
840     printf("[XBOX-REF] Using copied original GDR-8050L dumper flow for drive %c:.\n", driveLetter);
841     printf("[XBOX-REF] Handle mode: %s.\n", ownsHandle ? "opened by xbox_ref wrapper" : "using FriiDump's existing drive HANDLE");
842     printf("[XBOX-REF] Preserving reference timing sleeps: tray 3000ms, ready-settle 1500ms/10000ms fallback, RefreshVolume 1000ms + post-refresh 2000ms.\n");
843
844     if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) {
845         printf("[XBOX-REF] Fatal: invalid drive handle for %c:.\n", driveLetter);
846         goto cleanup;
847     }
848
849     {
850         uint32_t entrySectors;
851         uint32_t verifiedSectors;
852         BOOL preparedAfterHandshake = FALSE;
853
854         printf("Opening drive %c: for state-aware Xbox handshake...\n", driveLetter);
855         if (!EnsureDriveReady(hDevice, 30000)) {
856             printf("[XBOX-REF] Fatal: drive did not become ready before Xbox state preparation.\n");
857             goto cleanup;
858         }
859
860         entrySectors = GetTotalSectors(hDevice);
861         printf("[XBOX-REF] Entry READ CAPACITY: %u sectors.\n", entrySectors);
862
863         if (xbox_ref_capacity_is_game_view(entrySectors)) {
864             printf("[XBOX-REF] Entry state already exposes the Xbox game view; skipping the redundant initial handshake and tray cycle.\n");
865         } else {
866             printf("[XBOX-REF] Entry state appears locked/video; attempting the full handshake directly without a media transition.\n");
867             UnlockDrive(hDevice);
868             verifiedSectors = xbox_ref_refresh_ready_capacity(hDevice);
869             preparedAfterHandshake = TRUE;
870             printf("[XBOX-REF] Direct-handshake READ CAPACITY: %u sectors.\n", verifiedSectors);
871
872             if (!xbox_ref_capacity_is_game_view(verifiedSectors)) {
873                 printf("[XBOX-REF][WARN] Direct handshake did not expose the Xbox game view; performing one tray-cycle recovery and retry.\n");
874                 AutomateTrayCycle(hDevice);
875                 printf("[XBOX-REF] Re-applying the full handshake after recovery media change.\n");
876                 UnlockDrive(hDevice);
877                 verifiedSectors = xbox_ref_refresh_ready_capacity(hDevice);
878                 printf("[XBOX-REF] Recovery-handshake READ CAPACITY: %u sectors.\n", verifiedSectors);
879
880                 if (!xbox_ref_capacity_is_game_view(verifiedSectors)) {
881                     printf("[XBOX-REF] Fatal: Xbox game view was not established after direct and recovery handshakes.\n");
882                     goto cleanup;
883                 }
884             }
885         }
886
887         if (!preparedAfterHandshake)
888             xbox_ref_refresh_ready_capacity(hDevice);
889     }
890
891     printf("Setting drive to maximum performance mode...\n");
892     SetDriveSpeedMax(hDevice);
893
894     if (!DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &dwBytesReturned, NULL)) {
895         printf("[!] Warning: Could not lock volume. Close any open folders pointing to the drive.\n");
896         printf("[XBOX-REF] The reference dumper treats this as fatal before XBE/title probing.\n");
897         goto cleanup;
898     }
899     volumeLocked = TRUE;
900
901     printf("Searching for Xbox Game Signature (Raw SCSI Scan)...\n");
902     game = GetXboxGameInfo(hDevice);
903     printf("Game Title Raw: %s\n", game.TitleName);
904     ReadXboxGameDir(hDevice);
905
906     totalSectors = GetXboxPhysicalSectors(hDevice);
907     isDualLayerBool = false;
908     GetDiscMetadata(hDevice, &totalSectors, &isDualLayerBool, &game);
909     isDualLayer = isDualLayerBool ? TRUE : FALSE;
910     GetMediaID(hDevice, mediaId);
911
912     if (result) {
913         if (game.Success && game.TitleName[0]) {
914             strncpy(result->title, game.TitleName, sizeof(result->title) - 1);
915             result->title[sizeof(result->title) - 1] = '\0';
916         }
917         if (mediaId[0]) {
918             strncpy(result->media_id, mediaId, sizeof(result->media_id) - 1);
919             result->media_id[sizeof(result->media_id) - 1] = '\0';
920         }
921     }
922
923     if (game.Success) {
924         char titleCopy[sizeof(game.TitleName)];
925         memset(titleCopy, 0, sizeof(titleCopy));
926         strncpy(titleCopy, game.TitleName, sizeof(titleCopy) - 1);
927         SanitizeFilename(titleCopy);
928         if (filenameOverride) {
929             strncpy(finalFilename, filename, sizeof(finalFilename) - 1);
930             finalFilename[sizeof(finalFilename) - 1] = '\0';
931             printf("[XBOX-REF] Output filename override: %s\n", finalFilename);
932         } else {
933             snprintf(finalFilename, sizeof(finalFilename), "%s[%s].%s",
934                      titleCopy[0] ? titleCopy : "XboxDisc",
935                      mediaId[0] ? mediaId : "UNKNOWN_ID",
936                      (mode == '1') ? "iso" : "xiso");
937             printf("[XBOX-REF] XBE/DMI-derived output filename: %s\n", finalFilename);
938             xbox_ref_log_retarget(finalFilename);
939         }
940         DisplayXboxGameInfo(game);
941         printf("\n--- DISC INFORMATION ---\n");
942         printf("Physical Sectors: %u\n", totalSectors);
943         printf("Layers:           %d\n", isDualLayer ? 2 : 1);
944         printf("Media ID:         %s\n", mediaId);
945         printf("Target File:      %s\n", finalFilename);
946         printf("-------------------------------\n\n");
947     } else {
948         if (filenameOverride) {
949             strncpy(finalFilename, filename, sizeof(finalFilename) - 1);
950             finalFilename[sizeof(finalFilename) - 1] = '\0';
951             printf("Error: Could not retrieve Xbox game information; using override filename %s.\n", finalFilename);
952         } else {
953             printf("Error: Could not retrieve Xbox game information and no output filename override was supplied.\n");
954             printf("       Rerun with -i <file> or -X <file> if metadata probing cannot complete.\n");
955             goto cleanup;
956         }
957     }
958
959     if (result && finalFilename[0]) {
960         strncpy(result->output_path, finalFilename, sizeof(result->output_path) - 1);
961         result->output_path[sizeof(result->output_path) - 1] = '\0';
962     }
963
964     if (totalSectors != (uint32_t)(REDUMP_SECTORS)) {
965         printf("[WARNING] Unlocked drive-reported sectors: %u does not match expected Xbox game-view sectors: %u\n", totalSectors, (uint32_t)(REDUMP_SECTORS));
966         printf("-Homebrew games may have different sizes.\n\n");
967     }
968
969     if (totalSectors == 0) {
970         printf("Error: Could not determine total sectors for dumping.\n");
971         goto cleanup;
972     }
973
974     printf("Starting dump with copied DumpXboxGameDisc() mode %c...\n", mode);
975     ok = DumpXboxGameDisc(hDevice, finalFilename, mode, totalSectors, isDualLayerBool, FALSE, result);
976
977 cleanup:
978     if (volumeLocked)
979         DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwBytesReturned, NULL);
980     if (ownsHandle && hDevice != INVALID_HANDLE_VALUE && hDevice != NULL) {
981         printf("Issuing STOP UNIT / spin-down after dump attempt... ");
982         if (StopDriveUnit(hDevice)) printf("OK\n"); else printf("FAILED\n");
983         CloseDrive(hDevice);
984     } else {
985         printf("[XBOX-REF] Returning to FriiDump; final STOP UNIT cleanup will use the same handle.\n");
986     }
987     if (ownsHandle) {
988         printf("[XBOX-REF] Log file complete: %s\n", xbox_ref_log_path() ? xbox_ref_log_path() : "(unavailable)");
989         xbox_ref_log_close();
990     } else if (ok) {
991         printf("[XBOX-REF] Returning finalized output evidence to FriiDump's shared Redump verifier.\n");
992     } else {
993         printf("[XBOX-REF] No completed output evidence is available for shared Redump verification.\n");
994     }
995     return ok ? 0 : 1;
996 }
997
998 int xbox_ref_xgd1_layout_probe_with_handle(void *native_handle,
999                                                 const char *device,
1000                                                 const char *report_path)
1001 {
1002     return xbox_ref_xgd1_layout_probe_core((HANDLE)native_handle,
1003                                             device,
1004                                             report_path,
1005                                             FALSE);
1006 }
1007
1008 int xbox_ref_xgd1_layout_probe(const char *device,
1009                                const char *report_path)
1010 {
1011     char drive_letter;
1012     HANDLE hDevice;
1013
1014     drive_letter = xbox_ref_drive_letter_from_device(device);
1015     if (!drive_letter)
1016     {
1017         xbox_ref_log_open_for_target(report_path, 0);
1018         printf("[XGD1-PROBE] Fatal: could not parse drive letter from device '%s'.\n",
1019                device ? device : "(null)");
1020         xbox_ref_log_close();
1021         return 1;
1022     }
1023
1024     hDevice = OpenDrive(drive_letter);
1025     if (hDevice == INVALID_HANDLE_VALUE)
1026     {
1027         xbox_ref_log_open_for_target(report_path, drive_letter);
1028         printf("[XGD1-PROBE] Fatal: cannot open drive %c:. Run as Administrator.\n",
1029                drive_letter);
1030         xbox_ref_log_close();
1031         return 1;
1032     }
1033
1034     return xbox_ref_xgd1_layout_probe_core(hDevice,
1035                                             device,
1036                                             report_path,
1037                                             TRUE);
1038 }
1039
1040 int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
1041 {
1042     return xbox_ref_gdr8050l_dump_core((HANDLE)native_handle, device, filename, mode, FALSE, result);
1043 }
1044
1045 int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
1046 {
1047     char driveLetter;
1048     HANDLE hDevice;
1049
1050     driveLetter = xbox_ref_drive_letter_from_device(device);
1051     if (!driveLetter) {
1052         xbox_ref_log_open_for_target(filename, 0);
1053         printf("[XBOX-REF] Fatal: could not parse drive letter from device '%s'.\n", device ? device : "(null)");
1054         xbox_ref_log_close();
1055         return 1;
1056     }
1057
1058     hDevice = OpenDrive(driveLetter);
1059     if (hDevice == INVALID_HANDLE_VALUE) {
1060         xbox_ref_log_open_for_target(filename, driveLetter);
1061         printf("[XBOX-REF] Fatal: Cannot open drive %c:. Run as Administrator.\n", driveLetter);
1062         xbox_ref_log_close();
1063         return 1;
1064     }
1065
1066     return xbox_ref_gdr8050l_dump_core(hDevice, device, filename, mode, TRUE, result);
1067 }
1068 #else
1069 int xbox_ref_xgd1_layout_probe_with_handle(void *native_handle,
1070                                                 const char *device,
1071                                                 const char *report_path)
1072 {
1073     (void)native_handle;
1074     (void)device;
1075     (void)report_path;
1076     return 1;
1077 }
1078
1079 int xbox_ref_xgd1_layout_probe(const char *device,
1080                                const char *report_path)
1081 {
1082     (void)device;
1083     (void)report_path;
1084     return 1;
1085 }
1086
1087 int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
1088 {
1089     (void)native_handle; (void)device; (void)filename; (void)mode; xbox_ref_dump_result_init(result); return 1;
1090 }
1091
1092 int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode, xbox_ref_dump_result *result)
1093 {
1094     (void)device; (void)filename; (void)mode; xbox_ref_dump_result_init(result); return 1;
1095 }
1096 #endif