/***************************************************************************
 *   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 A class to send raw MMC commands to a CD/DVD-ROM drive.
 *
 * This class can be used to send raw MMC commands to a CD/DVD-ROM drive. It uses own structures and data types to represent the commands, which are
 * then transformed in the proper OS-dependent structures when the command is executed, achieving portability. Currently Linux and Windows are supported, but
 * all that is needed to add support to a new OS is a proper <code>dvd_execute_cmd()</code> function, so it should be very easy. I hope that someone can add
 * compatibility with MacOS X and *BSD: libcdio is a good place to understand how it should be done :). Actally, we could have used libcdio right from the start,
 * but I didn't want to add a dependency on a library that cannot be easily found in binary format for all the target OS's.
 *
 * This file contains code derived from the work of Kevin East (SeventhSon), kev@kev.nu, http://www.kev.nu/360/ , which, in turn, derives from work by
 * a lot of other people. See his page for full details.
 */

#include "rs.h"
#include "misc.h"
#include <stdio.h>
#include <sys/types.h>
//#include <sys/time.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include "dvd_drive.h"
#include "disc.h"
#include "sha1.h"
#include "xbox_ref/xbox_ref_log.h"

#ifdef WIN32
#include <windows.h>
#include <ntddscsi.h>
#else
#include <linux/cdrom.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif


/*! \brief Timeout for MMC commands.
 *
 * This must be expressed in seconds (Windows uses seconds, right?).
 */
#define MMC_CMD_TIMEOUT 10


/* Imported drive-specific functions */
int vanilla_2064_dvd_dump_mem	(dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf);
int vanilla_2384_dvd_dump_mem	(dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf);
int hitachi_dvd_dump_mem	(dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf);
int hitachi_dvd_dump_mem_type1	(dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf);
int liteon_dvd_dump_mem		(dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf);
int renesas_dvd_dump_mem	(dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf);


/*! \brief A structure that represents a CD/DVD-ROM drive.
 */
struct dvd_drive_s {
	/* Device special file */
	char *device;			//!< The path to the drive (i.e.: /dev/something on Unix, x: on Windows).

	/* Data about the drive */
	char *vendor;			//!< The drive vendor.
	char *prod_id;			//!< The drive product ID.
	char *prod_rev;			//!< The drive product revision (Usually firmware version).
	char *model_string;		//!< The above three strings, joined in a single one.
	u_int32_t def_method;
	u_int32_t command;
	u_int32_t hlds_e7_type;
	u_int32_t hlds_e7_cache_base;
	u_int32_t hlds_e7_mem_blocks;
	u_int32_t hlds_e7_static_cdb_base;
	u_int32_t hlds_e7_static_gate;
	int hlds_e7_preferred_method;
	const char *hlds_e7_profile_label;
	const char *hlds_e7_support_tier;
	const char *hlds_e7_family;
	const char *hlds_e7_tokens;
	const char *hlds_e7_record_id;
	const char *hlds_e7_notes;

	/* Last transport command evidence for release-build diagnostics. */
	dvd_command_diagnostic last_command;

	/* Device-dependent internal memory dump function */
	/*! The intended area should start where sector data is stored upon a READ command. Here we assume that sectors are
	 *  stored one after the other, as heuristics showed it is the case for the Hitachi MN103-based drives, but this model
	 *  might be changed in the future, if we get support for other drives.
	 */
	dvd_drive_memdump_func memdump;	//!< A pointer to a function that is able to dump the drive's internal memory area.
	bool supported;			//!< True if the drive is a supported model, false otherwise.


	/* File descriptor & stuff used to access drive */
#ifdef WIN32
	HANDLE fd;			//!< The HANDLE to interact with the drive on Windows.
#else
	int fd;				//!< The file descriptor to interact with the drive on Unix.
#endif
};


/** \brief Supported MMC commands.
 */
enum mmc_commands_e {
	SPC_TEST_UNIT_READY = 0x00,
	SPC_INQUIRY = 0x12,
	SPC_MODE_SELECT_6 = 0x15,
	MMC_START_STOP_UNIT = 0x1B,
	MMC_READ_CAPACITY_10 = 0x25,
	MMC_READ_10 = 0x28,
	SPC_MODE_SENSE_10 = 0x5A,
	SPC_MODE_SELECT_10 = 0x55,
	MMC_READ_12 = 0xA8,
	MMC_READ_DVD_STRUCTURE = 0xAD,
};

typedef struct {
	u_int8_t s[256];
	u_int8_t i;
	u_int8_t j;
} xbox_rc4_ctx;

static void xbox_rc4_init (xbox_rc4_ctx *ctx, const u_int8_t *key, size_t keylen) {
	u_int32_t i;
	u_int8_t j, tmp;

	for (i = 0; i < 256; i++)
		ctx -> s[i] = (u_int8_t) i;
	ctx -> i = 0;
	ctx -> j = 0;

	if (keylen == 0)
		return;

	j = 0;
	for (i = 0; i < 256; i++) {
		j = (u_int8_t) (j + ctx -> s[i] + key[i % keylen]);
		tmp = ctx -> s[i];
		ctx -> s[i] = ctx -> s[j];
		ctx -> s[j] = tmp;
	}
}

static void xbox_rc4_crypt (xbox_rc4_ctx *ctx, const u_int8_t *in, u_int8_t *out, size_t len) {
	size_t n;
	u_int8_t tmp, k;

	for (n = 0; n < len; n++) {
		ctx -> i = (u_int8_t) (ctx -> i + 1);
		ctx -> j = (u_int8_t) (ctx -> j + ctx -> s[ctx -> i]);
		tmp = ctx -> s[ctx -> i];
		ctx -> s[ctx -> i] = ctx -> s[ctx -> j];
		ctx -> s[ctx -> j] = tmp;
		k = ctx -> s[(u_int8_t) (ctx -> s[ctx -> i] + ctx -> s[ctx -> j])];
		out[n] = in[n] ^ k;
	}
}

static bool dvd_prod_has (dvd_drive *dvd, const char *needle) {
	return dvd && dvd -> prod_id && needle && strstr (dvd -> prod_id, needle) != NULL;
}

static bool dvd_vendor_is (dvd_drive *dvd, const char *vendor) {
	return dvd && dvd -> vendor && vendor && strcmp (dvd -> vendor, vendor) == 0;
}

static bool dvd_is_hlds_drive (dvd_drive *dvd) {
	return dvd_vendor_is (dvd, "HL-DT-ST");
}

static bool dvd_prod_has_any (dvd_drive *dvd, const char **needles, size_t count) {
	size_t i;
	for (i = 0; i < count; i++) {
		if (dvd_prod_has (dvd, needles[i]))
			return true;
	}
	return false;
}

static bool hlds_product_has (const char *product_id, const char *needle) {
	return product_id && needle && strstr (product_id, needle) != NULL;
}

static bool hlds_product_has_any (
	const char *product_id,
	const char **needles,
	size_t count
) {
	size_t i;
	for (i = 0; i < count; i++) {
		if (hlds_product_has (product_id, needles[i]))
			return true;
	}
	return false;
}

static bool dvd_is_hlds_gcc4243_4244_drive (dvd_drive *dvd) {
	static const char *names[] = {
		"GCC-4243N", "GCC4243N", "GCC4243",
		"GCC-4244N", "GCC4244N", "GCC4244"
	};
	return dvd_is_hlds_drive (dvd) && dvd_prod_has_any (dvd, names, sizeof (names) / sizeof (names[0]));
}

static bool dvd_is_hlds_gdr8050l_drive (dvd_drive *dvd) {
	static const char *names[] = {
		"GDR8050L", "GDR-8050L"
	};
	return dvd_is_hlds_drive (dvd) && dvd_prod_has_any (dvd, names, sizeof (names) / sizeof (names[0]));
}


typedef struct {
	const char *model;
	const char *firmware;
	u_int32_t type;
	u_int32_t cache_base;
	u_int32_t mem_blocks;
	int preferred_method;
	const char *label;
	const char *support_tier;
	const char *family;
	const char *tokens;
	const char *record_id;
	u_int32_t static_cdb_base;
	u_int32_t static_gate;
	const char *notes;
} hlds_e7_profile_desc;

