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