]> FriiDump Source - friidump.git/blob - libfriidump/xbox_xgd1_raw_id_probe.c
FriiDump 0.5.3.15: close XGD1 geometry and lead-in capture
[friidump.git] / libfriidump / xbox_xgd1_raw_id_probe.c
1 #include "xbox_ref_bridge.h"
2
3 #include <string.h>
4
5 #ifdef WIN32
6 #define WIN32_LEAN_AND_MEAN
7 #define _WIN32_WINNT 0x0500
8 #include <windows.h>
9 #include <winioctl.h>
10 #include <ntddscsi.h>
11 #include <stdio.h>
12 #include <stddef.h>
13 #include <io.h>
14
15 #ifndef SCSI_IOCTL_DATA_UNSPECIFIED
16 #define SCSI_IOCTL_DATA_UNSPECIFIED 2
17 #endif
18
19 #include "xbox_ref/utils.h"
20 #include "xbox_ref/sha1.h"
21 #include "xbox_ref/unlock.h"
22 #include "xbox_ref/xbox_ref_log.h"
23
24 #define printf xbox_ref_printf
25
26 #define XGD1_RAW_ID_PROBE_SCHEMA_VERSION 2
27 #define XGD1_RAW_ID_CACHE_BASE 0x80000000U
28 #define XGD1_RAW_SECTOR_BYTES 2064U
29 #define XGD1_LOGICAL_SECTOR_BYTES 2048U
30 #define XGD1_CACHE_FILL_SECTORS 16U
31 #define XGD1_RAW_ID_MAX_SAMPLES 16U
32
33 typedef struct xgd1_sptd_with_sense_s
34 {
35     SCSI_PASS_THROUGH_DIRECT sptd;
36     ULONG filler;
37     UCHAR sense[32];
38 } xgd1_sptd_with_sense;
39
40 typedef struct xgd1_cmd_result_s
41 {
42     int ok;
43     DWORD win32_error;
44     UCHAR scsi_status;
45     UCHAR sense_key;
46     UCHAR asc;
47     UCHAR ascq;
48 } xgd1_cmd_result;
49
50 typedef struct xgd1_raw_id_sample_s
51 {
52     uint32_t lba;
53     const char *label;
54     uint32_t cache_fill_lba;
55     uint32_t cache_index;
56     uint32_t cache_address;
57     xgd1_cmd_result cache_flush;
58     xgd1_cmd_result cache_fill;
59     xgd1_cmd_result memdump_header;
60     xgd1_cmd_result memdump_edc;
61     int raw_header_valid;
62     UCHAR sector_information;
63     uint32_t layer_number;
64     uint32_t physical_sector_number;
65     uint32_t normalized_physical_sector_number;
66     uint32_t output_lba;
67     uint32_t expected_layer_number;
68     uint32_t expected_physical_sector_number;
69     uint32_t expected_normalized_physical_sector_number;
70     int layer_matches_expected;
71     int psn_matches_expected;
72     int geometry_matches_expected;
73     char logical_sha1[41];
74     unsigned char logical[XGD1_LOGICAL_SECTOR_BYTES];
75     unsigned char raw_header[12];
76     unsigned char raw_edc[4];
77 } xgd1_raw_id_sample;
78
79 typedef struct xgd1_raw_id_state_s
80 {
81     const char *name;
82     int locked_view;
83     uint32_t capacity;
84     int capacity_valid;
85     int state_verified;
86     uint32_t sample_count;
87     uint32_t geometry_validation_failures;
88     int geometry_verified;
89     xgd1_raw_id_sample samples[XGD1_RAW_ID_MAX_SAMPLES];
90 } xgd1_raw_id_state;
91
92 typedef struct xgd1_probe_point_s
93 {
94     uint32_t lba;
95     const char *label;
96 } xgd1_probe_point;
97
98 static const xgd1_probe_point xgd1_locked_points[] = {
99     {0U, "locked_logical_start"},
100     {31U, "locked_early_lba_31"},
101     {32U, "locked_early_lba_32"},
102     {XGD1_VIDEO_L0_SECTORS - 1U, "video_l0_last"},
103     {XGD1_VIDEO_L0_SECTORS, "video_l1_first"},
104     {XGD1_VIDEO_TOTAL_SECTORS - 1U, "locked_video_last"}
105 };
106
107 static const xgd1_probe_point xgd1_unlocked_points[] = {
108     {0U, "unlocked_source_start"},
109     {XGD1_XISO_LEADIN_SECTORS - 1U, "unlocked_leadin_last"},
110     {XGD1_GAME_SOURCE_START_LBA, "xdfs_source_start"},
111     {XGD1_GAME_SOURCE_START_LBA + 1U, "xdfs_source_next"},
112     {(XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA) - 1U, "mapped_layer_break_source_minus_one"},
113     {XGD1_REDUMP_LAYER_BREAK_LBA - XGD1_GAME_OUTPUT_START_LBA, "mapped_layer_break_source"},
114     {REDUMP_SECTORS - 1U, "unlocked_source_last"}
115 };
116
117 static char xgd1_drive_letter_from_device(const char *device)
118 {
119     if (!device || !device[0]) return 0;
120     if (device[0] && device[1] == ':') return device[0];
121     if (device[0] == '\\' && device[1] == '\\' &&
122         device[2] == '.' && device[3] == '\\' &&
123         device[4] && device[5] == ':')
124         return device[4];
125     return device[0];
126 }
127
128 static int xgd1_capacity_is_locked(uint32_t sectors)
129 {
130     return sectors == XGD1_VIDEO_TOTAL_SECTORS;
131 }
132
133 static int xgd1_capacity_is_game(uint32_t sectors)
134 {
135     return sectors == REDUMP_SECTORS;
136 }
137
138 static uint32_t xgd1_refresh_ready_capacity(HANDLE hDevice)
139 {
140     RefreshVolume(hDevice);
141     Sleep(2000);
142     EnsureDriveReady(hDevice, 30000);
143     return GetTotalSectors(hDevice);
144 }
145
146 static void xgd1_parse_sense(const UCHAR *sense,
147                              UCHAR *sense_key,
148                              UCHAR *asc,
149                              UCHAR *ascq)
150 {
151     UCHAR response;
152
153     if (sense_key) *sense_key = 0;
154     if (asc) *asc = 0;
155     if (ascq) *ascq = 0;
156     if (!sense)
157         return;
158
159     response = (UCHAR)(sense[0] & 0x7f);
160     if (response == 0x70 || response == 0x71)
161     {
162         if (sense_key) *sense_key = (UCHAR)(sense[2] & 0x0f);
163         if (asc) *asc = sense[12];
164         if (ascq) *ascq = sense[13];
165     }
166     else if (response == 0x72 || response == 0x73)
167     {
168         if (sense_key) *sense_key = (UCHAR)(sense[1] & 0x0f);
169         if (asc) *asc = sense[2];
170         if (ascq) *ascq = sense[3];
171     }
172 }
173
174 static void xgd1_sha1(const unsigned char *data,
175                        uint32_t data_size,
176                        char out_sha1[41])
177 {
178     SHA1_CTX ctx;
179     unsigned char digest[20];
180     uint32_t i;
181
182     if (!out_sha1)
183         return;
184     out_sha1[0] = '\0';
185     if (!data || data_size == 0)
186         return;
187
188     SHA1_Init(&ctx);
189     SHA1_Update(&ctx, data, data_size);
190     SHA1_Final(digest, &ctx);
191
192     for (i = 0; i < 20U; ++i)
193         sprintf(out_sha1 + (i * 2U), "%02x", digest[i]);
194     out_sha1[40] = '\0';
195 }
196
197 static void xgd1_cmd_result_init(xgd1_cmd_result *result)
198 {
199     if (result)
200         memset(result, 0, sizeof(*result));
201 }
202
203 static void xgd1_execute_data_in(HANDLE hDevice,
204                                  const UCHAR *cdb,
205                                  UCHAR cdb_length,
206                                  unsigned char *buffer,
207                                  DWORD buffer_length,
208                                  DWORD timeout_seconds,
209                                  xgd1_cmd_result *result)
210 {
211     xgd1_sptd_with_sense packet;
212     DWORD bytes_returned;
213     BOOL ioctl_ok;
214
215     xgd1_cmd_result_init(result);
216     if (!result || !cdb || !buffer || buffer_length == 0U)
217         return;
218
219     memset(buffer, 0, buffer_length);
220     memset(&packet, 0, sizeof(packet));
221
222     packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
223     packet.sptd.CdbLength = cdb_length;
224     packet.sptd.SenseInfoLength = sizeof(packet.sense);
225     packet.sptd.DataIn = SCSI_IOCTL_DATA_IN;
226     packet.sptd.DataTransferLength = buffer_length;
227     packet.sptd.TimeOutValue = timeout_seconds;
228     packet.sptd.DataBuffer = buffer;
229     packet.sptd.SenseInfoOffset = offsetof(xgd1_sptd_with_sense, sense);
230     memcpy(packet.sptd.Cdb, cdb, cdb_length);
231
232     bytes_returned = 0;
233     ioctl_ok = DeviceIoControl(hDevice,
234                                IOCTL_SCSI_PASS_THROUGH_DIRECT,
235                                &packet,
236                                sizeof(packet),
237                                &packet,
238                                sizeof(packet),
239                                &bytes_returned,
240                                NULL);
241
242     result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
243     result->scsi_status = packet.sptd.ScsiStatus;
244     xgd1_parse_sense(packet.sense,
245                      &result->sense_key,
246                      &result->asc,
247                      &result->ascq);
248     result->ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
249 }
250
251 static void xgd1_execute_no_data(HANDLE hDevice,
252                                  const UCHAR *cdb,
253                                  UCHAR cdb_length,
254                                  DWORD timeout_seconds,
255                                  xgd1_cmd_result *result)
256 {
257     xgd1_sptd_with_sense packet;
258     DWORD bytes_returned;
259     BOOL ioctl_ok;
260
261     xgd1_cmd_result_init(result);
262     if (!result || !cdb)
263         return;
264
265     memset(&packet, 0, sizeof(packet));
266     packet.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
267     packet.sptd.CdbLength = cdb_length;
268     packet.sptd.SenseInfoLength = sizeof(packet.sense);
269     packet.sptd.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
270     packet.sptd.DataTransferLength = 0;
271     packet.sptd.TimeOutValue = timeout_seconds;
272     packet.sptd.DataBuffer = NULL;
273     packet.sptd.SenseInfoOffset = offsetof(xgd1_sptd_with_sense, sense);
274     memcpy(packet.sptd.Cdb, cdb, cdb_length);
275
276     bytes_returned = 0;
277     ioctl_ok = DeviceIoControl(hDevice,
278                                IOCTL_SCSI_PASS_THROUGH_DIRECT,
279                                &packet,
280                                sizeof(packet),
281                                &packet,
282                                sizeof(packet),
283                                &bytes_returned,
284                                NULL);
285
286     result->win32_error = ioctl_ok ? ERROR_SUCCESS : GetLastError();
287     result->scsi_status = packet.sptd.ScsiStatus;
288     xgd1_parse_sense(packet.sense,
289                      &result->sense_key,
290                      &result->asc,
291                      &result->ascq);
292     result->ok = (ioctl_ok && packet.sptd.ScsiStatus == 0) ? 1 : 0;
293 }
294
295 static void xgd1_set_lba(UCHAR *cdb, uint32_t lba)
296 {
297     cdb[2] = (UCHAR)((lba >> 24) & 0xff);
298     cdb[3] = (UCHAR)((lba >> 16) & 0xff);
299     cdb[4] = (UCHAR)((lba >> 8) & 0xff);
300     cdb[5] = (UCHAR)(lba & 0xff);
301 }
302
303 static void xgd1_build_memdump_cdb(UCHAR cdb[12],
304                                    uint32_t address,
305                                    uint32_t length)
306 {
307     memset(cdb, 0, 12);
308     cdb[0] = 0xE7;
309     cdb[1] = 0x48;
310     cdb[2] = 0x49;
311     cdb[3] = 0x54;
312     cdb[4] = 0x01;
313     cdb[6] = (UCHAR)((address >> 24) & 0xff);
314     cdb[7] = (UCHAR)((address >> 16) & 0xff);
315     cdb[8] = (UCHAR)((address >> 8) & 0xff);
316     cdb[9] = (UCHAR)(address & 0xff);
317     cdb[10] = (UCHAR)((length >> 8) & 0xff);
318     cdb[11] = (UCHAR)(length & 0xff);
319 }
320
321 static void xgd1_set_expected_geometry(int locked_view,
322                                        xgd1_raw_id_sample *sample)
323 {
324     uint32_t output_lba;
325     uint32_t layer_zero_last_psn;
326     uint32_t normalized_psn;
327     uint32_t expected_normalized_psn;
328     uint32_t expected_layer;
329     uint32_t expected_raw_psn;
330
331     if (!sample)
332         return;
333
334     if (locked_view)
335     {
336         if (sample->lba < XGD1_VIDEO_L0_SECTORS)
337             output_lba = sample->lba;
338         else
339             output_lba = XGD1_VIDEO_L1_OUTPUT_START_LBA +
340                          (sample->lba - XGD1_VIDEO_L0_SECTORS);
341     }
342     else
343     {
344         output_lba = XGD1_GAME_OUTPUT_START_LBA + sample->lba;
345     }
346
347     layer_zero_last_psn = 0x30000U + XGD1_REDUMP_LAYER_BREAK_LBA - 1U;
348     if (output_lba < XGD1_REDUMP_LAYER_BREAK_LBA)
349     {
350         expected_layer = 0U;
351         expected_normalized_psn = 0x30000U + output_lba;
352         expected_raw_psn = expected_normalized_psn;
353     }
354     else
355     {
356         expected_layer = 1U;
357         expected_normalized_psn = layer_zero_last_psn -
358             (output_lba - XGD1_REDUMP_LAYER_BREAK_LBA);
359         expected_raw_psn = (~expected_normalized_psn) & 0x00ffffffU;
360     }
361
362     normalized_psn = (sample->layer_number != 0U)
363         ? ((~sample->physical_sector_number) & 0x00ffffffU)
364         : sample->physical_sector_number;
365
366     sample->output_lba = output_lba;
367     sample->normalized_physical_sector_number = normalized_psn;
368     sample->expected_layer_number = expected_layer;
369     sample->expected_physical_sector_number = expected_raw_psn;
370     sample->expected_normalized_physical_sector_number = expected_normalized_psn;
371     sample->layer_matches_expected =
372         (sample->layer_number == expected_layer) ? 1 : 0;
373     sample->psn_matches_expected =
374         (sample->physical_sector_number == expected_raw_psn) ? 1 : 0;
375     sample->geometry_matches_expected =
376         (sample->layer_matches_expected && sample->psn_matches_expected) ? 1 : 0;
377 }
378
379
380 static void xgd1_capture_sample(HANDLE hDevice,
381                                 uint32_t capacity,
382                                 int locked_view,
383                                 const xgd1_probe_point *point,
384                                 xgd1_raw_id_sample *sample)
385 {
386     unsigned char *read_buffer;
387     UCHAR flush_cdb[12];
388     UCHAR read_cdb[12];
389     UCHAR e7_cdb[12];
390     uint32_t fill_lba;
391     uint32_t index;
392     uint32_t address;
393
394     if (!sample || !point)
395         return;
396
397     memset(sample, 0, sizeof(*sample));
398     sample->lba = point->lba;
399     sample->label = point->label;
400
401     if (capacity < XGD1_CACHE_FILL_SECTORS || point->lba >= capacity)
402     {
403         sample->cache_flush.win32_error = ERROR_INVALID_PARAMETER;
404         return;
405     }
406
407     fill_lba = point->lba & ~(XGD1_CACHE_FILL_SECTORS - 1U);
408     if (fill_lba > capacity - XGD1_CACHE_FILL_SECTORS)
409         fill_lba = capacity - XGD1_CACHE_FILL_SECTORS;
410     index = point->lba - fill_lba;
411     address = XGD1_RAW_ID_CACHE_BASE + index * XGD1_RAW_SECTOR_BYTES;
412
413     sample->cache_fill_lba = fill_lba;
414     sample->cache_index = index;
415     sample->cache_address = address;
416
417     memset(flush_cdb, 0, sizeof(flush_cdb));
418     flush_cdb[0] = 0xA8;
419     flush_cdb[1] = 0x08;
420     xgd1_set_lba(flush_cdb, fill_lba);
421     xgd1_execute_no_data(hDevice,
422                          flush_cdb,
423                          12,
424                          15,
425                          &sample->cache_flush);
426     if (!sample->cache_flush.ok)
427         return;
428
429     read_buffer = (unsigned char *)VirtualAlloc(
430         NULL,
431         XGD1_CACHE_FILL_SECTORS * XGD1_LOGICAL_SECTOR_BYTES,
432         MEM_COMMIT | MEM_RESERVE,
433         PAGE_READWRITE);
434     if (!read_buffer)
435     {
436         sample->cache_fill.win32_error = ERROR_NOT_ENOUGH_MEMORY;
437         return;
438     }
439
440     memset(read_cdb, 0, sizeof(read_cdb));
441     read_cdb[0] = 0xA8;
442     xgd1_set_lba(read_cdb, fill_lba);
443     read_cdb[9] = (UCHAR)XGD1_CACHE_FILL_SECTORS;
444     read_cdb[10] = 0x80;
445
446     xgd1_execute_data_in(hDevice,
447                          read_cdb,
448                          12,
449                          read_buffer,
450                          XGD1_CACHE_FILL_SECTORS * XGD1_LOGICAL_SECTOR_BYTES,
451                          15,
452                          &sample->cache_fill);
453
454     if (sample->cache_fill.ok)
455     {
456         memcpy(sample->logical,
457                read_buffer + index * XGD1_LOGICAL_SECTOR_BYTES,
458                XGD1_LOGICAL_SECTOR_BYTES);
459         xgd1_sha1(sample->logical,
460                   XGD1_LOGICAL_SECTOR_BYTES,
461                   sample->logical_sha1);
462
463         xgd1_build_memdump_cdb(e7_cdb, address, 12U);
464         xgd1_execute_data_in(hDevice,
465                              e7_cdb,
466                              12,
467                              sample->raw_header,
468                              12U,
469                              15,
470                              &sample->memdump_header);
471
472         xgd1_build_memdump_cdb(e7_cdb, address + 2060U, 4U);
473         xgd1_execute_data_in(hDevice,
474                              e7_cdb,
475                              12,
476                              sample->raw_edc,
477                              4U,
478                              15,
479                              &sample->memdump_edc);
480
481         if (sample->memdump_header.ok && sample->memdump_edc.ok)
482         {
483             sample->raw_header_valid = 1;
484             sample->sector_information = sample->raw_header[0];
485             sample->layer_number = (uint32_t)(sample->sector_information & 1U);
486             sample->physical_sector_number =
487                 ((uint32_t)sample->raw_header[1] << 16) |
488                 ((uint32_t)sample->raw_header[2] << 8) |
489                 (uint32_t)sample->raw_header[3];
490             xgd1_set_expected_geometry(locked_view, sample);
491         }
492     }
493
494     VirtualFree(read_buffer, 0, MEM_RELEASE);
495 }
496
497 static void xgd1_capture_state(HANDLE hDevice,
498                                xgd1_raw_id_state *state,
499                                const xgd1_probe_point *points,
500                                uint32_t point_count)
501 {
502     uint32_t i;
503
504     if (!state || !points || !state->state_verified)
505         return;
506     if (point_count > XGD1_RAW_ID_MAX_SAMPLES)
507         point_count = XGD1_RAW_ID_MAX_SAMPLES;
508
509     state->sample_count = point_count;
510     state->geometry_validation_failures = 0U;
511     for (i = 0; i < point_count; ++i)
512     {
513         printf("[XGD1-RAW-ID] %s: cache-flush, aligned READ(12), and HIT 0xE7 header/EDC capture for LBA %u (%s).\n",
514                state->name ? state->name : "state",
515                points[i].lba,
516                points[i].label ? points[i].label : "unlabeled");
517         xgd1_capture_sample(hDevice,
518                             state->capacity,
519                             state->locked_view,
520                             &points[i],
521                             &state->samples[i]);
522         if (!state->samples[i].cache_flush.ok ||
523             !state->samples[i].cache_fill.ok ||
524             !state->samples[i].raw_header_valid ||
525             !state->samples[i].geometry_matches_expected)
526             state->geometry_validation_failures++;
527     }
528
529     state->geometry_verified =
530         (state->state_verified &&
531          state->sample_count > 0U &&
532          state->geometry_validation_failures == 0U) ? 1 : 0;
533 }
534
535 static void xgd1_json_string(FILE *f, const char *s)
536 {
537     const unsigned char *p;
538
539     fputc('"', f);
540     if (s)
541     {
542         for (p = (const unsigned char *)s; *p; ++p)
543         {
544             switch (*p)
545             {
546                 case '\\': fputs("\\\\", f); break;
547                 case '"': fputs("\\\"", f); break;
548                 case '\b': fputs("\\b", f); break;
549                 case '\f': fputs("\\f", f); break;
550                 case '\n': fputs("\\n", f); break;
551                 case '\r': fputs("\\r", f); break;
552                 case '\t': fputs("\\t", f); break;
553                 default:
554                     if (*p < 0x20)
555                         fprintf(f, "\\u%04x", (unsigned int)*p);
556                     else
557                         fputc(*p, f);
558                     break;
559             }
560         }
561     }
562     fputc('"', f);
563 }
564
565 static void xgd1_json_hex(FILE *f,
566                           const unsigned char *data,
567                           uint32_t size)
568 {
569     uint32_t i;
570
571     fputc('"', f);
572     for (i = 0; i < size; ++i)
573         fprintf(f, "%02x", data[i]);
574     fputc('"', f);
575 }
576
577 static void xgd1_write_cmd_result(FILE *f,
578                                   const xgd1_cmd_result *r)
579 {
580     fprintf(f, "{\"ok\":%s,\"win32_error\":%lu,\"scsi_status\":%u,"
581                "\"sense_key\":%u,\"asc\":%u,\"ascq\":%u}",
582             (r && r->ok) ? "true" : "false",
583             (unsigned long)(r ? r->win32_error : 0),
584             (unsigned int)(r ? r->scsi_status : 0),
585             (unsigned int)(r ? r->sense_key : 0),
586             (unsigned int)(r ? r->asc : 0),
587             (unsigned int)(r ? r->ascq : 0));
588 }
589
590 static void xgd1_write_sample(FILE *f,
591                               const xgd1_raw_id_sample *s,
592                               int comma)
593 {
594     fprintf(f, "      {\n");
595     fprintf(f, "        \"lba\": %u,\n", s->lba);
596     fprintf(f, "        \"label\": ");
597     xgd1_json_string(f, s->label ? s->label : "");
598     fprintf(f, ",\n");
599     fprintf(f, "        \"cache_fill_lba\": %u,\n", s->cache_fill_lba);
600     fprintf(f, "        \"cache_index\": %u,\n", s->cache_index);
601     fprintf(f, "        \"cache_address\": \"0x%08x\",\n", s->cache_address);
602     fprintf(f, "        \"cache_flush\": ");
603     xgd1_write_cmd_result(f, &s->cache_flush);
604     fprintf(f, ",\n");
605     fprintf(f, "        \"cache_fill\": ");
606     xgd1_write_cmd_result(f, &s->cache_fill);
607     fprintf(f, ",\n");
608     fprintf(f, "        \"memdump_header\": ");
609     xgd1_write_cmd_result(f, &s->memdump_header);
610     fprintf(f, ",\n");
611     fprintf(f, "        \"memdump_edc\": ");
612     xgd1_write_cmd_result(f, &s->memdump_edc);
613     fprintf(f, ",\n");
614     fprintf(f, "        \"raw_header_valid\": %s,\n",
615             s->raw_header_valid ? "true" : "false");
616     if (s->raw_header_valid)
617     {
618         fprintf(f, "        \"sector_information\": %u,\n",
619                 (unsigned int)s->sector_information);
620         fprintf(f, "        \"layer_number\": %u,\n", s->layer_number);
621         fprintf(f, "        \"physical_sector_number\": %u,\n",
622                 s->physical_sector_number);
623         fprintf(f, "        \"normalized_physical_sector_number\": %u,\n",
624                 s->normalized_physical_sector_number);
625         fprintf(f, "        \"output_lba\": %u,\n", s->output_lba);
626         fprintf(f, "        \"expected_layer_number\": %u,\n",
627                 s->expected_layer_number);
628         fprintf(f, "        \"expected_physical_sector_number\": %u,\n",
629                 s->expected_physical_sector_number);
630         fprintf(f, "        \"expected_normalized_physical_sector_number\": %u,\n",
631                 s->expected_normalized_physical_sector_number);
632         fprintf(f, "        \"layer_matches_expected\": %s,\n",
633                 s->layer_matches_expected ? "true" : "false");
634         fprintf(f, "        \"psn_matches_expected\": %s,\n",
635                 s->psn_matches_expected ? "true" : "false");
636         fprintf(f, "        \"geometry_matches_expected\": %s,\n",
637                 s->geometry_matches_expected ? "true" : "false");
638         fprintf(f, "        \"logical_sha1\": \"%s\",\n", s->logical_sha1);
639         fprintf(f, "        \"raw_header_hex\": ");
640         xgd1_json_hex(f, s->raw_header, 12U);
641         fprintf(f, ",\n");
642         fprintf(f, "        \"raw_edc_hex\": ");
643         xgd1_json_hex(f, s->raw_edc, 4U);
644         fprintf(f, ",\n");
645         fprintf(f, "        \"logical_data_hex\": ");
646         xgd1_json_hex(f, s->logical, XGD1_LOGICAL_SECTOR_BYTES);
647         fprintf(f, "\n");
648     }
649     else
650     {
651         fprintf(f, "        \"sector_information\": null,\n");
652         fprintf(f, "        \"layer_number\": null,\n");
653         fprintf(f, "        \"physical_sector_number\": null,\n");
654         fprintf(f, "        \"normalized_physical_sector_number\": null,\n");
655         fprintf(f, "        \"output_lba\": null,\n");
656         fprintf(f, "        \"expected_layer_number\": null,\n");
657         fprintf(f, "        \"expected_physical_sector_number\": null,\n");
658         fprintf(f, "        \"expected_normalized_physical_sector_number\": null,\n");
659         fprintf(f, "        \"layer_matches_expected\": false,\n");
660         fprintf(f, "        \"psn_matches_expected\": false,\n");
661         fprintf(f, "        \"geometry_matches_expected\": false,\n");
662         fprintf(f, "        \"logical_sha1\": null,\n");
663         fprintf(f, "        \"raw_header_hex\": \"\",\n");
664         fprintf(f, "        \"raw_edc_hex\": \"\",\n");
665         fprintf(f, "        \"logical_data_hex\": \"\"\n");
666     }
667     fprintf(f, "      }%s\n", comma ? "," : "");
668 }
669
670 static void xgd1_write_state(FILE *f,
671                              const xgd1_raw_id_state *state,
672                              int comma)
673 {
674     uint32_t i;
675
676     fprintf(f, "    {\n");
677     fprintf(f, "      \"name\": ");
678     xgd1_json_string(f, state->name ? state->name : "");
679     fprintf(f, ",\n");
680     fprintf(f, "      \"capacity_valid\": %s,\n",
681             state->capacity_valid ? "true" : "false");
682     fprintf(f, "      \"capacity_sectors\": %u,\n", state->capacity);
683     fprintf(f, "      \"state_verified\": %s,\n",
684             state->state_verified ? "true" : "false");
685     fprintf(f, "      \"geometry_validation_failures\": %u,\n",
686             state->geometry_validation_failures);
687     fprintf(f, "      \"geometry_verified\": %s,\n",
688             state->geometry_verified ? "true" : "false");
689     fprintf(f, "      \"samples\": [\n");
690     for (i = 0; i < state->sample_count; ++i)
691         xgd1_write_sample(f,
692                           &state->samples[i],
693                           (i + 1U < state->sample_count));
694     fprintf(f, "      ]\n");
695     fprintf(f, "    }%s\n", comma ? "," : "");
696 }
697
698 static int xgd1_write_report(const char *report_path,
699                              const char *device,
700                              uint32_t entry_capacity,
701                              int entry_game_view,
702                              int volume_lock_ever,
703                              const xgd1_raw_id_state *locked_state,
704                              const xgd1_raw_id_state *unlocked_state,
705                              int restore_attempted,
706                              uint32_t restored_capacity,
707                              int restore_verified)
708 {
709     const char *final_path;
710     char tmp_path[MAX_PATH * 4];
711     FILE *f;
712     int fd;
713     BOOL moved;
714     uint32_t validation_failures;
715     int geometry_resolved;
716
717     final_path = (report_path && report_path[0])
718         ? report_path
719         : "xgd1_raw_id_probe.json";
720
721     validation_failures =
722         (locked_state ? locked_state->geometry_validation_failures : 0U) +
723         (unlocked_state ? unlocked_state->geometry_validation_failures : 0U);
724     geometry_resolved =
725         (locked_state && locked_state->geometry_verified &&
726          unlocked_state && unlocked_state->geometry_verified) ? 1 : 0;
727
728     if (strlen(final_path) + 32U >= sizeof(tmp_path))
729     {
730         printf("[XGD1-RAW-ID] Fatal: report path is too long.\n");
731         return 0;
732     }
733
734     sprintf(tmp_path,
735             "%s.tmp.%lu.%lu",
736             final_path,
737             (unsigned long)GetCurrentProcessId(),
738             (unsigned long)GetTickCount());
739
740     f = fopen(tmp_path, "wb");
741     if (!f)
742     {
743         printf("[XGD1-RAW-ID] Fatal: could not create temporary report %s.\n",
744                tmp_path);
745         return 0;
746     }
747
748     fprintf(f, "{\n");
749     fprintf(f, "  \"schema_version\": %u,\n",
750             XGD1_RAW_ID_PROBE_SCHEMA_VERSION);
751     fprintf(f, "  \"producer\": \"friidump-0.5.3.13\",\n");
752     fprintf(f, "  \"probe\": \"original_xbox_xgd1_cache_aligned_raw_id_geometry\",\n");
753     fprintf(f, "  \"device\": ");
754     xgd1_json_string(f, device ? device : "");
755     fprintf(f, ",\n");
756     fprintf(f, "  \"cache\": {\n");
757     fprintf(f, "    \"memdump_command\": \"E7 48 49 54 01\",\n");
758     fprintf(f, "    \"base_address\": \"0x%08x\",\n",
759             XGD1_RAW_ID_CACHE_BASE);
760     fprintf(f, "    \"raw_sector_stride\": %u,\n",
761             XGD1_RAW_SECTOR_BYTES);
762     fprintf(f, "    \"cache_flush_command\": \"READ(12), byte1=0x08, zero transfer length\",\n");
763     fprintf(f, "    \"cache_fill_command\": \"READ(12) streaming, 16 sectors, block-aligned LBA\",\n");
764     fprintf(f, "    \"memdump_regions\": \"12-byte ID/IED/CPR_MAI header plus 4-byte EDC only\",\n");
765     fprintf(f, "    \"raw_user_data_compared_to_logical\": false,\n");
766     fprintf(f, "    \"raw_user_data_note\": \"The proven Method 8 path replaces the drive-cache user field with READ(12) data before unscrambling; equality is not expected and is not a geometry test.\"\n");
767     fprintf(f, "  },\n");
768     fprintf(f, "  \"geometry_model\": {\n");
769     fprintf(f, "    \"dvd_start_psn\": 196608,\n");
770     fprintf(f, "    \"game_output_start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA);
771     fprintf(f, "    \"layer_break_lba\": %u,\n", XGD1_REDUMP_LAYER_BREAK_LBA);
772     fprintf(f, "    \"layer_zero_last_normalized_psn\": %u,\n",
773             0x30000U + XGD1_REDUMP_LAYER_BREAK_LBA - 1U);
774     fprintf(f, "    \"layer_one_raw_psn_is_24bit_complement\": true\n");
775     fprintf(f, "  },\n");
776     fprintf(f, "  \"safety\": {\n");
777     fprintf(f, "    \"read_only_sector_commands\": true,\n");
778     fprintf(f, "    \"authentication_handshake_used\": true,\n");
779     fprintf(f, "    \"tray_cycle_used\": true,\n");
780     fprintf(f, "    \"firmware_write_used\": false,\n");
781     fprintf(f, "    \"flash_command_used\": false\n");
782     fprintf(f, "  },\n");
783     fprintf(f, "  \"entry\": {\n");
784     fprintf(f, "    \"capacity_sectors\": %u,\n", entry_capacity);
785     fprintf(f, "    \"game_view\": %s,\n",
786             entry_game_view ? "true" : "false");
787     fprintf(f, "    \"classification\": \"%s\"\n",
788             entry_game_view ? "unlocked_game" : "locked_video_or_unknown");
789     fprintf(f, "  },\n");
790     fprintf(f, "  \"volume_lock_acquired_at_least_once\": %s,\n",
791             volume_lock_ever ? "true" : "false");
792     fprintf(f, "  \"required_states_verified\": %s,\n",
793             (locked_state && locked_state->state_verified &&
794              unlocked_state && unlocked_state->state_verified)
795                 ? "true" : "false");
796     fprintf(f, "  \"geometry_validation_failures\": %u,\n",
797             validation_failures);
798     fprintf(f, "  \"physical_geometry_resolved\": %s,\n",
799             geometry_resolved ? "true" : "false");
800     fprintf(f, "  \"states\": [\n");
801     xgd1_write_state(f, locked_state, 1);
802     xgd1_write_state(f, unlocked_state, 0);
803     fprintf(f, "  ],\n");
804     fprintf(f, "  \"restoration\": {\n");
805     fprintf(f, "    \"attempted\": %s,\n",
806             restore_attempted ? "true" : "false");
807     fprintf(f, "    \"capacity_sectors\": %u,\n", restored_capacity);
808     fprintf(f, "    \"verified\": %s\n",
809             restore_verified ? "true" : "false");
810     fprintf(f, "  },\n");
811     fprintf(f, "  \"interpretation_boundary\": {\n");
812     fprintf(f, "    \"physical_geometry_resolved\": %s,\n",
813             geometry_resolved ? "true" : "false");
814     fprintf(f, "    \"pregame_filler_content_resolved\": false,\n");
815     fprintf(f, "    \"postgame_filler_content_resolved\": false,\n");
816     fprintf(f, "    \"note\": \"A complete cache-aligned raw-ID match can validate the logical-to-physical placement used by the current reconstruction. It does not make inaccessible filler sectors readable and does not establish exact filler bytes.\"\n");
817     fprintf(f, "  }\n");
818     fprintf(f, "}\n");
819
820     if (fflush(f) != 0)
821     {
822         fclose(f);
823         DeleteFileA(tmp_path);
824         return 0;
825     }
826
827     fd = _fileno(f);
828     if (fd < 0 || _commit(fd) != 0)
829     {
830         fclose(f);
831         DeleteFileA(tmp_path);
832         return 0;
833     }
834
835     if (fclose(f) != 0)
836     {
837         DeleteFileA(tmp_path);
838         return 0;
839     }
840
841     moved = MoveFileExA(tmp_path,
842                         final_path,
843                         MOVEFILE_REPLACE_EXISTING |
844                         MOVEFILE_WRITE_THROUGH);
845     if (!moved)
846     {
847         printf("[XGD1-RAW-ID] Fatal: could not atomically publish report %s (error %lu).\n",
848                final_path,
849                (unsigned long)GetLastError());
850         DeleteFileA(tmp_path);
851         return 0;
852     }
853
854     printf("[XGD1-RAW-ID] Atomic report written: %s\n", final_path);
855     return 1;
856 }
857
858 static int xgd1_raw_id_probe_core(HANDLE hDevice,
859                                   const char *device,
860                                   const char *report_path,
861                                   BOOL owns_handle)
862 {
863     xgd1_raw_id_state locked_state;
864     xgd1_raw_id_state unlocked_state;
865     uint32_t entry_capacity;
866     uint32_t restored_capacity;
867     int entry_game_view;
868     int volume_lock_acquired;
869     int volume_lock_ever;
870     int restore_attempted;
871     int restore_verified;
872     int states_verified;
873     DWORD bytes_returned;
874     int report_ok;
875
876     memset(&locked_state, 0, sizeof(locked_state));
877     memset(&unlocked_state, 0, sizeof(unlocked_state));
878     locked_state.name = "locked_video";
879     locked_state.locked_view = 1;
880     unlocked_state.name = "unlocked_game";
881     unlocked_state.locked_view = 0;
882     entry_capacity = 0;
883     restored_capacity = 0;
884     entry_game_view = 0;
885     volume_lock_acquired = 0;
886     volume_lock_ever = 0;
887     restore_attempted = 0;
888     restore_verified = 0;
889     states_verified = 0;
890     bytes_returned = 0;
891
892     xbox_ref_log_open_for_target(report_path,
893                                  xgd1_drive_letter_from_device(device));
894     printf("[XGD1-RAW-ID] Starting cache-aligned raw-sector ID geometry probe.\n");
895     printf("[XGD1-RAW-ID] Requires modified GDR-8050L firmware with HIT 0xE7 memdump support.\n");
896     printf("[XGD1-RAW-ID] No ISO or firmware-write command will be issued.\n");
897
898     if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
899     {
900         printf("[XGD1-RAW-ID] Fatal: invalid drive handle.\n");
901         if (owns_handle)
902             xbox_ref_log_close();
903         return 1;
904     }
905
906     if (!EnsureDriveReady(hDevice, 30000))
907     {
908         printf("[XGD1-RAW-ID] Fatal: media did not become ready.\n");
909         if (owns_handle)
910             xbox_ref_log_close();
911         return 1;
912     }
913
914     entry_capacity = GetTotalSectors(hDevice);
915     entry_game_view = xgd1_capacity_is_game(entry_capacity) ? 1 : 0;
916     printf("[XGD1-RAW-ID] Entry READ CAPACITY: %u sectors (%s).\n",
917            entry_capacity,
918            entry_game_view ? "game view" : "locked/video or unknown view");
919
920     DeviceIoControl(hDevice,
921                     FSCTL_UNLOCK_VOLUME,
922                     NULL,
923                     0,
924                     NULL,
925                     0,
926                     &bytes_returned,
927                     NULL);
928
929     printf("[XGD1-RAW-ID] Cycling tray to establish locked/video view.\n");
930     AutomateTrayCycle(hDevice);
931     locked_state.capacity = xgd1_refresh_ready_capacity(hDevice);
932     locked_state.capacity_valid = locked_state.capacity != 0U;
933     locked_state.state_verified =
934         (locked_state.capacity_valid &&
935          xgd1_capacity_is_locked(locked_state.capacity)) ? 1 : 0;
936     printf("[XGD1-RAW-ID] Locked/video READ CAPACITY: %u sectors; verified=%s.\n",
937            locked_state.capacity,
938            locked_state.state_verified ? "yes" : "no");
939
940     if (DeviceIoControl(hDevice,
941                         FSCTL_LOCK_VOLUME,
942                         NULL,
943                         0,
944                         NULL,
945                         0,
946                         &bytes_returned,
947                         NULL))
948     {
949         volume_lock_acquired = 1;
950         volume_lock_ever = 1;
951         printf("[XGD1-RAW-ID] Windows volume lock acquired.\n");
952     }
953     else
954     {
955         printf("[XGD1-RAW-ID][WARN] Windows volume lock was not acquired.\n");
956     }
957
958     SetDriveSpeedMax(hDevice);
959     xgd1_capture_state(hDevice,
960                        &locked_state,
961                        xgd1_locked_points,
962                        (uint32_t)(sizeof(xgd1_locked_points) /
963                                   sizeof(xgd1_locked_points[0])));
964
965     printf("[XGD1-RAW-ID] Applying the full Xbox handshake to establish game view.\n");
966     UnlockDrive(hDevice);
967     unlocked_state.capacity = xgd1_refresh_ready_capacity(hDevice);
968     unlocked_state.capacity_valid = unlocked_state.capacity != 0U;
969     unlocked_state.state_verified =
970         (unlocked_state.capacity_valid &&
971          xgd1_capacity_is_game(unlocked_state.capacity)) ? 1 : 0;
972     printf("[XGD1-RAW-ID] Unlocked/game READ CAPACITY: %u sectors; verified=%s.\n",
973            unlocked_state.capacity,
974            unlocked_state.state_verified ? "yes" : "no");
975
976     xgd1_capture_state(hDevice,
977                        &unlocked_state,
978                        xgd1_unlocked_points,
979                        (uint32_t)(sizeof(xgd1_unlocked_points) /
980                                   sizeof(xgd1_unlocked_points[0])));
981
982     if (volume_lock_acquired)
983     {
984         DeviceIoControl(hDevice,
985                         FSCTL_UNLOCK_VOLUME,
986                         NULL,
987                         0,
988                         NULL,
989                         0,
990                         &bytes_returned,
991                         NULL);
992         volume_lock_acquired = 0;
993     }
994
995     if (!entry_game_view && entry_capacity != 0U)
996     {
997         restore_attempted = 1;
998         printf("[XGD1-RAW-ID] Restoring entry locked/video state with a final tray cycle.\n");
999         AutomateTrayCycle(hDevice);
1000         restored_capacity = xgd1_refresh_ready_capacity(hDevice);
1001         restore_verified =
1002             (restored_capacity != 0U &&
1003              xgd1_capacity_is_locked(restored_capacity)) ? 1 : 0;
1004     }
1005     else
1006     {
1007         restored_capacity = GetTotalSectors(hDevice);
1008         restore_verified =
1009             (entry_game_view &&
1010              xgd1_capacity_is_game(restored_capacity)) ? 1 : 0;
1011     }
1012
1013     states_verified =
1014         (locked_state.state_verified &&
1015          unlocked_state.state_verified) ? 1 : 0;
1016
1017     report_ok = xgd1_write_report(report_path,
1018                                   device,
1019                                   entry_capacity,
1020                                   entry_game_view,
1021                                   volume_lock_ever,
1022                                   &locked_state,
1023                                   &unlocked_state,
1024                                   restore_attempted,
1025                                   restored_capacity,
1026                                   restore_verified);
1027
1028     if (owns_handle)
1029     {
1030         printf("[XGD1-RAW-ID] Issuing STOP UNIT / spin-down after probe... ");
1031         printf("%s\n", StopDriveUnit(hDevice) ? "OK" : "FAILED");
1032         CloseDrive(hDevice);
1033         xbox_ref_log_close();
1034     }
1035
1036     if (!report_ok)
1037         return 1;
1038     if (!states_verified)
1039     {
1040         printf("[XGD1-RAW-ID] Probe report is partial because one or more states were not verified.\n");
1041         return 1;
1042     }
1043     if (!locked_state.geometry_verified || !unlocked_state.geometry_verified)
1044     {
1045         printf("[XGD1-RAW-ID] Geometry validation failed: locked failures=%u, unlocked failures=%u.\n",
1046                locked_state.geometry_validation_failures,
1047                unlocked_state.geometry_validation_failures);
1048         return 1;
1049     }
1050
1051     printf("[XGD1-RAW-ID] Probe complete. All selected raw IDs match the current XGD1 output geometry.\n");
1052     return 0;
1053 }
1054
1055 int xbox_ref_xgd1_raw_id_probe_with_handle(void *native_handle,
1056                                            const char *device,
1057                                            const char *report_path)
1058 {
1059     return xgd1_raw_id_probe_core((HANDLE)native_handle,
1060                                   device,
1061                                   report_path,
1062                                   FALSE);
1063 }
1064
1065 int xbox_ref_xgd1_raw_id_probe(const char *device,
1066                                const char *report_path)
1067 {
1068     char drive_letter;
1069     HANDLE hDevice;
1070
1071     drive_letter = xgd1_drive_letter_from_device(device);
1072     if (!drive_letter)
1073     {
1074         printf("[XGD1-RAW-ID] Fatal: could not parse drive letter from device.\n");
1075         return 1;
1076     }
1077
1078     hDevice = OpenDrive(drive_letter);
1079     if (hDevice == INVALID_HANDLE_VALUE)
1080     {
1081         printf("[XGD1-RAW-ID] Fatal: cannot open drive %c:. Run as Administrator.\n",
1082                drive_letter);
1083         return 1;
1084     }
1085
1086     return xgd1_raw_id_probe_core(hDevice,
1087                                   device,
1088                                   report_path,
1089                                   TRUE);
1090 }
1091
1092 #else
1093
1094 int xbox_ref_xgd1_raw_id_probe_with_handle(void *native_handle,
1095                                            const char *device,
1096                                            const char *report_path)
1097 {
1098     (void)native_handle;
1099     (void)device;
1100     (void)report_path;
1101     return 1;
1102 }
1103
1104 int xbox_ref_xgd1_raw_id_probe(const char *device,
1105                                const char *report_path)
1106 {
1107     (void)device;
1108     (void)report_path;
1109     return 1;
1110 }
1111
1112 #endif