static const hlds_e7_profile_desc hlds_e7_profiles[] = {
	{ "GCC-4241N", "A101", 21, 0x80000000U, 1, 8, "GCC-4241N A101 promoted E7 parser profile", "error_prone_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0064", 0x85cU, 0x900356b7U, "capable but error-prone; conservative Method 8 one-window validation profile" },
	{ "GCC-4242N", "0J06", 22, 0x80000000U, 1, 8, "GCC-4242N 0J06 promoted E7 parser profile", "error_prone_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0066", 0x824U, 0x90038621U, "capable but error-prone; conservative Method 8 one-window validation profile" },
	{ "GCC-4243N", "0000", 3, 0x80000000U, 5, 8, "GCC-4243N 0000 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0069", 0x884U, 0x90037929U, "known-supported GCC_424x profile hardening target" },
	{ "GCC-4243N", "1.08", 3, 0x80000000U, 5, 8, "GCC-4243N 1.08 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0071", 0x880U, 0x90036813U, "known-supported GCC_424x profile hardening target" },
	{ "GCC4243", "A102", 3, 0x80000000U, 5, 8, "GCC-4243N A102 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "hybrid_identity_v3:stage5b_0439", 0x880U, 0x9003731dU, "Live INQUIRY alias for GCC4243/A102; exact profile selection, 20-block seed retrieval, full GameCube dump, STOP UNIT, and exact Redump match validated with Sonic Mega Collection (US)" },
	{ "GCC-4243N", "A102", 3, 0x80000000U, 5, 8, "GCC-4243N A102 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "hybrid_identity_v3:stage5b_0439", 0x880U, 0x9003731dU, "Canonical model alias for GCC-4243N A102; exact profile selection, 20-block seed retrieval, full GameCube dump, STOP UNIT, and exact Redump match validated with Sonic Mega Collection (US)" },
	{ "GCC-4244N", "1.03", 3, 0x80000000U, 5, 8, "GCC-4244N 1.03 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0073", 0x88cU, 0x90037d06U, "P1 owned GCC_424x profile hardening target" },
	{ "GCC-4244N", "103", 3, 0x80000000U, 5, 8, "GCC-4244N 103 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0076", 0x894U, 0x900386fbU, "P1 owned GCC_424x profile hardening target" },
	{ "GCC4244", "B101", 3, 0x80000000U, 5, 8, "GCC-4244N B101 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "hybrid_identity_v3:stage5b_0426", 0x894U, 0x900386d2U, "Live INQUIRY alias for GCC4244/B101; exact profile selection, 20-block GameCube seed retrieval, and STOP UNIT validated; the earlier legacy Type3 fallback full dump matched Redump" },
	{ "GCC-4244N", "B101", 3, 0x80000000U, 5, 8, "GCC-4244N B101 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "hybrid_identity_v3:stage5b_0426", 0x894U, 0x900386d2U, "Canonical model alias for GCC-4244N B101; exact profile selection, 20-block GameCube seed retrieval, and STOP UNIT validated; the earlier legacy Type3 fallback full dump matched Redump" },
	{ "GCC4244", "B103", 3, 0x80000000U, 5, 8, "GCC-4244N B103 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "promoted_parser_signature_v23", 0x894U, 0x900386fbU, "Live INQUIRY alias for HL-DT-ST CDRW/DVD GCC4244 B103; case label B101; exact Stage5B parser signature recovered; media preflight, seed retrieval, full GameCube dump, STOP UNIT, and Redump hash match validated" },
	{ "GCC-4244N", "B103", 3, 0x80000000U, 5, 8, "GCC-4244N B103 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "promoted_parser_signature_v23", 0x894U, 0x900386fbU, "B103 shares the promoted parser signature gate/CDB with 103/104; live GCC4244/B103 hardware completed a Redump-matching GameCube dump" },
	{ "GCC-4244N", "104", 3, 0x80000000U, 5, 8, "GCC-4244N 104 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0078", 0x894U, 0x900386fbU, "P1 owned GCC_424x profile hardening target" },
	{ "GDR-3120L", "0046", 4, 0x80000000U, 5, 8, "GDR-3120L 0046 experimental GC/Wii E7 parser profile", "experimental_gc_wii_candidate", "GDR_3120x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0355", 0x5b8U, 0x90025bceU, "historically Xbox/reference; allow read-only GC/Wii Method 8 experiment, not proven support until dump validates" },
	{ "GDR-8050L", "0012", 44, 0x80000000U, 1, 8, "GDR-8050L 0012 hybrid cross-flash E7 parser profile", "hybrid_crossflash_modified_firmware_only", "GDR_8050x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "hybrid_identity_v3:gdr8050l_0012", 0x5d0U, 0x90026160U, "Operating identity is GDR-8050L 0012 on GDR-8163B physical hardware; package compatibility identity is GDR-8163B 0L23; GC/Wii 0xE7 access requires modified firmware; native GDR-8050L hardware is not owned" },
	{ "GDR-8082N", "0120", 4, 0x80000000U, 5, 9, "GDR-8082N 0120 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_808x", "HL;IT;RPC;RPC_SUFFIX", "stage5b_0103", 0x638U, 0x900282daU, "known-supported reference profile" },
	{ "GDR-8083N", "0K04", 4, 0x80000000U, 5, 9, "GDR-8083N 0K04 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_808x", "HL;IT;RPC;RPC_SUFFIX", "stage5b_0104", 0x638U, 0x900291fcU, "known-supported reference profile" },
	{ "GDR-8161B", "0102", 4, 0x80000000U, 5, 9, "GDR-8161B 0102 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_816x", "IT;RPC;RPC_SUFFIX", "stage5b_0109", 0x5a8U, 0x90025010U, "known-supported reference profile" },
	{ "GDR-8163B", "0L23", 4, 0x80000000U, 5, 9, "GDR-8163B 0L23 promoted E7 parser profile", "known_supported_profile_hardening_p0", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0110", 0x5e0U, 0x90024d5aU, "P0 owned profile hardening target" },
	{ "GDR-8163B", "0L30", 4, 0x80000000U, 5, 8, "GDR-8163B 0L30 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90025021U, "Germany-batch variant; Method 8 seed retrieval and full GameCube dump OK; exact Stage5B parser signature recovered" },
	{ "GDR-8163B", "0L20", 4, 0x80000000U, 5, -1, "GDR-8163B 0L20 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90024c8fU, "Germany-batch variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" },
	{ "GDR-8163B", "0D20", 4, 0x80000000U, 5, -1, "GDR-8163B 0D20 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90024ae7U, "Germany-batch variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" },
	{ "GDR-8163B", "0B30", 4, 0x80000000U, 5, -1, "GDR-8163B 0B30 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90025030U, "Germany-batch HP/OEM variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" },
	{ "GDR-8163B", "0E15", 4, 0x80000000U, 5, -1, "GDR-8163B 0E15 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5d8U, 0x900247d1U, "Germany-batch HP/OEM variant; exact Stage5B parser signature recovered with CDB base 0x5D8; full GameCube dump matches Redump" },
	{ "GDR-8163B", "0M26", 4, 0x80000000U, 5, -1, "GDR-8163B 0M26 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90024ff7U, "Germany-batch Lenovo/OEM Malaysia variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" },
	{ "GDR-8164B", "0L06", 4, 0x80000000U, 5, 9, "GDR-8164B 0L06 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0111/stage5b_0113", 0x5ccU, 0x90025bbeU, "known-supported reference profile; two firmware records agree" },
	{ NULL, NULL, 0, 0, 0, -1, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL }
};

static void hlds_normalize_model (const char *src, char *dst, size_t dst_size) {
	size_t i, j;
	if (!dst || dst_size == 0)
		return;
	dst[0] = 0;
	if (!src)
		return;
	for (i = 0, j = 0; src[i] && j + 1 < dst_size; i++) {
		unsigned char c = (unsigned char) src[i];
		if (isalnum (c))
			dst[j++] = (char) toupper (c);
	}
	dst[j] = 0;
}

static bool hlds_model_matches_value (const char *product_id, const char *model) {
	char prod_norm[64];
	char model_norm[64];
	if (!product_id || !model)
		return false;
	hlds_normalize_model (product_id, prod_norm, sizeof (prod_norm));
	hlds_normalize_model (model, model_norm, sizeof (model_norm));
	return prod_norm[0] && model_norm[0] && strstr (prod_norm, model_norm) != NULL;
}

static bool hlds_revision_matches_value (const char *actual, const char *expected) {
	const unsigned char *a;
	const unsigned char *b;
	if (!expected || !expected[0])
		return true;
	if (!actual)
		return false;
	a = (const unsigned char *) actual;
	b = (const unsigned char *) expected;
	while (*a && *b) {
		if (toupper (*a) != toupper (*b))
			return false;
		a++;
		b++;
	}
	return *a == 0 && *b == 0;
}

static const hlds_e7_profile_desc *hlds_find_e7_profile_for_identity (
	const char *vendor,
	const char *product_id,
	const char *revision
) {
	const hlds_e7_profile_desc *p;
	if (!vendor || strcmp (vendor, "HL-DT-ST") != 0)
		return NULL;
	for (p = hlds_e7_profiles; p -> model; p++) {
		if (
			hlds_model_matches_value (product_id, p -> model) &&
			hlds_revision_matches_value (revision, p -> firmware)
		)
			return p;
	}
	return NULL;
}

static const hlds_e7_profile_desc *dvd_find_hlds_e7_profile (dvd_drive *dvd) {
	if (!dvd)
		return NULL;
	return hlds_find_e7_profile_for_identity (
		dvd -> vendor,
		dvd -> prod_id,
		dvd -> prod_rev
	);
}

bool dvd_lookup_hlds_e7_profile (
	const char *vendor,
	const char *product_id,
	const char *revision,
	dvd_hlds_e7_profile_info *out
) {
	const hlds_e7_profile_desc *p;
	if (out)
		memset (out, 0, sizeof (*out));
	p = hlds_find_e7_profile_for_identity (vendor, product_id, revision);
	if (!p)
		return false;
	if (out) {
		out -> type = p -> type;
		out -> cache_base = p -> cache_base;
		out -> mem_blocks = p -> mem_blocks;
		out -> preferred_method = p -> preferred_method;
		out -> label = p -> label;
		out -> support_tier = p -> support_tier;
		out -> family = p -> family;
		out -> tokens = p -> tokens;
		out -> record_id = p -> record_id;
		out -> static_cdb_base = p -> static_cdb_base;
		out -> static_gate = p -> static_gate;
		out -> notes = p -> notes;
	}
	return true;
}

u_int32_t dvd_detect_hlds_e7_type_for_identity (
	const char *vendor,
	const char *product_id,
	const char *revision
) {
	const hlds_e7_profile_desc *profile =
		hlds_find_e7_profile_for_identity (vendor, product_id, revision);
	static const char *type1[] = {
		"GCC-4160N", "GCC4160N", "GCC4160",
		"GCC-4240N", "GCC4240N", "GCC4240"
	};
	static const char *type2_1[] = {
		"GCC-4241N", "GCC4241N", "GCC4241"
	};
	static const char *type2_2[] = {
		"GCC-4242N", "GCC4242N", "GCC4242"
	};
	static const char *gdr8081n[] = {
		"GDR8081N", "GDR-8081N"
	};
	static const char *type3[] = {
		"GCC4244", "GCC4244N", "GCC-4244N",
		"GCC4247", "GCC4247N", "GCC-4247N",
		"GDR8083N", "GDR8084N",
		"GCC-4243N", "GCC4243N", "GCC4243",
		"GCC-4246N", "GCC4246N", "GCC4246"
	};
	static const char *type4[] = {
		"DU10N", "GDR8082N", "GDR8161B", "GDR8162B",
		"GDR8163B", "GDR8164B", "GDR-T10N",
		/* Local project keeps GDR-3120L in the same transport family for
		 * classification, but Xbox dumping is still routed through the explicit
		 * Xbox paths rather than the GC/Wii Method 8/9 readers. */
		"GDR3120L", "GDR-3120L"
	};

	if (!vendor || strcmp (vendor, "HL-DT-ST") != 0)
		return 0;
	if (profile)
		return profile -> type;
	if (hlds_product_has_any (product_id, type1, sizeof (type1) / sizeof (type1[0])))
		return 1;
	if (hlds_product_has_any (product_id, type2_1, sizeof (type2_1) / sizeof (type2_1[0])))
		return 21;
	if (hlds_product_has_any (product_id, type2_2, sizeof (type2_2) / sizeof (type2_2[0])))
		return 22;
	if (hlds_product_has_any (product_id, gdr8081n, sizeof (gdr8081n) / sizeof (gdr8081n[0])))
		return 81;
	if (hlds_product_has_any (product_id, type3, sizeof (type3) / sizeof (type3[0])))
		return 3;
	if (hlds_product_has (product_id, "GDR8050L") || hlds_product_has (product_id, "GDR-8050L"))
		return 44;
	if (hlds_product_has_any (product_id, type4, sizeof (type4) / sizeof (type4[0])))
		return 4;
	/* DIC labels GSA-4163B as an Xbox swap candidate, not a normal 0xE7
	 * Nintendo-disc cache dump profile, so keep it out of the profile layer. */
	return 0;
}

static u_int32_t dvd_hlds_e7_detect_type (dvd_drive *dvd) {
	if (!dvd)
		return 0;
	return dvd_detect_hlds_e7_type_for_identity (
		dvd -> vendor,
		dvd -> prod_id,
		dvd -> prod_rev
	);
}

static bool dvd_is_hlds_gc_wii_drive (dvd_drive *dvd) {
	return dvd_hlds_e7_detect_type (dvd) != 0;
}

static const char *dvd_hlds_e7_profile_name_from_type (u_int32_t type) {
	switch (type) {
		case 1: return "Type1";
		case 21: return "Type2_1 experimental";
		case 22: return "Type2_2 experimental";
		case 3: return "Type3";
		case 4: return "Type4";
		case 44: return "GDR-8050L modified 0xE7 single-window proven fallback";
		case 45: return "GDR-8050L modified 0xE7 speed-probe pending";
		case 442: return "GDR-8050L modified 0xE7 probe B 2-window";
		case 443: return "GDR-8050L modified 0xE7 probe A 3-window";
		case 445: return "GDR-8050L modified 0xE7 probe C 5-window guarded";
		case 81: return "GDR-8081N experimental 0xE7 probe";
		case 811: return "GDR-8081N probe A Type4-derived";
		case 812: return "GDR-8081N probe B single-window";
		case 813: return "GDR-8081N probe C Type1-base";
		case 814: return "GDR-8081N probe E exact-offset moving-cache candidate";
		case 815: return "GDR-8081N probe A scan-guided Type4-derived";
		default: return "none";
	}
}

static void dvd_apply_hlds_e7_profile (dvd_drive *dvd) {
	const hlds_e7_profile_desc *profile = dvd_find_hlds_e7_profile (dvd);
	dvd -> hlds_e7_type = dvd_hlds_e7_detect_type (dvd);
	dvd -> hlds_e7_cache_base = 0x80000000U;
	dvd -> hlds_e7_mem_blocks = 5;
	dvd -> hlds_e7_static_cdb_base = 0;
	dvd -> hlds_e7_static_gate = 0;
	dvd -> hlds_e7_preferred_method = -1;
	dvd -> hlds_e7_profile_label = NULL;
	dvd -> hlds_e7_support_tier = NULL;
	dvd -> hlds_e7_family = NULL;
	dvd -> hlds_e7_tokens = NULL;
	dvd -> hlds_e7_record_id = NULL;
	dvd -> hlds_e7_notes = NULL;

	switch (dvd -> hlds_e7_type) {
		case 1:
			/* DIC Type1: GCC-4160N/GCC-4240N cache frames begin at 0x00a13000
			 * and only one 16-sector cache window is consumed per READ. */
			dvd -> hlds_e7_cache_base = 0x00a13000U;
			dvd -> hlds_e7_mem_blocks = 1;
			break;
		case 21:
		case 22:
			/* DIC Type2 uses a moving 0x80000000-derived cache address.  This
			 * branch logs/classifies it, but does not yet claim DIC parity. */
			dvd -> hlds_e7_cache_base = 0x80000000U;
			dvd -> hlds_e7_mem_blocks = 1;
			break;
		case 81:
			/* GDR-8081N is not in the confirmed DIC dump list, but local firmware
			 * analysis suggests an 0xE7 command surface.  Start with a Type4-derived
			 * candidate; disc.c probes and may switch to one of the 811..814 runtime
			 * profiles before seed cracking continues. */
			dvd -> hlds_e7_cache_base = 0x80000000U;
			dvd -> hlds_e7_mem_blocks = 5;
			break;
		case 44:
			/* Stock GDR-8050L firmware does not expose the HIT 0xE7 memdump command.
			 * The local test unit is GDR-8163B hardware cross-flashed with modified
			 * GDR-8050L firmware where 0xE7 memdump was added.  Single-window has
			 * completed and hash-matched Sonic, so it remains the proven fallback.
			 * Start in an explicit speed-probe-pending profile so the initial drive
			 * information does not look like the old static single-window build.
			 * disc.c promotes to 2/3/5 windows only after guarded validation, or
			 * settles back to the proven single-window profile. */
			dvd -> hlds_e7_type = 45;
			dvd -> hlds_e7_cache_base = 0x80000000U;
			dvd -> hlds_e7_mem_blocks = 1;
			break;
		case 3:
		case 4:
		default:
			dvd -> hlds_e7_cache_base = 0x80000000U;
			dvd -> hlds_e7_mem_blocks = 5;
			break;
	}

	if (profile) {
		dvd -> hlds_e7_cache_base = profile -> cache_base;
		dvd -> hlds_e7_mem_blocks = profile -> mem_blocks;
		dvd -> hlds_e7_static_cdb_base = profile -> static_cdb_base;
		dvd -> hlds_e7_static_gate = profile -> static_gate;
		dvd -> hlds_e7_preferred_method = profile -> preferred_method;
		dvd -> hlds_e7_profile_label = profile -> label;
		dvd -> hlds_e7_support_tier = profile -> support_tier;
		dvd -> hlds_e7_family = profile -> family;
		dvd -> hlds_e7_tokens = profile -> tokens;
		dvd -> hlds_e7_record_id = profile -> record_id;
		dvd -> hlds_e7_notes = profile -> notes;
		/* The modified GDR-8050L runtime profile still starts in the guarded
		 * speed-probe-pending state, but keeps its static parser evidence fields. */
		if (profile -> type == 44) {
			dvd -> hlds_e7_type = 45;
			dvd -> hlds_e7_mem_blocks = 1;
		}
	}
}

void dvd_set_hlds_e7_runtime_profile (dvd_drive *dvd, u_int32_t type, u_int32_t cache_base, u_int32_t mem_blocks) {
	if (!dvd)
		return;
	dvd -> hlds_e7_type = type;
	dvd -> hlds_e7_cache_base = cache_base;
	dvd -> hlds_e7_mem_blocks = mem_blocks;
}

static bool dvd_is_tsst_kreon_candidate (dvd_drive *dvd) {
	if (!(dvd_vendor_is (dvd, "TSSTcorp") || dvd_vendor_is (dvd, "SAMSUNG")))
		return false;
	return
		dvd_prod_has (dvd, "TS-H352C") ||
		dvd_prod_has (dvd, "TS-H353A") ||
		dvd_prod_has (dvd, "SH-D162C") ||
		dvd_prod_has (dvd, "SH-D162D") ||
		dvd_prod_has (dvd, "SH-D163A") ||
		dvd_prod_has (dvd, "SH-D163B");
}


/**
 * Initializes a structure representing an MMC command.
 * @param mmc A pointer to the MMC command structure.
 * @param buf The buffer where results of the MMC command execution provided by the drive should be stored, or NULL if no buffer will be provided.
 * @param len The length of the buffer (ignored in case buf is NULL).
 * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed, or NULL.
 */
void dvd_init_command (mmc_command *mmc, u_int8_t *buf, int len, req_sense *sense) {
	memset (mmc, 0, sizeof (mmc_command));
	if (buf)
		memset (buf, 0, len);
	mmc -> cmdlen = 12;
	mmc -> direction = buf && len > 0 ? DVD_DATA_IN : DVD_DATA_NONE;
	mmc -> buffer = buf;
	mmc -> buflen = buf ? len : 0;
	mmc -> sense = sense;
	
	return;
}


static void dvd_record_command_diagnostic (
	dvd_drive *dvd,
	mmc_command *mmc,
	int transport_result,
	int os_error,
	int scsi_status,
	int sense_key,
	int asc,
	int ascq
) {
	if (!dvd || !mmc)
		return;
	memset (&dvd -> last_command, 0, sizeof (dvd -> last_command));
	dvd -> last_command.valid = true;
	dvd -> last_command.transport_result = transport_result;
	dvd -> last_command.os_error = os_error;
	dvd -> last_command.scsi_status = scsi_status;
	dvd -> last_command.sense_key = sense_key & 0x0f;
	dvd -> last_command.asc = asc & 0xff;
	dvd -> last_command.ascq = ascq & 0xff;
	dvd -> last_command.cdb_length = mmc -> cmdlen;
	if (dvd -> last_command.cdb_length < 0)
		dvd -> last_command.cdb_length = 0;
	if (dvd -> last_command.cdb_length > (int) sizeof (dvd -> last_command.cdb))
		dvd -> last_command.cdb_length = (int) sizeof (dvd -> last_command.cdb);
	memcpy (dvd -> last_command.cdb, mmc -> cmd, sizeof (dvd -> last_command.cdb));
}

bool dvd_get_last_command_diagnostic (dvd_drive *dvd, dvd_command_diagnostic *out) {
	if (!dvd || !out || !dvd -> last_command.valid)
		return false;
	*out = dvd -> last_command;
	return true;
}

#ifdef WIN32

/* Doc is under the UNIX function */
int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) {
	SCSI_PASS_THROUGH_DIRECT *sptd;
	unsigned char sptd_sense[sizeof (*sptd) + 18], *sense;
	DWORD bytes;
	DWORD win_error;
	BOOL ioctl_ok;
	int out;

	sptd = (SCSI_PASS_THROUGH_DIRECT *) sptd_sense;
	sense = &sptd_sense[sizeof (*sptd)];
	
	memset (sptd, 0, sizeof (sptd_sense));
	memcpy (sptd -> Cdb, mmc -> cmd, sizeof (mmc -> cmd));
	sptd -> Length = sizeof (SCSI_PASS_THROUGH_DIRECT);
	sptd -> CdbLength = mmc -> cmdlen;
	sptd -> SenseInfoLength = 18;
	if (mmc -> direction == DVD_DATA_OUT)
		sptd -> DataIn = SCSI_IOCTL_DATA_OUT;
	else if (mmc -> direction == DVD_DATA_NONE)
		sptd -> DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
	else
		sptd -> DataIn = SCSI_IOCTL_DATA_IN;
	sptd -> DataBuffer = mmc -> buffer;
	/* Quick hack: Windows hates DataTransferLength = 1. */
	if (mmc -> buflen == 1)
		sptd -> DataTransferLength = 2;
	else
		sptd -> DataTransferLength = mmc -> buflen;
	sptd -> TimeOutValue = MMC_CMD_TIMEOUT;
	sptd -> SenseInfoOffset = sizeof (*sptd);

	if (mmc -> cmd[0] == 0xB6) {
		sptd -> DataIn = SCSI_IOCTL_DATA_OUT;
		sptd -> DataTransferLength = 28;
	}

	ioctl_ok = DeviceIoControl (dvd -> fd, IOCTL_SCSI_PASS_THROUGH_DIRECT,
		sptd, sizeof (*sptd) + 18, sptd, sizeof (*sptd) + 18, &bytes, NULL);
	win_error = ioctl_ok ? ERROR_SUCCESS : GetLastError ();
	/* DeviceIoControl may succeed while the drive returns CHECK CONDITION. */
	if (!ioctl_ok || sptd -> ScsiStatus != 0) {
		out = -1;
		if (!ignore_errors) {
			error ("Execution of MMC command failed: Win32=%lu SCSI=0x%02X",
				(unsigned long) win_error, sptd -> ScsiStatus);
			debug ("Command was: ");
			hex_and_ascii_print ("", mmc -> cmd, sizeof (mmc -> cmd));
			debug ("Sense data: %02X/%02X/%02X\n", sense[2] & 0x0F, sense[12], sense[13]);
		}
	} else {
		out = 0;
	}
	
	if (mmc -> sense) {
		mmc -> sense -> sense_key = sense[2];
		mmc -> sense -> asc = sense[12];
		mmc -> sense -> ascq = sense[13];
	}
	dvd_record_command_diagnostic (dvd, mmc, out, (int) win_error,
		(int) sptd -> ScsiStatus, sense[2], sense[12], sense[13]);
	
	return out;
}

#else

/**
 * Executes an MMC command.
 * @param dvd The DVD drive the command should be exectued on.
 * @param mmc The command to be executed.
 * @param ignore_errors If set to true, no error will be printed if the command fails.
 * @return 0 if the command was executed successfully, < 0 otherwise.
 */
int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) {
	int out;
	int saved_errno;
	struct cdrom_generic_command cgc;
	struct request_sense sense;
	
#if 0
	debug ("Executing MMC command: ");
	hex_and_ascii_print ("", mmc -> cmd, sizeof (mmc -> cmd));
#endif

	memset (&sense, 0, sizeof (sense));
	memset (&cgc, 0, sizeof (cgc));
	memcpy (cgc.cmd, mmc -> cmd, sizeof (mmc -> cmd));
	cgc.buffer = (unsigned char *) mmc -> buffer;
	cgc.buflen = mmc -> buflen;
	if (mmc -> direction == DVD_DATA_OUT)
		cgc.data_direction = CGC_DATA_WRITE;
	else if (mmc -> direction == DVD_DATA_NONE)
		cgc.data_direction = CGC_DATA_NONE;
	else
		cgc.data_direction = CGC_DATA_READ;
	cgc.timeout = MMC_CMD_TIMEOUT * 1000;
	cgc.sense = &sense;
	if (ioctl (dvd -> fd, CDROM_SEND_PACKET, &cgc) < 0) {
		saved_errno = errno;
		out = -1;
		if (!ignore_errors) {
			error ("Execution of MMC command failed: %s", strerror (saved_errno));
			debug ("Command was:");
			hex_and_ascii_print ("", cgc.cmd, sizeof (cgc.cmd));
			debug ("Sense data: %02X/%02X/%02X", sense.sense_key, sense.asc, sense.ascq);
		}
	} else {
		saved_errno = 0;
		out = 0;
	}
	
	if (mmc -> sense) {
		mmc -> sense -> sense_key = sense.sense_key;
		mmc -> sense -> asc = sense.asc;
		mmc -> sense -> ascq = sense.ascq;
	}
	dvd_record_command_diagnostic (dvd, mmc, out, saved_errno,
		(int) cgc.stat, sense.sense_key, sense.asc, sense.ascq);
	
	return out;
}
#endif

#ifndef WIN32
/*
 * Linux equivalent of the copied Windows SCSI_PASS_THROUGH_DIRECT transport
 * used by UnlockDrive().  CDROM_SEND_PACKET does not expose an explicit CDB
 * length and applies a single generic timeout.  The GDR-8050L handshake uses
 * 6-, 10-, and 12-byte CDBs plus 120-second command timeouts (10 seconds only
 * for sticky descrambling).  SG_IO preserves those boundaries exactly.
 */
static int dvd_xbox_sgio_exact (dvd_drive *dvd,
		const char *step,
		const u_int8_t *cdb,
		int cdb_len,
		u_int8_t *buf,
		u_int32_t buf_len,
		dvd_data_direction direction,
		unsigned int timeout_ms) {
	sg_io_hdr_t io;
	u_int8_t sense[32];
	mmc_command diagnostic;
	int rc;
	int saved_errno = 0;
	int transport_result;
	int i;

	if (!dvd || !cdb || cdb_len < 1 || cdb_len > 12)
		return -1;

	memset (&io, 0, sizeof (io));
	memset (sense, 0, sizeof (sense));
	memset (&diagnostic, 0, sizeof (diagnostic));

	io.interface_id = 'S';
	io.cmdp = (unsigned char *) cdb;
	io.cmd_len = (unsigned char) cdb_len;
	io.sbp = sense;
	io.mx_sb_len = sizeof (sense);
	io.timeout = timeout_ms;
	io.dxferp = buf;
	io.dxfer_len = buf_len;

	switch (direction) {
	case DVD_DATA_OUT:
		io.dxfer_direction = SG_DXFER_TO_DEV;
		break;
	case DVD_DATA_NONE:
		io.dxfer_direction = SG_DXFER_NONE;
		io.dxferp = NULL;
		io.dxfer_len = 0;
		break;
	case DVD_DATA_IN:
	default:
		io.dxfer_direction = SG_DXFER_FROM_DEV;
		break;
	}

	errno = 0;
	rc = ioctl (dvd -> fd, SG_IO, &io);
	if (rc < 0)
		saved_errno = errno;

	transport_result =
		(rc == 0 &&
		 io.status == 0 &&
		 io.host_status == 0 &&
		 io.driver_status == 0) ? 0 : -1;

	diagnostic.cmdlen = cdb_len;
	diagnostic.direction = direction;
	diagnostic.buffer = buf;
	diagnostic.buflen = (int) buf_len;
	memcpy (diagnostic.cmd, cdb, (size_t) cdb_len);
	dvd_record_command_diagnostic (
		dvd,
		&diagnostic,
		transport_result,
		saved_errno,
		(int) io.status,
		sense[2],
		sense[12],
		sense[13]);

	xbox_ref_log_fprintf (
		stderr,
		"[XBOX-SGIO] step=%s rc=%d errno=%d status=0x%02X host=0x%04X driver=0x%04X sense=%02X/%02X/%02X resid=%d timeout_ms=%u cdb=[",
		step ? step : "unnamed",
		rc,
		saved_errno,
		(unsigned int) io.status,
		(unsigned int) io.host_status,
		(unsigned int) io.driver_status,
		(unsigned int) (sense[2] & 0x0F),
		(unsigned int) sense[12],
		(unsigned int) sense[13],
		io.resid,
		timeout_ms);
	for (i = 0; i < cdb_len; i++)
		xbox_ref_log_fprintf (
			stderr,
			"%s%02X",
			i ? " " : "",
			(unsigned int) cdb[i]);
	xbox_ref_log_fprintf (
		stderr,
		"] xfer=%u direction=%s result=%s\n",
		buf_len,
		direction == DVD_DATA_OUT ? "out" :
		direction == DVD_DATA_NONE ? "none" : "in",
		transport_result == 0 ? "PASS" : "FAIL");

	return transport_result;
}

static int dvd_xbox_exact_read_capacity (dvd_drive *dvd,
		const char *step,
		u_int32_t *sectors,
		u_int32_t *sector_size) {
	u_int8_t cdb[10];
	u_int8_t buf[8];
	u_int32_t max_lba;

	memset (cdb, 0, sizeof (cdb));
	memset (buf, 0, sizeof (buf));
	cdb[0] = 0x25;

	if (dvd_xbox_sgio_exact (
			dvd, step, cdb, sizeof (cdb), buf, sizeof (buf),
			DVD_DATA_IN, 120000) < 0)
		return -1;

	max_lba =
		((u_int32_t) buf[0] << 24) |
		((u_int32_t) buf[1] << 16) |
		((u_int32_t) buf[2] << 8) |
		(u_int32_t) buf[3];

	if (sectors)
		*sectors = max_lba + 1;
	if (sector_size)
		*sector_size =
			((u_int32_t) buf[4] << 24) |
			((u_int32_t) buf[5] << 16) |
			((u_int32_t) buf[6] << 8) |
			(u_int32_t) buf[7];
	return 0;
}

static int dvd_xbox_exact_mode_sense_10 (dvd_drive *dvd,
		const char *step,
		u_int8_t page,
		u_int8_t *buf,
		size_t buf_len) {
	u_int8_t cdb[10];

	if (!buf || buf_len > 0xFFFF)
		return -1;
	memset (cdb, 0, sizeof (cdb));
	memset (buf, 0, buf_len);
	cdb[0] = 0x5A;
	cdb[2] = page;
	cdb[7] = (u_int8_t) ((buf_len >> 8) & 0xFF);
	cdb[8] = (u_int8_t) (buf_len & 0xFF);

	return dvd_xbox_sgio_exact (
		dvd, step, cdb, sizeof (cdb), buf, (u_int32_t) buf_len,
		DVD_DATA_IN, 120000);
}

static int dvd_xbox_exact_mode_select_10 (dvd_drive *dvd,
		const char *step,
		const u_int8_t *buf,
		size_t buf_len) {
	u_int8_t cdb[10];
	u_int8_t tmp[256];

	if (!buf || buf_len > sizeof (tmp) || buf_len > 0xFFFF)
		return -1;
	memset (cdb, 0, sizeof (cdb));
	memset (tmp, 0, sizeof (tmp));
	memcpy (tmp, buf, buf_len);
	cdb[0] = 0x55;
	cdb[7] = (u_int8_t) ((buf_len >> 8) & 0xFF);
	cdb[8] = (u_int8_t) (buf_len & 0xFF);

	return dvd_xbox_sgio_exact (
		dvd, step, cdb, sizeof (cdb), tmp, (u_int32_t) buf_len,
		DVD_DATA_OUT, 120000);
}

static int dvd_xbox_exact_mode_select_6 (dvd_drive *dvd,
		const char *step,
		const u_int8_t *buf,
		size_t buf_len) {
	u_int8_t cdb[6];
	u_int8_t tmp[64];

	if (!buf || buf_len > sizeof (tmp) || buf_len > 0xFF)
		return -1;
	memset (cdb, 0, sizeof (cdb));
	memset (tmp, 0, sizeof (tmp));
	memcpy (tmp, buf, buf_len);
	cdb[0] = 0x15;
	cdb[1] = 0x11;
	cdb[4] = (u_int8_t) buf_len;

	return dvd_xbox_sgio_exact (
		dvd, step, cdb, sizeof (cdb), tmp, (u_int32_t) buf_len,
		DVD_DATA_OUT, 10000);
}
#endif


/**
 * Sends an INQUIRY command to the drive to retrieve drive identification strings.
 * @param dvd The DVD drive the command should be exectued on.
 * @return 0 if the command was executed successfully, < 0 otherwise.
 */
static int dvd_get_drive_info (dvd_drive *dvd) {
	mmc_command mmc;
	int out;
	u_int8_t buf[36];
	char tmp[36 * 4];
	
	dvd_init_command (&mmc, buf, sizeof (buf), NULL);
	mmc.cmd[0] = SPC_INQUIRY;
	mmc.cmd[4] = sizeof (buf);
	if ((out = dvd_execute_cmd (dvd, &mmc, false)) >= 0) {
		my_strndup (dvd -> vendor, buf + 8, 8);
		strtrimr (dvd -> vendor);
		my_strndup (dvd -> prod_id, buf + 16, 16);
		strtrimr (dvd -> prod_id);
		my_strndup (dvd -> prod_rev, buf + 32, 4);
		strtrimr (dvd -> prod_rev);
		snprintf (tmp, sizeof (tmp), "%s/%s/%s", dvd -> vendor, dvd -> prod_id, dvd -> prod_rev);
		my_strdup (dvd -> model_string, tmp);
		
		debug ("DVD drive is \"%s\"", dvd -> model_string);
	} else {
		error ("Cannot identify DVD drive\n");
	}

	return (out);
}


/**
 * Assigns the proper memory dump functions to a dvd_drive object, according to vendor, model and other parameters. Actually this scheme probably needs to
 * to be improved, but it is enough for the moment.
 * @param dvd The DVD drive the command should be exectued on.
 */
static void dvd_assign_functions (dvd_drive *dvd, u_int32_t command) {
	dvd -> def_method = 0;
	if (dvd_is_hlds_gc_wii_drive (dvd)) {
		dvd_apply_hlds_e7_profile (dvd);
		debug ("Hitachi-LG MN103-family 0xE7 drive detected: profile=%s tier=%s family=%s base=0x%08x windows=%u cdb=0x%03x gate=0x%08x",
			dvd_get_hlds_e7_profile_name (dvd),
			dvd_get_hlds_e7_support_tier (dvd),
			dvd_get_hlds_e7_family (dvd),
			dvd -> hlds_e7_cache_base, dvd -> hlds_e7_mem_blocks,
			dvd -> hlds_e7_static_cdb_base, dvd -> hlds_e7_static_gate);
		dvd -> memdump = &hitachi_dvd_dump_mem;
		dvd -> command = 2;
		dvd -> supported = true;
		/* DIC Type1/Type3/Type4 use READ12 + HIT 0xE7 cache extraction.
		 * GCC-4244N is validated with Sonic Mega Collection and GCC-4243N is
		 * actively under test, so both remain Method 8.  Type1 drives get the
		 * DIC-derived 0x00a13000 / one-window cache profile and also default to
		 * Method 8 so GCC-4160N/GCC-4240N can be tested without forcing a method.
		 * GDR-8050L gets a proven single-window fallback plus guarded speed probes
		 * for cross-flashed/modified firmware with 0xE7 memdump added; stock
		 * GDR-8050L firmware does not expose this GC/Wii memdump path. GDR-8081N
		 * is an experimental probe target and needs Method 8 so seed probing can run.
		 * Type2 is still classified only; leave it on the older Method 9 path. */
		if (dvd -> hlds_e7_preferred_method >= 0)
			dvd -> def_method = (u_int32_t) dvd -> hlds_e7_preferred_method;
		else if (dvd -> hlds_e7_type == 1 || dvd -> hlds_e7_type == 44 || dvd -> hlds_e7_type == 45 || dvd -> hlds_e7_type == 81 || dvd_is_hlds_gcc4243_4244_drive (dvd))
			dvd -> def_method = 8;
		else
			dvd -> def_method = 9;

	} else if (strcmp (dvd -> vendor, "LITE-ON") == 0 && (
		strcmp (dvd ->prod_id, "DVDRW LH-18A1H") == 0 ||
		strcmp (dvd ->prod_id, "DVDRW LH-18A1P") == 0 ||
		strcmp (dvd ->prod_id, "DVDRW LH-20A1H") == 0 ||
		strcmp (dvd ->prod_id, "DVDRW LH-20A1P") == 0
	)) {
		debug ("Lite-On DVD drive detected, using Lite-On memory dump command");
		dvd -> memdump = &liteon_dvd_dump_mem;
		dvd -> command = 3;
		dvd -> supported = true;
		dvd -> def_method = 5;

	} else if (dvd_is_tsst_kreon_candidate (dvd) || (strcmp (dvd -> vendor, "TSSTcorp") == 0 && (
		strcmp (dvd ->prod_id, "DVD-ROM SH-D162A") == 0 ||
		strcmp (dvd ->prod_id, "DVD-ROM SH-D162B") == 0
	))) {
		debug ("Toshiba Samsung DVD drive detected, using vanilla 2384 memory dump command");
		dvd -> memdump = &vanilla_2384_dvd_dump_mem;
		dvd -> command = 1;
		dvd -> supported = true;
		dvd -> def_method = 0;

	} else if (strcmp (dvd -> vendor, "PLEXTOR") == 0) {
		debug ("Plextor DVD drive detected, using vanilla 2064 memory dump command");
		dvd -> memdump = &vanilla_2064_dvd_dump_mem;
		dvd -> command = 0;
		dvd -> supported = true;
		dvd -> def_method = 2;

	} else {
		/* This is an unsupported drive (yet). */
		dvd -> memdump = &vanilla_2064_dvd_dump_mem;
		dvd -> command = 0;
		dvd -> supported = false;
	}

	if (command!=-1) {
		dvd -> command = command;
		if	    (command == 0) dvd -> memdump = &vanilla_2064_dvd_dump_mem;
		else if	(command == 1) dvd -> memdump = &vanilla_2384_dvd_dump_mem;
		else if	(command == 2) dvd -> memdump = &hitachi_dvd_dump_mem;
		else if	(command == 3) dvd -> memdump = &liteon_dvd_dump_mem;
		else if	(command == 4) dvd -> memdump = &renesas_dvd_dump_mem;
	}


	//init Reed-Solomon for Lite-On
	generate_gf();
	gen_poly();

	return;
}


/**
 * Creates a new structure representing a CD/DVD-ROM drive.
 * @param 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, or NULL if the drive could not be initialized.
 */
dvd_drive *dvd_drive_new (char *device, u_int32_t command) {
	dvd_drive *dvd;
#ifdef WIN32
	HANDLE fd;
	char dev[40];
#else
	int fd;
#endif

	/* Force the dropping of privileges: in our model, privileges are only used to execute memory dump commands, the user
	   must gain access to the device somehow else (i. e. get added to the "cdrom" group or similar things) */
	drop_euid ();
	
	debug ("Trying to open DVD device %s", device);
#ifdef WIN32
	sprintf (dev, "\\\\.\\%c:", device[0]);
	if ((fd = CreateFile (dev, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) {
		error ("Cannot open drive: %d", GetLastError ());
#else
	if ((fd = open (device, O_RDONLY | O_NONBLOCK)) < 0) {
		perror ("Cannot open drive");
#endif
		dvd = NULL;
	} else {
		debug ("Opened successfully");
		drop_euid ();
		dvd = (dvd_drive *) malloc (sizeof (dvd_drive));
		if (!dvd) {
			fprintf (stderr, "malloc() failed\n");
			exit (100);
		}
		memset (dvd, 0, sizeof (dvd_drive));
		my_strdup (dvd -> device, device);
		dvd -> fd = fd;
		dvd_get_drive_info (dvd);
		dvd_assign_functions (dvd, command);
	}

	return (dvd);
}


/**
 * Frees resources used by a DVD drive structure and destroys it.
 * @param dvd The DVD drive structure to be destroyed.
 * @return NULL.
 */
void *dvd_drive_destroy (dvd_drive *dvd) {
	if (dvd) {
#ifdef WIN32
		CloseHandle (dvd -> fd);
#else
		close (dvd -> fd);
#endif
		my_free (dvd -> device);
		my_free (dvd -> vendor);
		my_free (dvd -> prod_id);
		my_free (dvd -> prod_rev);
		my_free (dvd);
	}

	return (NULL);
}


/**
 * Executes the drive-dependent function to dump the drive sector cache, and returns the dumped data.
 * @param dvd The DVD drive the command should be exectued on.
 * @param block_off The offset to start dumping, WRT the beginning of the sector cache.
 * @param block_len The number of blocks to dump.
 * @param block_size The block size to be used for dumping.
 * @param buf A buffer where to store the dumped data. Note that this must be able to hold at least block_len * block_size bytes.
 * @return 0 if the command was executed successfully, < 0 otherwise.
 */
int dvd_memdump (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) {
	int out;

	/* Upgrade privileges and call actual dump functions */
	upgrade_euid ();
	out = dvd -> memdump (dvd, block_off, block_len, block_size, buf);
	drop_euid ();

	return (out);
}


/**
 * Issues a READ(12) command without bothering to return the results. Uses the FUA (Force Unit Access bit) so that the requested sectors are actually read
 * at the beginning of the cache and can be dumped later.
 * @param dvd The DVD drive the command should be exectued on.
 * @param sector The sector to be read. What will be cached is the 16-sectors block to which the sector belongs.
 * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed.
 * @return 0 if the command was executed successfully, < 0 otherwise.
 */
int dvd_read_sector_dummy (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[64 * 1024], *buf;
	size_t bufsize;

	/* We need some buffer, be it provided externally or not */
	if (extbuf) {
		buf = extbuf;
		bufsize = extbufsize;
	} else {
		buf = intbuf;
		bufsize = sizeof (intbuf);
	}

	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = MMC_READ_12;
	mmc.cmd[1] = 0x08;	/* FUA bit set */
	mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24);	/* LBA from MSB to LSB */
	mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
	mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
	mmc.cmd[5] = (u_int8_t)  (sector & 0x000000FF);
	mmc.cmd[6] = (u_int8_t) ((sectors & 0xFF000000) >> 24);	/* Size from MSB to LSB */
	mmc.cmd[7] = (u_int8_t) ((sectors & 0x00FF0000) >> 16);
	mmc.cmd[8] = (u_int8_t) ((sectors & 0x0000FF00) >> 8);
	mmc.cmd[9] = (u_int8_t)  (sectors & 0x000000FF);
	out = dvd_execute_cmd (dvd, &mmc, true);		/* Ignore errors! */

	return (out);
}


/**
 * Issues a READ(12) command using the STREAMING bit, which causes the requested 16-sector block to be read into memory,
 * together with the following four. This way we will be able to dump 5 sector with a single READ request.
 *
 * Note the strange need for a big buffer even though we must only pass 0x10 as the transfer length, otherwise the drive will hang (!?).
 * @param dvd The DVD drive the command should be exectued on.
 * @param sector The sector to be read. What will be cached is the 16-sectors block to which the sector belongs, and the following 4 blocks.
 * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed.
 * @param extbuf A buffer where to store the read data, or NULL.
 * @param extbufsize The size of the buffer.
 * @return 
 */
int dvd_read_sector_streaming (dvd_drive *dvd, u_int32_t sector, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[2048 * 16], *buf;
	size_t bufsize;

	/* We need some buffer, be it provided externally or not */
	if (extbuf) {
		buf = extbuf;
		bufsize = extbufsize;
	} else {
		buf = intbuf;
		bufsize = sizeof (intbuf);
	}
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = MMC_READ_12;
	mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24);	/* LBA from MSB to LSB */
	mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
	mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
	mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF);
	mmc.cmd[6] = 0;
	mmc.cmd[7] = 0;
	mmc.cmd[8] = 0;
	mmc.cmd[9] = 0x10;
	mmc.cmd[10] = 0x80;	/* STREAMING bit set */
	out = dvd_execute_cmd (dvd, &mmc, true);		/* Ignore errors! */
	
	return (out);
}


int dvd_read_streaming (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[64 * 1024], *buf;
	size_t bufsize;

	/* We need some buffer, be it provided externally or not */
	if (extbuf) {
		buf = extbuf;
		bufsize = extbufsize;
	} else {
		buf = intbuf;
		bufsize = sizeof (intbuf);
	}
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = MMC_READ_12;
	mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24);	/* LBA from MSB to LSB */
	mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
	mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
	mmc.cmd[5] = (u_int8_t)  (sector & 0x000000FF);
	mmc.cmd[6] = (u_int8_t) ((sectors & 0xFF000000) >> 24);	/* Size from MSB to LSB */
	mmc.cmd[7] = (u_int8_t) ((sectors & 0x00FF0000) >> 16);
	mmc.cmd[8] = (u_int8_t) ((sectors & 0x0000FF00) >> 8);
	mmc.cmd[9] = (u_int8_t)  (sectors & 0x000000FF);
	mmc.cmd[10] = 0x80;	/* STREAMING bit set */
	out = dvd_execute_cmd (dvd, &mmc, true);		/* Ignore errors! */
	
	return (out);
}


int dvd_flush_cache_READ12 (dvd_drive *dvd, u_int32_t sector, req_sense *sense) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[64], *buf;
	size_t bufsize;

	buf = intbuf;
	bufsize = 0;
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = MMC_READ_12;
	mmc.cmd[1] = 0x08;
	mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24);	/* LBA from MSB to LSB */
	mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
	mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
	mmc.cmd[5] = (u_int8_t)  (sector & 0x000000FF);
	out = dvd_execute_cmd (dvd, &mmc, true);
	
	return (out);
}

static void dvd_sleep_ms (unsigned int ms) {
#ifdef WIN32
	Sleep (ms);
#else
	usleep ((useconds_t) ms * 1000);
#endif
}

int dvd_start_stop_unit (dvd_drive *dvd, bool start, bool load_eject, req_sense *sense) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[64], *buf;
	size_t bufsize;

	buf = intbuf;
	bufsize = 0;
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = 0x1B;
	/* START STOP UNIT byte 4: bit 1 = LoEj, bit 0 = Start. */
	mmc.cmd[4] = (load_eject ? 0x02 : 0x00) | (start ? 0x01 : 0x00);
	out = dvd_execute_cmd (dvd, &mmc, true);
	
	return (out);
}

int dvd_stop_unit (dvd_drive *dvd, bool start, req_sense *sense) {
	return dvd_start_stop_unit (dvd, start, false, sense);
}

int dvd_set_door_lock (dvd_drive *dvd, bool locked) {
#ifdef WIN32
	(void) dvd;
	(void) locked;
	return 0;
#else
	if (!dvd)
		return -EINVAL;
	if (ioctl (dvd -> fd, CDROM_LOCKDOOR, locked ? 1 : 0) < 0)
		return -errno;
	return 0;
#endif
}

int dvd_wait_ready (dvd_drive *dvd, unsigned int timeout_ms) {
	unsigned int waited = 0;
	if (!dvd)
		return -1;
	while (waited <= timeout_ms) {
		if (dvd_test_unit_ready (dvd, NULL) == 0)
			return 0;
		if (waited == 0)
			dvd_stop_unit (dvd, true, NULL); /* START UNIT, like EnsureDriveReady() */
		dvd_sleep_ms (500);
		waited += 500;
	}
	return -1;
}

int dvd_media_cycle (dvd_drive *dvd, req_sense *sense) {
	int i;
	int out;

#ifndef WIN32
	/* Linux commonly applies CDO_LOCK while an optical device is open.  A raw
	 * START STOP UNIT eject then fails immediately even though the process owns
	 * the only intentional handle.  Release that kernel/drive door lock before
	 * the GDR-8050L software tray cycle, then restore it after the tray is loaded
	 * and ready.  CDROM_LOCKDOOR is the documented Linux optical-door API. */
	out = dvd_set_door_lock (dvd, false);
	if (out < 0) {
		xbox_ref_log_fprintf (stderr,
			"[XBOX][FATAL] Linux media-cycle could not release the optical door lock: %s (errno=%d).\n",
			strerror (-out), -out);
		return out;
	}
	xbox_ref_log_fprintf (stderr, "[XBOX] Linux media-cycle released the optical door lock before software eject.\n");
#endif

	/* Match the original GDR-8050L dumper's AutomateTrayCycle(): eject, wait
	 * long enough for the tray to extend, close, poll readiness, then settle. */
	out = dvd_start_stop_unit (dvd, false, true, sense); /* LoEj=1, Start=0: eject */
	if (out < 0) {
		xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Media-cycle software eject command failed.\n");
		return out;
	}
	dvd_sleep_ms (3000);

	out = dvd_start_stop_unit (dvd, true, true, sense);  /* LoEj=1, Start=1: load */
	if (out < 0) {
		xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Media-cycle software load command failed; the door remains unlocked for recovery.\n");
		return out;
	}

	for (i = 0; i < 90; i++) {
		dvd_sleep_ms (500);
		if (dvd_test_unit_ready (dvd, NULL) == 0) {
			dvd_sleep_ms (1500);
#ifndef WIN32
			out = dvd_set_door_lock (dvd, true);
			if (out < 0)
				xbox_ref_log_fprintf (stderr,
					"[XBOX][WARN] Linux media-cycle completed, but the optical door could not be re-locked: %s (errno=%d).\n",
					strerror (-out), -out);
			else
				xbox_ref_log_fprintf (stderr, "[XBOX] Linux media-cycle restored the optical door lock after load.\n");
#endif
			return 0;
		}
	}

	/* Original dumper falls back to a fixed 10s settle delay if TUR never
	 * reports ready after tray close.  Keep the door unlocked on failure so the
	 * user can recover the media without power-cycling the external drive. */
	dvd_sleep_ms (10000);
	fprintf (stderr, "[XBOX][FATAL] Media-cycle tray closed, but the drive never became ready; the door remains unlocked for recovery.\n");
	return -1;
}

int dvd_set_speed (dvd_drive *dvd, u_int32_t speed, req_sense *sense) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[64], *buf;
	size_t bufsize;

	buf = intbuf;
	bufsize = 0;
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = 0xBB;
	mmc.cmd[2] = (u_int8_t) ((speed & 0x0000FF00) >> 8);
	mmc.cmd[3] = (u_int8_t)  (speed & 0x000000FF);
	out = dvd_execute_cmd (dvd, &mmc, true);
	
	return (out);
}

int dvd_get_size (dvd_drive *dvd, u_int32_t *size, req_sense *sense) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[64], *buf;
	size_t bufsize;

	buf = intbuf;
	bufsize = 0x22;
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = 0x52;
	mmc.cmd[1] = 0x01;
	mmc.cmd[5] = 0x01;
	mmc.cmd[8] = 0x22;
	out = dvd_execute_cmd (dvd, &mmc, true);

	*(size)=*(size) << 8 | intbuf[0x18];
	*(size)=*(size) << 8 | intbuf[0x19];
	*(size)=*(size) << 8 | intbuf[0x1a];
	*(size)=*(size) << 8 | intbuf[0x1b];

	return (out);
}

int dvd_get_layerbreak (dvd_drive *dvd, u_int32_t *layerbreak, req_sense *sense) {
	mmc_command mmc;
	int out;
	u_int8_t intbuf[2052], *buf;
	size_t bufsize;

	buf = intbuf;
	bufsize = 2052;
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[0] = 0xad;
	mmc.cmd[8] = 0x08;
	mmc.cmd[9] = 0x04;
	out = dvd_execute_cmd (dvd, &mmc, true);

	*(layerbreak)=*(layerbreak) << 8;
	*(layerbreak)=*(layerbreak) << 8 | intbuf[0x11];
	*(layerbreak)=*(layerbreak) << 8 | intbuf[0x12];
	*(layerbreak)=*(layerbreak) << 8 | intbuf[0x13];
	if (*(layerbreak) > 0) *(layerbreak)=*(layerbreak) - 0x30000 + 1;

	return (out);
}

int dvd_set_streaming (dvd_drive *dvd, u_int32_t speed, req_sense *sense) {
/*
DVD Decrypter->
DeviceIoControl    : \Device\CdRom5
Command            : IOCTL_SCSI_PASS_THROUGH_DIRECT
Length             : 44 (0x002C)
ScsiStatus         : 0
PathId             : 0
TargedId           : 0
Lun                : 0
CdbLength          : 12 (0x0C)
SenseInfoLength    : 24 (0x18)
DataTransferLength : 28 (0x0000001C)
DataIn             : 0
TimeOutValue       : 5000

CDB:
00000000  B6 00 00 00 00 00 00 00 00 00 1C 00               ...........    

Data Sent:
00000000  00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF   ............____
00000010  00 00 03 E8 FF FF FF FF 00 00 03 E8               ...____...    
*/
	mmc_command mmc;
	int out;
	u_int8_t inbuf[28], *buf;
	size_t bufsize;

	buf = inbuf;
	bufsize = 28;
	
	dvd_init_command (&mmc, buf, bufsize, sense);
	mmc.cmd[00] = 0xB6;
	mmc.cmd[10] = 28;

	*(buf+ 0)=0;//2
	*(buf+ 1)=0;
	*(buf+ 2)=0;
	*(buf+ 3)=0;
	*(buf+ 4)=0; //MSB
	*(buf+ 5)=0; //
	*(buf+ 6)=0; //
	*(buf+ 7)=0; //LSB

	*(buf+ 8)=0xff; //MSB
	*(buf+ 9)=0xff; //
	*(buf+10)=0xff; //
	*(buf+11)=0xff; //LSB

	*(buf+12)=(u_int8_t) ((speed & 0xFF000000) >> 24);
	*(buf+13)=(u_int8_t) ((speed & 0x00FF0000) >> 16);
	*(buf+14)=(u_int8_t) ((speed & 0x0000FF00) >> 8);
	*(buf+15)=(u_int8_t)  (speed & 0x000000FF);

	*(buf+16)=(u_int8_t) ((1000 & 0xFF000000) >> 24);
	*(buf+17)=(u_int8_t) ((1000 & 0x00FF0000) >> 16);
	*(buf+18)=(u_int8_t) ((1000 & 0x0000FF00) >> 8);
	*(buf+19)=(u_int8_t)  (1000 & 0x000000FF);

	*(buf+20)=(u_int8_t) ((speed & 0xFF000000) >> 24);
	*(buf+21)=(u_int8_t) ((speed & 0x00FF0000) >> 16);
	*(buf+22)=(u_int8_t) ((speed & 0x0000FF00) >> 8);
	*(buf+23)=(u_int8_t)  (speed & 0x000000FF);

	*(buf+24)=(u_int8_t) ((1000 & 0xFF000000) >> 24);
	*(buf+25)=(u_int8_t) ((1000 & 0x00FF0000) >> 16);
	*(buf+26)=(u_int8_t) ((1000 & 0x0000FF00) >> 8);
	*(buf+27)=(u_int8_t)  (1000 & 0x000000FF);

	out = dvd_execute_cmd (dvd, &mmc, true);

	return (out);
}


int dvd_test_unit_ready (dvd_drive *dvd, req_sense *sense) {
	mmc_command mmc;
	u_int8_t intbuf[1];

	dvd_init_command (&mmc, intbuf, 0, sense);
	mmc.cmd[0] = SPC_TEST_UNIT_READY;
	mmc.cmdlen = 6;
	mmc.direction = DVD_DATA_NONE;
	return dvd_execute_cmd (dvd, &mmc, true);
}

int dvd_read_capacity_10 (dvd_drive *dvd, u_int32_t *sectors, u_int32_t *sector_size, req_sense *sense) {
	mmc_command mmc;
	u_int8_t buf[8];
	int out;
	u_int32_t max_lba, block_len;

	dvd_init_command (&mmc, buf, sizeof (buf), sense);
	mmc.cmd[0] = MMC_READ_CAPACITY_10;
	mmc.cmdlen = 10;
	out = dvd_execute_cmd (dvd, &mmc, false);
	if (out >= 0) {
		max_lba = ((u_int32_t) buf[0] << 24) | ((u_int32_t) buf[1] << 16) | ((u_int32_t) buf[2] << 8) | buf[3];
		block_len = ((u_int32_t) buf[4] << 24) | ((u_int32_t) buf[5] << 16) | ((u_int32_t) buf[6] << 8) | buf[7];
		if (sectors)
			*sectors = max_lba + 1;
		if (sector_size)
			*sector_size = block_len;
	}

	return out;
}

int dvd_read_10 (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) {
	mmc_command mmc;
	u_int8_t intbuf[64 * 1024], *buf;
	size_t need, bufsize;

	need = (size_t) sectors * 2048;
	if (extbuf) {
		buf = extbuf;
		bufsize = extbufsize;
	} else {
		buf = intbuf;
		bufsize = sizeof (intbuf);
	}

	if (need > bufsize) {
		error ("dvd_read_10 buffer too small (%u sectors need %lu bytes)", sectors, (unsigned long) need);
		return -1;
	}

	dvd_init_command (&mmc, buf, (int) need, sense);
	mmc.cmd[0] = MMC_READ_10;
	mmc.cmdlen = 10;
	mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24);
	mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
	mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
	mmc.cmd[5] = (u_int8_t)  (sector & 0x000000FF);
	mmc.cmd[7] = (u_int8_t) ((sectors & 0x0000FF00) >> 8);
	mmc.cmd[8] = (u_int8_t)  (sectors & 0x000000FF);

	return dvd_execute_cmd (dvd, &mmc, true);
}

int dvd_mode_sense_10 (dvd_drive *dvd, u_int8_t page, u_int8_t *extbuf, size_t extbufsize, req_sense *sense) {
	mmc_command mmc;

	if (!extbuf || extbufsize > 0xFFFF)
		return -1;

	dvd_init_command (&mmc, extbuf, (int) extbufsize, sense);
	mmc.cmd[0] = SPC_MODE_SENSE_10;
	mmc.cmd[2] = page;
	mmc.cmd[7] = (u_int8_t) ((extbufsize & 0xFF00) >> 8);
	mmc.cmd[8] = (u_int8_t)  (extbufsize & 0x00FF);
	mmc.cmdlen = 10;

	return dvd_execute_cmd (dvd, &mmc, false);
}

int dvd_mode_select_10 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense) {
	mmc_command mmc;
	u_int8_t tmp[256];

	if (!buf || bufsize > sizeof (tmp) || bufsize > 0xFFFF)
		return -1;
	memset (tmp, 0, sizeof (tmp));
	memcpy (tmp, buf, bufsize);

	dvd_init_command (&mmc, tmp, (int) bufsize, sense);
	mmc.direction = DVD_DATA_OUT;
	mmc.cmd[0] = SPC_MODE_SELECT_10;
	mmc.cmd[7] = (u_int8_t) ((bufsize & 0xFF00) >> 8);
	mmc.cmd[8] = (u_int8_t)  (bufsize & 0x00FF);
	mmc.cmdlen = 10;

	return dvd_execute_cmd (dvd, &mmc, false);
}

int dvd_mode_select_6 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense) {
	mmc_command mmc;
	u_int8_t tmp[64];

	if (!buf || bufsize > sizeof (tmp) || bufsize > 0xFF)
		return -1;
	memset (tmp, 0, sizeof (tmp));
	memcpy (tmp, buf, bufsize);

	dvd_init_command (&mmc, tmp, (int) bufsize, sense);
	mmc.direction = DVD_DATA_OUT;
	mmc.cmd[0] = SPC_MODE_SELECT_6;
	mmc.cmd[1] = 0x11;
	mmc.cmd[4] = (u_int8_t) (bufsize & 0xFF);
	mmc.cmdlen = 6;

	return dvd_execute_cmd (dvd, &mmc, false);
}

int dvd_read_dvd_structure (dvd_drive *dvd, u_int8_t format, u_int8_t layer, u_int8_t *extbuf, size_t extbufsize, req_sense *sense) {
	mmc_command mmc;

	if (!extbuf || extbufsize > 0xFFFF)
		return -1;

	dvd_init_command (&mmc, extbuf, (int) extbufsize, sense);
	mmc.cmd[0] = MMC_READ_DVD_STRUCTURE;
	/* MMC READ DVD STRUCTURE places Format in CDB byte 7. Byte 11 is
	 * Control and must remain zero. The copied Windows GetMediaID() path
	 * uses the same byte-7 boundary for DMI format 0x04. */
	mmc.cmd[6] = layer;
	mmc.cmd[7] = format;
	mmc.cmd[8] = (u_int8_t) ((extbufsize & 0xFF00) >> 8);
	mmc.cmd[9] = (u_int8_t)  (extbufsize & 0x00FF);
	mmc.cmdlen = 12;

	return dvd_execute_cmd (dvd, &mmc, false);
}


static int dvd_xbox_vendor_command (dvd_drive *dvd, u_int8_t subcommand, u_int8_t value, u_int8_t *buf, size_t bufsize, dvd_data_direction direction) {
	mmc_command mmc;

	dvd_init_command (&mmc, buf, (int) bufsize, NULL);
	mmc.cmd[0] = 0xFF;
	mmc.cmd[1] = 0x08;
	mmc.cmd[2] = 0x01;
	mmc.cmd[3] = subcommand;
	mmc.cmd[4] = value;
	mmc.cmdlen = 10;
	mmc.direction = direction;
	if (direction == DVD_DATA_NONE) {
		mmc.buffer = NULL;
		mmc.buflen = 0;
	}
	return dvd_execute_cmd (dvd, &mmc, false);
}

static bool dvd_xbox_feature_list_has (const u_int16_t *features, size_t count, u_int16_t needle) {
	size_t i;
	if (!features)
		return false;
	for (i = 0; i < count && features[i] != 0; i++) {
		if (features[i] == needle)
			return true;
	}
	return false;
}

int dvd_xbox_vendor_get_feature_list (dvd_drive *dvd, u_int16_t *features, size_t max_features) {
	u_int8_t buf[26];
	size_t i, count;

	if (!dvd || !features || max_features == 0)
		return -1;
	memset (features, 0, max_features * sizeof (features[0]));
	memset (buf, 0, sizeof (buf));
	if (dvd_xbox_vendor_command (dvd, 0x10, 0, buf, sizeof (buf), DVD_DATA_IN) < 0)
		return -1;
	if ((((u_int16_t) buf[0] << 8) | buf[1]) != 0xA55A ||
	    (((u_int16_t) buf[2] << 8) | buf[3]) != 0x5AA5) {
		error ("Xbox vendor feature-list signature is invalid");
		return -1;
	}
	count = sizeof (buf) / 2;
	if (count > max_features)
		count = max_features;
	for (i = 0; i < count; i++)
		features[i] = ((u_int16_t) buf[i * 2] << 8) | buf[i * 2 + 1];
	return 0;
}

int dvd_xbox_vendor_lock (dvd_drive *dvd) {
	if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd))
		return -1;
	return dvd_xbox_vendor_command (dvd, 0x11, 0x00, NULL, 0, DVD_DATA_NONE);
}

int dvd_xbox_vendor_set_error_skip (dvd_drive *dvd, bool enabled) {
	if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd))
		return -1;
	return dvd_xbox_vendor_command (dvd, 0x15, enabled ? 0x01 : 0x00, NULL, 0, DVD_DATA_NONE);
}

