]> FriiDump Source - friidump.git/blobdiff - libfriidump/disc.c
FriiDump 0.5.3.16 candidate: add native reports and Linux Xbox support
[friidump.git] / libfriidump / disc.c
index c132d59b48c37a411b0d740ef306106f6ab51334..6e901389dba514285650799813dab4b540f6d213 100644 (file)
@@ -44,6 +44,8 @@
 #include "byteorder.h"
 #include "disc.h"
 #include "dvd_drive.h"
 #include "byteorder.h"
 #include "disc.h"
 #include "dvd_drive.h"
+#include "nintendo_disc_header.h"
+#include "xbox_ref/xbox_ref_log.h"
 
 static void hlds_e7_visible_probe_log (const char *fmt, ...) {
        static bool started = false;
 
 static void hlds_e7_visible_probe_log (const char *fmt, ...) {
        static bool started = false;
@@ -122,12 +124,109 @@ struct disc_s {
        /* Read cache */
        u_int32_t cache_size;                   //!< The number of blocks that will be cached when read.
        bool hlds_e7_read_schedule_logged;      //!< True once the selected HLDS 0xE7 read schedule has been logged for this run.
        /* Read cache */
        u_int32_t cache_size;                   //!< The number of blocks that will be cached when read.
        bool hlds_e7_read_schedule_logged;      //!< True once the selected HLDS 0xE7 read schedule has been logged for this run.
+       bool seed_diagnostic_active;            //!< True while the initial 20 Nintendo seed blocks are being acquired.
+       unsigned int seed_diagnostic_block;     //!< Zero-based seed-block index currently being acquired.
+       disc_seed_diagnostic seed_diagnostic;   //!< Last actionable seed/cache failure evidence.
        u_int8_t **raw_cache;                   //!< Memory area for raw sectors cache.
        u_int8_t **cache;                       //!< Memory area for unscrambled sectors cache.
        u_int32_t *cache_map;                   //!< Data structure used by the caching system to know which blocks are in memory.
 };
 
 
        u_int8_t **raw_cache;                   //!< Memory area for raw sectors cache.
        u_int8_t **cache;                       //!< Memory area for unscrambled sectors cache.
        u_int32_t *cache_map;                   //!< Data structure used by the caching system to know which blocks are in memory.
 };
 
 
+
+static void disc_seed_diag_clear (disc *d) {
+       if (!d)
+               return;
+       memset (&d -> seed_diagnostic, 0, sizeof (d -> seed_diagnostic));
+}
+
+static void disc_seed_diag_log (disc *d, const char *fmt, ...) {
+       va_list ap;
+       if (!d || !d -> seed_diagnostic_active || !fmt)
+               return;
+       xbox_ref_log_fprintf (stderr, "[SEED-DIAG] ");
+       va_start (ap, fmt);
+       /* xbox_ref_log_fprintf is variadic rather than vfprintf-based, so format
+        * into a bounded buffer before sending the line to both console and log. */
+       {
+               char line[768];
+               vsnprintf (line, sizeof (line), fmt, ap);
+               xbox_ref_log_fprintf (stderr, "%s\n", line);
+       }
+       va_end (ap);
+}
+
+static void disc_seed_diag_fail (
+       disc *d,
+       u_int32_t sector_start,
+       u_int32_t sector_end,
+       int retry,
+       const char *stage,
+       const char *detail,
+       int transport_result,
+       bool capture_command
+) {
+       dvd_command_diagnostic command;
+       char cdb_text[3 * 12 + 1];
+       int i;
+       int used;
+
+       if (!d || !d -> seed_diagnostic_active)
+               return;
+       disc_seed_diag_clear (d);
+       d -> seed_diagnostic.valid = true;
+       d -> seed_diagnostic.seed_block_index = d -> seed_diagnostic_block;
+       d -> seed_diagnostic.sector_start = sector_start;
+       d -> seed_diagnostic.sector_end = sector_end;
+       d -> seed_diagnostic.retry = retry;
+       snprintf (d -> seed_diagnostic.stage, sizeof (d -> seed_diagnostic.stage), "%s", stage ? stage : "unknown");
+       snprintf (d -> seed_diagnostic.detail, sizeof (d -> seed_diagnostic.detail), "%s", detail ? detail : "");
+       d -> seed_diagnostic.transport_result = transport_result;
+
+       memset (&command, 0, sizeof (command));
+       if (capture_command && dvd_get_last_command_diagnostic (d -> dvd, &command)) {
+               d -> seed_diagnostic.transport_result = command.transport_result;
+               d -> seed_diagnostic.os_error = command.os_error;
+               d -> seed_diagnostic.scsi_status = command.scsi_status;
+               d -> seed_diagnostic.sense_key = command.sense_key;
+               d -> seed_diagnostic.asc = command.asc;
+               d -> seed_diagnostic.ascq = command.ascq;
+               d -> seed_diagnostic.cdb_length = command.cdb_length;
+               memcpy (d -> seed_diagnostic.cdb, command.cdb, sizeof (d -> seed_diagnostic.cdb));
+       }
+
+       cdb_text[0] = '\0';
+       used = 0;
+       for (i = 0; i < d -> seed_diagnostic.cdb_length && i < 12; i++) {
+               int wrote = snprintf (cdb_text + used, sizeof (cdb_text) - (size_t) used,
+                       "%s%02X", i ? " " : "", d -> seed_diagnostic.cdb[i]);
+               if (wrote < 0 || wrote >= (int) (sizeof (cdb_text) - (size_t) used))
+                       break;
+               used += wrote;
+       }
+
+       disc_seed_diag_log (d,
+               "block=%u/20 sectors=%u..%u retry=%d stage=%s result=FAIL rc=%d os=%d scsi=0x%02X sense=%02X/%02X/%02X cdb=[%s] detail=%s",
+               d -> seed_diagnostic.seed_block_index + 1,
+               sector_start, sector_end, retry,
+               d -> seed_diagnostic.stage,
+               d -> seed_diagnostic.transport_result,
+               d -> seed_diagnostic.os_error,
+               d -> seed_diagnostic.scsi_status & 0xff,
+               d -> seed_diagnostic.sense_key & 0xff,
+               d -> seed_diagnostic.asc & 0xff,
+               d -> seed_diagnostic.ascq & 0xff,
+               cdb_text[0] ? cdb_text : "none",
+               d -> seed_diagnostic.detail[0] ? d -> seed_diagnostic.detail : "none");
+}
+
+bool disc_get_seed_diagnostic (disc *d, disc_seed_diagnostic *out) {
+       if (!d || !out || !d -> seed_diagnostic.valid)
+               return false;
+       *out = d -> seed_diagnostic;
+       return true;
+}
+
 static void disc_cache_init (disc *d, u_int32_t size) {
        u_int32_t i;
 
 static void disc_cache_init (disc *d, u_int32_t size) {
        u_int32_t i;
 
@@ -522,6 +621,8 @@ static bool disc_read_sector_8_split_recover_block (disc *d, u_int32_t block_sec
        u_int8_t raw_block[RAW_BLOCK_SIZE];
        u_int8_t iso_block[BLOCK_SIZE];
        u_int8_t readbuf[BLOCK_SIZE];
        u_int8_t raw_block[RAW_BLOCK_SIZE];
        u_int8_t iso_block[BLOCK_SIZE];
        u_int8_t readbuf[BLOCK_SIZE];
+       char detail[160];
+       req_sense sense;
 
        block_no = block_sector / SECTORS_PER_BLOCK;
 
 
        block_no = block_sector / SECTORS_PER_BLOCK;
 
@@ -530,22 +631,42 @@ static bool disc_read_sector_8_split_recover_block (disc *d, u_int32_t block_sec
                memset (raw_block, 0, sizeof (raw_block));
                memset (iso_block, 0, sizeof (iso_block));
                out = true;
                memset (raw_block, 0, sizeof (raw_block));
                memset (iso_block, 0, sizeof (iso_block));
                out = true;
+               disc_seed_diag_clear (d);
 
                warning ("Method 8 split recovery: trying %d-sector chunks for sectors %u..%u", chunk_len, block_sector, block_sector + SECTORS_PER_BLOCK - 1);
 
                warning ("Method 8 split recovery: trying %d-sector chunks for sectors %u..%u", chunk_len, block_sector, block_sector + SECTORS_PER_BLOCK - 1);
+               disc_seed_diag_log (d,
+                       "block=%u/20 sectors=%u..%u retry=%d stage=split-profile result=BEGIN detail=chunk-sectors-%d",
+                       d -> seed_diagnostic_block + 1, block_sector,
+                       block_sector + SECTORS_PER_BLOCK - 1, c, chunk_len);
 
                for (chunk_start = 0; chunk_start < SECTORS_PER_BLOCK && out; chunk_start += chunk_len) {
                        u_int32_t chunk_sector = block_sector + (u_int32_t) chunk_start;
                        memset (readbuf, 0, sizeof (readbuf));
 
 
                for (chunk_start = 0; chunk_start < SECTORS_PER_BLOCK && out; chunk_start += chunk_len) {
                        u_int32_t chunk_sector = block_sector + (u_int32_t) chunk_start;
                        memset (readbuf, 0, sizeof (readbuf));
 
+                       memset (&sense, 0, sizeof (sense));
                        if (chunk_sector + 992 + 16 <= d -> sectors_no)
                        if (chunk_sector + 992 + 16 <= d -> sectors_no)
-                               dvd_read_sector_dummy (d -> dvd, chunk_sector + 992, 16, NULL, NULL, 0);
+                               ret = dvd_read_sector_dummy (d -> dvd, chunk_sector + 992, 16, &sense, NULL, 0);
                        else if (chunk_sector >= 992)
                        else if (chunk_sector >= 992)
-                               dvd_read_sector_dummy (d -> dvd, chunk_sector - 992, 16, NULL, NULL, 0);
+                               ret = dvd_read_sector_dummy (d -> dvd, chunk_sector - 992, 16, &sense, NULL, 0);
                        else
                        else
-                               dvd_flush_cache_READ12 (d -> dvd, chunk_sector, NULL);
+                               ret = dvd_flush_cache_READ12 (d -> dvd, chunk_sector, &sense);
+                       if (ret < 0) {
+                               disc_seed_diag_log (d,
+                                       "block=%u/20 sectors=%u..%u retry=%d stage=split-prefetch result=NONFATAL-FAIL rc=%d sense=%02X/%02X/%02X detail=chunk-sectors-%d",
+                                       d -> seed_diagnostic_block + 1, chunk_sector,
+                                       chunk_sector + (u_int32_t) chunk_len - 1, c, ret,
+                                       sense.sense_key & 0xff, sense.asc & 0xff, sense.ascq & 0xff, chunk_len);
+                       }
 
 
-                       ret = dvd_read_streaming (d -> dvd, chunk_sector, (u_int32_t) chunk_len, NULL, readbuf, (size_t) chunk_len * SECTOR_SIZE);
+                       memset (&sense, 0, sizeof (sense));
+                       ret = dvd_read_streaming (d -> dvd, chunk_sector, (u_int32_t) chunk_len,
+                               &sense, readbuf, (size_t) chunk_len * SECTOR_SIZE);
                        if (ret < 0) {
                        if (ret < 0) {
+                               snprintf (detail, sizeof (detail), "chunk-sectors=%d sense=%02X/%02X/%02X",
+                                       chunk_len, sense.sense_key & 0xff, sense.asc & 0xff, sense.ascq & 0xff);
+                               disc_seed_diag_fail (d, chunk_sector,
+                                       chunk_sector + (u_int32_t) chunk_len - 1, c,
+                                       "split-streaming-read", detail, ret, true);
                                warning ("Method 8 split recovery: READ12 streaming failed for sectors %u..%u with %d", chunk_sector, chunk_sector + (u_int32_t) chunk_len - 1, ret);
                                out = false;
                                break;
                                warning ("Method 8 split recovery: READ12 streaming failed for sectors %u..%u with %d", chunk_sector, chunk_sector + (u_int32_t) chunk_len - 1, ret);
                                out = false;
                                break;
@@ -555,12 +676,24 @@ static bool disc_read_sector_8_split_recover_block (disc *d, u_int32_t block_sec
                                sect = &raw_block[(chunk_start + k) * RAW_SECTOR_SIZE];
                                ram_offset = (u_int32_t) k * RAW_SECTOR_SIZE;
 
                                sect = &raw_block[(chunk_start + k) * RAW_SECTOR_SIZE];
                                ram_offset = (u_int32_t) k * RAW_SECTOR_SIZE;
 
-                               if (dvd_memdump (d -> dvd, ram_offset, 1, 12, sect) < 0) {
+                               ret = dvd_memdump (d -> dvd, ram_offset, 1, 12, sect);
+                               if (ret < 0) {
+                                       snprintf (detail, sizeof (detail), "chunk-sectors=%d raw-offset=%u sector-index=%d",
+                                               chunk_len, ram_offset, k);
+                                       disc_seed_diag_fail (d, chunk_sector + (u_int32_t) k,
+                                               chunk_sector + (u_int32_t) k, c,
+                                               "split-header-memdump", detail, ret, true);
                                        warning ("Method 8 split recovery: header memdump failed at sector %u", chunk_sector + (u_int32_t) k);
                                        out = false;
                                        break;
                                }
                                        warning ("Method 8 split recovery: header memdump failed at sector %u", chunk_sector + (u_int32_t) k);
                                        out = false;
                                        break;
                                }
-                               if (dvd_memdump (d -> dvd, ram_offset + 2060, 1, 4, sect + 2060) < 0) {
+                               ret = dvd_memdump (d -> dvd, ram_offset + 2060, 1, 4, sect + 2060);
+                               if (ret < 0) {
+                                       snprintf (detail, sizeof (detail), "chunk-sectors=%d raw-offset=%u sector-index=%d",
+                                               chunk_len, ram_offset + 2060, k);
+                                       disc_seed_diag_fail (d, chunk_sector + (u_int32_t) k,
+                                               chunk_sector + (u_int32_t) k, c,
+                                               "split-edc-memdump", detail, ret, true);
                                        warning ("Method 8 split recovery: EDC memdump failed at sector %u", chunk_sector + (u_int32_t) k);
                                        out = false;
                                        break;
                                        warning ("Method 8 split recovery: EDC memdump failed at sector %u", chunk_sector + (u_int32_t) k);
                                        out = false;
                                        break;
@@ -571,6 +704,10 @@ static bool disc_read_sector_8_split_recover_block (disc *d, u_int32_t block_sec
                }
 
                if (out && !unscrambler_unscramble_16sectors (d -> u, block_sector, raw_block, iso_block)) {
                }
 
                if (out && !unscrambler_unscramble_16sectors (d -> u, block_sector, raw_block, iso_block)) {
+                       snprintf (detail, sizeof (detail), "chunk-sectors=%d", chunk_len);
+                       disc_seed_diag_fail (d, block_sector,
+                               block_sector + SECTORS_PER_BLOCK - 1, c,
+                               "split-edc-unscramble-validation", detail, -1, false);
                        warning ("Method 8 split recovery: EDC/unscramble validation failed for %d-sector chunks at sectors %u..%u", chunk_len, block_sector, block_sector + SECTORS_PER_BLOCK - 1);
                        out = false;
                }
                        warning ("Method 8 split recovery: EDC/unscramble validation failed for %d-sector chunks at sectors %u..%u", chunk_len, block_sector, block_sector + SECTORS_PER_BLOCK - 1);
                        out = false;
                }
@@ -578,6 +715,10 @@ static bool disc_read_sector_8_split_recover_block (disc *d, u_int32_t block_sec
                if (out) {
                        disc_cache_add_block (d, block_no, iso_block, raw_block);
                        warning ("Method 8 split recovery: recovered sectors %u..%u using %d-sector chunks", block_sector, block_sector + SECTORS_PER_BLOCK - 1, chunk_len);
                if (out) {
                        disc_cache_add_block (d, block_no, iso_block, raw_block);
                        warning ("Method 8 split recovery: recovered sectors %u..%u using %d-sector chunks", block_sector, block_sector + SECTORS_PER_BLOCK - 1, chunk_len);
+                       disc_seed_diag_log (d,
+                               "block=%u/20 sectors=%u..%u retry=%d stage=split-profile result=PASS detail=chunk-sectors-%d",
+                               d -> seed_diagnostic_block + 1, block_sector,
+                               block_sector + SECTORS_PER_BLOCK - 1, c, chunk_len);
                        return true;
                }
        }
                        return true;
                }
        }
@@ -791,18 +932,22 @@ static int disc_read_sector_8_gdr8081n_search_guided (disc *d, u_int32_t sector_
 }
 
 static int disc_read_sector_8 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
 }
 
 static int disc_read_sector_8 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
-       if (disc_hlds_type_is_gdr8081n_search_guided (dvd_get_hlds_e7_type (d -> dvd)))
-               return disc_read_sector_8_gdr8081n_search_guided (d, sector_no, data, rawdata);
-
        bool out;
        u_int32_t ram_offset;
        bool out;
        u_int32_t ram_offset;
+       u_int32_t start_block;
+       u_int32_t profile_blocks;
+       u_int32_t prefetch_sector;
        int j, k, ret, retry;
        int j, k, ret, retry;
+       int prefetch_ret;
        u_int8_t *sect, buf[5][RAW_BLOCK_SIZE];
        u_int8_t readbuf[BLOCK_SIZE];
        u_int8_t buf_unscrambled[5][BLOCK_SIZE];
        u_int8_t *sect, buf[5][RAW_BLOCK_SIZE];
        u_int8_t readbuf[BLOCK_SIZE];
        u_int8_t buf_unscrambled[5][BLOCK_SIZE];
-       u_int32_t start_block;
-       u_int32_t profile_blocks;
-//fprintf (stdout,"disc_read_sector_8");
+       req_sense sense;
+       char detail[160];
+
+       if (disc_hlds_type_is_gdr8081n_search_guided (dvd_get_hlds_e7_type (d -> dvd)))
+               return disc_read_sector_8_gdr8081n_search_guided (d, sector_no, data, rawdata);
+
        start_block = sector_no / SECTORS_PER_BLOCK;
        profile_blocks = dvd_get_hlds_e7_mem_blocks (d -> dvd);
        if (profile_blocks < 1 || profile_blocks > 5)
        start_block = sector_no / SECTORS_PER_BLOCK;
        profile_blocks = dvd_get_hlds_e7_mem_blocks (d -> dvd);
        if (profile_blocks < 1 || profile_blocks > 5)
@@ -810,88 +955,138 @@ static int disc_read_sector_8 (disc *d, u_int32_t sector_no, u_int8_t **data, u_
        
        out = false;
        for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) {
        
        out = false;
        for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) {
-               /* Assume everything will turn out well */
                out = true;
                out = true;
+               disc_seed_diag_clear (d);
+               disc_seed_diag_log (d,
+                       "block=%u/20 sectors=%u..%u retry=%d stage=normal-profile result=BEGIN detail=windows-%u",
+                       d -> seed_diagnostic_block + 1, sector_no,
+                       sector_no + SECTORS_PER_BLOCK - 1, retry, profile_blocks);
 
                if (retry > 0) {
                        warning ("Read retry %d for sector %u", retry, sector_no);
 
                if (retry > 0) {
                        warning ("Read retry %d for sector %u", retry, sector_no);
-
-                       /* Try to reset in-memory data by seeking to a distant sector */
-//                     if (sector_no > 1000)
-//                             dvd_read_sector_streaming (d -> dvd, 0, NULL, NULL, 0);
-//                     else
-//                             dvd_read_sector_streaming (d -> dvd, 1500, NULL, NULL, 0);
-                       if (sector_no +992 +16 <= d -> sectors_no) //smaller than last sector
-                               dvd_read_sector_dummy (d -> dvd, sector_no +992, 16, NULL, NULL, 0);
-                       else if (sector_no -992 >= 0)             //larger than first sector
-                               dvd_read_sector_dummy (d -> dvd, sector_no -992, 16, NULL, NULL, 0);
-                       else dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL);
+                       memset (&sense, 0, sizeof (sense));
+                       if (sector_no + 992 + 16 <= d -> sectors_no)
+                               ret = dvd_read_sector_dummy (d -> dvd, sector_no + 992, 16, &sense, NULL, 0);
+                       else if (sector_no >= 992)
+                               ret = dvd_read_sector_dummy (d -> dvd, sector_no - 992, 16, &sense, NULL, 0);
+                       else
+                               ret = dvd_flush_cache_READ12 (d -> dvd, sector_no, &sense);
+                       if (ret < 0) {
+                               disc_seed_diag_log (d,
+                                       "block=%u/20 sectors=%u..%u retry=%d stage=retry-cache-reset result=NONFATAL-FAIL rc=%d sense=%02X/%02X/%02X",
+                                       d -> seed_diagnostic_block + 1, sector_no,
+                                       sector_no + SECTORS_PER_BLOCK - 1, retry, ret,
+                                       sense.sense_key & 0xff, sense.asc & 0xff, sense.ascq & 0xff);
+                       }
                }
 
                }
 
-                       /* First READ command.  Type3/Type4 drives can expose several 16-sector
-                * cache windows after a nearby READ.  The modified GDR-8050L test firmware
-                * proved seed retrieval and the first data runs, but failed when we drove it
-                * with the normal five-window prefetch schedule.  For that profile, avoid
-                * the distant prefetch and consume only the current 16-sector window. */
+               /* Prime the volatile cache, then immediately consume it. */
+               memset (&sense, 0, sizeof (sense));
                if (disc_hlds_type_is_gdr8050l_no_prefetch (dvd_get_hlds_e7_type (d -> dvd))) {
                if (disc_hlds_type_is_gdr8050l_no_prefetch (dvd_get_hlds_e7_type (d -> dvd))) {
-                       /* GDR-8050L modified-firmware no-prefetch scheduling is selected/logged
-                        * by the profile probe, not from this hot per-read path.  Keeping
-                        * logging here spams one line for every 16-sector read/cache probe. */
-                       dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL);
+                       prefetch_sector = sector_no;
+                       prefetch_ret = dvd_flush_cache_READ12 (d -> dvd, sector_no, &sense);
                } else {
                        if (sector_no > d -> sectors_no - 1000)
                } else {
                        if (sector_no > d -> sectors_no - 1000)
-                               dvd_read_sector_streaming (d -> dvd, sector_no - 16 * 5 * 2, NULL, NULL, 0);
+                               prefetch_sector = sector_no - 16 * 5 * 2;
                        else
                        else
-                               dvd_read_sector_streaming (d -> dvd, sector_no + 16 * 5, NULL, NULL, 0);
+                               prefetch_sector = sector_no + 16 * 5;
+                       prefetch_ret = dvd_read_sector_streaming (d -> dvd, prefetch_sector, &sense, NULL, 0);
+               }
+               if (prefetch_ret < 0) {
+                       disc_seed_diag_log (d,
+                               "block=%u/20 sectors=%u..%u retry=%d stage=prefetch result=NONFATAL-FAIL rc=%d sense=%02X/%02X/%02X detail=prefetch-lba-%u",
+                               d -> seed_diagnostic_block + 1, sector_no,
+                               sector_no + SECTORS_PER_BLOCK - 1, retry, prefetch_ret,
+                               sense.sense_key & 0xff, sense.asc & 0xff, sense.ascq & 0xff,
+                               prefetch_sector);
                }
                }
-               if ((ret = dvd_read_sector_streaming (d -> dvd, sector_no, NULL, readbuf, sizeof (readbuf))) >= 0) {
+
+               memset (&sense, 0, sizeof (sense));
+               ret = dvd_read_sector_streaming (d -> dvd, sector_no, &sense, readbuf, sizeof (readbuf));
+               if (ret >= 0) {
                        for (j = 0; j < (int) profile_blocks && sector_no + j * 16 < d -> sectors_no && out; j++) {
                        for (j = 0; j < (int) profile_blocks && sector_no + j * 16 < d -> sectors_no && out; j++) {
-                               /* Reconstruct raw sectors */
                                for (k = 0; k < 16; k++) {
                                        sect = &buf[j][k * RAW_SECTOR_SIZE];
                                        ram_offset = (j * RAW_BLOCK_SIZE) + k * RAW_SECTOR_SIZE;
                                for (k = 0; k < 16; k++) {
                                        sect = &buf[j][k * RAW_SECTOR_SIZE];
                                        ram_offset = (j * RAW_BLOCK_SIZE) + k * RAW_SECTOR_SIZE;
-                                       /* Get first 12 bytes (ID. IED and CPR_MAI fields) and last 4 bytes (EDC field) with memdump */
-                                       if (dvd_memdump (d -> dvd, ram_offset, 1, 12, sect) < 0) {
+                                       ret = dvd_memdump (d -> dvd, ram_offset, 1, 12, sect);
+                                       if (ret < 0) {
+                                               snprintf (detail, sizeof (detail),
+                                                       "window=%d sector-index=%d raw-offset=%u", j, k, ram_offset);
+                                               disc_seed_diag_fail (d, sector_no + (u_int32_t) (j * 16 + k),
+                                                       sector_no + (u_int32_t) (j * 16 + k), retry,
+                                                       "header-memdump", detail, ret, true);
                                                error ("Memdump (1) failed");
                                                out = false;
                                                error ("Memdump (1) failed");
                                                out = false;
-                                               retry = MAX_READ_RETRIES;               /* Well, if this fails going on is useless */
-                                       } else if (dvd_memdump (d -> dvd, ram_offset + 2060, 1, 4, sect + 2060) < 0) {  /* Dumping in a single block is faster */
-                                               error ("Memdump (2) failed");
-                                               out = false;
+                                               retry = MAX_READ_RETRIES;
+                                       } else {
+                                               ret = dvd_memdump (d -> dvd, ram_offset + 2060, 1, 4, sect + 2060);
+                                               if (ret < 0) {
+                                                       snprintf (detail, sizeof (detail),
+                                                               "window=%d sector-index=%d raw-offset=%u", j, k, ram_offset + 2060);
+                                                       disc_seed_diag_fail (d, sector_no + (u_int32_t) (j * 16 + k),
+                                                               sector_no + (u_int32_t) (j * 16 + k), retry,
+                                                               "edc-memdump", detail, ret, true);
+                                                       error ("Memdump (2) failed");
+                                                       out = false;
+                                               }
                                        }
                                }
                        }
 
                                        }
                                }
                        }
 
-                       /* Now the same for remaining cached 16-sector blocks.  Type1 drives only
-                        * expose one validated cache window at their DIC-derived base address. */
                        for (j = 0; j < (int) profile_blocks && sector_no + j * 16 < d -> sectors_no && out; j++) {
                        for (j = 0; j < (int) profile_blocks && sector_no + j * 16 < d -> sectors_no && out; j++) {
-                               if (j == 0 || (ret = dvd_read_sector_streaming (d -> dvd, sector_no + j * 16, NULL, readbuf, sizeof (readbuf))) >= 0) {
-                                       /* Copy "user data" field which has been incorrectly unscrambled by the DVD drive firmware */
-                                       for (k = 0; k < 16; k++) {
-                                               sect = &buf[j][k * RAW_SECTOR_SIZE];
-                                               memcpy (sect + 12, readbuf + k * SECTOR_SIZE, SECTOR_SIZE);
+                               if (j != 0) {
+                                       memset (&sense, 0, sizeof (sense));
+                                       ret = dvd_read_sector_streaming (d -> dvd, sector_no + (u_int32_t) j * 16,
+                                               &sense, readbuf, sizeof (readbuf));
+                                       if (ret < 0) {
+                                               snprintf (detail, sizeof (detail),
+                                                       "window=%d sense=%02X/%02X/%02X", j,
+                                                       sense.sense_key & 0xff, sense.asc & 0xff, sense.ascq & 0xff);
+                                               disc_seed_diag_fail (d, sector_no + (u_int32_t) j * 16,
+                                                       sector_no + (u_int32_t) j * 16 + SECTORS_PER_BLOCK - 1,
+                                                       retry, "cached-window-streaming-read", detail, ret, true);
+                                               error ("dvd_read_sector_streaming() failed with %d", ret);
+                                               out = false;
+                                               break;
                                        }
                                        }
+                               }
+
+                               for (k = 0; k < 16; k++) {
+                                       sect = &buf[j][k * RAW_SECTOR_SIZE];
+                                       memcpy (sect + 12, readbuf + k * SECTOR_SIZE, SECTOR_SIZE);
+                               }
 #ifdef DEBUG
 #ifdef DEBUG
-                                       if (d -> unscrambling) {
+                               if (d -> unscrambling) {
 #endif
 #endif
-                                               /* Try to unscramble all data to see if EDC fails */
-                                               if (!unscrambler_unscramble_16sectors (d -> u, sector_no + (j * 16), buf[j], buf_unscrambled[j]))
-                                                       out = false;
-#ifdef DEBUG
+                                       if (!unscrambler_unscramble_16sectors (d -> u,
+                                               sector_no + (u_int32_t) (j * 16), buf[j], buf_unscrambled[j])) {
+                                               snprintf (detail, sizeof (detail), "window=%d", j);
+                                               disc_seed_diag_fail (d,
+                                                       sector_no + (u_int32_t) j * 16,
+                                                       sector_no + (u_int32_t) j * 16 + SECTORS_PER_BLOCK - 1,
+                                                       retry, "edc-unscramble-validation", detail, -1, false);
+                                               out = false;
                                        }
                                        }
-#endif
-                               } else {
-                                       error ("dvd_read_sector_streaming() failed with %d", ret);
-                                       out = false;
+#ifdef DEBUG
                                }
                                }
+#endif
                        }
 
                        if (out) {
                        }
 
                        if (out) {
-                               /* It seems all data were unscrambled correctly, so cache them out */
                                for (j = 0; j < (int) profile_blocks && sector_no + j * SECTORS_PER_BLOCK < d -> sectors_no; j++)
                                        disc_cache_add_block (d, start_block + j, buf_unscrambled[j], buf[j]);
                                for (j = 0; j < (int) profile_blocks && sector_no + j * SECTORS_PER_BLOCK < d -> sectors_no; j++)
                                        disc_cache_add_block (d, start_block + j, buf_unscrambled[j], buf[j]);
+                               disc_seed_diag_clear (d);
+                               disc_seed_diag_log (d,
+                                       "block=%u/20 sectors=%u..%u retry=%d stage=normal-profile result=PASS detail=windows-%u",
+                                       d -> seed_diagnostic_block + 1, sector_no,
+                                       sector_no + SECTORS_PER_BLOCK - 1, retry, profile_blocks);
                        }
                } else {
                        }
                } else {
+                       snprintf (detail, sizeof (detail), "sense=%02X/%02X/%02X",
+                               sense.sense_key & 0xff, sense.asc & 0xff, sense.ascq & 0xff);
+                       disc_seed_diag_fail (d, sector_no,
+                               sector_no + SECTORS_PER_BLOCK - 1, retry,
+                               "streaming-read", detail, ret, true);
                        error ("dvd_read_sector_streaming() failed with %d", ret);
                        out = false;
                }
                        error ("dvd_read_sector_streaming() failed with %d", ret);
                        out = false;
                }
@@ -912,13 +1107,17 @@ static int disc_read_sector_8 (disc *d, u_int32_t sector_no, u_int8_t **data, u_
        if (!out) {
                u_int32_t block_sector = start_block * SECTORS_PER_BLOCK;
                warning ("Method 8 normal profile read failed at sectors %u..%u; entering split recovery", block_sector, block_sector + SECTORS_PER_BLOCK - 1);
        if (!out) {
                u_int32_t block_sector = start_block * SECTORS_PER_BLOCK;
                warning ("Method 8 normal profile read failed at sectors %u..%u; entering split recovery", block_sector, block_sector + SECTORS_PER_BLOCK - 1);
+               disc_seed_diag_log (d,
+                       "block=%u/20 sectors=%u..%u retry=%d stage=normal-profile result=EXHAUSTED detail=entering-split-recovery",
+                       d -> seed_diagnostic_block + 1, block_sector,
+                       block_sector + SECTORS_PER_BLOCK - 1, MAX_READ_RETRIES - 1);
                out = disc_read_sector_8_split_recover_block (d, block_sector);
        }
 
        if (!out)
                error ("Too many retries, giving up");
 
                out = disc_read_sector_8_split_recover_block (d, block_sector);
        }
 
        if (!out)
                error ("Too many retries, giving up");
 
-       return (out);
+       return out;
 }
 
 
 }
 
 
@@ -1139,10 +1338,30 @@ int disc_read_sector (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **
 }
 
 
 }
 
 
-static bool disc_analyze (disc *d) {
+static const char *disc_detected_type_name (disc_type type) {
+       switch (type) {
+               case DISC_TYPE_GAMECUBE:
+                       return "GameCube";
+               case DISC_TYPE_WII:
+                       return "Wii";
+               case DISC_TYPE_WII_DL:
+                       return "Wii_DL";
+               case DISC_TYPE_DVD:
+                       return "DVD";
+               case DISC_TYPE_XBOX:
+                       return "Xbox";
+               default:
+                       return "Unknown";
+       }
+}
+
+
+static bool disc_analyze (disc *d, bool allow_type_correction, bool allow_size_correction) {
        u_int8_t *buf;
        char tmp[0x03E0 + 1];
        bool unscramble_old, out;
        u_int8_t *buf;
        char tmp[0x03E0 + 1];
        bool unscramble_old, out;
+       nintendo_disc_header_type header_type;
+       disc_type previous_type;
 
        /* Force unscrambling for this read */
        unscramble_old = d -> unscrambling;
 
        /* Force unscrambling for this read */
        unscramble_old = d -> unscrambling;
@@ -1151,16 +1370,41 @@ static bool disc_analyze (disc *d) {
        if (disc_read_sector (d, 0, &buf, NULL)) {
                /* System ID */
                d -> system_id = buf[0];
        if (disc_read_sector (d, 0, &buf, NULL)) {
                /* System ID */
                d -> system_id = buf[0];
-//             if (d -> system_id == 'G') {
-//                     d -> type = DISC_TYPE_GAMECUBE;
-//                     d -> sectors_no = DISC_GAMECUBE_SECTORS_NO;
-//             } else if (d -> system_id == 'R') {
-//                     d -> type = DISC_TYPE_WII;
-//                     d -> sectors_no = DISC_WII_SECTORS_NO;
-//             } else {
-//                     error ("Unknown system ID: '%c'", d -> system_id);
-//                     MY_ASSERT (false);
-//             }
+
+               /*
+                * The legacy capacity probe can misclassify GameCube media on some
+                * HLDS drives because an out-of-range dummy read does not always
+                * return the expected ILLEGAL REQUEST / LBA OUT OF RANGE sense data.
+                * Once sector 0 has been unscrambled, its platform magic is the
+                * authoritative discriminator.
+                *
+                * Explicit -T selections remain authoritative and are not changed.
+                * Explicit -S geometry also remains authoritative.
+                */
+               header_type = nintendo_disc_header_detect (buf, SECTOR_SIZE);
+               previous_type = d -> type;
+
+               if (allow_type_correction &&
+                   header_type == NINTENDO_DISC_HEADER_GAMECUBE) {
+                       d -> type = DISC_TYPE_GAMECUBE;
+                       if (allow_size_correction)
+                               d -> sectors_no = DISC_GAMECUBE_SECTORS_NO;
+               } else if (allow_type_correction &&
+                          header_type == NINTENDO_DISC_HEADER_WII &&
+                          d -> type != DISC_TYPE_WII &&
+                          d -> type != DISC_TYPE_WII_DL) {
+                       d -> type = DISC_TYPE_WII;
+                       if (allow_size_correction)
+                               d -> sectors_no = DISC_WII_SECTORS_NO_SL;
+               }
+
+               if (allow_type_correction &&
+                   header_type != NINTENDO_DISC_HEADER_UNKNOWN &&
+                   previous_type != d -> type) {
+                       warning ("Disc type corrected from %s to %s using sector-0 platform magic",
+                                disc_detected_type_name (previous_type),
+                                disc_detected_type_name (d -> type));
+               }
 
                /* Game ID */
                strncpy (d -> game_id, (char *) buf + 1, 2);
 
                /* Game ID */
                strncpy (d -> game_id, (char *) buf + 1, 2);
@@ -2998,17 +3242,56 @@ static bool disc_probe_gdr8081n_e7_profile (disc *d) {
 static bool disc_crack_seeds (disc *d) {
        int i;
 
 static bool disc_crack_seeds (disc *d) {
        int i;
 
-       /* As a Nintendo GameCube/Wii disc should not have too many keys, 20 should be enough */
+       /* As a Nintendo GameCube/Wii disc should not have too many keys, 20 should be enough. */
        debug ("Retrieving all DVD seeds");
        debug ("Retrieving all DVD seeds");
-       if (!disc_probe_gdr8050l_e7_speed_profile (d))
+       disc_seed_diag_clear (d);
+       d -> seed_diagnostic_active = true;
+       d -> seed_diagnostic_block = 0;
+       xbox_ref_log_fprintf (stderr, "\n");
+       disc_seed_diag_log (d,
+               "campaign=begin blocks=20 method=%d profile=%s cache-base=0x%08X windows=%u",
+               d -> read_method,
+               dvd_get_hlds_e7_profile_name (d -> dvd),
+               dvd_get_hlds_e7_cache_base (d -> dvd),
+               dvd_get_hlds_e7_mem_blocks (d -> dvd));
+
+       if (!disc_probe_gdr8050l_e7_speed_profile (d)) {
+               disc_seed_diag_fail (d, 0, 15, 0, "profile-probe",
+                       "GDR-8050L runtime profile probe failed", -1, false);
+               d -> seed_diagnostic_active = false;
                return false;
                return false;
-       if (!disc_probe_gdr8081n_e7_profile (d))
+       }
+       if (!disc_probe_gdr8081n_e7_profile (d)) {
+               disc_seed_diag_fail (d, 0, 15, 0, "profile-probe",
+                       "GDR-8081N runtime profile probe failed", -1, false);
+               d -> seed_diagnostic_active = false;
                return false;
                return false;
+       }
+
        for (i = 0; i < 20 * 16; i += 16) {
        for (i = 0; i < 20 * 16; i += 16) {
-               if (!disc_read_sector (d, i, NULL, NULL))
+               d -> seed_diagnostic_block = (unsigned int) (i / 16);
+               disc_seed_diag_clear (d);
+               disc_seed_diag_log (d,
+                       "block=%u/20 sectors=%d..%d stage=seed-block result=BEGIN",
+                       d -> seed_diagnostic_block + 1, i, i + 15);
+               if (!disc_read_sector (d, (u_int32_t) i, NULL, NULL)) {
+                       if (!d -> seed_diagnostic.valid)
+                               disc_seed_diag_fail (d, (u_int32_t) i, (u_int32_t) i + 15,
+                                       0, "seed-block-read", "disc_read_sector returned failure without a classified sub-stage", -1, false);
+                       disc_seed_diag_log (d,
+                               "campaign=fail block=%u/20 sectors=%d..%d",
+                               d -> seed_diagnostic_block + 1, i, i + 15);
+                       d -> seed_diagnostic_active = false;
                        return false;
                        return false;
+               }
+               disc_seed_diag_log (d,
+                       "block=%u/20 sectors=%d..%d stage=seed-block result=PASS",
+                       d -> seed_diagnostic_block + 1, i, i + 15);
        }
 
        }
 
+       disc_seed_diag_clear (d);
+       disc_seed_diag_log (d, "campaign=pass blocks=20");
+       d -> seed_diagnostic_active = false;
        return true;
 }
 
        return true;
 }
 
@@ -3118,7 +3401,11 @@ bool disc_init (disc *d, u_int32_t disctype, u_int32_t sectors_no) {
                disc_set_unscrambling (d, false);
                out = true;
        }
                disc_set_unscrambling (d, false);
                out = true;
        }
-       else if (disc_analyze (d)) {
+       else if (disc_analyze (
+                    d,
+                    disctype > 4,
+                    sectors_no == (u_int32_t) -1
+                )) {
                disc_check_update (d);
                out = true;
        } else {
                disc_check_update (d);
                out = true;
        } else {
@@ -3228,10 +3515,14 @@ int disc_xbox_read_capacity_10 (disc *d, u_int32_t *sectors, u_int32_t *sector_s
 
 
 
 
 
 
-int disc_xbox_recovery_kick (disc *d, bool auth_recovery) {
+int disc_xbox_prepare_game_view (disc *d, u_int32_t *sectors, u_int32_t *sector_size) {
+       int out;
        if (!d || d -> type != DISC_TYPE_XBOX)
                return -1;
        if (!d || d -> type != DISC_TYPE_XBOX)
                return -1;
-       return dvd_xbox_recovery_kick (d -> dvd, auth_recovery);
+       out = dvd_xbox_prepare_game_view (d -> dvd, sectors, sector_size);
+       if (out == 0 && sectors)
+               d -> sectors_no = *sectors;
+       return out;
 }
 
 int disc_refresh_volume (disc *d) {
 }
 
 int disc_refresh_volume (disc *d) {
@@ -3270,8 +3561,20 @@ int disc_xbox_wait_ready (disc *d, unsigned int timeout_ms) {
        return dvd_wait_ready (d -> dvd, timeout_ms);
 }
 
        return dvd_wait_ready (d -> dvd, timeout_ms);
 }
 
+char *disc_get_drive_vendor (disc *d) {
+       return d ? dvd_get_vendor (d -> dvd) : NULL;
+}
+
+char *disc_get_drive_product_id (disc *d) {
+       return d ? dvd_get_product_id (d -> dvd) : NULL;
+}
+
+char *disc_get_drive_firmware_revision (disc *d) {
+       return d ? dvd_get_product_revision (d -> dvd) : NULL;
+}
+
 char *disc_get_drive_model_string (disc *d) {
 char *disc_get_drive_model_string (disc *d) {
-       return (dvd_get_model_string (d -> dvd));
+       return d ? dvd_get_model_string (d -> dvd) : NULL;
 }
 
 
 }