+/***************************************************************************
+ * Copyright (C) 2007 by Arep *
+ * Support is provided through the forums at *
+ * http://wii.console-tribe.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
+ ***************************************************************************/
+
+/*! \file
+ * \brief Analyser and dumper for Nintendo GameCube/Wii discs.
+ *
+ * The functions in this file can be used to retrieve information about a Nintendo GameCube/Wii optical disc. Information is both structural (i.e.: Number of
+ * sectors, partitions, etc) and game-related (i.e.: Game Title, version, etc). This is the main object that should be used by applications.
+ *
+ * Most of the disc structure information used in this file comes from http://www.gc-linux.org/docs/yagcd.html and
+ * http://www.wiili.org/index.php/GameCube_Optical_Disc .
+ */
+
+#include "misc.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+//#include <time.h>
+#include "constants.h"
+#include "byteorder.h"
+#include "disc.h"
+#include "dvd_drive.h"
+
+static void hlds_e7_visible_probe_log (const char *fmt, ...) {
+ static bool started = false;
+ va_list ap;
+ if (!started) {
+ fprintf (stderr, "\n");
+ started = true;
+ }
+ va_start (ap, fmt);
+ vfprintf (stderr, fmt, ap);
+ va_end (ap);
+ fprintf (stderr, "\n");
+ fflush (stderr);
+}
+#include "unscrambler.h"
+
+// #define cachedebug(...) debug (__VA_ARGS__);
+#define cachedebug(...)
+
+
+/* Cache always deals with 16-sector blocks. All numbers refer to the 16-sector blocks */
+#define DISC_MINIMUM_CACHE_SIZE 5
+#define DISC_DEFAULT_CACHE_SIZE 40
+#define CACHE_ENTRY_INVALID ((u_int32_t) -1)
+
+
+#define DISC_GAMECUBE_SECTORS_NO 0x0AE0B0 /* 712880 */
+#define DISC_WII_SECTORS_NO_SL 0x230480 /* 2294912 */
+#define DISC_WII_SECTORS_NO_DL 0x3F69C0 /* 4155840 */
+#define DISC_XBOX_GDR8050L_UNLOCKED_SECTORS_NO 0x345B60 /* 3431264 */
+
+
+#define MAX_READ_RETRIES 5
+
+#define DEFAULT_READ_METHOD 0
+#define DEFAULT_READ_SECTOR disc_read_sector_0
+
+
+typedef int (*disc_read_sector_func) (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata);
+
+u_int8_t buf[1024*1024*4];
+u_int8_t buf_unscrambled[1024*1024*4];
+
+//struct timeval tim;
+//double t1, t2;
+
+/*! \brief A structure that represents a Nintendo GameCube/Wii optical disc.
+ */
+struct disc_s {
+ dvd_drive *dvd; //!< The structure for the DVD-drive the disc is inserted in.
+ disc_type type; //!< The disc type.
+ char system_id; //!< A letter identifying the target system.
+ char game_id[2 + 1]; //!< Two letters identifying the game.
+ disc_region region; //!< The disc region.
+ char maker[3]; //!< Two letters identifying the maker of the game.
+ u_int8_t version; //!< A number identifying the game version.
+ char *version_string; //!< The same as <code>version</code>, in a more human-understandable format.
+ char *title; //!< The game title.
+ bool has_update; //!< True if the game contains a system update (Only possible for Wii discs).
+ u_int32_t sectors_no; //!< The number of sectors of the disc.
+ u_int32_t layerbreak; //!< For dual-layer DVDs.
+
+ u_int32_t sec_disc;
+ u_int32_t sec_mem;
+ u_int32_t max_cnt;
+ u_int32_t max_blk;
+
+ /* Read function & stuff */
+ int command; //!< Buffer access command ID.
+ int read_method; //!< The read method ID.
+// int def_read_method; //!< Default read method ID.
+ disc_read_sector_func read_sector; //!< The actual function that will be used to perform read operations, corresponding to <code>read_method</code>.
+ bool unscrambling; //!< If true, raw data read from the disc will be unscrambled to assure it is error-free. Disabling this is only useful for raw performance tests.
+ unscrambler *u; //!< The unscrambler structure that will be used to perform the unscrambling.
+
+ /* 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.
+ 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_cache_init (disc *d, u_int32_t size) {
+ u_int32_t i;
+
+ if (size < DISC_MINIMUM_CACHE_SIZE) {
+ error ("Invalid cache size %u (must be >= %u)", size, DISC_MINIMUM_CACHE_SIZE);
+ exit (3);
+ } else {
+ d -> cache_size = size;
+ d -> cache = (u_int8_t **) malloc (sizeof (u_int8_t *) * size);
+ d -> raw_cache = (u_int8_t **) malloc (sizeof (u_int8_t *) * size);
+ for (i = 0; i < size; i++) {
+ d -> cache[i] = (u_int8_t *) malloc (sizeof (u_int8_t) * BLOCK_SIZE);
+ d -> raw_cache[i] = (u_int8_t *) malloc (sizeof (u_int8_t) * RAW_BLOCK_SIZE);
+ }
+
+ d -> cache_map = (u_int32_t *) malloc (sizeof (u_int32_t) * size);
+ for (i = 0; i < size; i++)
+ d -> cache_map[i] = CACHE_ENTRY_INVALID;
+ }
+
+ return;
+}
+
+
+static void disc_cache_destroy (disc *d) {
+ u_int32_t i;
+
+ my_free (d -> cache_map);
+
+ for (i = 0; i < d -> cache_size; i++) {
+ my_free (d -> cache[i]);
+ my_free (d -> raw_cache[i]);
+ }
+ my_free (d -> cache);
+ my_free (d -> raw_cache);
+ d -> cache_size = 0;
+
+ return;
+}
+
+static void disc_cache_clear (disc *d) {
+ u_int32_t i;
+
+ if (!d || !d -> cache_map)
+ return;
+ for (i = 0; i < d -> cache_size; i++)
+ d -> cache_map[i] = CACHE_ENTRY_INVALID;
+}
+
+
+void disc_cache_add_block (disc *d, u_int32_t block, u_int8_t *data, u_int8_t *rawdata) {
+ u_int32_t pos;
+ u_int32_t cnt;
+
+ pos = block % d -> cache_size;
+ //uniform unscrambled output
+ memcpy (d -> cache[pos], data, BLOCK_SIZE);
+ if (d -> type == DISC_TYPE_DVD || d -> type == DISC_TYPE_XBOX) {
+ for (cnt = 0; cnt < SECTORS_PER_BLOCK; cnt++) {
+ memcpy (rawdata+(cnt*RAW_SECTOR_SIZE)+12, data+(cnt*SECTOR_SIZE), SECTOR_SIZE);
+ }
+ } else {
+ for (cnt = 0; cnt < SECTORS_PER_BLOCK; cnt++) {
+ memcpy (rawdata+(cnt*RAW_SECTOR_SIZE)+6, data+(cnt*SECTOR_SIZE), SECTOR_SIZE);
+ }
+ }
+ memcpy (d -> raw_cache[pos], rawdata, RAW_BLOCK_SIZE);
+ d -> cache_map[pos] = block;
+
+ cachedebug ("Cached block %u (sectors %u-%u) at position %u", block, block * SECTORS_PER_BLOCK, (block + 1) * SECTORS_PER_BLOCK - 1, pos);
+
+ return;
+}
+
+
+static bool disc_cache_lookup_block (disc *d, u_int32_t block, u_int8_t **data, u_int8_t **rawdata) {
+ u_int32_t pos;
+ bool out;
+
+ pos = block % d -> cache_size;
+
+ if (d -> cache_map[pos] == block) {
+ cachedebug ("Cache HIT for block %u", block);
+ if (data)
+ *data = d -> cache[pos];
+ if (rawdata)
+ *rawdata = d -> raw_cache[pos];
+ out = true;
+ } else {
+ cachedebug ("Cache MISS for block %u", block);
+ if (data)
+ *data = NULL;
+ if (rawdata)
+ *rawdata = NULL;
+ out = false;
+ }
+
+ return (out);
+}
+
+
+static int disc_read_sector_generic (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata, u_int32_t method) {
+ bool out;
+ u_int32_t start_block;
+ int ret, retry;
+ u_int32_t step, cnt, max_cnt, max_blk;
+ u_int32_t block_len, block_size, _block_size, last_block_size, block_cnt;
+//fprintf (stdout,"disc_read_sector_%d", method);
+ start_block = sector_no / SECTORS_PER_BLOCK;
+
+ out = false;
+ step = d->sec_mem;
+ max_cnt = d->max_cnt;
+ max_blk = d->max_blk;
+
+ block_size = step*2064;
+ last_block_size = block_size;
+ block_len = 1;
+ if (block_size > 27 * 2064) {
+ block_len = block_size / (27*2064);
+ if (block_size % (27*2064) != 0) block_len += 1;
+ block_size = 27*2064;
+ last_block_size = (step*2064) - (27*2064*(block_len-1));
+ }
+ _block_size=block_size;
+
+ for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) {
+ /* Assume everything will turn out well */
+ out = true;
+
+ //Streaming read
+ if (retry < 3) {
+ cnt=0;
+ while (cnt <= max_cnt){
+
+ _block_size=block_size;
+ if (method == 0 || method == 1 || method == 4) {
+ if (sector_no+(cnt*step) +992 +16 <= d -> sectors_no) //smaller than last sector
+ dvd_read_sector_dummy (d -> dvd, sector_no+(cnt*step) +992, 16, NULL, NULL, 0);
+ else if (sector_no+(cnt*step) -992 >= 0) //larger than first sector
+ dvd_read_sector_dummy (d -> dvd, sector_no+(cnt*step) -992, 16, NULL, NULL, 0);
+ else dvd_flush_cache_READ12 (d -> dvd, sector_no+(cnt*step), NULL);
+ }
+
+ if (method == 0 || method == 2 || method == 5) dvd_flush_cache_READ12 (d -> dvd, sector_no+(cnt*step), NULL);
+ if (method == 0 || method == 1 || method == 2 || method == 3) ret = dvd_read_sector_dummy (d -> dvd, sector_no+(cnt*step), d->sec_disc, NULL, &buf_unscrambled[0], 2064*step);
+ if (method == 4 || method == 5 || method == 6) ret = dvd_read_streaming (d -> dvd, sector_no+(cnt*step), d->sec_disc, NULL, &buf_unscrambled[0], 2064*step);
+ if (ret >= 0) {
+ for (block_cnt=0; block_cnt<block_len; block_cnt++) {
+ if (dvd_memdump (d -> dvd, block_cnt*27*2064, 1, _block_size, &buf[(cnt*(2064 * step))+(block_cnt*27*2064)]) < 0) {
+ error ("Memdump failed");
+ //retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */ //no it's not!
+ out = false;
+ break;
+ }
+ if (block_cnt==block_len-1) _block_size = last_block_size;
+ }
+ if (!out) break;
+ //do this check only on 1st layer
+ else if (((buf[cnt*(2064*step)] & 1) == 0) && ((buf[cnt*(2064*step)+1]<<16)+(buf[cnt*(2064*step)+2]<<8)+(buf[cnt*(2064*step)+3]) != 0x30000 + sector_no+(cnt*step))) {
+ out = false;
+ break;
+ }
+ else cnt += 1;
+ } else {
+ error ("dvd_read_streaming() failed with %d", ret);
+ out = false;
+ break;
+ }
+
+ }
+
+ if (cnt < max_cnt) out = false;
+ else {
+#ifdef DEBUG
+ if (d -> unscrambling) {
+#endif
+ /* Try to unscramble all data to see if EDC fails */
+ //for(cnt=0; cnt <= 4; cnt++) {
+ for(cnt=max_blk; cnt--;) {
+ if (!unscrambler_unscramble_16sectors (d -> u, sector_no+(cnt*16), &buf[cnt*(2064*16)], &buf_unscrambled[cnt*(2048*16)]))
+ out = false;
+ }
+#ifdef DEBUG
+ }
+#endif
+ }
+ if (out) {
+ /* If data were unscrambled correctly, add them to the cache */
+ //for(cnt = 0; cnt <= 4; cnt++) {
+ for(cnt=max_blk; cnt--;) {
+ disc_cache_add_block (d, start_block+cnt, &buf_unscrambled[cnt*(2048*16)], &buf[cnt*(2064*16)]);
+ }
+ }
+ } //if (retry < 3)
+
+ //Simple read on 4rth try
+ else {
+ 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);
+
+ dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL);
+ ret = dvd_read_sector_dummy (d -> dvd, sector_no, SECTORS_PER_BLOCK, NULL, NULL, 0);
+ if (ret >= 0) {
+ if (dvd_memdump (d -> dvd, 0, 1, RAW_BLOCK_SIZE, buf) < 0) {
+ error ("Memdump failed");
+ //retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */
+ out = false;
+ }
+ else if ( ((*(buf) & 1) == 0) && ((*(buf+1)<<16)+(*(buf+2)<<8)+(*(buf+3)) != 0x30000+sector_no) ) out = false;
+ else {
+#ifdef DEBUG
+ if (d -> unscrambling) {
+#endif
+ /* Try to unscramble all data to see if EDC fails */
+ if (!unscrambler_unscramble_16sectors (d -> u, sector_no, buf, buf_unscrambled))
+ out = false;
+#ifdef DEBUG
+ }
+#endif
+ }
+ if (out) {
+ /* If data were unscrambled correctly, add them to the cache */
+ disc_cache_add_block (d, start_block, buf_unscrambled, buf);
+ }
+ } else {
+ error ("dvd_read_sector_dummy() failed with %d", ret);
+ out = false;
+ }
+ } //else
+ } //for
+
+ if (!out)
+ error ("Too many retries, giving up");
+
+ return (out);
+}
+
+
+static int disc_read_sector_xbox (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ bool out;
+ u_int32_t start_block, block_start, sectors_to_read;
+ u_int8_t readbuf[BLOCK_SIZE];
+ u_int8_t rawbuf[RAW_BLOCK_SIZE];
+
+ (void) data;
+ (void) rawdata;
+
+ start_block = sector_no / SECTORS_PER_BLOCK;
+ block_start = start_block * SECTORS_PER_BLOCK;
+ if (block_start >= d -> sectors_no)
+ return false;
+
+ sectors_to_read = SECTORS_PER_BLOCK;
+ if (block_start + sectors_to_read > d -> sectors_no)
+ sectors_to_read = d -> sectors_no - block_start;
+
+ memset (readbuf, 0, sizeof (readbuf));
+ memset (rawbuf, 0, sizeof (rawbuf));
+
+ out = dvd_read_10 (d -> dvd, block_start, sectors_to_read, NULL, readbuf, sizeof (readbuf)) >= 0;
+ if (out)
+ disc_cache_add_block (d, start_block, readbuf, rawbuf);
+ else
+ error ("Xbox READ(10) failed at sector %u", block_start);
+
+ return out;
+}
+
+
+///////////////////////////// General /////////////////////////////
+static int disc_read_sector_0 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ return disc_read_sector_generic (d, sector_no, data, rawdata, 0);
+}
+
+
+
+////////////////////////// Non-Streaming //////////////////////////
+static int disc_read_sector_1 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ return disc_read_sector_generic (d, sector_no, data, rawdata, 1);
+
+}
+
+static int disc_read_sector_2 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ return disc_read_sector_generic (d, sector_no, data, rawdata, 2);
+
+}
+
+
+static int disc_read_sector_3 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ return disc_read_sector_generic (d, sector_no, data, rawdata, 3);
+
+}
+
+
+
+//////////////////////////// Streaming ////////////////////////////
+static int disc_read_sector_4 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ return disc_read_sector_generic (d, sector_no, data, rawdata, 4);
+}
+
+
+static int disc_read_sector_5 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ return disc_read_sector_generic (d, sector_no, data, rawdata, 5);
+}
+
+
+static int disc_read_sector_6 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ return disc_read_sector_generic (d, sector_no, data, rawdata, 6);
+}
+
+
+
+///////////////////////////// Hitachi /////////////////////////////
+static int disc_read_sector_7 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ bool out;
+ u_int32_t start_block;
+ int j, ret, retry;
+ u_int8_t buf[5][16 * 2064];
+ u_int8_t buf_unscrambled[5][16 * 2048];
+//fprintf (stdout,"disc_read_sector_7");
+ start_block = sector_no / SECTORS_PER_BLOCK;
+
+ out = false;
+ for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) {
+ /* Assume everything will turn out well */
+ out = true;
+
+ 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);
+ }
+
+ if ((ret = dvd_read_sector_streaming (d -> dvd, sector_no, NULL, NULL, 0)) >= 0) {
+ for (j = 0; j < 5 && sector_no + j * 16 < d -> sectors_no && out; j++) {
+ if (dvd_memdump (d -> dvd, 0 + (j * 16 * 2064), 1, 16 * 2064, buf[j]) < 0) { /* Dumping in a single block is faster */
+ error ("Memdump failed");
+ out = false;
+ retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */
+ } else {
+#ifdef DEBUG
+ if (d -> unscrambling) {
+#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
+ }
+#endif
+ }
+ }
+
+ if (out) {
+ /* It seems all data was unscrambled correctly, so cache them out */
+ for (j = 0; j < 5 && sector_no + j * 16 < d -> sectors_no; j++)
+ disc_cache_add_block (d, start_block + j, buf_unscrambled[j], buf[j]);
+
+ }
+ } else {
+ error ("dvd_read_sector_streaming() failed with %d", ret);
+ out = false;
+ }
+ }
+
+ if (!out)
+ error ("Too many retries, giving up");
+
+ return (out);
+}
+
+
+
+static bool disc_read_sector_8_split_recover_block (disc *d, u_int32_t block_sector) {
+ static const int chunk_sizes[] = { 8, 4, 2, 1 };
+ bool out;
+ int c, chunk_len, chunk_start, k, ret;
+ u_int32_t ram_offset, block_no;
+ u_int8_t *sect;
+ u_int8_t raw_block[RAW_BLOCK_SIZE];
+ u_int8_t iso_block[BLOCK_SIZE];
+ u_int8_t readbuf[BLOCK_SIZE];
+
+ block_no = block_sector / SECTORS_PER_BLOCK;
+
+ for (c = 0; c < (int) (sizeof (chunk_sizes) / sizeof (chunk_sizes[0])); c++) {
+ chunk_len = chunk_sizes[c];
+ memset (raw_block, 0, sizeof (raw_block));
+ memset (iso_block, 0, sizeof (iso_block));
+ out = true;
+
+ warning ("Method 8 split recovery: trying %d-sector chunks for sectors %u..%u", chunk_len, block_sector, block_sector + SECTORS_PER_BLOCK - 1);
+
+ 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));
+
+ if (chunk_sector + 992 + 16 <= d -> sectors_no)
+ dvd_read_sector_dummy (d -> dvd, chunk_sector + 992, 16, NULL, NULL, 0);
+ else if (chunk_sector >= 992)
+ dvd_read_sector_dummy (d -> dvd, chunk_sector - 992, 16, NULL, NULL, 0);
+ else
+ dvd_flush_cache_READ12 (d -> dvd, chunk_sector, NULL);
+
+ ret = dvd_read_streaming (d -> dvd, chunk_sector, (u_int32_t) chunk_len, NULL, readbuf, (size_t) chunk_len * SECTOR_SIZE);
+ if (ret < 0) {
+ 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;
+ }
+
+ for (k = 0; k < chunk_len; k++) {
+ 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) {
+ 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) {
+ warning ("Method 8 split recovery: EDC memdump failed at sector %u", chunk_sector + (u_int32_t) k);
+ out = false;
+ break;
+ }
+
+ memcpy (sect + 12, readbuf + ((size_t) k * SECTOR_SIZE), SECTOR_SIZE);
+ }
+ }
+
+ if (out && !unscrambler_unscramble_16sectors (d -> u, block_sector, raw_block, iso_block)) {
+ 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;
+ }
+
+ 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);
+ return true;
+ }
+ }
+
+ warning ("Method 8 split recovery: all chunk sizes failed for sectors %u..%u", block_sector, block_sector + SECTORS_PER_BLOCK - 1);
+ return false;
+}
+
+static bool disc_hlds_type_is_gdr8050l_accel (u_int32_t type) {
+ return type == 442 || type == 443 || type == 445;
+}
+
+static bool disc_hlds_type_is_gdr8050l_no_prefetch (u_int32_t type) {
+ return type == 44 || type == 45 || disc_hlds_type_is_gdr8050l_accel (type);
+}
+
+
+static bool disc_hlds_type_is_gdr8081n_search_guided (u_int32_t type) {
+ return type == 815;
+}
+
+static bool hlds_e7_find_exact_raw_header_offset (const u_int8_t *dumpbuf, size_t dump_len, u_int32_t sector_no, size_t *out_off) {
+ size_t off;
+ u_int32_t expected;
+ u_int32_t got;
+
+ if (out_off)
+ *out_off = (size_t) -1;
+ if (!dumpbuf || dump_len < RAW_SECTOR_SIZE)
+ return false;
+ expected = 0x30000U + sector_no;
+ for (off = 0; off + RAW_SECTOR_SIZE <= dump_len; off++) {
+ got = ((u_int32_t) dumpbuf[off + 1] << 16) | ((u_int32_t) dumpbuf[off + 2] << 8) | (u_int32_t) dumpbuf[off + 3];
+ if (got != expected)
+ continue;
+ /* Avoid all-zero/all-ff false positives. Raw headers observed on HLDS
+ * families can have different first-byte control bits, so do not require
+ * exact parity here; the final unscrambler/EDC pass is the authority. */
+ if (((dumpbuf[off] | dumpbuf[off + 1] | dumpbuf[off + 2] | dumpbuf[off + 3]) == 0x00) ||
+ ((dumpbuf[off] & dumpbuf[off + 1] & dumpbuf[off + 2] & dumpbuf[off + 3]) == 0xFF))
+ continue;
+ if (out_off)
+ *out_off = off;
+ return true;
+ }
+ return false;
+}
+
+static int hlds_e7_count_exact_raw_headers_for_block (const u_int8_t *dumpbuf, size_t dump_len, u_int32_t block_sector, size_t offsets[SECTORS_PER_BLOCK]) {
+ int k;
+ int count;
+ size_t off;
+ count = 0;
+ if (offsets) {
+ for (k = 0; k < SECTORS_PER_BLOCK; k++)
+ offsets[k] = (size_t) -1;
+ }
+ for (k = 0; k < SECTORS_PER_BLOCK; k++) {
+ if (hlds_e7_find_exact_raw_header_offset (dumpbuf, dump_len, block_sector + (u_int32_t) k, &off)) {
+ if (offsets)
+ offsets[k] = off;
+ count++;
+ }
+ }
+ return count;
+}
+
+static bool hlds_e7_raw_header_offsets_are_sector_shaped (const size_t offsets[SECTORS_PER_BLOCK]) {
+ int k;
+ int stride_matches;
+ size_t expected;
+ if (!offsets)
+ return false;
+ for (k = 0; k < SECTORS_PER_BLOCK; k++) {
+ if (offsets[k] == (size_t) -1)
+ return false;
+ }
+ /* A real raw cache block has one 2064-byte raw sector per logical sector.
+ * False positives observed on GDR-8081N v4 looked like SRAM/tables with
+ * sector-number patterns only 4 bytes apart, so require a sane 2064-byte
+ * sector stride before treating matches as real cache sectors. */
+ stride_matches = 0;
+ for (k = 1; k < SECTORS_PER_BLOCK; k++) {
+ expected = offsets[0] + ((size_t) k * RAW_SECTOR_SIZE);
+ if (offsets[k] == expected)
+ stride_matches++;
+ }
+ return stride_matches >= 12;
+}
+
+static size_t hlds_e7_find_command_echo_offset (const u_int8_t *buf, size_t len) {
+ static const u_int8_t sig[] = {0xE7, 0x48, 0x49, 0x54, 0x01};
+ size_t off;
+ if (!buf || len < sizeof (sig))
+ return (size_t) -1;
+ for (off = 0; off + sizeof (sig) <= len; off++) {
+ if (memcmp (buf + off, sig, sizeof (sig)) == 0)
+ return off;
+ }
+ return (size_t) -1;
+}
+
+static int disc_read_sector_8_gdr8081n_search_guided (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ static bool logged = false;
+ bool out;
+ u_int32_t block_sector;
+ u_int32_t start_block;
+ u_int32_t profile_blocks;
+ u_int32_t scan_len;
+ int k;
+ int retry;
+ int ret;
+ int found_count;
+ int first_missing;
+ bool sector_shaped;
+ size_t offsets[SECTORS_PER_BLOCK];
+ u_int8_t *scanbuf;
+ u_int8_t *sect;
+ u_int8_t raw_block[RAW_BLOCK_SIZE];
+ u_int8_t iso_block[BLOCK_SIZE];
+ u_int8_t readbuf[BLOCK_SIZE];
+
+ start_block = sector_no / SECTORS_PER_BLOCK;
+ block_sector = start_block * SECTORS_PER_BLOCK;
+ profile_blocks = dvd_get_hlds_e7_mem_blocks (d -> dvd);
+ if (profile_blocks < 1 || profile_blocks > 5)
+ profile_blocks = 5;
+ scan_len = profile_blocks * RAW_BLOCK_SIZE;
+ scanbuf = (u_int8_t *) malloc (scan_len);
+ if (!scanbuf) {
+ error ("GDR-8081N scan-guided Method 8: unable to allocate %u-byte scan buffer", scan_len);
+ return false;
+ }
+ if (!logged) {
+ hlds_e7_visible_probe_log ("GDR-8081N 0xE7: using scan-guided single-block Method 8 profile at 0x%08x, scan windows=%u", dvd_get_hlds_e7_cache_base (d -> dvd), profile_blocks);
+ logged = true;
+ }
+
+ out = false;
+ for (retry = 0; !out && retry < 1; retry++) {
+ out = true;
+ if (retry > 0)
+ warning ("GDR-8081N scan-guided Method 8 retry %d for sectors %u..%u", retry, block_sector, block_sector + SECTORS_PER_BLOCK - 1);
+
+ /* Keep this conservative: the v2 scanner showed sector-cache material inside
+ * the 0x80000000 five-window range, but not necessarily in the exact Type4
+ * j/k slot map. For now, reconstruct only the requested 16-sector block
+ * from a full-window search. Do not require all five cached windows to map;
+ * that made v3 reject useful data before seed cracking could start. */
+ if (block_sector > d -> sectors_no - 1000)
+ dvd_read_sector_streaming (d -> dvd, block_sector - 16 * 5 * 2, NULL, NULL, 0);
+ else
+ dvd_read_sector_streaming (d -> dvd, block_sector + 16 * 5, NULL, NULL, 0);
+
+ if ((ret = dvd_read_sector_streaming (d -> dvd, block_sector, NULL, readbuf, sizeof (readbuf))) < 0) {
+ error ("GDR-8081N scan-guided Method 8: dvd_read_sector_streaming(%u) failed with %d", block_sector, ret);
+ out = false;
+ continue;
+ }
+
+ memset (scanbuf, 0, scan_len);
+ if (dvd_memdump (d -> dvd, 0, profile_blocks, RAW_BLOCK_SIZE, scanbuf) < 0) {
+ error ("GDR-8081N scan-guided Method 8: full-window memdump failed");
+ out = false;
+ continue;
+ }
+
+ found_count = hlds_e7_count_exact_raw_headers_for_block (scanbuf, scan_len, block_sector, offsets);
+ sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (offsets);
+ if (found_count == SECTORS_PER_BLOCK && !sector_shaped) {
+ warning ("GDR-8081N scan-guided Method 8: found 16/16 sector-number patterns for sectors %u..%u, but offsets are not 2064-byte sector-shaped; treating as SRAM/table false positive",
+ block_sector, block_sector + SECTORS_PER_BLOCK - 1);
+ out = false;
+ break;
+ }
+ if (found_count != SECTORS_PER_BLOCK) {
+ first_missing = -1;
+ for (k = 0; k < SECTORS_PER_BLOCK; k++) {
+ if (offsets[k] == (size_t) -1) {
+ first_missing = k;
+ break;
+ }
+ }
+ warning ("GDR-8081N scan-guided Method 8: found %d/16 exact raw headers for sectors %u..%u; first missing sector %u",
+ found_count, block_sector, block_sector + SECTORS_PER_BLOCK - 1,
+ first_missing >= 0 ? block_sector + (u_int32_t) first_missing : block_sector);
+ out = false;
+ continue;
+ }
+
+ for (k = 0; k < SECTORS_PER_BLOCK; k++) {
+ sect = &raw_block[k * RAW_SECTOR_SIZE];
+ memcpy (sect, scanbuf + offsets[k], 12);
+ memcpy (sect + 12, readbuf + ((size_t) k * SECTOR_SIZE), SECTOR_SIZE);
+ memcpy (sect + 2060, scanbuf + offsets[k] + 2060, 4);
+ }
+
+ if (!unscrambler_unscramble_16sectors (d -> u, block_sector, raw_block, iso_block)) {
+ warning ("GDR-8081N scan-guided Method 8: EDC/unscramble validation failed for sectors %u..%u after finding all 16 headers", block_sector, block_sector + SECTORS_PER_BLOCK - 1);
+ out = false;
+ continue;
+ }
+
+ disc_cache_add_block (d, start_block, iso_block, raw_block);
+ }
+
+ free (scanbuf);
+ if (!out)
+ error ("GDR-8081N scan-guided Method 8: strict sector-layout validation failed");
+ return out;
+}
+
+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;
+ int j, k, ret, retry;
+ 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");
+ start_block = sector_no / SECTORS_PER_BLOCK;
+ profile_blocks = dvd_get_hlds_e7_mem_blocks (d -> dvd);
+ if (profile_blocks < 1 || profile_blocks > 5)
+ profile_blocks = 5;
+
+ out = false;
+ for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) {
+ /* Assume everything will turn out well */
+ out = true;
+
+ 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);
+ }
+
+ /* 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. */
+ 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);
+ } else {
+ if (sector_no > d -> sectors_no - 1000)
+ dvd_read_sector_streaming (d -> dvd, sector_no - 16 * 5 * 2, NULL, NULL, 0);
+ else
+ dvd_read_sector_streaming (d -> dvd, sector_no + 16 * 5, NULL, NULL, 0);
+ }
+ if ((ret = dvd_read_sector_streaming (d -> dvd, sector_no, NULL, readbuf, sizeof (readbuf))) >= 0) {
+ 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;
+ /* 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) {
+ 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;
+ }
+ }
+ }
+
+ /* 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++) {
+ 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);
+ }
+#ifdef DEBUG
+ if (d -> unscrambling) {
+#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
+ }
+#endif
+ } else {
+ error ("dvd_read_sector_streaming() failed with %d", ret);
+ out = false;
+ }
+ }
+
+ 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]);
+ }
+ } else {
+ error ("dvd_read_sector_streaming() failed with %d", ret);
+ out = false;
+ }
+ }
+
+ if (!out && disc_hlds_type_is_gdr8050l_accel (dvd_get_hlds_e7_type (d -> dvd))) {
+ u_int32_t failed_type = dvd_get_hlds_e7_type (d -> dvd);
+ warning ("GDR-8050L modified 0xE7: accelerated profile %s failed at sector %u; falling back to proven single-window profile for this run",
+ dvd_get_hlds_e7_profile_name (d -> dvd), sector_no);
+ dvd_set_hlds_e7_runtime_profile (d -> dvd, 44, 0x80000000U, 1);
+ d -> hlds_e7_read_schedule_logged = false;
+ disc_cache_clear (d);
+ out = disc_read_sector_8 (d, sector_no, data, rawdata);
+ if (!out)
+ warning ("GDR-8050L modified 0xE7: fallback from accelerated profile %u also failed", failed_type);
+ }
+
+ 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);
+ out = disc_read_sector_8_split_recover_block (d, block_sector);
+ }
+
+ if (!out)
+ error ("Too many retries, giving up");
+
+ return (out);
+}
+
+
+static int disc_read_sector_9 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ bool out;
+ u_int32_t ram_offset;
+ int j, k, ret, retry;
+ u_int8_t *sect, buf[5][RAW_BLOCK_SIZE];
+ u_int8_t readbuf[BLOCK_SIZE], tmp[16];
+ u_int8_t buf_unscrambled[5][BLOCK_SIZE];
+ u_int32_t start_block;
+//fprintf (stdout,"disc_read_sector_9");
+ start_block = sector_no / SECTORS_PER_BLOCK;
+
+ out = false;
+ for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) {
+ /* Assume everything will turn out well */
+ out = true;
+
+ 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);
+ }
+
+ /* First READ command, this will cache 5 16-sector blocks. Immediately dump relevant data */
+ if (sector_no > d -> sectors_no - 1000)
+ dvd_read_sector_streaming (d -> dvd, sector_no - 16 * 5 * 2, NULL, NULL, 0);
+ else
+ dvd_read_sector_streaming (d -> dvd, sector_no + 16 * 5, NULL, NULL, 0);
+ if ((ret = dvd_read_sector_streaming (d -> dvd, sector_no, NULL, readbuf, BLOCK_SIZE)) >= 0) {
+ for (j = 0; j < 5 && 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;
+ /* Get first 12 bytes (ID. IED and CPR_MAI fields) and last 4 bytes (EDC field) with memdump */
+ if (j == 0 && k == 0) {
+ if (dvd_memdump (d -> dvd, ram_offset, 1, 12, sect) < 0) {
+ error ("Memdump (1) failed");
+ out = false;
+ retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */
+ }
+ } else {
+ memcpy (sect, tmp + 4, 12);
+ }
+
+ if (out && dvd_memdump (d -> dvd, ram_offset + 2060, 1, 16, tmp) < 0) { /* Dumping in a single block is faster */
+ error ("Memdump (2) failed");
+ out = false;
+ } else {
+ memcpy (sect + 2060, tmp, 4);
+ }
+ }
+ }
+
+ /* Now the same for remaining 4 16-sector blocks */
+ for (j = 0; j < 5 && 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, BLOCK_SIZE)) >= 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);
+ }
+#ifdef DEBUG
+ if (d -> unscrambling) {
+#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
+ }
+#endif
+ } else {
+ error ("dvd_read_sector_streaming() failed with %d", ret);
+ out = false;
+ }
+ }
+
+ if (out) {
+ /* It seems all data were unscrambled correctly, so cache them out */
+ for (j = 0; j < 5 && sector_no + j * SECTORS_PER_BLOCK < d -> sectors_no; j++)
+ disc_cache_add_block (d, start_block + j, buf_unscrambled[j], buf[j]);
+ }
+ } else {
+ error ("dvd_read_sector_streaming() failed with %d", ret);
+ out = false;
+ }
+ }
+
+ if (!out)
+ error ("Too many retries, giving up");
+
+ return (out);
+}
+
+
+/* We could also use the 'System ID' (first byte of the image) to tell the discs apart */
+static disc_type disc_detect_type (disc *d, u_int32_t forced_type, u_int32_t sectors_no) {
+ req_sense sense;
+
+ if (forced_type==0) {
+ d -> type = DISC_TYPE_GAMECUBE;
+ d -> sectors_no = DISC_GAMECUBE_SECTORS_NO;
+ } else if (forced_type==1) {
+ d -> type = DISC_TYPE_WII;
+ d -> sectors_no = DISC_WII_SECTORS_NO_SL;
+ } else if (forced_type==2) {
+ d -> type = DISC_TYPE_WII_DL;
+ d -> sectors_no = DISC_WII_SECTORS_NO_DL;
+ //dvd_get_layerbreak(d->dvd, &(d -> layerbreak), NULL);
+ } else if (forced_type==3) {
+ d -> type = DISC_TYPE_DVD;
+ if (sectors_no == -1) dvd_get_size(d->dvd, &(d -> sectors_no), NULL);
+ dvd_get_layerbreak(d->dvd, &(d -> layerbreak), NULL);
+ } else if (forced_type==4) {
+ d -> type = DISC_TYPE_XBOX;
+ d -> read_sector = disc_read_sector_xbox;
+ d -> read_method = 10;
+ if (sectors_no == -1) {
+ u_int32_t sector_size = 0;
+ /* Do not run the GDR-8050L handshake during type detection.
+ * Redump-style Xbox output must capture the visible DVD-video view
+ * before switching the drive into the unlocked game view. */
+ if (dvd_read_capacity_10(d->dvd, &(d -> sectors_no), §or_size, NULL) < 0 || sector_size != SECTOR_SIZE)
+ d -> sectors_no = DISC_XBOX_GDR8050L_UNLOCKED_SECTORS_NO;
+ }
+ } else {
+
+ if (dvd_is_xbox_drive(d->dvd)) {
+ d -> type = DISC_TYPE_XBOX;
+ d -> read_sector = disc_read_sector_xbox;
+ d -> read_method = 10;
+ {
+ u_int32_t sector_size = 0;
+ /* Keep the drive in its current/locked view for dump planning.
+ * The Xbox dumper explicitly unlocks only when it needs the
+ * game/XDVDFS view. */
+ if (dvd_read_capacity_10(d->dvd, &(d -> sectors_no), §or_size, NULL) < 0 || sector_size != SECTOR_SIZE)
+ d -> sectors_no = DISC_XBOX_GDR8050L_UNLOCKED_SECTORS_NO;
+ }
+ if (sectors_no != -1) d -> sectors_no = sectors_no;
+ return (d -> type);
+ }
+
+ /* Try to read a sector beyond the end of GameCube discs */
+ if (!dvd_read_sector_dummy (d -> dvd, DISC_GAMECUBE_SECTORS_NO + 100, SECTORS_PER_BLOCK, &sense, NULL, 0) && sense.sense_key == 0x05 && sense.asc == 0x21) {
+ d -> type = DISC_TYPE_GAMECUBE;
+ d -> sectors_no = DISC_GAMECUBE_SECTORS_NO;
+ } else {
+ if (!dvd_read_sector_dummy (d -> dvd, DISC_WII_SECTORS_NO_SL + 100, SECTORS_PER_BLOCK, &sense, NULL, 0) && sense.sense_key == 0x05 && sense.asc == 0x21) {
+ d -> type = DISC_TYPE_WII;
+ d -> sectors_no = DISC_WII_SECTORS_NO_SL;
+ } else {
+ d -> type = DISC_TYPE_WII_DL;
+ d -> sectors_no = DISC_WII_SECTORS_NO_DL;
+ //dvd_get_layerbreak(d->dvd, &(d -> layerbreak), NULL);
+ }
+ }
+
+ }
+ if (sectors_no != -1) d -> sectors_no = sectors_no;
+
+ return (d -> type);
+}
+
+
+/**
+ * Reads a sector from the disc (or from the cache), using the preset read method.
+ * @param d The disc structure.
+ * @param sector_no The requested sector number.
+ * @param data A buffer to hold the unscrambled sector data (or NULL).
+ * @param rawdata A buffer to hold the raw sector data (or NULL).
+ * @return
+ */
+int disc_read_sector (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) {
+ u_int32_t block;
+ u_int8_t *cdata, *crawdata;
+ int out;
+
+ /* Unscrambled data cannot be requested if unscrambling was disabled */
+ MY_ASSERT (!(data && !d -> unscrambling && d -> type != DISC_TYPE_XBOX));
+
+ block = sector_no / SECTORS_PER_BLOCK;
+
+ /* See if sector is in cache */
+ if (!(out = disc_cache_lookup_block (d, block, &cdata, &crawdata))) {
+ /* Requested block is not in cache, try to read it from media */
+ out = d -> read_sector (d, sector_no, data, rawdata);
+
+ /* Now requested sector is in cache, for sure ;) */
+ if (out)
+ MY_ASSERT (disc_cache_lookup_block (d, block, &cdata, &crawdata));
+ }
+
+ if (out) {
+ if (data)
+ *data = cdata + (sector_no % SECTORS_PER_BLOCK) * SECTOR_SIZE;
+ if (rawdata)
+ *rawdata = crawdata + (sector_no % SECTORS_PER_BLOCK) * RAW_SECTOR_SIZE;
+ } else {
+ if (data)
+ *data = NULL;
+ if (rawdata)
+ *rawdata = NULL;
+ }
+
+ return (out);
+}
+
+
+static bool disc_analyze (disc *d) {
+ u_int8_t *buf;
+ char tmp[0x03E0 + 1];
+ bool unscramble_old, out;
+
+ /* Force unscrambling for this read */
+ unscramble_old = d -> unscrambling;
+ disc_set_unscrambling (d, true);
+
+ 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);
+// }
+
+ /* Game ID */
+ strncpy (d -> game_id, (char *) buf + 1, 2);
+ d -> game_id[2] = '\0';
+
+ /* Region */
+ switch (buf[3]) {
+ case 'P':
+ d -> region = DISC_REGION_PAL;
+ break;
+ case 'E':
+ d -> region = DISC_REGION_NTSC;
+ break;
+ case 'J':
+ d -> region = DISC_REGION_JAPAN;
+ break;
+ case 'U':
+ d -> region = DISC_REGION_AUSTRALIA;
+ break;
+ case 'F':
+ d -> region = DISC_REGION_FRANCE;
+ break;
+ case 'D':
+ d -> region = DISC_REGION_GERMANY;
+ break;
+ case 'I':
+ d -> region = DISC_REGION_ITALY;
+ break;
+ case 'S':
+ d -> region = DISC_REGION_SPAIN;
+ break;
+ case 'X':
+ d -> region = DISC_REGION_PAL_X;
+ break;
+ case 'Y':
+ d -> region = DISC_REGION_PAL_Y;
+ break;
+ default:
+ d -> region = DISC_REGION_UNKNOWN;
+ break;
+ }
+
+ /* Maker code */
+ strncpy (d -> maker, (char *) buf + 4, 2);
+ d -> maker[2] = '\0';
+
+ /* Version */
+ d -> version = buf[7];
+ snprintf (tmp, sizeof (tmp), "1.%02u", d -> version);
+ my_strdup (d -> version_string, tmp);
+
+ /* Game title */
+ memcpy (tmp, buf + 0x0020, sizeof (tmp) - 1);
+ tmp[sizeof (tmp) - 1] = '\0';
+ strtrimr (tmp);
+ my_strdup (d -> title, tmp);
+
+ out = true;
+ } else {
+ error ("Cannot analyze disc");
+ out = false;
+ }
+
+ disc_set_unscrambling (d, unscramble_old);
+
+ return (out);
+}
+
+
+static char disc_type_strings[5][15] = {
+ "GameCube",
+ "Wii",
+ "Wii_DL",
+ "DVD",
+ "Xbox"
+};
+
+/**
+ * Retrieves the disc type.
+ * @param d The disc structure.
+ * @param dt This will be set to the disc type.
+ * @param dt_s This will point to a string describing the disc type.
+ * @return A string describing the disc type.
+ */
+char *disc_get_type (disc *d, disc_type *dt, char **dt_s) {
+ if (dt)
+ *dt = d -> type;
+
+ if (dt_s) {
+ if (d -> type <= DISC_TYPE_XBOX)
+ *dt_s = disc_type_strings[d -> type];
+ else
+ *dt_s = disc_type_strings[DISC_TYPE_DVD];
+ }
+
+ return (*dt_s);
+}
+
+
+/**
+ * Retrieves the disc game ID.
+ * @param d The disc structure.
+ * @param gid_s This will point to a string containing the game ID.
+ * @return A string containing the game ID.
+ */
+char *disc_get_gameid (disc *d, char **gid_s) {
+ if (gid_s)
+ *gid_s = d -> game_id;
+
+ return (*gid_s);
+}
+
+
+static char disc_region_strings[11][15] = {
+ "Europe/PAL",
+ "USA/NTSC",
+ "Japan/NTSC",
+ "Australia/PAL",
+ "France/PAL",
+ "Germany/PAL",
+ "Italy/PAL",
+ "Spain/PAL",
+ "Europe(X)/PAL",
+ "Europe(Y)/PAL",
+ "Unknown"
+};
+
+/**
+ * Retrieves the disc region.
+ * @param d The disc structure.
+ * @param dr This will be set to the disc region.
+ * @param dr_s This will point to a string describing the disc region.
+ * @return A string describing the disc region.
+ */
+char *disc_get_region (disc *d, disc_region *dr, char **dr_s) {
+ if (dr)
+ *dr = d -> region;
+
+ if (dr_s) {
+ if (d -> region < DISC_REGION_UNKNOWN)
+ *dr_s = disc_region_strings[d -> region];
+ else
+ *dr_s = disc_region_strings[DISC_REGION_UNKNOWN];
+ }
+
+ return (*dr_s);
+}
+
+
+/* The following list has been derived from http://wiitdb.com/Company/HomePage */
+static struct {
+ char *code;
+ char *name;
+} makers[] = {
+ {"0A", "Jaleco"},
+ {"0B", "Coconuts Japan"},
+ {"0C", "Coconuts Japan / G.X.Media"},
+ {"0D", "Micronet"},
+ {"0E", "Technos"},
+ {"0F", "Mebio Software"},
+ {"0G", "Shouei System"},
+ {"0H", "Starfish"},
+ {"0J", "Mitsui Fudosan / Dentsu"},
+ {"0L", "Warashi Inc."},
+ {"0N", "Nowpro"},
+ {"0P", "Game Village"},
+ {"0Q", "IE Institute"},
+ {"01", "Nintendo"},
+ {"02", "Rocket Games / Ajinomoto"},
+ {"03", "Imagineer-Zoom"},
+ {"04", "Gray Matter"},
+ {"05", "Zamuse"},
+ {"06", "Falcom"},
+ {"07", "Enix"},
+ {"08", "Capcom"},
+ {"09", "Hot B Co."},
+ {"1A", "Yanoman"},
+ {"1C", "Tecmo Products"},
+ {"1D", "Japan Glary Business"},
+ {"1E", "Forum / OpenSystem"},
+ {"1F", "Virgin Games (Japan)"},
+ {"1G", "SMDE"},
+ {"1J", "Daikokudenki"},
+ {"1P", "Creatures Inc."},
+ {"1Q", "TDK Deep Impresion"},
+ {"2A", "Culture Brain"},
+ {"2C", "Palsoft"},
+ {"2D", "Visit Co.,Ltd."},
+ {"2E", "Intec"},
+ {"2F", "System Sacom"},
+ {"2G", "Poppo"},
+ {"2H", "Ubisoft Japan"},
+ {"2J", "Media Works"},
+ {"2K", "NEC InterChannel"},
+ {"2L", "Tam"},
+ {"2M", "Jordan"},
+ {"2N", "Smilesoft / Rocket"},
+ {"2Q", "Mediakite"},
+ {"3B", "Arcade Zone Ltd"},
+ {"3C", "Entertainment International / Empire Software"},
+ {"3D", "Loriciel"},
+ {"3E", "Gremlin Graphics"},
+ {"3F", "K.Amusement Leasing Co."},
+ {"4B", "Raya Systems"},
+ {"4C", "Renovation Products"},
+ {"4D", "Malibu Games"},
+ {"4F", "Eidos"},
+ {"4G", "Playmates Interactive"},
+ {"4J", "Fox Interactive"},
+ {"4K", "Time Warner Interactive"},
+ {"4Q", "Disney Interactive"},
+ {"4S", "Black Pearl"},
+ {"4U", "Advanced Productions"},
+ {"4X", "GT Interactive"},
+ {"4Y", "RARE"},
+ {"4Z", "Crave Entertainment"},
+ {"5A", "Mindscape / Red Orb Entertainment"},
+ {"5B", "Romstar"},
+ {"5C", "Taxan"},
+ {"5D", "Midway / Tradewest"},
+ {"5F", "American Softworks"},
+ {"5G", "Majesco Sales Inc"},
+ {"5H", "3DO"},
+ {"5K", "Hasbro"},
+ {"5L", "NewKidCo"},
+ {"5M", "Telegames"},
+ {"5N", "Metro3D"},
+ {"5P", "Vatical Entertainment"},
+ {"5Q", "LEGO Media"},
+ {"5S", "Xicat Interactive"},
+ {"5T", "Cryo Interactive"},
+ {"5W", "Red Storm Entertainment"},
+ {"5X", "Microids"},
+ {"5Z", "Data Design / Conspiracy / Swing"},
+ {"6B", "Laser Beam"},
+ {"6E", "Elite Systems"},
+ {"6F", "Electro Brain"},
+ {"6G", "The Learning Company"},
+ {"6H", "BBC"},
+ {"6J", "Software 2000"},
+ {"6K", "UFO Interactive Games"},
+ {"6L", "BAM! Entertainment"},
+ {"6M", "Studio 3"},
+ {"6Q", "Classified Games"},
+ {"6S", "TDK Mediactive"},
+ {"6U", "DreamCatcher"},
+ {"6V", "JoWood Produtions"},
+ {"6W", "Sega"},
+ {"6X", "Wannado Edition"},
+ {"6Y", "LSP (Light & Shadow Prod.)"},
+ {"6Z", "ITE Media"},
+ {"7A", "Triffix Entertainment"},
+ {"7C", "Microprose Software"},
+ {"7D", "Sierra / Universal Interactive"},
+ {"7F", "Kemco"},
+ {"7G", "Rage Software"},
+ {"7H", "Encore"},
+ {"7J", "Zoo"},
+ {"7K", "Kiddinx"},
+ {"7L", "Simon & Schuster Interactive"},
+ {"7M", "Asmik Ace Entertainment Inc."},
+ {"7N", "Empire Interactive"},
+ {"7Q", "Jester Interactive"},
+ {"7S", "Rockstar Games"},
+ {"7T", "Scholastic"},
+ {"7U", "Ignition Entertainment"},
+ {"7V", "Summitsoft"},
+ {"7W", "Stadlbauer"},
+ {"8B", "BulletProof Software (BPS)"},
+ {"8C", "Vic Tokai Inc."},
+ {"8E", "Character Soft"},
+ {"8F", "I'Max"},
+ {"8G", "Saurus"},
+ {"8J", "General Entertainment"},
+ {"8N", "Success"},
+ {"8P", "Sega Japan"},
+ {"9A", "Nichibutsu / Nihon Bussan"},
+ {"9B", "Tecmo"},
+ {"9C", "Imagineer"},
+ {"9F", "Nova"},
+ {"9G", "Take2 / Den'Z / Global Star"},
+ {"9H", "Bottom Up"},
+ {"9J", "TGL (Technical Group Laboratory)"},
+ {"9L", "Hasbro Japan"},
+ {"9N", "Marvelous Entertainment"},
+ {"9P", "Keynet Inc."},
+ {"9Q", "Hands-On Entertainment"},
+ {"12", "Infocom"},
+ {"13", "Electronic Arts Japan"},
+ {"15", "Cobra Team"},
+ {"16", "Human / Field"},
+ {"17", "KOEI"},
+ {"18", "Hudson Soft"},
+ {"19", "S.C.P."},
+ {"20", "Destination Software / Zoo Games / KSS"},
+ {"21", "Sunsoft / Tokai Engineering"},
+ {"22", "POW (Planning Office Wada) / VR1 Japan"},
+ {"23", "Micro World"},
+ {"25", "San-X"},
+ {"26", "Enix"},
+ {"27", "Loriciel / Electro Brain"},
+ {"28", "Kemco Japan"},
+ {"29", "Seta"},
+ {"30", "Viacom"},
+ {"31", "Carrozzeria"},
+ {"32", "Dynamic"},
+ {"34", "Magifact"},
+ {"35", "Hect"},
+ {"36", "Codemasters"},
+ {"37", "Taito / GAGA Communications"},
+ {"38", "Laguna"},
+ {"39", "Telstar / Event / Taito"},
+ {"40", "Seika Corp."},
+ {"41", "Ubi Soft Entertainment"},
+ {"42", "Sunsoft US"},
+ {"44", "Life Fitness"},
+ {"46", "System 3"},
+ {"47", "Spectrum Holobyte"},
+ {"49", "IREM"},
+ {"50", "Absolute Entertainment"},
+ {"51", "Acclaim"},
+ {"52", "Activision"},
+ {"53", "American Sammy"},
+ {"54", "Take 2 Interactive / GameTek"},
+ {"55", "Hi Tech"},
+ {"56", "LJN LTD."},
+ {"58", "Mattel"},
+ {"60", "Titus"},
+ {"61", "Virgin Interactive"},
+ {"62", "Maxis"},
+ {"64", "LucasArts Entertainment"},
+ {"67", "Ocean"},
+ {"68", "Bethesda Softworks"},
+ {"69", "Electronic Arts"},
+ {"70", "Atari (Infogrames)"},
+ {"71", "Interplay"},
+ {"72", "JVC (US)"},
+ {"73", "Parker Brothers"},
+ {"75", "Sales Curve (Storm / SCI)"},
+ {"78", "THQ"},
+ {"79", "Accolade"},
+ {"80", "Misawa"},
+ {"81", "Teichiku"},
+ {"82", "Namco Ltd."},
+ {"83", "LOZC"},
+ {"84", "KOEI"},
+ {"86", "Tokuma Shoten Intermedia"},
+ {"87", "Tsukuda Original"},
+ {"88", "DATAM-Polystar"},
+ {"90", "Takara Amusement"},
+ {"91", "Chun Soft"},
+ {"92", "Video System / Mc O' River"},
+ {"93", "BEC"},
+ {"95", "Varie"},
+ {"96", "Yonezawa / S'pal"},
+ {"97", "Kaneko"},
+ {"99", "Marvelous Entertainment"},
+ {"A0", "Telenet"},
+ {"A1", "Hori"},
+ {"A4", "Konami"},
+ {"A5", "K.Amusement Leasing Co."},
+ {"A6", "Kawada"},
+ {"A7", "Takara"},
+ {"A9", "Technos Japan Corp."},
+ {"AA", "JVC / Victor"},
+ {"AC", "Toei Animation"},
+ {"AD", "Toho"},
+ {"AF", "Namco"},
+ {"AG", "Media Rings Corporation"},
+ {"AH", "J-Wing"},
+ {"AJ", "Pioneer LDC"},
+ {"AK", "KID"},
+ {"AL", "Mediafactory"},
+ {"AP", "Infogrames / Hudson"},
+ {"AQ", "Kiratto. Ludic Inc"},
+ {"B0", "Acclaim Japan"},
+ {"B1", "ASCII"},
+ {"B2", "Bandai"},
+ {"B4", "Enix"},
+ {"B6", "HAL Laboratory"},
+ {"B7", "SNK"},
+ {"B9", "Pony Canyon"},
+ {"BA", "Culture Brain"},
+ {"BB", "Sunsoft"},
+ {"BC", "Toshiba EMI"},
+ {"BD", "Sony Imagesoft"},
+ {"BF", "Sammy"},
+ {"BG", "Magical"},
+ {"BH", "Visco"},
+ {"BJ", "Compile"},
+ {"BL", "MTO Inc."},
+ {"BN", "Sunrise Interactive"},
+ {"BP", "Global A Entertainment"},
+ {"BQ", "Fuuki"},
+ {"C0", "Taito"},
+ {"C2", "Kemco"},
+ {"C3", "Square"},
+ {"C4", "Tokuma Shoten"},
+ {"C5", "Data East"},
+ {"C6", "Tonkin House / Tokyo Shoseki"},
+ {"C8", "Koei"},
+ {"CA", "Konami / Ultra / Palcom"},
+ {"CB", "NTVIC / VAP"},
+ {"CC", "Use Co.,Ltd."},
+ {"CD", "Meldac"},
+ {"CE", "Pony Canyon / FCI"},
+ {"CF", "Angel / Sotsu Agency / Sunrise"},
+ {"CG", "Yumedia / Aroma Co., Ltd"},
+ {"CJ", "Boss"},
+ {"CK", "Axela / Crea-Tech"},
+ {"CL", "Sekaibunka-Sha / Sumire Kobo / Marigul Management Inc."},
+ {"CM", "Konami Computer Entertainment Osaka"},
+ {"CN", "NEC Interchannel"},
+ {"CP", "Enterbrain"},
+ {"CQ", "From Software"},
+ {"D0", "Taito / Disco"},
+ {"D1", "Sofel"},
+ {"D2", "Quest / Bothtec"},
+ {"D3", "Sigma"},
+ {"D4", "Ask Kodansha"},
+ {"D6", "Naxat"},
+ {"D7", "Copya System"},
+ {"D8", "Capcom Co., Ltd."},
+ {"D9", "Banpresto"},
+ {"DA", "Tomy"},
+ {"DB", "LJN Japan"},
+ {"DD", "NCS"},
+ {"DE", "Human Entertainment"},
+ {"DF", "Altron"},
+ {"DG", "Jaleco"},
+ {"DH", "Gaps Inc."},
+ {"DN", "Elf"},
+ {"DQ", "Compile Heart"},
+ {"E0", "Jaleco"},
+ {"E2", "Yutaka"},
+ {"E3", "Varie"},
+ {"E4", "T&ESoft"},
+ {"E5", "Epoch"},
+ {"E7", "Athena"},
+ {"E8", "Asmik"},
+ {"E9", "Natsume"},
+ {"EA", "King Records"},
+ {"EB", "Atlus"},
+ {"EC", "Epic / Sony Records"},
+ {"EE", "IGS (Information Global Service)"},
+ {"EG", "Chatnoir"},
+ {"EH", "Right Stuff"},
+ {"EL", "Spike"},
+ {"EM", "Konami Computer Entertainment Tokyo"},
+ {"EN", "Alphadream Corporation"},
+ {"EP", "Sting"},
+ {"ES", "Star-Fish"},
+ {"F0", "A Wave"},
+ {"F1", "Motown Software"},
+ {"F2", "Left Field Entertainment"},
+ {"F3", "Extreme Ent. Grp."},
+ {"F4", "TecMagik"},
+ {"F9", "Cybersoft"},
+ {"FB", "Psygnosis"},
+ {"FE", "Davidson / Western Tech."},
+ {"FK", "The Game Factory"},
+ {"FL", "Hip Games"},
+ {"FM", "Aspyr"},
+ {"FP", "Mastiff"},
+ {"FQ", "iQue"},
+ {"FR", "Digital Tainment Pool"},
+ {"FS", "XS Games / Jack Of All Games"},
+ {"FT", "Daiwon"},
+ {"G0", "Alpha Unit"},
+ {"G1", "PCCW Japan"},
+ {"G2", "Yuke's Media Creations"},
+ {"G4", "KiKi Co Ltd"},
+ {"G5", "Open Sesame Inc"},
+ {"G6", "Sims"},
+ {"G7", "Broccoli"},
+ {"G8", "Avex"},
+ {"G9", "D3 Publisher"},
+ {"GB", "Konami Computer Entertainment Japan"},
+ {"GD", "Square-Enix"},
+ {"GE", "KSG"},
+ {"GF", "Micott & Basara Inc."},
+ {"GH", "Orbital Media"},
+ {"GJ", "Detn8 Games"},
+ {"GL", "Gameloft / Ubi Soft"},
+ {"GM", "Gamecock Media Group"},
+ {"GN", "Oxygen Games"},
+ {"GT", "505 Games"},
+ {"GY", "The Game Factory"},
+ {"H1", "Treasure"},
+ {"H2", "Aruze"},
+ {"H3", "Ertain"},
+ {"H4", "SNK Playmore"},
+ {"HJ", "Genius Products"},
+ {"HY", "Reef Entertainment"},
+ {"HZ", "Nordcurrent"},
+ {"IH", "Yojigen"},
+ {"J9", "AQ Interactive"},
+ {"JF", "Arc System Works"},
+ {"JW", "Atari"},
+ {"K6", "Nihon System"},
+ {"KB", "NIS America"},
+ {"KM", "Deep Silver"},
+ {"LH", "Trend Verlag / East Entertainment"},
+ {"LT", "Legacy Interactive"},
+ {"MJ", "Mumbo Jumbo"},
+ {"MR", "Mindscape"},
+ {"MS", "Milestone / UFO Interactive"},
+ {"MT", "Blast !"},
+ {"N9", "Terabox"},
+ {"NK", "Neko Entertainment / Diffusion / Naps team"},
+ {"NP", "Nobilis"},
+ {"NR", "Data Design / Destineer Studios"},
+ {"PL", "Playlogic"},
+ {"RM", "Rondomedia"},
+ {"RS", "Warner Bros. Interactive Entertainment Inc."},
+ {"RT", "RTL Games"},
+ {"RW", "RealNetworks"},
+ {"S5", "Southpeak Interactive"},
+ {"SP", "Blade Interactive Studios"},
+ {"SV", "SevenGames"},
+ {"TK", "Tasuke / Works"},
+ {"UG", "Metro 3D / Data Design"},
+ {"VN", "Valcon Games"},
+ {"VP", "Virgin Play"},
+ {"WR", "Warner Bros. Interactive Entertainment Inc."},
+ {"XJ", "Xseed Games"},
+ {"XS", "Aksys Games"},
+ {NULL, NULL}
+};
+
+/**
+ * Retrieves the disk maker.
+ * @param d The disc structure.
+ * @param m This will point to a string containing the disc maker ID.
+ * @param m_s This will point to a string describing the disc maker.
+ * @return A string describing the disc maker.
+ */
+char *disc_get_maker (disc *d, char **m, char **m_s) {
+ u_int32_t i;
+
+ if (m)
+ *m = d -> maker;
+
+ if (m_s) {
+ for (i = 0; makers[i].code; i++) {
+ if (strcasecmp (d -> maker, makers[i].code) == 0) {
+ *m_s = makers[i].name;
+ break;
+ }
+ }
+ if (!makers[i].code) {
+ *m_s = "Unknown";
+ }
+ }
+
+ return (*m_s);
+}
+
+
+/**
+ * Retrieves the disc version.
+ * @param d The disc structure.
+ * @param v This will contain the version ID.
+ * @param v_s This will point to a string describing the disc version.
+ * @return A string describing the disc version.
+ */
+char *disc_get_version (disc *d, u_int8_t *v, char **v_s) {
+ if (v)
+ *v = d -> version;
+
+ if (v_s)
+ *v_s = d -> version_string;
+
+ return (*v_s);
+}
+
+
+/**
+ * Retrieves the disc game title.
+ * @param d The disc structure.
+ * @param t_s This will point to a string describing the disc title.
+ * @return A string describing the disc title.
+ */
+char *disc_get_title (disc *d, char **t_s) {
+ if (t_s)
+ *t_s = d -> title;
+
+ return (*t_s);
+}
+
+
+/**
+ * Retrieves if the disc has an update.
+ * @param d The disc structure.
+ * @return True if the disc contains an update, false otherwise.
+ */
+bool disc_get_update (disc *d) {
+ return (d -> has_update);
+}
+
+
+/**
+ * Retrieves the number of sectors of the disc.
+ * @param d The disc structure.
+ * @return The number of sectors.
+ */
+u_int32_t disc_get_sectors_no (disc *d) {
+ return (d -> sectors_no);
+}
+
+u_int32_t disc_get_layerbreak (disc *d) {
+ return (d -> layerbreak);
+}
+
+u_int32_t disc_get_command (disc *d) {
+ return (d -> command);
+}
+
+u_int32_t disc_get_method (disc *d) {
+ return (d -> read_method);
+}
+
+u_int32_t disc_get_def_method (disc *d) {
+ return dvd_get_def_method(d -> dvd);//(d -> def_read_method);
+}
+
+u_int32_t disc_get_sec_disc (disc *d) {
+ return (d -> sec_disc);
+}
+
+u_int32_t disc_get_sec_mem (disc *d) {
+ return (d -> sec_mem);
+}
+
+/* wiidevel@stacktic.org */
+static bool disc_check_update (disc *d) {
+ u_int8_t *buf;
+ u_int32_t x;
+ bool unscramble_old;
+
+ if (d -> type == DISC_TYPE_WII || d -> type == DISC_TYPE_WII_DL) {
+ /* Force unscrambling for this read */
+ unscramble_old = d -> unscrambling;
+ disc_set_unscrambling (d, true);
+
+ /* We need to read offset 0x50004 of the disc. Sector 160 has offset 0x50000 */
+ if (disc_read_sector (d, 160, &buf, NULL)) {
+ x = my_ntohl (*(u_int32_t *) (buf + 4));
+ if (x == 0xA5BED6AE)
+ d -> has_update = false;
+ else
+ d -> has_update = true;
+ } else {
+ error ("disc_check_update() failed");
+ }
+
+ disc_set_unscrambling (d, unscramble_old);
+ } else {
+ /* GameCube discs never have an update, as actually the GC firmware cannot be upgrade */
+ d -> has_update = false;
+ }
+
+ return (d -> has_update);
+}
+
+
+/**
+ * Sets the disc read method.
+ * @param d The disc structure.
+ * @param method The requested method.
+ * @return True if the method was set correctly, false otherwise (i. e.: method too small/big).
+ */
+bool disc_set_read_method (disc *d, int method) {
+ bool out;
+ u_int32_t deviation;
+ u_int32_t counter;
+ u_int32_t cnt1;
+
+ d -> command = dvd_get_command(d -> dvd);
+// d -> def_read_method = dvd_get_def_method(d -> dvd);
+ d -> read_method = method;
+
+ out = true;
+ switch (method) {
+ case 0:
+ d -> read_sector = disc_read_sector_0;
+ break;
+ case 1:
+ d -> read_sector = disc_read_sector_1;
+ break;
+ case 2:
+ d -> read_sector = disc_read_sector_2;
+ break;
+ case 3:
+ d -> read_sector = disc_read_sector_3;
+ break;
+ case 4:
+ d -> read_sector = disc_read_sector_4;
+ break;
+ case 5:
+ d -> read_sector = disc_read_sector_5;
+ break;
+ case 6:
+ d -> read_sector = disc_read_sector_6;
+ break;
+ case 7:
+ d -> read_sector = disc_read_sector_7;
+ break;
+ case 8:
+ d -> read_sector = disc_read_sector_8;
+ break;
+ case 9:
+ d -> read_sector = disc_read_sector_9;
+ break;
+ case 10:
+ d -> read_sector = disc_read_sector_xbox;
+ break;
+ default:
+ switch (dvd_get_def_method(d -> dvd)) {
+ case 0:
+ d -> read_method = 0;
+ d -> read_sector = disc_read_sector_0;
+ break;
+ case 1:
+ d -> read_method = 1;
+ d -> read_sector = disc_read_sector_1;
+ break;
+ case 2:
+ d -> read_method = 2;
+ d -> read_sector = disc_read_sector_2;
+ break;
+ case 3:
+ d -> read_method = 3;
+ d -> read_sector = disc_read_sector_3;
+ break;
+ case 4:
+ d -> read_method = 4;
+ d -> read_sector = disc_read_sector_4;
+ break;
+ case 5:
+ d -> read_method = 5;
+ d -> read_sector = disc_read_sector_5;
+ break;
+ case 6:
+ d -> read_method = 6;
+ d -> read_sector = disc_read_sector_6;
+ break;
+ case 7:
+ d -> read_method = 7;
+ d -> read_sector = disc_read_sector_7;
+ break;
+ case 8:
+ d -> read_method = 8;
+ d -> read_sector = disc_read_sector_8;
+ break;
+ case 9:
+ d -> read_method = 9;
+ d -> read_sector = disc_read_sector_9;
+ break;
+ case 10:
+ d -> read_method = 10;
+ d -> read_sector = disc_read_sector_xbox;
+ break;
+ default:
+ d -> read_method = DEFAULT_READ_METHOD;
+ d -> read_sector = DEFAULT_READ_SECTOR;
+ break;
+ }
+ }
+
+ if (d->sec_disc==-1) {
+ if ((d->read_method == 4) || (d->read_method == 5) || (d->read_method == 6))
+ d->sec_disc=27;
+ else
+ d->sec_disc=16;
+ }
+ if (d->sec_mem==-1) {
+ if ((d->read_method == 4) || (d->read_method == 5) || (d->read_method == 6))
+ d->sec_mem=27;
+ else
+ d->sec_mem=16;
+ }
+
+ deviation = d->sec_mem % SECTORS_PER_BLOCK;
+ counter=0;
+
+ if (deviation>3) {
+ cnt1=deviation;
+ while (1==1) {
+ cnt1+=deviation;
+ counter++;
+ if (cnt1%SECTORS_PER_BLOCK<=1) break;
+ }
+ }
+ d -> max_cnt = counter;
+ d -> max_blk = ((d->sec_mem*(d->max_cnt+1))-((d->sec_mem*(d->max_cnt+1)) % SECTORS_PER_BLOCK)) / 16;
+
+ if (out) {
+ debug ("Read method set to %d", d -> read_method);
+ } else {
+ error ("Cannot set read method\n");
+ }
+
+ return (out);
+}
+
+
+/**
+ * Controls the unscrambling process.
+ * @param d The disc structure.
+ * @param unscramble If true, every raw sectors read will be unscrambled to check if they are error-free, otherwise read data will be returned as-is.
+ */
+void disc_set_unscrambling (disc *d, bool unscramble) {
+ d -> unscrambling = unscramble;
+ debug ("Sectors unscrambling %s", unscramble ? "enabled" : "disabled");
+
+ return;
+}
+
+
+
+static unsigned int hlds_e7_sector_header_value (const u_int8_t *hdr) {
+ if (!hdr)
+ return 0xFFFFFFFFU;
+ return ((unsigned int) hdr[1] << 16) | ((unsigned int) hdr[2] << 8) | (unsigned int) hdr[3];
+}
+
+static int hlds_e7_score_sector_header (const u_int8_t *hdr, u_int32_t sector_no) {
+ unsigned int got;
+ unsigned int expected;
+ int score;
+
+ if (!hdr)
+ return 0;
+ got = hlds_e7_sector_header_value (hdr);
+ expected = 0x30000U + sector_no;
+ score = 0;
+ if ((hdr[0] & 1) == 0)
+ score += 5;
+ if (got == expected)
+ score += 100;
+ if ((hdr[0] | hdr[1] | hdr[2] | hdr[3]) == 0x00)
+ score -= 10;
+ if ((hdr[0] & hdr[1] & hdr[2] & hdr[3]) == 0xFF)
+ score -= 10;
+ return score;
+}
+
+static void hlds_e7_json_escape (FILE *f, const char *s) {
+ const unsigned char *p;
+ if (!f)
+ return;
+ if (!s)
+ s = "";
+ for (p = (const unsigned char *) s; *p; p++) {
+ if (*p == '"' || *p == '\\')
+ fprintf (f, "\\%c", *p);
+ else if (*p == '\n')
+ fprintf (f, "\\n");
+ else if (*p == '\r')
+ fprintf (f, "\\r");
+ else if (*p == '\t')
+ fprintf (f, "\\t");
+ else if (*p < 32)
+ fprintf (f, "\\u%04x", (unsigned int) *p);
+ else
+ fputc (*p, f);
+ }
+}
+
+static void hlds_e7_json_bytes (FILE *f, const u_int8_t *b, size_t n) {
+ size_t i;
+ fprintf (f, "\"");
+ if (b) {
+ for (i = 0; i < n; i++) {
+ if (i)
+ fprintf (f, " ");
+ fprintf (f, "%02x", (unsigned int) b[i]);
+ }
+ }
+ fprintf (f, "\"");
+}
+
+static u_int32_t hlds_e7_fnv1a32 (const u_int8_t *buf, size_t len) {
+ size_t i;
+ u_int32_t h;
+ h = 2166136261U;
+ if (!buf)
+ return 0;
+ for (i = 0; i < len; i++) {
+ h ^= (u_int32_t) buf[i];
+ h *= 16777619U;
+ }
+ return h;
+}
+
+static size_t hlds_e7_count_byte_diffs (const u_int8_t *a, const u_int8_t *b, size_t len) {
+ size_t i;
+ size_t out;
+ out = 0;
+ if (!a || !b)
+ return 0;
+ for (i = 0; i < len; i++) {
+ if (a[i] != b[i])
+ out++;
+ }
+ return out;
+}
+
+static bool hlds_e7_probe_bytes_useful (const u_int8_t *p, size_t len) {
+ size_t i;
+ unsigned int orv;
+ unsigned int andv;
+ if (!p || len == 0)
+ return false;
+ orv = 0;
+ andv = 0xFF;
+ for (i = 0; i < len; i++) {
+ orv |= p[i];
+ andv &= p[i];
+ }
+ return !(orv == 0x00 || andv == 0xFF);
+}
+
+static int hlds_e7_find_raw_header_match (const u_int8_t *buf, size_t len, u_int32_t block_sector, size_t *match_offset, u_int32_t *match_sector) {
+ size_t off;
+ int k;
+ u_int32_t expected;
+ int best_score;
+ int score;
+ best_score = 0;
+ if (match_offset)
+ *match_offset = (size_t) -1;
+ if (match_sector)
+ *match_sector = 0xFFFFFFFFU;
+ if (!buf || len < 4)
+ return 0;
+ for (off = 0; off + 4 <= len; off++) {
+ for (k = 0; k < SECTORS_PER_BLOCK; k++) {
+ expected = 0x30000U + block_sector + (u_int32_t) k;
+ if (((u_int32_t) buf[off + 1] << 16 | (u_int32_t) buf[off + 2] << 8 | (u_int32_t) buf[off + 3]) == expected) {
+ score = 90;
+ if ((buf[off] & 1) == 0)
+ score += 10;
+ if ((off % RAW_SECTOR_SIZE) == (size_t) (k * RAW_SECTOR_SIZE))
+ score += 25;
+ else if ((off % RAW_SECTOR_SIZE) == 0)
+ score += 10;
+ if (score > best_score) {
+ best_score = score;
+ if (match_offset)
+ *match_offset = off;
+ if (match_sector)
+ *match_sector = block_sector + (u_int32_t) k;
+ }
+ }
+ }
+ }
+ return best_score;
+}
+
+static int hlds_e7_find_user_data_match (const u_int8_t *dumpbuf, size_t dump_len, const u_int8_t *readbuf, size_t read_len, size_t *match_offset, u_int32_t *match_sector, size_t *read_offset) {
+ static const size_t probe_offsets[] = {0x00, 0x20, 0x80, 0x100, 0x400, 0x700};
+ const size_t probe_len = 32;
+ size_t k;
+ size_t po;
+ size_t off;
+ const u_int8_t *needle;
+ if (match_offset)
+ *match_offset = (size_t) -1;
+ if (match_sector)
+ *match_sector = 0xFFFFFFFFU;
+ if (read_offset)
+ *read_offset = (size_t) -1;
+ if (!dumpbuf || !readbuf || dump_len < probe_len || read_len < SECTOR_SIZE)
+ return 0;
+ for (k = 0; k < SECTORS_PER_BLOCK && ((k * SECTOR_SIZE) + SECTOR_SIZE) <= read_len; k++) {
+ for (po = 0; po < sizeof (probe_offsets) / sizeof (probe_offsets[0]); po++) {
+ if (probe_offsets[po] + probe_len > SECTOR_SIZE)
+ continue;
+ needle = readbuf + k * SECTOR_SIZE + probe_offsets[po];
+ if (!hlds_e7_probe_bytes_useful (needle, probe_len))
+ continue;
+ for (off = 0; off + probe_len <= dump_len; off++) {
+ if (memcmp (dumpbuf + off, needle, probe_len) == 0) {
+ if (match_offset)
+ *match_offset = off;
+ if (match_sector)
+ *match_sector = (u_int32_t) k;
+ if (read_offset)
+ *read_offset = probe_offsets[po];
+ return 70;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+typedef struct {
+ u_int32_t type;
+ u_int32_t base;
+ u_int32_t windows;
+ const char *label;
+} hlds_e7_probe_candidate;
+
+bool disc_hlds_e7_scan (disc *d, const char *json_path, const char *dump_prefix) {
+ typedef struct {
+ const char *label;
+ u_int32_t base;
+ u_int32_t windows;
+ const char *origin;
+ } scan_candidate;
+ static const scan_candidate candidates[] = {
+ {"type4_base_5win", 0x80000000U, 5, "known Type3/Type4 family base"},
+ {"type4_base_1win", 0x80000000U, 1, "known Type3/Type4 base, conservative window"},
+ {"type4_plus_0x8000", 0x80008000U, 1, "nearby +0x8000 alias candidate"},
+ {"type4_plus_0x10000", 0x80010000U, 1, "nearby +0x10000 alias candidate"},
+ {"type4_plus_0x20000", 0x80020000U, 1, "nearby +0x20000 alias candidate"},
+ {"type4_plus_0x30000", 0x80030000U, 1, "nearby +0x30000 alias candidate"},
+ {"type4_minus_0x8000", 0x7FFF8000U, 1, "moving/boundary candidate"},
+ {"type4_minus_0x10000", 0x7FFF0000U, 1, "moving/boundary candidate"},
+ {"type4_minus_0x18000", 0x7FFE8000U, 1, "moving/boundary candidate"},
+ {"type1_a00000", 0x00A00000U, 1, "Type1 neighborhood"},
+ {"type1_a13000", 0x00A13000U, 1, "GCC-4160N Type1 known base"},
+ {"firmware_table_00380000",0x00380000U, 1, "observed 0x00380030 neighborhood, aligned down"},
+ {"firmware_table_00380030",0x00380030U, 1, "observed stale/profile-garbage value; test only"},
+ {"low_sram_00000000", 0x00000000U, 1, "low SRAM alias"},
+ {"low_sram_00008000", 0x00008000U, 1, "low SRAM alias +0x8000"},
+ {"low_sram_00010000", 0x00010000U, 1, "low SRAM alias +0x10000"},
+ {"low_sram_00020000", 0x00020000U, 1, "low SRAM alias +0x20000"},
+ {"firmware_sram_00001800", 0x00001800U, 1, "GDR-8081N plaintext firmware references 0x18xx SRAM/MMIO neighborhood"},
+ {"firmware_sram_000018a8", 0x000018A8U, 1, "GDR-8081N plaintext firmware references 0x18a8"},
+ {"firmware_sram_00009300", 0x00009300U, 1, "GDR-8081N plaintext firmware references 0x93xx"},
+ {"firmware_sram_00009b00", 0x00009B00U, 1, "GDR-8081N plaintext firmware references 0x9bxx"},
+ {"firmware_sram_0000a800", 0x0000A800U, 1, "GDR-8081N plaintext firmware references 0xa800"},
+ {"firmware_alias_40000000",0x40000000U, 1, "firmware mapping base as alias sanity check"}
+ };
+ static const u_int32_t probe_sectors[] = {0U, 320U};
+ FILE *f;
+ u_int8_t sample[16];
+ u_int8_t readbuf[BLOCK_SIZE];
+ u_int8_t *dumpbuf;
+ u_int8_t *first_dump;
+ u_int32_t old_type;
+ u_int32_t old_base;
+ u_int32_t old_windows;
+ size_t i;
+ size_t j;
+ int k;
+ size_t scan_len;
+ size_t max_scan_len;
+ size_t raw_offset;
+ size_t exact_offsets[SECTORS_PER_BLOCK];
+ int exact_count;
+ bool sector_shaped;
+ size_t command_echo_offset;
+ size_t user_offset;
+ size_t read_offset;
+ size_t window_diff;
+ u_int32_t raw_sector;
+ u_int32_t user_sector;
+ u_int32_t hash;
+ int read_ret;
+ int dump_ret;
+ int raw_score;
+ int user_score;
+ int sector_score;
+ char dump_path[512];
+ FILE *df;
+ int total_score;
+ int best_score;
+ u_int32_t best_base;
+ u_int32_t best_windows;
+ const char *path;
+ bool any_readable;
+
+ if (!d || !d -> dvd)
+ return false;
+ path = (json_path && json_path[0]) ? json_path : "hlds_e7_scan.json";
+ f = fopen (path, "wb");
+ if (!f) {
+ warning ("HLDS 0xE7 scan: could not open %s for writing", path);
+ return false;
+ }
+
+ max_scan_len = 5U * RAW_BLOCK_SIZE;
+ dumpbuf = (u_int8_t *) malloc (max_scan_len);
+ first_dump = (u_int8_t *) malloc (max_scan_len);
+ if (!dumpbuf || !first_dump) {
+ if (dumpbuf)
+ free (dumpbuf);
+ if (first_dump)
+ free (first_dump);
+ fclose (f);
+ warning ("HLDS 0xE7 scan: out of memory");
+ return false;
+ }
+
+ old_type = dvd_get_hlds_e7_type (d -> dvd);
+ old_base = dvd_get_hlds_e7_cache_base (d -> dvd);
+ old_windows = dvd_get_hlds_e7_mem_blocks (d -> dvd);
+ best_score = -999999;
+ best_base = 0;
+ best_windows = 0;
+ any_readable = false;
+
+ fprintf (stderr, "\nHLDS 0xE7 scan mode v5: strict cache/memdump validation without seed cracking\n");
+ fprintf (stderr, "HLDS 0xE7 scan mode v5: requiring 2064-byte raw-sector stride or READ payload echo; table/command echoes are not promoted\n");
+ fprintf (stderr, "HLDS 0xE7 scan mode v5: writing JSON report to %s\n", path);
+
+ fprintf (f, "{\n");
+ fprintf (f, " \"scan_version\": \"v5_strict_sector_shape_command_echo\",\n");
+ fprintf (f, " \"drive\": \"");
+ hlds_e7_json_escape (f, disc_get_drive_model_string (d));
+ fprintf (f, "\",\n");
+ fprintf (f, " \"initial_profile\": \"");
+ hlds_e7_json_escape (f, dvd_get_hlds_e7_profile_name (d -> dvd));
+ fprintf (f, "\",\n");
+ fprintf (f, " \"initial_cache_base\": \"0x%08x\",\n", old_base);
+ fprintf (f, " \"initial_windows\": %u,\n", old_windows);
+ fprintf (f, " \"probe_sectors\": [%u, %u],\n", probe_sectors[0], probe_sectors[1]);
+ fprintf (f, " \"notes\": \"v5 does not promote sector-number table matches. It requires exact raw-sector IDs to be laid out with a 2064-byte stride or a direct READ payload echo. It also records HIT command-echo offsets because those indicate SRAM/command buffers, not proven sector cache.\",\n");
+ fprintf (f, " \"candidates\": [\n");
+
+ for (i = 0; i < sizeof (candidates) / sizeof (candidates[0]); i++) {
+ scan_len = candidates[i].windows * RAW_BLOCK_SIZE;
+ if (scan_len == 0 || scan_len > max_scan_len)
+ scan_len = RAW_BLOCK_SIZE;
+ total_score = 0;
+ window_diff = 0;
+ dvd_set_hlds_e7_runtime_profile (d -> dvd, 9000U + (u_int32_t) i, candidates[i].base, candidates[i].windows);
+ fprintf (stderr, " [%02u/%02u] %-25s base=0x%08x windows=%u scan=%lu... ",
+ (unsigned int) (i + 1), (unsigned int) (sizeof (candidates) / sizeof (candidates[0])),
+ candidates[i].label, candidates[i].base, candidates[i].windows, (unsigned long) scan_len);
+ fprintf (f, " {\n");
+ fprintf (f, " \"label\": \"");
+ hlds_e7_json_escape (f, candidates[i].label);
+ fprintf (f, "\",\n");
+ fprintf (f, " \"base\": \"0x%08x\",\n", candidates[i].base);
+ fprintf (f, " \"windows\": %u,\n", candidates[i].windows);
+ fprintf (f, " \"scan_bytes\": %lu,\n", (unsigned long) scan_len);
+ fprintf (f, " \"origin\": \"");
+ hlds_e7_json_escape (f, candidates[i].origin);
+ fprintf (f, "\",\n");
+ fprintf (f, " \"sector_tests\": [\n");
+ for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) {
+ memset (sample, 0, sizeof (sample));
+ memset (readbuf, 0, sizeof (readbuf));
+ memset (dumpbuf, 0, scan_len);
+ dvd_flush_cache_READ12 (d -> dvd, probe_sectors[j], NULL);
+ read_ret = dvd_read_sector_streaming (d -> dvd, probe_sectors[j], NULL, readbuf, sizeof (readbuf));
+ dump_ret = dvd_memdump (d -> dvd, 0, candidates[i].windows ? candidates[i].windows : 1, RAW_BLOCK_SIZE, dumpbuf);
+ if (dump_ret >= 0) {
+ if (dump_prefix && dump_prefix[0]) {
+ snprintf (dump_path, sizeof (dump_path), "%s_%02lu_%s_sector_%u.bin", dump_prefix, (unsigned long) (i + 1), candidates[i].label, probe_sectors[j]);
+ df = fopen (dump_path, "wb");
+ if (df) {
+ fwrite (dumpbuf, 1, scan_len, df);
+ fclose (df);
+ }
+ }
+ any_readable = true;
+ memcpy (sample, dumpbuf, sizeof (sample));
+ hash = hlds_e7_fnv1a32 (dumpbuf, scan_len);
+ (void) hlds_e7_find_raw_header_match (dumpbuf, scan_len, probe_sectors[j], &raw_offset, &raw_sector);
+ exact_count = hlds_e7_count_exact_raw_headers_for_block (dumpbuf, scan_len, probe_sectors[j], exact_offsets);
+ sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (exact_offsets);
+ command_echo_offset = hlds_e7_find_command_echo_offset (dumpbuf, scan_len);
+ if (sector_shaped)
+ raw_score = 220;
+ else if (exact_count > 0)
+ raw_score = exact_count;
+ else
+ raw_score = 0;
+ user_score = hlds_e7_find_user_data_match (dumpbuf, scan_len, readbuf, sizeof (readbuf), &user_offset, &user_sector, &read_offset);
+ sector_score = raw_score + user_score;
+ if (command_echo_offset != (size_t) -1 && !sector_shaped && user_score <= 0)
+ sector_score -= 10;
+ if (j == 0)
+ memcpy (first_dump, dumpbuf, scan_len);
+ else {
+ window_diff = hlds_e7_count_byte_diffs (first_dump, dumpbuf, scan_len);
+ if (window_diff > 4096)
+ sector_score += 40;
+ else if (window_diff > 512)
+ sector_score += 20;
+ else if (window_diff < 16)
+ sector_score -= 20;
+ }
+ } else {
+ hash = 0;
+ raw_score = -50;
+ user_score = 0;
+ sector_score = -50;
+ raw_offset = (size_t) -1;
+ exact_count = 0;
+ for (k = 0; k < SECTORS_PER_BLOCK; k++)
+ exact_offsets[k] = (size_t) -1;
+ raw_sector = 0xFFFFFFFFU;
+ sector_shaped = false;
+ command_echo_offset = (size_t) -1;
+ user_offset = (size_t) -1;
+ user_sector = 0xFFFFFFFFU;
+ read_offset = (size_t) -1;
+ }
+ if (read_ret < 0)
+ sector_score -= 20;
+ total_score += sector_score;
+ fprintf (f, " {\"sector\": %u, \"read_ret\": %d, \"window_memdump_ret\": %d, \"score\": %d, ",
+ probe_sectors[j], read_ret, dump_ret, sector_score);
+ fprintf (f, "\"raw_header_score\": %d, \"raw_header_offset\": ", raw_score);
+ if (raw_offset == (size_t) -1)
+ fprintf (f, "null, \"raw_header_sector\": null, ");
+ else
+ fprintf (f, "%lu, \"raw_header_sector\": %u, ", (unsigned long) raw_offset, raw_sector);
+ fprintf (f, "\"raw_header_found_count\": %d, \"raw_header_offsets\": [", exact_count);
+ for (k = 0; k < SECTORS_PER_BLOCK; k++) {
+ if (k)
+ fprintf (f, ", ");
+ if (exact_offsets[k] == (size_t) -1)
+ fprintf (f, "null");
+ else
+ fprintf (f, "%lu", (unsigned long) exact_offsets[k]);
+ }
+ fprintf (f, "], ");
+ fprintf (f, "\"raw_header_sector_shaped\": %s, ", sector_shaped ? "true" : "false");
+ fprintf (f, "\"command_echo_offset\": ");
+ if (command_echo_offset == (size_t) -1)
+ fprintf (f, "null, ");
+ else
+ fprintf (f, "%lu, ", (unsigned long) command_echo_offset);
+ fprintf (f, "\"user_data_score\": %d, \"user_data_offset\": ", user_score);
+ if (user_offset == (size_t) -1)
+ fprintf (f, "null, \"user_data_sector_index\": null, \"read_probe_offset\": null, ");
+ else
+ fprintf (f, "%lu, \"user_data_sector_index\": %u, \"read_probe_offset\": %lu, ", (unsigned long) user_offset, user_sector, (unsigned long) read_offset);
+ fprintf (f, "\"window_hash_fnv1a32\": \"0x%08x\", \"sample\": ", hash);
+ hlds_e7_json_bytes (f, sample, sizeof (sample));
+ fprintf (f, "}%s\n", (j + 1 < sizeof (probe_sectors) / sizeof (probe_sectors[0])) ? "," : "");
+ }
+ if (window_diff > 4096)
+ total_score += 40;
+ else if (window_diff > 512)
+ total_score += 20;
+ else if (window_diff < 16)
+ total_score -= 20;
+ fprintf (f, " ],\n");
+ fprintf (f, " \"sector_window_diff_bytes\": %lu,\n", (unsigned long) window_diff);
+ fprintf (f, " \"sector_window_diff_per_1000\": %lu,\n", scan_len ? (unsigned long) ((window_diff * 1000U) / scan_len) : 0UL);
+ fprintf (f, " \"total_score\": %d,\n", total_score);
+ fprintf (f, " \"classification\": \"%s\"\n", total_score >= 220 ? "strict_cache_candidate" : (total_score >= 80 ? "needs_more_candidates" : (total_score > 0 ? "sram_or_table_match" : "no_match")));
+ fprintf (f, " }%s\n", (i + 1 < sizeof (candidates) / sizeof (candidates[0])) ? "," : "");
+ fprintf (stderr, "score=%d diff=%lu%s\n", total_score, (unsigned long) window_diff, total_score >= 220 ? " STRICT" : (total_score >= 80 ? " REVIEW" : ""));
+ if (total_score > best_score) {
+ best_score = total_score;
+ best_base = candidates[i].base;
+ best_windows = candidates[i].windows;
+ }
+ }
+
+ fprintf (f, " ],\n");
+ fprintf (f, " \"best\": {\"base\": \"0x%08x\", \"windows\": %u, \"score\": %d, \"confidence\": \"%s\"},\n",
+ best_base, best_windows, best_score, best_score >= 220 ? "strict" : (best_score >= 80 ? "review" : (best_score > 0 ? "weak" : "none")));
+ fprintf (f, " \"sector_cache_candidate_found\": %s,\n", best_score >= 220 ? "true" : "false");
+ fprintf (f, " \"promotion_recommendation\": \"%s\",\n", best_score >= 220 ? "candidate may be promoted into an experimental dump profile" : "do not promote; scan found command/SRAM/table echoes but no strict 2064-byte sector cache");
+ fprintf (f, " \"next_recommended_action\": \"%s\",\n", best_score >= 220 ? "try the strict scan-guided normal probe" : "do not run normal seed retrieval yet; use firmware analysis or a wider address/subcommand sweep to find the real cache path");
+ fprintf (f, " \"e7_memdump_command\": \"%s\"\n", any_readable ? "accepted_by_at_least_one_candidate" : "no_successful_window_memdump");
+ fprintf (f, "}\n");
+ fclose (f);
+
+ dvd_set_hlds_e7_runtime_profile (d -> dvd, old_type, old_base, old_windows);
+ fprintf (stderr, "HLDS 0xE7 scan mode v5 complete: best base=0x%08x windows=%u score=%d (%s; %s)\n",
+ best_base, best_windows, best_score,
+ best_score >= 220 ? "strict" : (best_score >= 80 ? "review" : (best_score > 0 ? "weak" : "no match")),
+ best_score >= 220 ? "promotion allowed" : "do not promote");
+ free (dumpbuf);
+ free (first_dump);
+ return best_score >= 80;
+}
+
+
+static int hlds_e7_raw_hit_data_in (disc *d, u_int8_t subcmd, u_int32_t offset, u_int32_t length, u_int8_t *buf) {
+ mmc_command mmc;
+ if (!d || !d -> dvd || !buf || length == 0 || length > 65535U)
+ return -1;
+ dvd_init_command (&mmc, buf, (int) length, NULL);
+ mmc.cmd[0] = 0xE7;
+ mmc.cmd[1] = 0x48; /* H */
+ mmc.cmd[2] = 0x49; /* I */
+ mmc.cmd[3] = 0x54; /* T */
+ mmc.cmd[4] = subcmd;
+ mmc.cmd[6] = (u_int8_t) ((offset >> 24) & 0xFF);
+ mmc.cmd[7] = (u_int8_t) ((offset >> 16) & 0xFF);
+ mmc.cmd[8] = (u_int8_t) ((offset >> 8) & 0xFF);
+ mmc.cmd[9] = (u_int8_t) (offset & 0xFF);
+ mmc.cmd[10] = (u_int8_t) ((length >> 8) & 0xFF);
+ mmc.cmd[11] = (u_int8_t) (length & 0xFF);
+ return dvd_execute_cmd (d -> dvd, &mmc, true);
+}
+
+typedef struct {
+ u_int8_t subcmd;
+ const char *label;
+} hlds_e7_subcmd_probe;
+
+typedef struct {
+ u_int32_t address;
+ const char *label;
+} hlds_e7_raw_addr_probe;
+
+bool disc_hlds_e7_subcmd_sweep (disc *d, const char *json_path, const char *dump_prefix) {
+ static const hlds_e7_subcmd_probe subcmds[] = {
+ {0x00, "subcmd_00"}, {0x01, "subcmd_01_known_memdump"},
+ {0x02, "subcmd_02"}, {0x03, "subcmd_03"},
+ {0x04, "subcmd_04"}, {0x05, "subcmd_05"},
+ {0x06, "subcmd_06"}, {0x07, "subcmd_07"},
+ {0x08, "subcmd_08"}, {0x09, "subcmd_09"},
+ {0x0A, "subcmd_0a"}, {0x0B, "subcmd_0b"},
+ {0x0C, "subcmd_0c"}, {0x0D, "subcmd_0d"},
+ {0x0E, "subcmd_0e"}, {0x0F, "subcmd_0f"}
+ };
+ static const hlds_e7_raw_addr_probe addrs[] = {
+ {0x80000000U, "type4_base"},
+ {0x80008000U, "type4_plus_8000"},
+ {0x80010000U, "type4_plus_10000"},
+ {0x00000000U, "low_sram_0"},
+ {0x00001800U, "firmware_sram_1800"},
+ {0x000018A8U, "firmware_sram_18a8"},
+ {0x00380000U, "table_00380000"},
+ {0x40000000U, "firmware_alias_40000000"}
+ };
+ static const u_int32_t probe_sectors[] = {0, 320};
+ const char *path;
+ FILE *f;
+ FILE *df;
+ char dump_path[512];
+ u_int8_t *buf;
+ u_int8_t readbuf[BLOCK_SIZE];
+ u_int32_t len;
+ size_t i, a, j;
+ int ret;
+ int read_ret;
+ int exact_count;
+ int raw_score;
+ int user_score;
+ int best_score;
+ int total_promotable;
+ size_t raw_offset;
+ size_t user_offset;
+ size_t read_offset;
+ size_t command_echo_offset;
+ size_t offsets[SECTORS_PER_BLOCK];
+ u_int32_t raw_sector;
+ u_int32_t user_sector;
+ u_int32_t hash;
+ bool sector_shaped;
+ u_int8_t sample[16];
+
+ if (!d)
+ return false;
+ path = (json_path && json_path[0]) ? json_path : "hlds_e7_subcmd_sweep.json";
+ len = RAW_BLOCK_SIZE; /* one 16-sector raw-cache-sized window; enough to find strict stride without huge runtimes */
+ buf = (u_int8_t *) malloc (len);
+ if (!buf) {
+ warning ("HLDS 0xE7 subcmd sweep: out of memory");
+ return false;
+ }
+ f = fopen (path, "wb");
+ if (!f) {
+ free (buf);
+ warning ("HLDS 0xE7 subcmd sweep: could not open %s for writing", path);
+ return false;
+ }
+
+ fprintf (stderr, "\nHLDS 0xE7 subcommand sweep v2: probing HIT subcommands 0x00..0x0f without seed cracking\n");
+ fprintf (stderr, "HLDS 0xE7 subcommand sweep v2: data-in only, %u-byte reads, no dump attempt\n", len);
+ fprintf (stderr, "HLDS 0xE7 subcommand sweep v2: writing JSON report to %s\n", path);
+
+ best_score = -999999;
+ total_promotable = 0;
+
+ fprintf (f, "{\n");
+ fprintf (f, " \"sweep_version\": \"v1_hlds_hit_subcmd_address_probe\",\n");
+ fprintf (f, " \"drive\": \"");
+ hlds_e7_json_escape (f, disc_get_drive_model_string (d));
+ fprintf (f, "\",\n");
+ fprintf (f, " \"notes\": \"This diagnostic sends HIT 0xE7 data-in commands with subcommands 0x00..0x0f over a small address set. It does not crack seeds or dump the disc. A promotable result requires sector-shaped 2064-byte raw headers or a direct READ-payload echo; command echoes alone are not promoted.\",\n");
+ fprintf (f, " \"read_length\": %u,\n", len);
+ fprintf (f, " \"probe_sectors\": [%u, %u],\n", probe_sectors[0], probe_sectors[1]);
+ fprintf (f, " \"results\": [\n");
+
+ for (i = 0; i < sizeof (subcmds) / sizeof (subcmds[0]); i++) {
+ for (a = 0; a < sizeof (addrs) / sizeof (addrs[0]); a++) {
+ int total_score = 0;
+ int promotable = 0;
+ fprintf (stderr, " subcmd=0x%02x %-24s addr=0x%08x... ",
+ (unsigned int) subcmds[i].subcmd, subcmds[i].label, addrs[a].address);
+ fprintf (f, " {\n");
+ fprintf (f, " \"subcmd\": \"0x%02x\",\n", (unsigned int) subcmds[i].subcmd);
+ fprintf (f, " \"subcmd_label\": \"");
+ hlds_e7_json_escape (f, subcmds[i].label);
+ fprintf (f, "\",\n");
+ fprintf (f, " \"address\": \"0x%08x\",\n", addrs[a].address);
+ fprintf (f, " \"address_label\": \"");
+ hlds_e7_json_escape (f, addrs[a].label);
+ fprintf (f, "\",\n");
+ fprintf (f, " \"sector_tests\": [\n");
+ for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) {
+ int score = 0;
+ int kk;
+ memset (buf, 0, len);
+ memset (readbuf, 0, sizeof (readbuf));
+ memset (sample, 0, sizeof (sample));
+ read_ret = dvd_read_sector_streaming (d -> dvd, probe_sectors[j], NULL, readbuf, sizeof (readbuf));
+ ret = hlds_e7_raw_hit_data_in (d, subcmds[i].subcmd, addrs[a].address, len, buf);
+ raw_offset = (size_t) -1;
+ user_offset = (size_t) -1;
+ read_offset = (size_t) -1;
+ command_echo_offset = (size_t) -1;
+ raw_sector = 0xFFFFFFFFU;
+ user_sector = 0xFFFFFFFFU;
+ exact_count = 0;
+ for (kk = 0; kk < SECTORS_PER_BLOCK; kk++)
+ offsets[kk] = (size_t) -1;
+ sector_shaped = false;
+ raw_score = 0;
+ user_score = 0;
+ hash = 0;
+ if (ret >= 0) {
+ memcpy (sample, buf, sizeof (sample));
+ hash = hlds_e7_fnv1a32 (buf, len);
+ (void) hlds_e7_find_raw_header_match (buf, len, probe_sectors[j], &raw_offset, &raw_sector);
+ exact_count = hlds_e7_count_exact_raw_headers_for_block (buf, len, probe_sectors[j], offsets);
+ sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (offsets);
+ command_echo_offset = hlds_e7_find_command_echo_offset (buf, len);
+ user_score = hlds_e7_find_user_data_match (buf, len, readbuf, sizeof (readbuf), &user_offset, &user_sector, &read_offset);
+ raw_score = sector_shaped ? 220 : exact_count;
+ }
+ if (ret < 0)
+ score -= 50;
+ if (read_ret < 0)
+ score -= 20;
+ score += raw_score + user_score;
+ if (command_echo_offset != (size_t) -1 && !sector_shaped && user_score <= 0)
+ score -= 10;
+ if (sector_shaped || user_score > 0)
+ promotable++;
+ total_score += score;
+ fprintf (f, " {\"sector\": %u, \"read_ret\": %d, \"e7_ret\": %d, \"score\": %d, ",
+ probe_sectors[j], read_ret, ret, score);
+ fprintf (f, "\"raw_header_found_count\": %d, \"raw_header_sector_shaped\": %s, ",
+ exact_count, sector_shaped ? "true" : "false");
+ fprintf (f, "\"raw_header_offset\": ");
+ if (raw_offset == (size_t) -1)
+ fprintf (f, "null, \"raw_header_sector\": null, ");
+ else
+ fprintf (f, "%lu, \"raw_header_sector\": %u, ", (unsigned long) raw_offset, raw_sector);
+ fprintf (f, "\"command_echo_offset\": ");
+ if (command_echo_offset == (size_t) -1)
+ fprintf (f, "null, ");
+ else
+ fprintf (f, "%lu, ", (unsigned long) command_echo_offset);
+ fprintf (f, "\"user_data_score\": %d, \"user_data_offset\": ", user_score);
+ if (user_offset == (size_t) -1)
+ fprintf (f, "null, \"user_data_sector_index\": null, \"read_probe_offset\": null, ");
+ else
+ fprintf (f, "%lu, \"user_data_sector_index\": %u, \"read_probe_offset\": %lu, ",
+ (unsigned long) user_offset, user_sector, (unsigned long) read_offset);
+ fprintf (f, "\"window_hash_fnv1a32\": \"0x%08x\", \"sample\": ", hash);
+ hlds_e7_json_bytes (f, sample, sizeof (sample));
+ fprintf (f, "}%s\n", (j + 1 < sizeof (probe_sectors) / sizeof (probe_sectors[0])) ? "," : "");
+
+ /*
+ * If the caller requested raw sweep dumps, write every successful
+ * HIT 0xE7 data-in response, not only promotable sector-cache hits.
+ *
+ * v7 only dumped promotable windows. That meant a useful negative
+ * sweep produced no gdr8081n_subcmd_*.bin files at all, even though
+ * non-promotable command/SRAM echoes were exactly what we needed to
+ * inspect next.
+ */
+ if (dump_prefix && dump_prefix[0] && ret >= 0) {
+ snprintf (dump_path, sizeof (dump_path), "%s_sub%02x_%s_sector_%u.bin",
+ dump_prefix, (unsigned int) subcmds[i].subcmd, addrs[a].label, probe_sectors[j]);
+ df = fopen (dump_path, "wb");
+ if (df) {
+ fwrite (buf, 1, len, df);
+ fclose (df);
+ }
+ }
+ }
+ fprintf (f, " ],\n");
+ fprintf (f, " \"total_score\": %d,\n", total_score);
+ fprintf (f, " \"promotable_sector_tests\": %d,\n", promotable);
+ fprintf (f, " \"classification\": \"%s\"\n", promotable > 0 ? "promotable_candidate" : (total_score > 0 ? "responds_nonpromotable" : "no_useful_response"));
+ fprintf (f, " }%s\n",
+ (i + 1 == sizeof (subcmds) / sizeof (subcmds[0]) && a + 1 == sizeof (addrs) / sizeof (addrs[0])) ? "" : ",");
+ fprintf (stderr, "score=%d%s\n", total_score, promotable > 0 ? " PROMOTABLE" : "");
+ if (total_score > best_score)
+ best_score = total_score;
+ total_promotable += promotable;
+ }
+ }
+
+ fprintf (f, " ],\n");
+ fprintf (f, " \"promotable_candidate_found\": %s,\n", total_promotable > 0 ? "true" : "false");
+ fprintf (f, " \"promotion_recommendation\": \"%s\",\n", total_promotable > 0 ? "review promotable candidates and try a targeted profile" : "do not promote; no subcommand/address pair exposed sector-shaped cache or READ-payload echo");
+ fprintf (f, " \"next_recommended_action\": \"%s\"\n", total_promotable > 0 ? "send the JSON and any dumped promotable windows" : "continue firmware handler analysis; avoid normal seed retrieval on GDR-8081N until a promotable candidate appears");
+ fprintf (f, "}\n");
+ fclose (f);
+ free (buf);
+
+ fprintf (stderr, "HLDS 0xE7 subcommand sweep v2 complete: promotable candidates=%d (%s)\n",
+ total_promotable, total_promotable > 0 ? "review JSON" : "none found");
+ return true;
+}
+
+
+typedef struct {
+ u_int32_t start;
+ u_int32_t end;
+ u_int32_t step;
+ const char *label;
+} hlds_e7_range_probe;
+
+bool disc_hlds_e7_memrange_sweep (disc *d, const char *json_path, const char *dump_prefix) {
+ static const hlds_e7_range_probe ranges[] = {
+ {0x7FFE0000U, 0x80080000U, 0x00000800U, "type4_dense_neighborhood"},
+ {0x00000000U, 0x00040000U, 0x00000800U, "low_sram_dense"},
+ {0x00370000U, 0x00390000U, 0x00000800U, "table_0038_dense"},
+ {0x00A00000U, 0x00A40000U, 0x00000800U, "type1_dense_neighborhood"},
+ {0x40000000U, 0x40010000U, 0x00000800U, "firmware_alias_dense"}
+ };
+ static const u_int32_t probe_sectors[] = {0, 320};
+ const char *path;
+ FILE *f;
+ FILE *df;
+ char dump_path[512];
+ u_int8_t *buf;
+ u_int8_t readbuf[BLOCK_SIZE];
+ u_int32_t len;
+ size_t r, j;
+ u_int32_t addr;
+ unsigned long tested;
+ unsigned long nonzero_windows;
+ unsigned long command_echo_windows;
+ unsigned long raw_table_windows;
+ unsigned long promotable_windows;
+ int best_score;
+ u_int32_t best_addr;
+ const char *best_range;
+
+ if (!d)
+ return false;
+ path = (json_path && json_path[0]) ? json_path : "hlds_e7_memrange_sweep.json";
+ len = RAW_BLOCK_SIZE;
+ buf = (u_int8_t *) malloc (len);
+ if (!buf) {
+ warning ("HLDS 0xE7 memrange sweep: out of memory");
+ return false;
+ }
+ f = fopen (path, "wb");
+ if (!f) {
+ free (buf);
+ warning ("HLDS 0xE7 memrange sweep: could not open %s for writing", path);
+ return false;
+ }
+
+ fprintf (stderr, "\nHLDS 0xE7 memory-range sweep v1: using known memdump subcmd 0x01 only\n");
+ fprintf (stderr, "HLDS 0xE7 memory-range sweep v1: dense address stride, no seed cracking, no dump attempt\n");
+ fprintf (stderr, "HLDS 0xE7 memory-range sweep v1: writing JSON report to %s\n", path);
+
+ tested = 0;
+ nonzero_windows = 0;
+ command_echo_windows = 0;
+ raw_table_windows = 0;
+ promotable_windows = 0;
+ best_score = -999999;
+ best_addr = 0;
+ best_range = "none";
+
+ fprintf (f, "{\n");
+ fprintf (f, " \"sweep_version\": \"v1_known_memdump_dense_address_range\",\n");
+ fprintf (f, " \"drive\": \"");
+ hlds_e7_json_escape (f, disc_get_drive_model_string (d));
+ fprintf (f, "\",\n");
+ fprintf (f, " \"notes\": \"This diagnostic uses only HIT 0xE7 subcmd 0x01, because the subcommand sweep showed only that subcommand returns nonzero data. It densely sweeps address ranges and promotes only sector-shaped 2064-byte raw headers or direct READ-payload echoes. Command echoes and sector-number table matches are recorded but not promoted.\",\n");
+ fprintf (f, " \"read_length\": %u,\n", len);
+ fprintf (f, " \"probe_sectors\": [%u, %u],\n", probe_sectors[0], probe_sectors[1]);
+ fprintf (f, " \"ranges\": [\n");
+ for (r = 0; r < sizeof (ranges) / sizeof (ranges[0]); r++) {
+ fprintf (f, " {\"label\": \"");
+ hlds_e7_json_escape (f, ranges[r].label);
+ fprintf (f, "\", \"start\": \"0x%08x\", \"end\": \"0x%08x\", \"step\": \"0x%08x\"}%s\n",
+ ranges[r].start, ranges[r].end, ranges[r].step,
+ (r + 1 < sizeof (ranges) / sizeof (ranges[0])) ? "," : "");
+ }
+ fprintf (f, " ],\n");
+ fprintf (f, " \"results\": [\n");
+
+ for (r = 0; r < sizeof (ranges) / sizeof (ranges[0]); r++) {
+ fprintf (stderr, " range %-28s 0x%08x..0x%08x step=0x%04x\n",
+ ranges[r].label, ranges[r].start, ranges[r].end, ranges[r].step);
+ for (addr = ranges[r].start; addr < ranges[r].end; addr += ranges[r].step) {
+ int addr_score = 0;
+ int addr_promotable = 0;
+ int addr_nonzero = 0;
+ int addr_command_echo = 0;
+ int addr_raw_table = 0;
+ bool first_result = (tested == 0);
+
+ tested++;
+ if (!first_result)
+ fprintf (f, ",\n");
+ fprintf (f, " {\n");
+ fprintf (f, " \"range\": \"");
+ hlds_e7_json_escape (f, ranges[r].label);
+ fprintf (f, "\",\n");
+ fprintf (f, " \"address\": \"0x%08x\",\n", addr);
+ fprintf (f, " \"sector_tests\": [\n");
+
+ for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) {
+ int ret;
+ int read_ret;
+ int score = 0;
+ int exact_count = 0;
+ int raw_score = 0;
+ int user_score = 0;
+ int kk;
+ size_t raw_offset = (size_t) -1;
+ size_t user_offset = (size_t) -1;
+ size_t read_offset = (size_t) -1;
+ size_t command_echo_offset = (size_t) -1;
+ size_t offsets[SECTORS_PER_BLOCK];
+ u_int32_t raw_sector = 0xFFFFFFFFU;
+ u_int32_t user_sector = 0xFFFFFFFFU;
+ u_int32_t hash = 0;
+ bool sector_shaped = false;
+ bool is_zero = true;
+ u_int8_t sample[16];
+
+ memset (buf, 0, len);
+ memset (readbuf, 0, sizeof (readbuf));
+ memset (sample, 0, sizeof (sample));
+ for (kk = 0; kk < SECTORS_PER_BLOCK; kk++)
+ offsets[kk] = (size_t) -1;
+ read_ret = dvd_read_sector_streaming (d -> dvd, probe_sectors[j], NULL, readbuf, sizeof (readbuf));
+ ret = hlds_e7_raw_hit_data_in (d, 0x01, addr, len, buf);
+ if (ret >= 0) {
+ size_t zi;
+ memcpy (sample, buf, sizeof (sample));
+ hash = hlds_e7_fnv1a32 (buf, len);
+ for (zi = 0; zi < len; zi++) {
+ if (buf[zi] != 0) {
+ is_zero = false;
+ break;
+ }
+ }
+ (void) hlds_e7_find_raw_header_match (buf, len, probe_sectors[j], &raw_offset, &raw_sector);
+ exact_count = hlds_e7_count_exact_raw_headers_for_block (buf, len, probe_sectors[j], offsets);
+ sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (offsets);
+ command_echo_offset = hlds_e7_find_command_echo_offset (buf, len);
+ user_score = hlds_e7_find_user_data_match (buf, len, readbuf, sizeof (readbuf), &user_offset, &user_sector, &read_offset);
+ raw_score = sector_shaped ? 220 : exact_count;
+ score += raw_score + user_score;
+ if (command_echo_offset != (size_t) -1 && !sector_shaped && user_score <= 0)
+ score -= 10;
+ if (!is_zero)
+ addr_nonzero++;
+ if (command_echo_offset != (size_t) -1)
+ addr_command_echo++;
+ if (exact_count > 0 && !sector_shaped)
+ addr_raw_table++;
+ if (sector_shaped || user_score > 0)
+ addr_promotable++;
+ if (dump_prefix && dump_prefix[0] && !is_zero) {
+ snprintf (dump_path, sizeof (dump_path), "%s_%s_0x%08x_sector_%u.bin",
+ dump_prefix, ranges[r].label, addr, probe_sectors[j]);
+ df = fopen (dump_path, "wb");
+ if (df) {
+ fwrite (buf, 1, len, df);
+ fclose (df);
+ }
+ }
+ } else {
+ score -= 50;
+ }
+ if (read_ret < 0)
+ score -= 20;
+ addr_score += score;
+
+ fprintf (f, " {\"sector\": %u, \"read_ret\": %d, \"e7_ret\": %d, \"score\": %d, ",
+ probe_sectors[j], read_ret, ret, score);
+ fprintf (f, "\"nonzero\": %s, \"raw_header_found_count\": %d, \"raw_header_sector_shaped\": %s, ",
+ is_zero ? "false" : "true", exact_count, sector_shaped ? "true" : "false");
+ fprintf (f, "\"raw_header_offset\": ");
+ if (raw_offset == (size_t) -1)
+ fprintf (f, "null, \"raw_header_sector\": null, ");
+ else
+ fprintf (f, "%lu, \"raw_header_sector\": %u, ", (unsigned long) raw_offset, raw_sector);
+ fprintf (f, "\"command_echo_offset\": ");
+ if (command_echo_offset == (size_t) -1)
+ fprintf (f, "null, ");
+ else
+ fprintf (f, "%lu, ", (unsigned long) command_echo_offset);
+ fprintf (f, "\"user_data_score\": %d, \"user_data_offset\": ", user_score);
+ if (user_offset == (size_t) -1)
+ fprintf (f, "null, \"user_data_sector_index\": null, \"read_probe_offset\": null, ");
+ else
+ fprintf (f, "%lu, \"user_data_sector_index\": %u, \"read_probe_offset\": %lu, ",
+ (unsigned long) user_offset, user_sector, (unsigned long) read_offset);
+ fprintf (f, "\"window_hash_fnv1a32\": \"0x%08x\", \"sample\": ", hash);
+ hlds_e7_json_bytes (f, sample, sizeof (sample));
+ fprintf (f, "}%s\n", (j + 1 < sizeof (probe_sectors) / sizeof (probe_sectors[0])) ? "," : "");
+ }
+
+ if (addr_nonzero)
+ nonzero_windows += addr_nonzero;
+ if (addr_command_echo)
+ command_echo_windows += addr_command_echo;
+ if (addr_raw_table)
+ raw_table_windows += addr_raw_table;
+ if (addr_promotable)
+ promotable_windows += addr_promotable;
+ if (addr_score > best_score) {
+ best_score = addr_score;
+ best_addr = addr;
+ best_range = ranges[r].label;
+ }
+
+ fprintf (f, " ],\n");
+ fprintf (f, " \"total_score\": %d,\n", addr_score);
+ fprintf (f, " \"nonzero_sector_tests\": %d,\n", addr_nonzero);
+ fprintf (f, " \"command_echo_sector_tests\": %d,\n", addr_command_echo);
+ fprintf (f, " \"raw_table_like_sector_tests\": %d,\n", addr_raw_table);
+ fprintf (f, " \"promotable_sector_tests\": %d,\n", addr_promotable);
+ fprintf (f, " \"classification\": \"%s\"\n",
+ addr_promotable > 0 ? "promotable_candidate" : (addr_raw_table || addr_command_echo ? "sram_or_table_match" : (addr_nonzero ? "nonzero_no_cache" : "zero_or_no_response")));
+ fprintf (f, " }");
+ }
+ }
+
+ fprintf (f, "\n ],\n");
+ fprintf (f, " \"addresses_tested\": %lu,\n", tested);
+ fprintf (f, " \"nonzero_windows\": %lu,\n", nonzero_windows);
+ fprintf (f, " \"command_echo_windows\": %lu,\n", command_echo_windows);
+ fprintf (f, " \"raw_table_like_windows\": %lu,\n", raw_table_windows);
+ fprintf (f, " \"promotable_windows\": %lu,\n", promotable_windows);
+ fprintf (f, " \"best\": {\"range\": \"");
+ hlds_e7_json_escape (f, best_range);
+ fprintf (f, "\", \"address\": \"0x%08x\", \"score\": %d},\n", best_addr, best_score);
+ fprintf (f, " \"promotable_candidate_found\": %s,\n", promotable_windows ? "true" : "false");
+ fprintf (f, " \"promotion_recommendation\": \"%s\",\n",
+ promotable_windows ? "review promotable address and try a targeted profile" : "do not promote; dense subcmd 0x01 address sweep found no sector-shaped cache or READ-payload echo");
+ fprintf (f, " \"next_recommended_action\": \"%s\"\n",
+ promotable_windows ? "send JSON and matching dumped windows" : "focus on firmware handler/control-flow analysis or different pre-read/cache-fill sequences before another seed attempt");
+ fprintf (f, "}\n");
+ fclose (f);
+ free (buf);
+
+ fprintf (stderr, "HLDS 0xE7 memory-range sweep v1 complete: addresses=%lu promotable_windows=%lu nonzero_windows=%lu command_echo_windows=%lu\n",
+ tested, promotable_windows, nonzero_windows, command_echo_windows);
+ return true;
+}
+
+
+static bool disc_probe_gdr8050l_e7_speed_profile (disc *d) {
+ static const hlds_e7_probe_candidate candidates[] = {
+ {443, 0x80000000U, 3, "Probe A 3-window no-prefetch: base 0x80000000, 3 windows"},
+ {442, 0x80000000U, 2, "Probe B 2-window no-prefetch: base 0x80000000, 2 windows"},
+ {445, 0x80000000U, 5, "Probe C 5-window guarded no-prefetch: base 0x80000000, 5 windows"},
+ {44, 0x80000000U, 1, "Probe D proven fallback: base 0x80000000, 1 window"}
+ };
+ static const u_int32_t probe_sectors[] = {0, 320};
+ size_t i, j;
+ bool ok;
+
+ if (!d || (dvd_get_hlds_e7_type (d -> dvd) != 44 && dvd_get_hlds_e7_type (d -> dvd) != 45))
+ return true;
+
+ hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: single-window is proven; trying guarded no-prefetch multi-window profiles before seed cracking");
+ for (i = 0; i < sizeof (candidates) / sizeof (candidates[0]); i++) {
+ hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: %s", candidates[i].label);
+ dvd_set_hlds_e7_runtime_profile (d -> dvd, candidates[i].type, candidates[i].base, candidates[i].windows);
+ ok = true;
+ for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) {
+ disc_cache_clear (d);
+ if (!disc_read_sector (d, probe_sectors[j], NULL, NULL) || dvd_get_hlds_e7_type (d -> dvd) != candidates[i].type) {
+ ok = false;
+ break;
+ }
+ }
+ disc_cache_clear (d);
+ if (ok) {
+ hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: selected %s", candidates[i].label);
+ return true;
+ }
+ hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: failed %s", candidates[i].label);
+ }
+
+ dvd_set_hlds_e7_runtime_profile (d -> dvd, 44, 0x80000000U, 1);
+ disc_cache_clear (d);
+ hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: all accelerated profiles failed; using proven single-window fallback");
+ return true;
+}
+
+static bool disc_probe_gdr8081n_e7_profile (disc *d) {
+ static const hlds_e7_probe_candidate candidates[] = {
+ {815, 0x80000000U, 5, "Probe A strict scan-guided Type4-derived: base 0x80000000, 5 windows"}
+ };
+ size_t i;
+
+ if (!d || dvd_get_hlds_e7_type (d -> dvd) != 81)
+ return true;
+
+ hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: drive is experimental; trying strict scan-guided profile only; exact-offset fallbacks were removed to avoid 10-minute false-negative loops");
+ for (i = 0; i < sizeof (candidates) / sizeof (candidates[0]); i++) {
+ hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: %s", candidates[i].label);
+ dvd_set_hlds_e7_runtime_profile (d -> dvd, candidates[i].type, candidates[i].base, candidates[i].windows);
+ disc_cache_clear (d);
+ if (disc_read_sector (d, 0, NULL, NULL)) {
+ hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: selected %s", candidates[i].label);
+ return true;
+ }
+ hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: failed %s", candidates[i].label);
+ }
+
+ dvd_set_hlds_e7_runtime_profile (d -> dvd, 81, 0x80000000U, 5);
+ disc_cache_clear (d);
+ hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: strict scan-guided candidate failed; run --hlds-e7-scan with --scan-dump-prefix and inspect strict/user-data fields before another seed attempt");
+ return false;
+}
+
+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 */
+ debug ("Retrieving all DVD seeds");
+ if (!disc_probe_gdr8050l_e7_speed_profile (d))
+ return false;
+ if (!disc_probe_gdr8081n_e7_profile (d))
+ return false;
+ for (i = 0; i < 20 * 16; i += 16) {
+ if (!disc_read_sector (d, i, NULL, NULL))
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Creates a new structure representing a Nintendo GameCube/Wii optical disc.
+ * @param dvd_device The CD/DVD-ROM device, in OS-dependent format (i.e.: /dev/something on Unix, x: on Windows).
+ * @return The newly-created structure, to be used with the other commands.
+ */
+disc *disc_new (char *dvd_device, u_int32_t command) {
+ dvd_drive *dvd;
+ disc *d;
+
+ if ((dvd = dvd_drive_new (dvd_device, command))) {
+ d = (disc *) malloc (sizeof (disc));
+ memset (d, 0, sizeof (disc));
+ d -> dvd = dvd;
+ d -> u = unscrambler_new ();
+ disc_set_unscrambling (d, true); // Unscramble by default
+ disc_set_read_method (d, DEFAULT_READ_METHOD);
+ disc_cache_init (d, DISC_DEFAULT_CACHE_SIZE);
+ } else {
+ d = NULL;
+ }
+
+ return (d);
+}
+
+
+int disc_media_preflight (disc *d, unsigned int timeout_ms, int *sense_key, int *asc, int *ascq) {
+ req_sense sense;
+ unsigned int elapsed = 0;
+ const unsigned int interval_ms = 500;
+ int rc;
+
+ if (sense_key) *sense_key = 0;
+ if (asc) *asc = 0;
+ if (ascq) *ascq = 0;
+ if (!d || !d -> dvd)
+ return -1;
+
+ for (;;) {
+ u_int32_t sectors = 0, sector_size = 0;
+
+ memset (&sense, 0, sizeof (sense));
+ rc = dvd_test_unit_ready (d -> dvd, &sense);
+ if (rc >= 0) {
+ /* Some optical drives and USB bridges report TEST UNIT READY=GOOD
+ * with an empty tray. Require a second, media-dependent command
+ * before allowing vendor seed/cache reads. */
+ memset (&sense, 0, sizeof (sense));
+ rc = dvd_read_capacity_10 (d -> dvd, §ors, §or_size, &sense);
+ if (rc >= 0 && sectors > 1 && sector_size == SECTOR_SIZE)
+ return 1;
+ /* A successful command with zero/invalid capacity is not proof of media. */
+ if (rc >= 0) {
+ if (sense_key) *sense_key = 0;
+ if (asc) *asc = 0;
+ if (ascq) *ascq = 0;
+ return 0;
+ }
+ }
+
+ if (sense_key) *sense_key = sense.sense_key;
+ if (asc) *asc = sense.asc;
+ if (ascq) *ascq = sense.ascq;
+
+ /* SPC/MMC: NOT READY / MEDIUM NOT PRESENT. */
+ if ((sense.sense_key & 0x0f) == 0x02 && sense.asc == 0x3a)
+ return 0;
+
+ /* Retry transient becoming-ready / unit-attention states. */
+ if (!(((sense.sense_key & 0x0f) == 0x02 && sense.asc == 0x04) ||
+ ((sense.sense_key & 0x0f) == 0x06 && (sense.asc == 0x28 || sense.asc == 0x29))))
+ return -1;
+ if (elapsed >= timeout_ms)
+ return -1;
+#ifdef WIN32
+ Sleep (interval_ms);
+#else
+ usleep ((useconds_t) interval_ms * 1000);
+#endif
+ elapsed += interval_ms;
+ }
+}
+
+bool disc_init (disc *d, u_int32_t disctype, u_int32_t sectors_no) {
+ bool out;
+
+ d -> sectors_no = 1000; // TODO
+ disc_detect_type (d, disctype, sectors_no);
+ if (d -> type != DISC_TYPE_XBOX && !disc_crack_seeds (d))
+ return false;
+// unscrambler_set_bruteforce (d -> u, false); // Disabling bruteforcing will allow us to detect errors more quickly
+ unscrambler_set_bruteforce (d -> u, true);
+ if (d -> type==DISC_TYPE_DVD) {
+ my_strdup (d -> title, "DVD");
+ out = true;
+ }
+ else if (d -> type==DISC_TYPE_XBOX) {
+ my_strdup (d -> title, "Xbox DVD");
+ d -> system_id = 'X';
+ strncpy (d -> game_id, "XB", sizeof (d -> game_id));
+ strncpy (d -> maker, "MS", sizeof (d -> maker));
+ my_strdup (d -> version_string, "N/A");
+ d -> has_update = false;
+ disc_set_unscrambling (d, false);
+ out = true;
+ }
+ else if (disc_analyze (d)) {
+ disc_check_update (d);
+ out = true;
+ } else {
+ out = false;
+ }
+
+ return (out);
+}
+
+
+/**
+ * Frees resources used by a disc structure and destroys it.
+ * @param d The disc structure.
+ * @return NULL.
+ */
+void *disc_destroy (disc *d) {
+ disc_cache_destroy (d);
+ unscrambler_destroy (d -> u);
+ my_free (d -> version_string);
+ my_free (d -> title);
+ dvd_drive_destroy (d -> dvd);
+ my_free (d);
+
+ return (NULL);
+}
+
+
+bool disc_is_xbox_unlock_drive (disc *d) {
+ return d && dvd_is_xbox_unlock_drive (d -> dvd);
+}
+
+bool disc_is_xbox_challenge_drive (disc *d) {
+ return d && dvd_is_xbox_challenge_drive (d -> dvd);
+}
+
+bool disc_is_xbox_vendor_unlock_drive (disc *d) {
+ return d && dvd_is_xbox_vendor_unlock_drive (d -> dvd);
+}
+
+int disc_xbox_lock (disc *d) {
+ u_int32_t sectors = 0;
+ u_int32_t sector_size = 0;
+
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+
+ if (dvd_is_xbox_vendor_unlock_drive (d -> dvd)) {
+ if (dvd_xbox_vendor_lock (d -> dvd) < 0)
+ return -1;
+ }
+
+ if (dvd_read_capacity_10 (d -> dvd, §ors, §or_size, NULL) == 0 && sector_size == SECTOR_SIZE)
+ d -> sectors_no = sectors;
+ return 0;
+}
+
+
+int disc_xbox_unlock (disc *d) {
+ u_int32_t sectors = 0;
+ u_int32_t sector_size = 0;
+
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+
+ if (dvd_is_xbox_challenge_drive (d -> dvd)) {
+ if (dvd_xbox_gdr8050l_unlock (d -> dvd, §ors) < 0)
+ return -1;
+ d -> sectors_no = sectors;
+ return 0;
+ }
+
+ if (dvd_is_xbox_vendor_unlock_drive (d -> dvd)) {
+ if (dvd_xbox_vendor_unlock_wxripper (d -> dvd, §ors) < 0)
+ return -1;
+ d -> sectors_no = sectors;
+ return 0;
+ }
+
+ /* Forced Xbox mode on an unknown drive keeps FriiDump's direct READ(10)
+ * experiment path, but no model-specific unlock is applied. */
+ if (dvd_read_capacity_10 (d -> dvd, §ors, §or_size, NULL) == 0 && sector_size == SECTOR_SIZE)
+ d -> sectors_no = sectors;
+
+ return 0;
+}
+
+
+int disc_xbox_read_10 (disc *d, u_int32_t sector, u_int32_t sectors, u_int8_t *buf, size_t bufsize) {
+ if (!d || d -> type != DISC_TYPE_XBOX || !buf)
+ return -1;
+ return dvd_read_10 (d -> dvd, sector, sectors, NULL, buf, bufsize);
+}
+
+
+int disc_xbox_read_dvd_structure (disc *d, u_int8_t format, u_int8_t layer, u_int8_t *buf, size_t bufsize) {
+ if (!d || d -> type != DISC_TYPE_XBOX || !buf)
+ return -1;
+ return dvd_read_dvd_structure (d -> dvd, format, layer, buf, bufsize, NULL);
+}
+
+
+int disc_xbox_read_capacity_10 (disc *d, u_int32_t *sectors, u_int32_t *sector_size) {
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+ return dvd_read_capacity_10 (d -> dvd, sectors, sector_size, NULL);
+}
+
+
+
+int disc_xbox_recovery_kick (disc *d, bool auth_recovery) {
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+ return dvd_xbox_recovery_kick (d -> dvd, auth_recovery);
+}
+
+int disc_refresh_volume (disc *d) {
+ if (!d)
+ return -1;
+ return dvd_refresh_volume (d -> dvd);
+}
+
+int disc_lock_volume (disc *d) {
+ if (!d)
+ return -1;
+ return dvd_lock_volume (d -> dvd);
+}
+
+int disc_xbox_refresh_volume (disc *d) {
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+ return dvd_xbox_refresh_volume (d -> dvd);
+}
+
+int disc_xbox_lock_volume (disc *d) {
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+ return dvd_xbox_lock_volume (d -> dvd);
+}
+
+int disc_xbox_media_cycle (disc *d) {
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+ return dvd_media_cycle (d -> dvd, NULL);
+}
+
+int disc_xbox_wait_ready (disc *d, unsigned int timeout_ms) {
+ if (!d || d -> type != DISC_TYPE_XBOX)
+ return -1;
+ return dvd_wait_ready (d -> dvd, timeout_ms);
+}
+
+char *disc_get_drive_model_string (disc *d) {
+ return (dvd_get_model_string (d -> dvd));
+}
+
+
+char *disc_get_device (disc *d) {
+ return (dvd_get_device (d -> dvd));
+}
+
+void *disc_get_native_handle (disc *d) {
+ if (!d) return NULL;
+ return dvd_get_native_handle (d -> dvd);
+}
+
+
+bool disc_get_drive_support_status (disc *d) {
+ return (dvd_get_support_status (d -> dvd));
+}
+
+const char *disc_get_hlds_e7_profile_name (disc *d) {
+ return d ? dvd_get_hlds_e7_profile_name (d -> dvd) : "none";
+}
+
+const char *disc_get_hlds_e7_support_tier (disc *d) {
+ return d ? dvd_get_hlds_e7_support_tier (d -> dvd) : "none";
+}
+
+const char *disc_get_hlds_e7_family (disc *d) {
+ return d ? dvd_get_hlds_e7_family (d -> dvd) : "none";
+}
+
+const char *disc_get_hlds_e7_tokens (disc *d) {
+ return d ? dvd_get_hlds_e7_tokens (d -> dvd) : "";
+}
+
+const char *disc_get_hlds_e7_record_id (disc *d) {
+ return d ? dvd_get_hlds_e7_record_id (d -> dvd) : "";
+}
+
+const char *disc_get_hlds_e7_notes (disc *d) {
+ return d ? dvd_get_hlds_e7_notes (d -> dvd) : "";
+}
+
+u_int32_t disc_get_hlds_e7_type (disc *d) {
+ return d ? dvd_get_hlds_e7_type (d -> dvd) : 0;
+}
+
+u_int32_t disc_get_hlds_e7_cache_base (disc *d) {
+ return d ? dvd_get_hlds_e7_cache_base (d -> dvd) : 0;
+}
+
+u_int32_t disc_get_hlds_e7_mem_blocks (disc *d) {
+ return d ? dvd_get_hlds_e7_mem_blocks (d -> dvd) : 0;
+}
+
+u_int32_t disc_get_hlds_e7_static_cdb_base (disc *d) {
+ return d ? dvd_get_hlds_e7_static_cdb_base (d -> dvd) : 0;
+}
+
+u_int32_t disc_get_hlds_e7_static_gate (disc *d) {
+ return d ? dvd_get_hlds_e7_static_gate (d -> dvd) : 0;
+}
+
+int disc_get_hlds_e7_preferred_method (disc *d) {
+ return d ? dvd_get_hlds_e7_preferred_method (d -> dvd) : -1;
+}
+
+void disc_set_speed (disc *d, u_int32_t speed) {
+ if (speed != -1) dvd_set_speed (d -> dvd, speed, NULL);
+}
+
+void disc_set_streaming_speed (disc *d, u_int32_t speed) {
+ if (speed != -1) dvd_set_streaming (d -> dvd, speed, NULL);
+}
+
+bool disc_stop_unit (disc *d, bool start) {
+ if (dvd_stop_unit (d -> dvd, start, NULL) == 0) return true;
+ else return false;
+}
+
+void init_range (disc *d, u_int32_t sec_disc, u_int32_t sec_mem) {
+ if ((sec_disc>=1)&&(sec_disc<=100)) d->sec_disc = sec_disc;
+ else d->sec_disc = -1;
+ if ((sec_mem>=16)&&(sec_mem<=100)) d->sec_mem = sec_mem;
+ else d->sec_mem = -1;
+}
\ No newline at end of file