int dvd_xbox_vendor_unlock_wxripper (dvd_drive *dvd, u_int32_t *unlocked_sectors) {
	u_int16_t features[13];
	u_int32_t sectors = 0, sector_size = 0;

	if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd))
		return -1;

	if (dvd_xbox_vendor_get_feature_list (dvd, features, sizeof (features) / sizeof (features[0])) < 0)
		return -1;
	if (!dvd_xbox_feature_list_has (features, sizeof (features) / sizeof (features[0]), 0x0201) &&
	    !dvd_xbox_feature_list_has (features, sizeof (features) / sizeof (features[0]), 0x0221))
		warning ("Xbox vendor feature list did not advertise Xbox unlock state 2/full challenge support; trying wxripper state anyway");

	if (dvd_xbox_vendor_command (dvd, 0x11, 0x02, NULL, 0, DVD_DATA_NONE) < 0)
		return -1;

	/* DiscImageCreator disables error-skip before dumping; keep that behavior so
	 * real read errors are visible to FriiDump unless a future option says otherwise. */
	dvd_xbox_vendor_set_error_skip (dvd, false);

	if (dvd_read_capacity_10 (dvd, &sectors, &sector_size, NULL) < 0)
		return -1;
	if (sector_size != 2048 || sectors < 1000000) {
		error ("Xbox vendor unlock did not expose the expected 2048-byte view");
		return -1;
	}
	if (unlocked_sectors)
		*unlocked_sectors = sectors;
	return 0;
}


#define XBOX_LOCKED_VIDEO_VIEW_MAX_SECTORS 200000U

static int dvd_xbox_refresh_ready_capacity (dvd_drive *dvd,
		u_int32_t *sectors,
		u_int32_t *sector_size) {
	u_int32_t observed_sectors = 0;
	u_int32_t observed_sector_size = 0;

	if (!dvd)
		return -1;

	/* Exact portable equivalent of xbox_ref_refresh_ready_capacity():
	 * RefreshVolume(); Sleep(2000); EnsureDriveReady(30000); GetTotalSectors(). */
	dvd_refresh_volume (dvd);
	dvd_sleep_ms (2000);
	if (dvd_wait_ready (dvd, 30000) < 0)
		return -1;
	if (dvd_read_capacity_10 (dvd, &observed_sectors, &observed_sector_size, NULL) < 0)
		return -1;

	if (sectors)
		*sectors = observed_sectors;
	if (sector_size)
		*sector_size = observed_sector_size;
	return 0;
}

int dvd_xbox_prepare_game_view (dvd_drive *dvd,
		u_int32_t *sectors,
		u_int32_t *sector_size) {
	u_int32_t entry_sectors = 0;
	u_int32_t observed_sectors = 0;
	u_int32_t observed_sector_size = 0;

	if (!dvd || !dvd_is_xbox_challenge_drive (dvd))
		return -1;

	/* Port xbox_ref_gdr8050l_dump_core() state preparation exactly, replacing
	 * Win32 handle/volume calls with FriiDump's portable Linux equivalents.
	 * No RecoveryKick, media-auth kick, or synthetic LBA-zero read cadence is
	 * part of this stock/cross-flashed GDR-8050L sequence. */
	if (dvd_wait_ready (dvd, 30000) < 0) {
		xbox_ref_log_fprintf (stderr,
			"[XBOX-WINSEQ][FATAL] Drive did not become ready before Xbox state preparation.\n");
		return -1;
	}
	if (dvd_read_capacity_10 (dvd, &entry_sectors, &observed_sector_size, NULL) < 0) {
		xbox_ref_log_fprintf (stderr,
			"[XBOX-WINSEQ][FATAL] Entry READ CAPACITY failed.\n");
		return -1;
	}
	xbox_ref_log_fprintf (stderr,
		"[XBOX-WINSEQ] Entry READ CAPACITY: %u sectors.\n",
		entry_sectors);

	if (entry_sectors > XBOX_LOCKED_VIDEO_VIEW_MAX_SECTORS) {
		xbox_ref_log_fprintf (stderr,
			"[XBOX-WINSEQ] Entry state already exposes the Xbox game view; skipping the redundant initial handshake and tray cycle.\n");
		if (dvd_xbox_refresh_ready_capacity (dvd, &observed_sectors, &observed_sector_size) < 0)
			return -1;
	} else {
		xbox_ref_log_fprintf (stderr,
			"[XBOX-WINSEQ] Entry state appears locked/video; attempting the full handshake directly without a media transition.\n");
		if (dvd_xbox_gdr8050l_unlock (dvd, NULL) < 0)
			xbox_ref_log_fprintf (stderr,
				"[XBOX-WINSEQ][WARN] Direct UnlockDrive transport returned failure; READ CAPACITY remains authoritative.\n");
		if (dvd_xbox_refresh_ready_capacity (dvd, &observed_sectors, &observed_sector_size) < 0)
			return -1;
		xbox_ref_log_fprintf (stderr,
			"[XBOX-WINSEQ] Direct-handshake READ CAPACITY: %u sectors.\n",
			observed_sectors);

		if (observed_sectors <= XBOX_LOCKED_VIDEO_VIEW_MAX_SECTORS) {
			xbox_ref_log_fprintf (stderr,
				"[XBOX-WINSEQ][WARN] Direct handshake did not expose the Xbox game view; performing one tray-cycle recovery and retry.\n");
			if (dvd_media_cycle (dvd, NULL) < 0)
				return -1;
			xbox_ref_log_fprintf (stderr,
				"[XBOX-WINSEQ] Re-applying the full handshake after recovery media change.\n");
			if (dvd_xbox_gdr8050l_unlock (dvd, NULL) < 0)
				xbox_ref_log_fprintf (stderr,
					"[XBOX-WINSEQ][WARN] Recovery UnlockDrive transport returned failure; READ CAPACITY remains authoritative.\n");
			if (dvd_xbox_refresh_ready_capacity (dvd, &observed_sectors, &observed_sector_size) < 0)
				return -1;
			xbox_ref_log_fprintf (stderr,
				"[XBOX-WINSEQ] Recovery-handshake READ CAPACITY: %u sectors.\n",
				observed_sectors);

			if (observed_sectors <= XBOX_LOCKED_VIDEO_VIEW_MAX_SECTORS) {
				xbox_ref_log_fprintf (stderr,
					"[XBOX-WINSEQ][FATAL] Xbox game view was not established after direct and recovery handshakes.\n");
				return -1;
			}
		}
	}

	/* Exact next Windows step after state preparation. */
	dvd_set_speed (dvd, 0xFFFF, NULL);

	if (sectors)
		*sectors = observed_sectors;
	if (sector_size)
		*sector_size = observed_sector_size;
	return 0;
}

int dvd_refresh_volume (dvd_drive *dvd) {
#ifdef WIN32
	DWORD bytesReturned = 0;
	if (!dvd)
		return -1;
	/* Match RefreshVolume() from the reference dumper: update properties only;
	 * do not dismount here because that can reset drive state.  This is now
	 * shared by Xbox and HLDS 0xE7 GC/Wii paths so Windows is less likely to
	 * keep stale filesystem/probe state attached to odd discs. */
	DeviceIoControl (dvd -> fd, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL);
	dvd_sleep_ms (1000);
	return 0;
#else
	(void) dvd;
	return 0;
#endif
}

int dvd_lock_volume (dvd_drive *dvd) {
#ifdef WIN32
	DWORD bytesReturned = 0;
	if (!dvd)
		return DVD_VOLUME_LOCK_FAILED;
	return DeviceIoControl (dvd -> fd, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL)
		? DVD_VOLUME_LOCK_OK
		: DVD_VOLUME_LOCK_FAILED;
#else
	/* Linux CDROM_SEND_PACKET has no FSCTL_LOCK_VOLUME equivalent here.
	 * Return a distinct result instead of falsely reporting an exclusive lock.
	 * The caller may continue after warning about automount/media polling. */
	(void) dvd;
	return DVD_VOLUME_LOCK_UNAVAILABLE;
#endif
}

int dvd_xbox_refresh_volume (dvd_drive *dvd) {
	return dvd_refresh_volume (dvd);
}

int dvd_xbox_lock_volume (dvd_drive *dvd) {
	return dvd_lock_volume (dvd);
}

static int dvd_xbox_read_host_challenge_table (dvd_drive *dvd, u_int8_t *table, size_t table_len) {
	mmc_command mmc;
	int out;

	if (!table || table_len < 0x664)
		return -1;

#ifdef WIN32
	/* GDR-8050L / Xbox READ DVD STRUCTURE format C0. */
	dvd_init_command (&mmc, table, 0x664, NULL);
	mmc.cmd[0] = MMC_READ_DVD_STRUCTURE;
	mmc.cmd[2] = 0xFF;
	mmc.cmd[3] = 0x02;
	mmc.cmd[4] = 0xFD;
	mmc.cmd[5] = 0xFF;
	mmc.cmd[6] = 0xFE;
	mmc.cmd[8] = 0x06;
	mmc.cmd[9] = 0x64;
	mmc.cmd[11] = 0xC0;
	mmc.cmdlen = 12;
	out = dvd_execute_cmd (dvd, &mmc, true);

	/* Some Hitachi-family drives expose the same table via vendor command 0xFD. */
	if (out < 0 || table[772] != 1 || table[773] == 0) {
		dvd_init_command (&mmc, table, 0x664, NULL);
		mmc.cmd[0] = 0xFD;
		mmc.cmd[1] = 0x01;
		mmc.cmd[8] = 0x06;
		mmc.cmd[9] = 0x64;
		mmc.cmdlen = 12;
		out = dvd_execute_cmd (dvd, &mmc, true);
	}
#else
	{
		u_int8_t cdb[12];

		memset (cdb, 0, sizeof (cdb));
		memset (table, 0, table_len);
		cdb[0] = 0xAD;
		cdb[2] = 0xFF;
		cdb[3] = 0x02;
		cdb[4] = 0xFD;
		cdb[5] = 0xFF;
		cdb[6] = 0xFE;
		cdb[8] = 0x06;
		cdb[9] = 0x64;
		cdb[11] = 0xC0;
		out = dvd_xbox_sgio_exact (
			dvd,
			"3-read-dvd-structure-c0",
			cdb,
			sizeof (cdb),
			table,
			0x664,
			DVD_DATA_IN,
			120000);

		if (out < 0 || table[772] != 1 || table[773] == 0) {
			memset (cdb, 0, sizeof (cdb));
			memset (table, 0, table_len);
			cdb[0] = 0xFD;
			cdb[1] = 0x01;
			cdb[8] = 0x06;
			cdb[9] = 0x64;
			out = dvd_xbox_sgio_exact (
				dvd,
				"3-read-host-table-fallback-fd",
				cdb,
				sizeof (cdb),
				table,
				0x664,
				DVD_DATA_IN,
				120000);
		}
	}
#endif

	if (out < 0 || table[772] != 1)
		return -1;

	xbox_ref_log_fprintf (
		stderr,
		"[XBOX-SGIO] challenge-table marker=%u entries-byte=%u result=PASS\n",
		(unsigned int) table[772],
		(unsigned int) table[773]);
	return 0;
}

int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) {
	int i, k, l;
	int out;
	int chalpos[24];
	u_int8_t table[0x664];
	u_int8_t restable[261];
	u_int8_t hash[0x2C];
	SHA1_HASH digest;
	u_int8_t page[28];
	u_int8_t sticky[12];
	u_int32_t sectors = 0, sector_size = 0;
	xbox_rc4_ctx rc4;

	if (!dvd || !dvd_is_xbox_unlock_drive (dvd))
		return -1;

	/* Step 1/2: read current capacity and the Xbox mode page. If the drive is
	 * already unlocked, this is harmless; the final capacity check below becomes
	 * the authority. */
#ifdef WIN32
	dvd_read_capacity_10 (dvd, &sectors, &sector_size, NULL);
	dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL);
#else
	dvd_xbox_exact_read_capacity (
		dvd, "1-initial-read-capacity", &sectors, &sector_size);
	dvd_xbox_exact_mode_sense_10 (
		dvd, "2-mode-sense-3e", 0x3E, page, sizeof (page));
#endif

	/* Step 3: retrieve and decode the host challenge table. */
	if (dvd_xbox_read_host_challenge_table (dvd, table, sizeof (table)) < 0) {
		error ("Cannot retrieve Xbox host challenge table");
		return -1;
	}

	for (i = 0; i < 0x2C; i++)
		hash[i] = table[0x4A3 + i];
	Sha1Calculate (hash, 0x2C, &digest);

	for (i = 0; i <= 260; i++)
		restable[i] = table[774 + i];
	xbox_rc4_init (&rc4, digest.bytes, 7);
	xbox_rc4_crypt (&rc4, restable, restable, 0xFD);

	k = 0;
	for (l = 0; l <= 23; l++) {
		if (restable[l * 11] == 1) {
			chalpos[k++] = l;
			if (k == (int) (sizeof (chalpos) / sizeof (chalpos[0])))
				break;
		}
	}
	if (k < 2) {
		error ("Xbox challenge table does not contain enough usable entries");
		return -1;
	}

	/* Step 4: first host challenge. */
	memset (page, 0, sizeof (page));
	page[1] = 0x1A;
	page[8] = 0x3E;
	page[9] = 0x12;
	page[11] = 0x01;
	page[13] = 0xD1;
	page[14] = 0x01;
	memcpy (&page[15], &restable[1 + chalpos[k - 2] * 11], 5);
	/* Match the original dumper: send the challenge and continue even if
	 * Windows reports a transport failure.  The later XDVDFS probe is the
	 * authority for whether the drive actually entered the game view. */
#ifdef WIN32
	dvd_mode_select_10 (dvd, page, sizeof (page), NULL);
	dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL);
#else
	dvd_xbox_exact_mode_select_10 (
		dvd, "4-mode-select-challenge-1", page, sizeof (page));
	dvd_xbox_exact_mode_sense_10 (
		dvd, "5-mode-sense-verify-1", 0x3E, page, sizeof (page));
#endif

	/* Step 6: second host challenge. */
	memset (page, 0, sizeof (page));
	page[1] = 0x1A;
	page[8] = 0x3E;
	page[9] = 0x12;
	page[12] = 0x01;
	memcpy (&page[15], &restable[1 + chalpos[k - 1] * 11], 5);
#ifdef WIN32
	dvd_mode_select_10 (dvd, page, sizeof (page), NULL);
	dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL);
#else
	dvd_xbox_exact_mode_select_10 (
		dvd, "6-mode-select-challenge-2", page, sizeof (page));
	dvd_xbox_exact_mode_sense_10 (
		dvd, "7-mode-sense-verify-2", 0x3E, page, sizeof (page));
#endif

	/* Step 8: unlock partition 1. */
	memset (page, 0, sizeof (page));
	page[1] = 0x1A;
	page[8] = 0x3E;
	page[9] = 0x12;
	page[10] = 0x01;
	page[11] = 0x01;
	page[12] = 0x01;
	page[13] = 0xD1;
	page[14] = 0x01;
	memcpy (&page[15], &restable[1 + chalpos[k - 1] * 11], 5);
#ifdef WIN32
	dvd_mode_select_10 (dvd, page, sizeof (page), NULL);
#else
	dvd_xbox_exact_mode_select_10 (
		dvd, "8-mode-select-partition-1-unlock", page, sizeof (page));
#endif

	/* Step 9: sticky descrambling, mode page 0x31. */
	memset (sticky, 0, sizeof (sticky));
	sticky[4] = 0x31;
	sticky[5] = 0x06;
	sticky[6] = 0x01;
#ifdef WIN32
	dvd_mode_select_6 (dvd, sticky, sizeof (sticky), NULL);
#else
	dvd_xbox_exact_mode_select_6 (
		dvd, "9-mode-select-sticky-descrambling", sticky, sizeof (sticky));
#endif

	/* Step 10: final capacity observation.  The original UnlockDrive() only
	 * prints this verification and does not fail if the capacity has not changed
	 * yet.  This matters for the first GDR-8050L handshake, whose purpose is to
	 * prime the drive before the required media-change event. */
	sectors = 0;
	sector_size = 0;
#ifdef WIN32
	out = dvd_read_capacity_10 (dvd, &sectors, &sector_size, NULL);
#else
	out = dvd_xbox_exact_read_capacity (
		dvd, "10-final-read-capacity", &sectors, &sector_size);
#endif
	if (out == 0) {
		if (unlocked_sectors)
			*unlocked_sectors = sectors;
		xbox_ref_log_fprintf (
			stderr,
			"[XBOX] GDR-8050L handshake complete; READ CAPACITY reports %u sectors of %u bytes.\n",
			sectors,
			sector_size);
	} else {
		xbox_ref_log_fprintf (
			stderr,
			"[XBOX] GDR-8050L handshake sent; final READ CAPACITY verify failed, continuing like original dumper.\n");
	}

	return 0;
}

bool dvd_is_xbox_challenge_drive (dvd_drive *dvd) {
	return dvd_is_hlds_drive (dvd) && dvd_prod_has (dvd, "GDR8050L");
}

bool dvd_is_xbox_vendor_unlock_drive (dvd_drive *dvd) {
	return (dvd_is_hlds_drive (dvd) && (dvd_prod_has (dvd, "GDR3120L") || dvd_prod_has (dvd, "GDR-3120L"))) ||
	       dvd_is_tsst_kreon_candidate (dvd);
}

bool dvd_is_xbox_unlock_drive (dvd_drive *dvd) {
	return dvd_is_xbox_challenge_drive (dvd) || dvd_is_xbox_vendor_unlock_drive (dvd);
}

bool dvd_is_xbox_drive (dvd_drive *dvd) {
	/* Autodetect only the two native Xbox profiles currently wired into the
	 * Xbox dump planner. Other candidate drives keep FriiDump's normal GC/Wii/DVD
	 * behavior unless the user explicitly forces Xbox mode with -T 4. */
	return dvd_is_xbox_challenge_drive (dvd) ||
	       (dvd_is_hlds_drive (dvd) && (dvd_prod_has (dvd, "GDR3120L") || dvd_prod_has (dvd, "GDR-3120L")));
}



const char *dvd_get_hlds_e7_profile_name (dvd_drive *dvd) {
	if (!dvd) return "none";
	if (dvd -> hlds_e7_profile_label) return dvd -> hlds_e7_profile_label;
	return dvd_hlds_e7_profile_name_from_type (dvd -> hlds_e7_type);
}

const char *dvd_get_hlds_e7_support_tier (dvd_drive *dvd) {
	if (!dvd || dvd -> hlds_e7_type == 0) return "none";
	return dvd -> hlds_e7_support_tier ? dvd -> hlds_e7_support_tier : "legacy_detected";
}

const char *dvd_get_hlds_e7_family (dvd_drive *dvd) {
	if (!dvd || dvd -> hlds_e7_type == 0) return "none";
	return dvd -> hlds_e7_family ? dvd -> hlds_e7_family : dvd_hlds_e7_profile_name_from_type (dvd -> hlds_e7_type);
}

const char *dvd_get_hlds_e7_tokens (dvd_drive *dvd) {
	if (!dvd || dvd -> hlds_e7_type == 0) return "";
	return dvd -> hlds_e7_tokens ? dvd -> hlds_e7_tokens : "";
}

const char *dvd_get_hlds_e7_record_id (dvd_drive *dvd) {
	if (!dvd || dvd -> hlds_e7_type == 0) return "";
	return dvd -> hlds_e7_record_id ? dvd -> hlds_e7_record_id : "";
}

const char *dvd_get_hlds_e7_notes (dvd_drive *dvd) {
	if (!dvd || dvd -> hlds_e7_type == 0) return "";
	return dvd -> hlds_e7_notes ? dvd -> hlds_e7_notes : "";
}

u_int32_t dvd_get_hlds_e7_static_cdb_base (dvd_drive *dvd) {
	return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_static_cdb_base : 0;
}

u_int32_t dvd_get_hlds_e7_static_gate (dvd_drive *dvd) {
	return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_static_gate : 0;
}

int dvd_get_hlds_e7_preferred_method (dvd_drive *dvd) {
	return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_preferred_method : -1;
}

u_int32_t dvd_get_hlds_e7_type (dvd_drive *dvd) {
	return dvd ? dvd -> hlds_e7_type : 0;
}

u_int32_t dvd_get_hlds_e7_cache_base (dvd_drive *dvd) {
	return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_cache_base : 0;
}

u_int32_t dvd_get_hlds_e7_mem_blocks (dvd_drive *dvd) {
	return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_mem_blocks : 0;
}

char *dvd_get_vendor (dvd_drive *dvd) {
	return (dvd -> vendor);
}


char *dvd_get_product_id (dvd_drive *dvd) {
	return (dvd -> prod_id);
}


char *dvd_get_product_revision (dvd_drive *dvd) {
	return (dvd -> prod_rev);
}


char *dvd_get_model_string (dvd_drive *dvd) {
	return (dvd -> model_string);
}


char *dvd_get_device (dvd_drive *dvd) {
	return (dvd -> device);
}

void *dvd_get_native_handle (dvd_drive *dvd) {
	if (!dvd) return NULL;
#ifdef WIN32
	return (void *) dvd -> fd;
#else
	return NULL;
#endif
}


bool dvd_get_support_status (dvd_drive *dvd) {
	return (dvd -> supported);
}

u_int32_t dvd_get_def_method (dvd_drive *dvd){
	return (dvd -> def_method);
}

u_int32_t dvd_get_command (dvd_drive *dvd){
	return (dvd -> command);
}