1 /***************************************************************************
2 * Copyright (C) 2007 by Arep *
3 * Support is provided through the forums at *
4 * http://wii.console-tribe.com *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
20 ***************************************************************************/
23 * \brief A class to send raw MMC commands to a CD/DVD-ROM drive.
25 * 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
26 * then transformed in the proper OS-dependent structures when the command is executed, achieving portability. Currently Linux and Windows are supported, but
27 * 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
28 * 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,
29 * 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.
31 * 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
32 * a lot of other people. See his page for full details.
38 #include <sys/types.h>
39 //#include <sys/time.h>
44 #include "dvd_drive.h"
52 #include <linux/cdrom.h>
53 #include <sys/ioctl.h>
60 /*! \brief Timeout for MMC commands.
62 * This must be expressed in seconds (Windows uses seconds, right?).
64 #define MMC_CMD_TIMEOUT 10
67 /* Imported drive-specific functions */
68 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);
69 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);
70 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);
71 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);
72 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);
73 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);
76 /*! \brief A structure that represents a CD/DVD-ROM drive.
79 /* Device special file */
80 char *device; //!< The path to the drive (i.e.: /dev/something on Unix, x: on Windows).
82 /* Data about the drive */
83 char *vendor; //!< The drive vendor.
84 char *prod_id; //!< The drive product ID.
85 char *prod_rev; //!< The drive product revision (Usually firmware version).
86 char *model_string; //!< The above three strings, joined in a single one.
89 u_int32_t hlds_e7_type;
90 u_int32_t hlds_e7_cache_base;
91 u_int32_t hlds_e7_mem_blocks;
92 u_int32_t hlds_e7_static_cdb_base;
93 u_int32_t hlds_e7_static_gate;
94 int hlds_e7_preferred_method;
95 const char *hlds_e7_profile_label;
96 const char *hlds_e7_support_tier;
97 const char *hlds_e7_family;
98 const char *hlds_e7_tokens;
99 const char *hlds_e7_record_id;
100 const char *hlds_e7_notes;
102 /* Device-dependent internal memory dump function */
103 /*! The intended area should start where sector data is stored upon a READ command. Here we assume that sectors are
104 * stored one after the other, as heuristics showed it is the case for the Hitachi MN103-based drives, but this model
105 * might be changed in the future, if we get support for other drives.
107 dvd_drive_memdump_func memdump; //!< A pointer to a function that is able to dump the drive's internal memory area.
108 bool supported; //!< True if the drive is a supported model, false otherwise.
111 /* File descriptor & stuff used to access drive */
113 HANDLE fd; //!< The HANDLE to interact with the drive on Windows.
115 int fd; //!< The file descriptor to interact with the drive on Unix.
120 /** \brief Supported MMC commands.
122 enum mmc_commands_e {
123 SPC_TEST_UNIT_READY = 0x00,
125 SPC_MODE_SELECT_6 = 0x15,
126 MMC_START_STOP_UNIT = 0x1B,
127 MMC_READ_CAPACITY_10 = 0x25,
129 SPC_MODE_SENSE_10 = 0x5A,
130 SPC_MODE_SELECT_10 = 0x55,
132 MMC_READ_DVD_STRUCTURE = 0xAD,
141 static void xbox_rc4_init (xbox_rc4_ctx *ctx, const u_int8_t *key, size_t keylen) {
145 for (i = 0; i < 256; i++)
146 ctx -> s[i] = (u_int8_t) i;
154 for (i = 0; i < 256; i++) {
155 j = (u_int8_t) (j + ctx -> s[i] + key[i % keylen]);
157 ctx -> s[i] = ctx -> s[j];
162 static void xbox_rc4_crypt (xbox_rc4_ctx *ctx, const u_int8_t *in, u_int8_t *out, size_t len) {
166 for (n = 0; n < len; n++) {
167 ctx -> i = (u_int8_t) (ctx -> i + 1);
168 ctx -> j = (u_int8_t) (ctx -> j + ctx -> s[ctx -> i]);
169 tmp = ctx -> s[ctx -> i];
170 ctx -> s[ctx -> i] = ctx -> s[ctx -> j];
171 ctx -> s[ctx -> j] = tmp;
172 k = ctx -> s[(u_int8_t) (ctx -> s[ctx -> i] + ctx -> s[ctx -> j])];
177 static bool dvd_prod_has (dvd_drive *dvd, const char *needle) {
178 return dvd && dvd -> prod_id && needle && strstr (dvd -> prod_id, needle) != NULL;
181 static bool dvd_vendor_is (dvd_drive *dvd, const char *vendor) {
182 return dvd && dvd -> vendor && vendor && strcmp (dvd -> vendor, vendor) == 0;
185 static bool dvd_is_hlds_drive (dvd_drive *dvd) {
186 return dvd_vendor_is (dvd, "HL-DT-ST");
189 static bool dvd_prod_has_any (dvd_drive *dvd, const char **needles, size_t count) {
191 for (i = 0; i < count; i++) {
192 if (dvd_prod_has (dvd, needles[i]))
198 static bool dvd_is_hlds_gcc4243_4244_drive (dvd_drive *dvd) {
199 static const char *names[] = {
200 "GCC-4243N", "GCC4243N", "GCC4243",
201 "GCC-4244N", "GCC4244N", "GCC4244"
203 return dvd_is_hlds_drive (dvd) && dvd_prod_has_any (dvd, names, sizeof (names) / sizeof (names[0]));
206 static bool dvd_is_hlds_gdr8050l_drive (dvd_drive *dvd) {
207 static const char *names[] = {
208 "GDR8050L", "GDR-8050L"
210 return dvd_is_hlds_drive (dvd) && dvd_prod_has_any (dvd, names, sizeof (names) / sizeof (names[0]));
216 const char *firmware;
218 u_int32_t cache_base;
219 u_int32_t mem_blocks;
220 int preferred_method;
222 const char *support_tier;
225 const char *record_id;
226 u_int32_t static_cdb_base;
227 u_int32_t static_gate;
229 } hlds_e7_profile_desc;
231 static const hlds_e7_profile_desc hlds_e7_profiles[] = {
232 { "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" },
233 { "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" },
234 { "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" },
235 { "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" },
236 { "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" },
237 { "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" },
238 { "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" },
239 { "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" },
240 { "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" },
241 { "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" },
242 { "GDR-8050L", "0L23", 44, 0x80000000U, 1, 8, "GDR-8050L 0L23 donor/modified-firmware E7 parser profile", "donor_reference_modified_firmware_only", "GDR_8050x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0099", 0x5d0U, 0x90026160U, "stock firmware is Xbox-only here; GC/Wii 0xE7 path requires modified firmware with memdump support" },
243 { "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" },
244 { "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" },
245 { "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" },
246 { "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" },
247 { "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" },
248 { "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" },
249 { "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" },
250 { "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" },
251 { "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" },
252 { "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" },
253 { "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" },
254 { NULL, NULL, 0, 0, 0, -1, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL }
257 static void hlds_normalize_model (const char *src, char *dst, size_t dst_size) {
259 if (!dst || dst_size == 0)
264 for (i = 0, j = 0; src[i] && j + 1 < dst_size; i++) {
265 unsigned char c = (unsigned char) src[i];
267 dst[j++] = (char) toupper (c);
272 static bool hlds_model_matches (dvd_drive *dvd, const char *model) {
275 if (!dvd || !dvd -> prod_id || !model)
277 hlds_normalize_model (dvd -> prod_id, prod_norm, sizeof (prod_norm));
278 hlds_normalize_model (model, model_norm, sizeof (model_norm));
279 return prod_norm[0] && model_norm[0] && strstr (prod_norm, model_norm) != NULL;
282 static bool hlds_revision_matches (dvd_drive *dvd, const char *revision) {
283 const unsigned char *a;
284 const unsigned char *b;
285 if (!revision || !revision[0])
287 if (!dvd || !dvd -> prod_rev)
289 a = (const unsigned char *) dvd -> prod_rev;
290 b = (const unsigned char *) revision;
292 if (toupper (*a) != toupper (*b))
297 return *a == 0 && *b == 0;
300 static const hlds_e7_profile_desc *dvd_find_hlds_e7_profile (dvd_drive *dvd) {
301 const hlds_e7_profile_desc *p;
302 if (!dvd_is_hlds_drive (dvd))
304 for (p = hlds_e7_profiles; p -> model; p++) {
305 if (hlds_model_matches (dvd, p -> model) && hlds_revision_matches (dvd, p -> firmware))
311 static u_int32_t dvd_hlds_e7_detect_type (dvd_drive *dvd) {
312 const hlds_e7_profile_desc *profile = dvd_find_hlds_e7_profile (dvd);
313 static const char *type1[] = {
314 "GCC-4160N", "GCC4160N", "GCC4160",
315 "GCC-4240N", "GCC4240N", "GCC4240"
317 static const char *type2_1[] = {
318 "GCC-4241N", "GCC4241N", "GCC4241"
320 static const char *type2_2[] = {
321 "GCC-4242N", "GCC4242N", "GCC4242"
323 static const char *gdr8081n[] = {
324 "GDR8081N", "GDR-8081N"
326 static const char *type3[] = {
327 "GCC4244", "GCC4244N", "GCC-4244N",
328 "GCC4247", "GCC4247N", "GCC-4247N",
329 "GDR8083N", "GDR8084N",
330 "GCC-4243N", "GCC4243N", "GCC4243",
331 "GCC-4246N", "GCC4246N", "GCC4246"
333 static const char *type4[] = {
334 "DU10N", "GDR8082N", "GDR8161B", "GDR8162B",
335 "GDR8163B", "GDR8164B", "GDR-T10N",
336 /* Local project keeps GDR-3120L in the same transport family for
337 * classification, but Xbox dumping is still routed through the explicit
338 * Xbox paths rather than the GC/Wii Method 8/9 readers. */
339 "GDR3120L", "GDR-3120L"
342 if (!dvd_is_hlds_drive (dvd))
345 return profile -> type;
346 if (dvd_prod_has_any (dvd, type1, sizeof (type1) / sizeof (type1[0])))
348 if (dvd_prod_has_any (dvd, type2_1, sizeof (type2_1) / sizeof (type2_1[0])))
350 if (dvd_prod_has_any (dvd, type2_2, sizeof (type2_2) / sizeof (type2_2[0])))
352 if (dvd_prod_has_any (dvd, gdr8081n, sizeof (gdr8081n) / sizeof (gdr8081n[0])))
354 if (dvd_prod_has_any (dvd, type3, sizeof (type3) / sizeof (type3[0])))
356 if (dvd_is_hlds_gdr8050l_drive (dvd))
358 if (dvd_prod_has_any (dvd, type4, sizeof (type4) / sizeof (type4[0])))
360 /* DIC labels GSA-4163B as an Xbox swap candidate, not a normal 0xE7
361 * Nintendo-disc cache dump profile, so keep it out of the profile layer. */
365 static bool dvd_is_hlds_gc_wii_drive (dvd_drive *dvd) {
366 return dvd_hlds_e7_detect_type (dvd) != 0;
369 static const char *dvd_hlds_e7_profile_name_from_type (u_int32_t type) {
371 case 1: return "Type1";
372 case 21: return "Type2_1 experimental";
373 case 22: return "Type2_2 experimental";
374 case 3: return "Type3";
375 case 4: return "Type4";
376 case 44: return "GDR-8050L modified 0xE7 single-window proven fallback";
377 case 45: return "GDR-8050L modified 0xE7 speed-probe pending";
378 case 442: return "GDR-8050L modified 0xE7 probe B 2-window";
379 case 443: return "GDR-8050L modified 0xE7 probe A 3-window";
380 case 445: return "GDR-8050L modified 0xE7 probe C 5-window guarded";
381 case 81: return "GDR-8081N experimental 0xE7 probe";
382 case 811: return "GDR-8081N probe A Type4-derived";
383 case 812: return "GDR-8081N probe B single-window";
384 case 813: return "GDR-8081N probe C Type1-base";
385 case 814: return "GDR-8081N probe E exact-offset moving-cache candidate";
386 case 815: return "GDR-8081N probe A scan-guided Type4-derived";
387 default: return "none";
391 static void dvd_apply_hlds_e7_profile (dvd_drive *dvd) {
392 const hlds_e7_profile_desc *profile = dvd_find_hlds_e7_profile (dvd);
393 dvd -> hlds_e7_type = dvd_hlds_e7_detect_type (dvd);
394 dvd -> hlds_e7_cache_base = 0x80000000U;
395 dvd -> hlds_e7_mem_blocks = 5;
396 dvd -> hlds_e7_static_cdb_base = 0;
397 dvd -> hlds_e7_static_gate = 0;
398 dvd -> hlds_e7_preferred_method = -1;
399 dvd -> hlds_e7_profile_label = NULL;
400 dvd -> hlds_e7_support_tier = NULL;
401 dvd -> hlds_e7_family = NULL;
402 dvd -> hlds_e7_tokens = NULL;
403 dvd -> hlds_e7_record_id = NULL;
404 dvd -> hlds_e7_notes = NULL;
406 switch (dvd -> hlds_e7_type) {
408 /* DIC Type1: GCC-4160N/GCC-4240N cache frames begin at 0x00a13000
409 * and only one 16-sector cache window is consumed per READ. */
410 dvd -> hlds_e7_cache_base = 0x00a13000U;
411 dvd -> hlds_e7_mem_blocks = 1;
415 /* DIC Type2 uses a moving 0x80000000-derived cache address. This
416 * branch logs/classifies it, but does not yet claim DIC parity. */
417 dvd -> hlds_e7_cache_base = 0x80000000U;
418 dvd -> hlds_e7_mem_blocks = 1;
421 /* GDR-8081N is not in the confirmed DIC dump list, but local firmware
422 * analysis suggests an 0xE7 command surface. Start with a Type4-derived
423 * candidate; disc.c probes and may switch to one of the 811..814 runtime
424 * profiles before seed cracking continues. */
425 dvd -> hlds_e7_cache_base = 0x80000000U;
426 dvd -> hlds_e7_mem_blocks = 5;
429 /* Stock GDR-8050L firmware does not expose the HIT 0xE7 memdump command.
430 * The local test unit is GDR-8163B hardware cross-flashed with modified
431 * GDR-8050L firmware where 0xE7 memdump was added. Single-window has
432 * completed and hash-matched Sonic, so it remains the proven fallback.
433 * Start in an explicit speed-probe-pending profile so the initial drive
434 * information does not look like the old static single-window build.
435 * disc.c promotes to 2/3/5 windows only after guarded validation, or
436 * settles back to the proven single-window profile. */
437 dvd -> hlds_e7_type = 45;
438 dvd -> hlds_e7_cache_base = 0x80000000U;
439 dvd -> hlds_e7_mem_blocks = 1;
444 dvd -> hlds_e7_cache_base = 0x80000000U;
445 dvd -> hlds_e7_mem_blocks = 5;
450 dvd -> hlds_e7_cache_base = profile -> cache_base;
451 dvd -> hlds_e7_mem_blocks = profile -> mem_blocks;
452 dvd -> hlds_e7_static_cdb_base = profile -> static_cdb_base;
453 dvd -> hlds_e7_static_gate = profile -> static_gate;
454 dvd -> hlds_e7_preferred_method = profile -> preferred_method;
455 dvd -> hlds_e7_profile_label = profile -> label;
456 dvd -> hlds_e7_support_tier = profile -> support_tier;
457 dvd -> hlds_e7_family = profile -> family;
458 dvd -> hlds_e7_tokens = profile -> tokens;
459 dvd -> hlds_e7_record_id = profile -> record_id;
460 dvd -> hlds_e7_notes = profile -> notes;
461 /* The modified GDR-8050L runtime profile still starts in the guarded
462 * speed-probe-pending state, but keeps its static parser evidence fields. */
463 if (profile -> type == 44) {
464 dvd -> hlds_e7_type = 45;
465 dvd -> hlds_e7_mem_blocks = 1;
470 void dvd_set_hlds_e7_runtime_profile (dvd_drive *dvd, u_int32_t type, u_int32_t cache_base, u_int32_t mem_blocks) {
473 dvd -> hlds_e7_type = type;
474 dvd -> hlds_e7_cache_base = cache_base;
475 dvd -> hlds_e7_mem_blocks = mem_blocks;
478 static bool dvd_is_tsst_kreon_candidate (dvd_drive *dvd) {
479 if (!(dvd_vendor_is (dvd, "TSSTcorp") || dvd_vendor_is (dvd, "SAMSUNG")))
482 dvd_prod_has (dvd, "TS-H352C") ||
483 dvd_prod_has (dvd, "TS-H353A") ||
484 dvd_prod_has (dvd, "SH-D162C") ||
485 dvd_prod_has (dvd, "SH-D162D") ||
486 dvd_prod_has (dvd, "SH-D163A") ||
487 dvd_prod_has (dvd, "SH-D163B");
492 * Initializes a structure representing an MMC command.
493 * @param mmc A pointer to the MMC command structure.
494 * @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.
495 * @param len The length of the buffer (ignored in case buf is NULL).
496 * @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.
498 void dvd_init_command (mmc_command *mmc, u_int8_t *buf, int len, req_sense *sense) {
499 memset (mmc, 0, sizeof (mmc_command));
501 memset (buf, 0, len);
503 mmc -> direction = buf && len > 0 ? DVD_DATA_IN : DVD_DATA_NONE;
505 mmc -> buflen = buf ? len : 0;
506 mmc -> sense = sense;
514 /* Doc is under the UNIX function */
515 int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) {
516 SCSI_PASS_THROUGH_DIRECT *sptd;
517 unsigned char sptd_sense[sizeof (*sptd) + 18], *sense;
521 sptd = (SCSI_PASS_THROUGH_DIRECT *) sptd_sense;
522 sense = &sptd_sense[sizeof (*sptd)];
524 memset (sptd, 0, sizeof (sptd_sense));
525 memcpy (sptd -> Cdb, mmc -> cmd, sizeof (mmc -> cmd));
526 sptd -> Length = sizeof (SCSI_PASS_THROUGH_DIRECT);
527 sptd -> CdbLength = mmc -> cmdlen;
528 sptd -> SenseInfoLength = 18;
529 if (mmc -> direction == DVD_DATA_OUT)
530 sptd -> DataIn = SCSI_IOCTL_DATA_OUT;
531 else if (mmc -> direction == DVD_DATA_NONE)
532 sptd -> DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
534 sptd -> DataIn = SCSI_IOCTL_DATA_IN;
535 sptd -> DataBuffer = mmc -> buffer;
536 // Quick hack: Windows hates sptd->DataTransferLength = 1, so we set it to 2 and ignore the second byte.
537 if (mmc -> buflen == 1) // TODO
538 sptd -> DataTransferLength = 2;
540 sptd -> DataTransferLength = mmc -> buflen;
541 sptd -> TimeOutValue = MMC_CMD_TIMEOUT;
542 sptd -> SenseInfoOffset = sizeof (*sptd);
544 //fprintf (stdout,"mmc->cmd[00] = %d \n",mmc->cmd[00]);
546 if (mmc->cmd[00]==0xB6) {
547 sptd -> DataIn = SCSI_IOCTL_DATA_OUT;
548 sptd -> DataTransferLength = 28;
552 BOOL ioctl_ok = DeviceIoControl (dvd -> fd, IOCTL_SCSI_PASS_THROUGH_DIRECT,
553 sptd, sizeof (*sptd) + 18, sptd, sizeof (*sptd) + 18, &bytes, NULL);
554 /* DeviceIoControl may succeed while the drive returns CHECK CONDITION.
555 * Treat any non-GOOD SCSI status as command failure. ignore_errors only
556 * suppresses diagnostics; it must not turn failed commands into success. */
557 if (!ioctl_ok || sptd -> ScsiStatus != 0) {
558 out = -1; /* Failure */
559 if (!ignore_errors) {
560 error ("Execution of MMC command failed: Win32=%lu SCSI=0x%02X",
561 (unsigned long) GetLastError (), sptd -> ScsiStatus);
562 debug ("Command was: ");
563 hex_and_ascii_print ("", mmc -> cmd, sizeof (mmc -> cmd));
564 debug ("Sense data: %02X/%02X/%02X\n", sense[2] & 0x0F, sense[12], sense[13]);
572 mmc -> sense -> sense_key = sense[2];
573 mmc -> sense -> asc = sense[12];
574 mmc -> sense -> ascq = sense[13];
583 * Executes an MMC command.
584 * @param dvd The DVD drive the command should be exectued on.
585 * @param mmc The command to be executed.
586 * @param ignore_errors If set to true, no error will be printed if the command fails.
587 * @return 0 if the command was executed successfully, < 0 otherwise.
589 int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) {
591 struct cdrom_generic_command cgc;
592 struct request_sense sense;
595 debug ("Executing MMC command: ");
596 hex_and_ascii_print ("", mmc -> cmd, sizeof (mmc -> cmd));
599 /* Init Linux-format MMC command */
600 memset (&cgc, 0, sizeof (struct cdrom_generic_command));
601 memcpy (cgc.cmd, mmc -> cmd, sizeof (mmc -> cmd));
602 cgc.buffer = (unsigned char *) mmc -> buffer;
603 cgc.buflen = mmc -> buflen;
604 if (mmc -> direction == DVD_DATA_OUT)
605 cgc.data_direction = CGC_DATA_WRITE;
606 else if (mmc -> direction == DVD_DATA_NONE)
607 cgc.data_direction = CGC_DATA_NONE;
609 cgc.data_direction = CGC_DATA_READ;
610 cgc.timeout = MMC_CMD_TIMEOUT * 1000; /* Linux uses milliseconds */
612 if (ioctl (dvd -> fd, CDROM_SEND_PACKET, &cgc) < 0) {
613 out = -1; /* Failure */
614 if (!ignore_errors) {
615 error ("Execution of MMC command failed: %s", strerror (errno));
616 debug ("Command was:");
617 hex_and_ascii_print ("", cgc.cmd, sizeof (cgc.cmd));
618 debug ("Sense data: %02X/%02X/%02X", sense.sense_key, sense.asc, sense.ascq);
625 mmc -> sense -> sense_key = sense.sense_key;
626 mmc -> sense -> asc = sense.asc;
627 mmc -> sense -> ascq = sense.ascq;
636 * Sends an INQUIRY command to the drive to retrieve drive identification strings.
637 * @param dvd The DVD drive the command should be exectued on.
638 * @return 0 if the command was executed successfully, < 0 otherwise.
640 static int dvd_get_drive_info (dvd_drive *dvd) {
646 dvd_init_command (&mmc, buf, sizeof (buf), NULL);
647 mmc.cmd[0] = SPC_INQUIRY;
648 mmc.cmd[4] = sizeof (buf);
649 if ((out = dvd_execute_cmd (dvd, &mmc, false)) >= 0) {
650 my_strndup (dvd -> vendor, buf + 8, 8);
651 strtrimr (dvd -> vendor);
652 my_strndup (dvd -> prod_id, buf + 16, 16);
653 strtrimr (dvd -> prod_id);
654 my_strndup (dvd -> prod_rev, buf + 32, 4);
655 strtrimr (dvd -> prod_rev);
656 snprintf (tmp, sizeof (tmp), "%s/%s/%s", dvd -> vendor, dvd -> prod_id, dvd -> prod_rev);
657 my_strdup (dvd -> model_string, tmp);
659 debug ("DVD drive is \"%s\"", dvd -> model_string);
661 error ("Cannot identify DVD drive\n");
669 * Assigns the proper memory dump functions to a dvd_drive object, according to vendor, model and other parameters. Actually this scheme probably needs to
670 * to be improved, but it is enough for the moment.
671 * @param dvd The DVD drive the command should be exectued on.
673 static void dvd_assign_functions (dvd_drive *dvd, u_int32_t command) {
674 dvd -> def_method = 0;
675 if (dvd_is_hlds_gc_wii_drive (dvd)) {
676 dvd_apply_hlds_e7_profile (dvd);
677 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",
678 dvd_get_hlds_e7_profile_name (dvd),
679 dvd_get_hlds_e7_support_tier (dvd),
680 dvd_get_hlds_e7_family (dvd),
681 dvd -> hlds_e7_cache_base, dvd -> hlds_e7_mem_blocks,
682 dvd -> hlds_e7_static_cdb_base, dvd -> hlds_e7_static_gate);
683 dvd -> memdump = &hitachi_dvd_dump_mem;
685 dvd -> supported = true;
686 /* DIC Type1/Type3/Type4 use READ12 + HIT 0xE7 cache extraction.
687 * GCC-4244N is validated with Sonic Mega Collection and GCC-4243N is
688 * actively under test, so both remain Method 8. Type1 drives get the
689 * DIC-derived 0x00a13000 / one-window cache profile and also default to
690 * Method 8 so GCC-4160N/GCC-4240N can be tested without forcing a method.
691 * GDR-8050L gets a proven single-window fallback plus guarded speed probes
692 * for cross-flashed/modified firmware with 0xE7 memdump added; stock
693 * GDR-8050L firmware does not expose this GC/Wii memdump path. GDR-8081N
694 * is an experimental probe target and needs Method 8 so seed probing can run.
695 * Type2 is still classified only; leave it on the older Method 9 path. */
696 if (dvd -> hlds_e7_preferred_method >= 0)
697 dvd -> def_method = (u_int32_t) dvd -> hlds_e7_preferred_method;
698 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))
699 dvd -> def_method = 8;
701 dvd -> def_method = 9;
703 } else if (strcmp (dvd -> vendor, "LITE-ON") == 0 && (
704 strcmp (dvd ->prod_id, "DVDRW LH-18A1H") == 0 ||
705 strcmp (dvd ->prod_id, "DVDRW LH-18A1P") == 0 ||
706 strcmp (dvd ->prod_id, "DVDRW LH-20A1H") == 0 ||
707 strcmp (dvd ->prod_id, "DVDRW LH-20A1P") == 0
709 debug ("Lite-On DVD drive detected, using Lite-On memory dump command");
710 dvd -> memdump = &liteon_dvd_dump_mem;
712 dvd -> supported = true;
713 dvd -> def_method = 5;
715 } else if (dvd_is_tsst_kreon_candidate (dvd) || (strcmp (dvd -> vendor, "TSSTcorp") == 0 && (
716 strcmp (dvd ->prod_id, "DVD-ROM SH-D162A") == 0 ||
717 strcmp (dvd ->prod_id, "DVD-ROM SH-D162B") == 0
719 debug ("Toshiba Samsung DVD drive detected, using vanilla 2384 memory dump command");
720 dvd -> memdump = &vanilla_2384_dvd_dump_mem;
722 dvd -> supported = true;
723 dvd -> def_method = 0;
725 } else if (strcmp (dvd -> vendor, "PLEXTOR") == 0) {
726 debug ("Plextor DVD drive detected, using vanilla 2064 memory dump command");
727 dvd -> memdump = &vanilla_2064_dvd_dump_mem;
729 dvd -> supported = true;
730 dvd -> def_method = 2;
733 /* This is an unsupported drive (yet). */
734 dvd -> memdump = &vanilla_2064_dvd_dump_mem;
736 dvd -> supported = false;
740 dvd -> command = command;
741 if (command == 0) dvd -> memdump = &vanilla_2064_dvd_dump_mem;
742 else if (command == 1) dvd -> memdump = &vanilla_2384_dvd_dump_mem;
743 else if (command == 2) dvd -> memdump = &hitachi_dvd_dump_mem;
744 else if (command == 3) dvd -> memdump = &liteon_dvd_dump_mem;
745 else if (command == 4) dvd -> memdump = &renesas_dvd_dump_mem;
749 //init Reed-Solomon for Lite-On
758 * Creates a new structure representing a CD/DVD-ROM drive.
759 * @param device The CD/DVD-ROM device, in OS-dependent format (i.e.: /dev/something on Unix, x: on Windows).
760 * @return The newly-created structure, to be used with the other commands, or NULL if the drive could not be initialized.
762 dvd_drive *dvd_drive_new (char *device, u_int32_t command) {
771 /* Force the dropping of privileges: in our model, privileges are only used to execute memory dump commands, the user
772 must gain access to the device somehow else (i. e. get added to the "cdrom" group or similar things) */
775 debug ("Trying to open DVD device %s", device);
777 sprintf (dev, "\\\\.\\%c:", device[0]);
778 if ((fd = CreateFile (dev, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) {
779 error ("Cannot open drive: %d", GetLastError ());
781 if ((fd = open (device, O_RDONLY | O_NONBLOCK)) < 0) {
782 perror ("Cannot open drive");
786 debug ("Opened successfully");
788 dvd = (dvd_drive *) malloc (sizeof (dvd_drive));
790 fprintf (stderr, "malloc() failed\n");
793 memset (dvd, 0, sizeof (dvd_drive));
794 my_strdup (dvd -> device, device);
796 dvd_get_drive_info (dvd);
797 dvd_assign_functions (dvd, command);
805 * Frees resources used by a DVD drive structure and destroys it.
806 * @param dvd The DVD drive structure to be destroyed.
809 void *dvd_drive_destroy (dvd_drive *dvd) {
812 CloseHandle (dvd -> fd);
816 my_free (dvd -> device);
817 my_free (dvd -> vendor);
818 my_free (dvd -> prod_id);
819 my_free (dvd -> prod_rev);
828 * Executes the drive-dependent function to dump the drive sector cache, and returns the dumped data.
829 * @param dvd The DVD drive the command should be exectued on.
830 * @param block_off The offset to start dumping, WRT the beginning of the sector cache.
831 * @param block_len The number of blocks to dump.
832 * @param block_size The block size to be used for dumping.
833 * @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.
834 * @return 0 if the command was executed successfully, < 0 otherwise.
836 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) {
839 /* Upgrade privileges and call actual dump functions */
841 out = dvd -> memdump (dvd, block_off, block_len, block_size, buf);
849 * 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
850 * at the beginning of the cache and can be dumped later.
851 * @param dvd The DVD drive the command should be exectued on.
852 * @param sector The sector to be read. What will be cached is the 16-sectors block to which the sector belongs.
853 * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed.
854 * @return 0 if the command was executed successfully, < 0 otherwise.
856 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) {
859 u_int8_t intbuf[64 * 1024], *buf;
862 /* We need some buffer, be it provided externally or not */
865 bufsize = extbufsize;
868 bufsize = sizeof (intbuf);
871 dvd_init_command (&mmc, buf, bufsize, sense);
872 mmc.cmd[0] = MMC_READ_12;
873 mmc.cmd[1] = 0x08; /* FUA bit set */
874 mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */
875 mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
876 mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
877 mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF);
878 mmc.cmd[6] = (u_int8_t) ((sectors & 0xFF000000) >> 24); /* Size from MSB to LSB */
879 mmc.cmd[7] = (u_int8_t) ((sectors & 0x00FF0000) >> 16);
880 mmc.cmd[8] = (u_int8_t) ((sectors & 0x0000FF00) >> 8);
881 mmc.cmd[9] = (u_int8_t) (sectors & 0x000000FF);
882 out = dvd_execute_cmd (dvd, &mmc, true); /* Ignore errors! */
889 * Issues a READ(12) command using the STREAMING bit, which causes the requested 16-sector block to be read into memory,
890 * together with the following four. This way we will be able to dump 5 sector with a single READ request.
892 * Note the strange need for a big buffer even though we must only pass 0x10 as the transfer length, otherwise the drive will hang (!?).
893 * @param dvd The DVD drive the command should be exectued on.
894 * @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.
895 * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed.
896 * @param extbuf A buffer where to store the read data, or NULL.
897 * @param extbufsize The size of the buffer.
900 int dvd_read_sector_streaming (dvd_drive *dvd, u_int32_t sector, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) {
903 u_int8_t intbuf[2048 * 16], *buf;
906 /* We need some buffer, be it provided externally or not */
909 bufsize = extbufsize;
912 bufsize = sizeof (intbuf);
915 dvd_init_command (&mmc, buf, bufsize, sense);
916 mmc.cmd[0] = MMC_READ_12;
917 mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */
918 mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
919 mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
920 mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF);
925 mmc.cmd[10] = 0x80; /* STREAMING bit set */
926 out = dvd_execute_cmd (dvd, &mmc, true); /* Ignore errors! */
932 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) {
935 u_int8_t intbuf[64 * 1024], *buf;
938 /* We need some buffer, be it provided externally or not */
941 bufsize = extbufsize;
944 bufsize = sizeof (intbuf);
947 dvd_init_command (&mmc, buf, bufsize, sense);
948 mmc.cmd[0] = MMC_READ_12;
949 mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */
950 mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
951 mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
952 mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF);
953 mmc.cmd[6] = (u_int8_t) ((sectors & 0xFF000000) >> 24); /* Size from MSB to LSB */
954 mmc.cmd[7] = (u_int8_t) ((sectors & 0x00FF0000) >> 16);
955 mmc.cmd[8] = (u_int8_t) ((sectors & 0x0000FF00) >> 8);
956 mmc.cmd[9] = (u_int8_t) (sectors & 0x000000FF);
957 mmc.cmd[10] = 0x80; /* STREAMING bit set */
958 out = dvd_execute_cmd (dvd, &mmc, true); /* Ignore errors! */
964 int dvd_flush_cache_READ12 (dvd_drive *dvd, u_int32_t sector, req_sense *sense) {
967 u_int8_t intbuf[64], *buf;
973 dvd_init_command (&mmc, buf, bufsize, sense);
974 mmc.cmd[0] = MMC_READ_12;
976 mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */
977 mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
978 mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
979 mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF);
980 out = dvd_execute_cmd (dvd, &mmc, true);
985 static void dvd_sleep_ms (unsigned int ms) {
989 usleep ((useconds_t) ms * 1000);
993 int dvd_start_stop_unit (dvd_drive *dvd, bool start, bool load_eject, req_sense *sense) {
996 u_int8_t intbuf[64], *buf;
1002 dvd_init_command (&mmc, buf, bufsize, sense);
1004 /* START STOP UNIT byte 4: bit 1 = LoEj, bit 0 = Start. */
1005 mmc.cmd[4] = (load_eject ? 0x02 : 0x00) | (start ? 0x01 : 0x00);
1006 out = dvd_execute_cmd (dvd, &mmc, true);
1011 int dvd_stop_unit (dvd_drive *dvd, bool start, req_sense *sense) {
1012 return dvd_start_stop_unit (dvd, start, false, sense);
1015 int dvd_wait_ready (dvd_drive *dvd, unsigned int timeout_ms) {
1016 unsigned int waited = 0;
1019 while (waited <= timeout_ms) {
1020 if (dvd_test_unit_ready (dvd, NULL) == 0)
1023 dvd_stop_unit (dvd, true, NULL); /* START UNIT, like EnsureDriveReady() */
1030 int dvd_media_cycle (dvd_drive *dvd, req_sense *sense) {
1034 /* Match the original GDR-8050L dumper's AutomateTrayCycle(): eject, wait
1035 * long enough for the tray to extend, close, poll readiness, then settle. */
1036 out = dvd_start_stop_unit (dvd, false, true, sense); /* LoEj=1, Start=0: eject */
1039 dvd_sleep_ms (3000);
1041 out = dvd_start_stop_unit (dvd, true, true, sense); /* LoEj=1, Start=1: load */
1045 for (i = 0; i < 90; i++) {
1047 if (dvd_test_unit_ready (dvd, NULL) == 0) {
1048 dvd_sleep_ms (1500);
1053 /* Original dumper falls back to a fixed 10s settle delay if TUR never
1054 * reports ready after tray close. */
1055 dvd_sleep_ms (10000);
1059 int dvd_set_speed (dvd_drive *dvd, u_int32_t speed, req_sense *sense) {
1062 u_int8_t intbuf[64], *buf;
1068 dvd_init_command (&mmc, buf, bufsize, sense);
1070 mmc.cmd[2] = (u_int8_t) ((speed & 0x0000FF00) >> 8);
1071 mmc.cmd[3] = (u_int8_t) (speed & 0x000000FF);
1072 out = dvd_execute_cmd (dvd, &mmc, true);
1077 int dvd_get_size (dvd_drive *dvd, u_int32_t *size, req_sense *sense) {
1080 u_int8_t intbuf[64], *buf;
1086 dvd_init_command (&mmc, buf, bufsize, sense);
1091 out = dvd_execute_cmd (dvd, &mmc, true);
1093 *(size)=*(size) << 8 | intbuf[0x18];
1094 *(size)=*(size) << 8 | intbuf[0x19];
1095 *(size)=*(size) << 8 | intbuf[0x1a];
1096 *(size)=*(size) << 8 | intbuf[0x1b];
1101 int dvd_get_layerbreak (dvd_drive *dvd, u_int32_t *layerbreak, req_sense *sense) {
1104 u_int8_t intbuf[2052], *buf;
1110 dvd_init_command (&mmc, buf, bufsize, sense);
1114 out = dvd_execute_cmd (dvd, &mmc, true);
1116 *(layerbreak)=*(layerbreak) << 8;
1117 *(layerbreak)=*(layerbreak) << 8 | intbuf[0x11];
1118 *(layerbreak)=*(layerbreak) << 8 | intbuf[0x12];
1119 *(layerbreak)=*(layerbreak) << 8 | intbuf[0x13];
1120 if (*(layerbreak) > 0) *(layerbreak)=*(layerbreak) - 0x30000 + 1;
1125 int dvd_set_streaming (dvd_drive *dvd, u_int32_t speed, req_sense *sense) {
1128 DeviceIoControl : \Device\CdRom5
1129 Command : IOCTL_SCSI_PASS_THROUGH_DIRECT
1130 Length : 44 (0x002C)
1135 CdbLength : 12 (0x0C)
1136 SenseInfoLength : 24 (0x18)
1137 DataTransferLength : 28 (0x0000001C)
1142 00000000 B6 00 00 00 00 00 00 00 00 00 1C 00 ...........
1145 00000000 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF ............____
1146 00000010 00 00 03 E8 FF FF FF FF 00 00 03 E8 ...____...
1150 u_int8_t inbuf[28], *buf;
1156 dvd_init_command (&mmc, buf, bufsize, sense);
1169 *(buf+ 8)=0xff; //MSB
1172 *(buf+11)=0xff; //LSB
1174 *(buf+12)=(u_int8_t) ((speed & 0xFF000000) >> 24);
1175 *(buf+13)=(u_int8_t) ((speed & 0x00FF0000) >> 16);
1176 *(buf+14)=(u_int8_t) ((speed & 0x0000FF00) >> 8);
1177 *(buf+15)=(u_int8_t) (speed & 0x000000FF);
1179 *(buf+16)=(u_int8_t) ((1000 & 0xFF000000) >> 24);
1180 *(buf+17)=(u_int8_t) ((1000 & 0x00FF0000) >> 16);
1181 *(buf+18)=(u_int8_t) ((1000 & 0x0000FF00) >> 8);
1182 *(buf+19)=(u_int8_t) (1000 & 0x000000FF);
1184 *(buf+20)=(u_int8_t) ((speed & 0xFF000000) >> 24);
1185 *(buf+21)=(u_int8_t) ((speed & 0x00FF0000) >> 16);
1186 *(buf+22)=(u_int8_t) ((speed & 0x0000FF00) >> 8);
1187 *(buf+23)=(u_int8_t) (speed & 0x000000FF);
1189 *(buf+24)=(u_int8_t) ((1000 & 0xFF000000) >> 24);
1190 *(buf+25)=(u_int8_t) ((1000 & 0x00FF0000) >> 16);
1191 *(buf+26)=(u_int8_t) ((1000 & 0x0000FF00) >> 8);
1192 *(buf+27)=(u_int8_t) (1000 & 0x000000FF);
1194 out = dvd_execute_cmd (dvd, &mmc, true);
1200 int dvd_test_unit_ready (dvd_drive *dvd, req_sense *sense) {
1204 dvd_init_command (&mmc, intbuf, 0, sense);
1205 mmc.cmd[0] = SPC_TEST_UNIT_READY;
1207 mmc.direction = DVD_DATA_NONE;
1208 return dvd_execute_cmd (dvd, &mmc, true);
1211 int dvd_read_capacity_10 (dvd_drive *dvd, u_int32_t *sectors, u_int32_t *sector_size, req_sense *sense) {
1215 u_int32_t max_lba, block_len;
1217 dvd_init_command (&mmc, buf, sizeof (buf), sense);
1218 mmc.cmd[0] = MMC_READ_CAPACITY_10;
1220 out = dvd_execute_cmd (dvd, &mmc, false);
1222 max_lba = ((u_int32_t) buf[0] << 24) | ((u_int32_t) buf[1] << 16) | ((u_int32_t) buf[2] << 8) | buf[3];
1223 block_len = ((u_int32_t) buf[4] << 24) | ((u_int32_t) buf[5] << 16) | ((u_int32_t) buf[6] << 8) | buf[7];
1225 *sectors = max_lba + 1;
1227 *sector_size = block_len;
1233 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) {
1235 u_int8_t intbuf[64 * 1024], *buf;
1236 size_t need, bufsize;
1238 need = (size_t) sectors * 2048;
1241 bufsize = extbufsize;
1244 bufsize = sizeof (intbuf);
1247 if (need > bufsize) {
1248 error ("dvd_read_10 buffer too small (%u sectors need %lu bytes)", sectors, (unsigned long) need);
1252 dvd_init_command (&mmc, buf, (int) need, sense);
1253 mmc.cmd[0] = MMC_READ_10;
1255 mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24);
1256 mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16);
1257 mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8);
1258 mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF);
1259 mmc.cmd[7] = (u_int8_t) ((sectors & 0x0000FF00) >> 8);
1260 mmc.cmd[8] = (u_int8_t) (sectors & 0x000000FF);
1262 return dvd_execute_cmd (dvd, &mmc, true);
1265 int dvd_mode_sense_10 (dvd_drive *dvd, u_int8_t page, u_int8_t *extbuf, size_t extbufsize, req_sense *sense) {
1268 if (!extbuf || extbufsize > 0xFFFF)
1271 dvd_init_command (&mmc, extbuf, (int) extbufsize, sense);
1272 mmc.cmd[0] = SPC_MODE_SENSE_10;
1274 mmc.cmd[7] = (u_int8_t) ((extbufsize & 0xFF00) >> 8);
1275 mmc.cmd[8] = (u_int8_t) (extbufsize & 0x00FF);
1278 return dvd_execute_cmd (dvd, &mmc, false);
1281 int dvd_mode_select_10 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense) {
1285 if (!buf || bufsize > sizeof (tmp) || bufsize > 0xFFFF)
1287 memset (tmp, 0, sizeof (tmp));
1288 memcpy (tmp, buf, bufsize);
1290 dvd_init_command (&mmc, tmp, (int) bufsize, sense);
1291 mmc.direction = DVD_DATA_OUT;
1292 mmc.cmd[0] = SPC_MODE_SELECT_10;
1293 mmc.cmd[7] = (u_int8_t) ((bufsize & 0xFF00) >> 8);
1294 mmc.cmd[8] = (u_int8_t) (bufsize & 0x00FF);
1297 return dvd_execute_cmd (dvd, &mmc, false);
1300 int dvd_mode_select_6 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense) {
1304 if (!buf || bufsize > sizeof (tmp) || bufsize > 0xFF)
1306 memset (tmp, 0, sizeof (tmp));
1307 memcpy (tmp, buf, bufsize);
1309 dvd_init_command (&mmc, tmp, (int) bufsize, sense);
1310 mmc.direction = DVD_DATA_OUT;
1311 mmc.cmd[0] = SPC_MODE_SELECT_6;
1313 mmc.cmd[4] = (u_int8_t) (bufsize & 0xFF);
1316 return dvd_execute_cmd (dvd, &mmc, false);
1319 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) {
1322 if (!extbuf || extbufsize > 0xFFFF)
1325 dvd_init_command (&mmc, extbuf, (int) extbufsize, sense);
1326 mmc.cmd[0] = MMC_READ_DVD_STRUCTURE;
1328 mmc.cmd[8] = (u_int8_t) ((extbufsize & 0xFF00) >> 8);
1329 mmc.cmd[9] = (u_int8_t) (extbufsize & 0x00FF);
1330 mmc.cmd[11] = format;
1333 return dvd_execute_cmd (dvd, &mmc, false);
1337 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) {
1340 dvd_init_command (&mmc, buf, (int) bufsize, NULL);
1344 mmc.cmd[3] = subcommand;
1347 mmc.direction = direction;
1348 if (direction == DVD_DATA_NONE) {
1352 return dvd_execute_cmd (dvd, &mmc, false);
1355 static bool dvd_xbox_feature_list_has (const u_int16_t *features, size_t count, u_int16_t needle) {
1359 for (i = 0; i < count && features[i] != 0; i++) {
1360 if (features[i] == needle)
1366 int dvd_xbox_vendor_get_feature_list (dvd_drive *dvd, u_int16_t *features, size_t max_features) {
1370 if (!dvd || !features || max_features == 0)
1372 memset (features, 0, max_features * sizeof (features[0]));
1373 memset (buf, 0, sizeof (buf));
1374 if (dvd_xbox_vendor_command (dvd, 0x10, 0, buf, sizeof (buf), DVD_DATA_IN) < 0)
1376 if ((((u_int16_t) buf[0] << 8) | buf[1]) != 0xA55A ||
1377 (((u_int16_t) buf[2] << 8) | buf[3]) != 0x5AA5) {
1378 error ("Xbox vendor feature-list signature is invalid");
1381 count = sizeof (buf) / 2;
1382 if (count > max_features)
1383 count = max_features;
1384 for (i = 0; i < count; i++)
1385 features[i] = ((u_int16_t) buf[i * 2] << 8) | buf[i * 2 + 1];
1389 int dvd_xbox_vendor_lock (dvd_drive *dvd) {
1390 if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd))
1392 return dvd_xbox_vendor_command (dvd, 0x11, 0x00, NULL, 0, DVD_DATA_NONE);
1395 int dvd_xbox_vendor_set_error_skip (dvd_drive *dvd, bool enabled) {
1396 if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd))
1398 return dvd_xbox_vendor_command (dvd, 0x15, enabled ? 0x01 : 0x00, NULL, 0, DVD_DATA_NONE);
1401 int dvd_xbox_vendor_unlock_wxripper (dvd_drive *dvd, u_int32_t *unlocked_sectors) {
1402 u_int16_t features[13];
1403 u_int32_t sectors = 0, sector_size = 0;
1405 if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd))
1408 if (dvd_xbox_vendor_get_feature_list (dvd, features, sizeof (features) / sizeof (features[0])) < 0)
1410 if (!dvd_xbox_feature_list_has (features, sizeof (features) / sizeof (features[0]), 0x0201) &&
1411 !dvd_xbox_feature_list_has (features, sizeof (features) / sizeof (features[0]), 0x0221))
1412 warning ("Xbox vendor feature list did not advertise Xbox unlock state 2/full challenge support; trying wxripper state anyway");
1414 if (dvd_xbox_vendor_command (dvd, 0x11, 0x02, NULL, 0, DVD_DATA_NONE) < 0)
1417 /* DiscImageCreator disables error-skip before dumping; keep that behavior so
1418 * real read errors are visible to FriiDump unless a future option says otherwise. */
1419 dvd_xbox_vendor_set_error_skip (dvd, false);
1421 if (dvd_read_capacity_10 (dvd, §ors, §or_size, NULL) < 0)
1423 if (sector_size != 2048 || sectors < 1000000) {
1424 error ("Xbox vendor unlock did not expose the expected 2048-byte view");
1427 if (unlocked_sectors)
1428 *unlocked_sectors = sectors;
1433 int dvd_xbox_recovery_kick (dvd_drive *dvd, bool auth_recovery) {
1435 u_int8_t dummy[2048];
1436 u_int8_t auth_probe_buf[8];
1442 /* Mirrors the proven dumper recovery cadence: after the GDR-8050L
1443 * authentication/view switch, re-trigger the Xbox media auth command, set
1444 * maximum speed, then issue a few harmless READ(10) probes. All commands
1445 * deliberately ignore errors because the point is to wake/re-prime firmware
1446 * state, not to make any one probe authoritative.
1448 if (auth_recovery) {
1449 /* GDR-8050L / HLDS auth kick: READ DVD STRUCTURE C0 magic trigger. */
1450 dvd_init_command (&mmc, auth_probe_buf, sizeof (auth_probe_buf), NULL);
1451 mmc.cmd[0] = MMC_READ_DVD_STRUCTURE;
1457 dvd_execute_cmd (dvd, &mmc, true);
1459 /* Hitachi/Kreon-style fallback auth kick: FF 08 01. */
1460 dvd_init_command (&mmc, NULL, 0, NULL);
1465 mmc.direction = DVD_DATA_NONE;
1466 dvd_execute_cmd (dvd, &mmc, true);
1469 dvd_set_speed (dvd, 0xFFFF, NULL);
1471 for (i = 0; i < 10; i++)
1472 dvd_read_10 (dvd, 0, 1, NULL, dummy, sizeof (dummy));
1477 int dvd_refresh_volume (dvd_drive *dvd) {
1479 DWORD bytesReturned = 0;
1482 /* Match RefreshVolume() from the reference dumper: update properties only;
1483 * do not dismount here because that can reset drive state. This is now
1484 * shared by Xbox and HLDS 0xE7 GC/Wii paths so Windows is less likely to
1485 * keep stale filesystem/probe state attached to odd discs. */
1486 DeviceIoControl (dvd -> fd, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL);
1487 dvd_sleep_ms (1000);
1495 int dvd_lock_volume (dvd_drive *dvd) {
1497 DWORD bytesReturned = 0;
1500 return DeviceIoControl (dvd -> fd, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL) ? 0 : -1;
1507 int dvd_xbox_refresh_volume (dvd_drive *dvd) {
1508 return dvd_refresh_volume (dvd);
1511 int dvd_xbox_lock_volume (dvd_drive *dvd) {
1512 return dvd_lock_volume (dvd);
1515 static int dvd_xbox_read_host_challenge_table (dvd_drive *dvd, u_int8_t *table, size_t table_len) {
1519 if (!table || table_len < 0x664)
1522 /* GDR-8050L / Xbox READ DVD STRUCTURE format C0. */
1523 dvd_init_command (&mmc, table, 0x664, NULL);
1524 mmc.cmd[0] = MMC_READ_DVD_STRUCTURE;
1534 out = dvd_execute_cmd (dvd, &mmc, true);
1536 /* Some Hitachi-family drives expose the same table via vendor command 0xFD. */
1537 if (out < 0 || table[772] != 1 || table[773] == 0) {
1538 dvd_init_command (&mmc, table, 0x664, NULL);
1544 out = dvd_execute_cmd (dvd, &mmc, true);
1547 if (out < 0 || table[772] != 1)
1552 int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) {
1555 u_int8_t table[0x664];
1556 u_int8_t restable[261];
1557 u_int8_t hash[0x2C];
1560 u_int8_t sticky[12];
1561 u_int32_t sectors = 0, sector_size = 0;
1564 if (!dvd || !dvd_is_xbox_unlock_drive (dvd))
1567 /* Step 1/2: read current capacity and the Xbox mode page. If the drive is
1568 * already unlocked, this is harmless; the final capacity check below becomes
1570 dvd_read_capacity_10 (dvd, §ors, §or_size, NULL);
1571 dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL);
1573 /* Step 3: retrieve and decode the host challenge table. */
1574 if (dvd_xbox_read_host_challenge_table (dvd, table, sizeof (table)) < 0) {
1575 error ("Cannot retrieve Xbox host challenge table");
1579 for (i = 0; i < 0x2C; i++)
1580 hash[i] = table[0x4A3 + i];
1581 Sha1Calculate (hash, 0x2C, &digest);
1583 for (i = 0; i <= 260; i++)
1584 restable[i] = table[774 + i];
1585 xbox_rc4_init (&rc4, digest.bytes, 7);
1586 xbox_rc4_crypt (&rc4, restable, restable, 0xFD);
1589 for (l = 0; l <= 23; l++) {
1590 if (restable[l * 11] == 1) {
1592 if (k == (int) (sizeof (chalpos) / sizeof (chalpos[0])))
1597 error ("Xbox challenge table does not contain enough usable entries");
1601 /* Step 4: first host challenge. */
1602 memset (page, 0, sizeof (page));
1609 memcpy (&page[15], &restable[1 + chalpos[k - 2] * 11], 5);
1610 /* Match the original dumper: send the challenge and continue even if
1611 * Windows reports a transport failure. The later XDVDFS probe is the
1612 * authority for whether the drive actually entered the game view. */
1613 dvd_mode_select_10 (dvd, page, sizeof (page), NULL);
1614 dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL);
1616 /* Step 6: second host challenge. */
1617 memset (page, 0, sizeof (page));
1622 memcpy (&page[15], &restable[1 + chalpos[k - 1] * 11], 5);
1623 dvd_mode_select_10 (dvd, page, sizeof (page), NULL);
1624 dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL);
1626 /* Step 8: unlock partition 1. */
1627 memset (page, 0, sizeof (page));
1636 memcpy (&page[15], &restable[1 + chalpos[k - 1] * 11], 5);
1637 dvd_mode_select_10 (dvd, page, sizeof (page), NULL);
1639 /* Step 9: sticky descrambling, mode page 0x31. */
1640 memset (sticky, 0, sizeof (sticky));
1644 dvd_mode_select_6 (dvd, sticky, sizeof (sticky), NULL);
1646 /* Step 10: final capacity observation. The original UnlockDrive() only
1647 * prints this verification and does not fail if the capacity has not changed
1648 * yet. This matters for the first GDR-8050L handshake, whose purpose is to
1649 * prime the drive before the required media-change event. */
1652 if (dvd_read_capacity_10 (dvd, §ors, §or_size, NULL) == 0) {
1653 if (unlocked_sectors)
1654 *unlocked_sectors = sectors;
1655 fprintf (stderr, "[XBOX] GDR-8050L handshake complete; READ CAPACITY reports %u sectors of %u bytes.\n", sectors, sector_size);
1657 fprintf (stderr, "[XBOX] GDR-8050L handshake sent; final READ CAPACITY verify failed, continuing like original dumper.\n");
1663 bool dvd_is_xbox_challenge_drive (dvd_drive *dvd) {
1664 return dvd_is_hlds_drive (dvd) && dvd_prod_has (dvd, "GDR8050L");
1667 bool dvd_is_xbox_vendor_unlock_drive (dvd_drive *dvd) {
1668 return (dvd_is_hlds_drive (dvd) && (dvd_prod_has (dvd, "GDR3120L") || dvd_prod_has (dvd, "GDR-3120L"))) ||
1669 dvd_is_tsst_kreon_candidate (dvd);
1672 bool dvd_is_xbox_unlock_drive (dvd_drive *dvd) {
1673 return dvd_is_xbox_challenge_drive (dvd) || dvd_is_xbox_vendor_unlock_drive (dvd);
1676 bool dvd_is_xbox_drive (dvd_drive *dvd) {
1677 /* Autodetect only the two native Xbox profiles currently wired into the
1678 * Xbox dump planner. Other candidate drives keep FriiDump's normal GC/Wii/DVD
1679 * behavior unless the user explicitly forces Xbox mode with -T 4. */
1680 return dvd_is_xbox_challenge_drive (dvd) ||
1681 (dvd_is_hlds_drive (dvd) && (dvd_prod_has (dvd, "GDR3120L") || dvd_prod_has (dvd, "GDR-3120L")));
1686 const char *dvd_get_hlds_e7_profile_name (dvd_drive *dvd) {
1687 if (!dvd) return "none";
1688 if (dvd -> hlds_e7_profile_label) return dvd -> hlds_e7_profile_label;
1689 return dvd_hlds_e7_profile_name_from_type (dvd -> hlds_e7_type);
1692 const char *dvd_get_hlds_e7_support_tier (dvd_drive *dvd) {
1693 if (!dvd || dvd -> hlds_e7_type == 0) return "none";
1694 return dvd -> hlds_e7_support_tier ? dvd -> hlds_e7_support_tier : "legacy_detected";
1697 const char *dvd_get_hlds_e7_family (dvd_drive *dvd) {
1698 if (!dvd || dvd -> hlds_e7_type == 0) return "none";
1699 return dvd -> hlds_e7_family ? dvd -> hlds_e7_family : dvd_hlds_e7_profile_name_from_type (dvd -> hlds_e7_type);
1702 const char *dvd_get_hlds_e7_tokens (dvd_drive *dvd) {
1703 if (!dvd || dvd -> hlds_e7_type == 0) return "";
1704 return dvd -> hlds_e7_tokens ? dvd -> hlds_e7_tokens : "";
1707 const char *dvd_get_hlds_e7_record_id (dvd_drive *dvd) {
1708 if (!dvd || dvd -> hlds_e7_type == 0) return "";
1709 return dvd -> hlds_e7_record_id ? dvd -> hlds_e7_record_id : "";
1712 const char *dvd_get_hlds_e7_notes (dvd_drive *dvd) {
1713 if (!dvd || dvd -> hlds_e7_type == 0) return "";
1714 return dvd -> hlds_e7_notes ? dvd -> hlds_e7_notes : "";
1717 u_int32_t dvd_get_hlds_e7_static_cdb_base (dvd_drive *dvd) {
1718 return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_static_cdb_base : 0;
1721 u_int32_t dvd_get_hlds_e7_static_gate (dvd_drive *dvd) {
1722 return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_static_gate : 0;
1725 int dvd_get_hlds_e7_preferred_method (dvd_drive *dvd) {
1726 return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_preferred_method : -1;
1729 u_int32_t dvd_get_hlds_e7_type (dvd_drive *dvd) {
1730 return dvd ? dvd -> hlds_e7_type : 0;
1733 u_int32_t dvd_get_hlds_e7_cache_base (dvd_drive *dvd) {
1734 return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_cache_base : 0;
1737 u_int32_t dvd_get_hlds_e7_mem_blocks (dvd_drive *dvd) {
1738 return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_mem_blocks : 0;
1741 char *dvd_get_vendor (dvd_drive *dvd) {
1742 return (dvd -> vendor);
1746 char *dvd_get_product_id (dvd_drive *dvd) {
1747 return (dvd -> prod_id);
1751 char *dvd_get_product_revision (dvd_drive *dvd) {
1752 return (dvd -> prod_rev);
1756 char *dvd_get_model_string (dvd_drive *dvd) {
1757 return (dvd -> model_string);
1761 char *dvd_get_device (dvd_drive *dvd) {
1762 return (dvd -> device);
1765 void *dvd_get_native_handle (dvd_drive *dvd) {
1766 if (!dvd) return NULL;
1768 return (void *) dvd -> fd;
1775 bool dvd_get_support_status (dvd_drive *dvd) {
1776 return (dvd -> supported);
1779 u_int32_t dvd_get_def_method (dvd_drive *dvd){
1780 return (dvd -> def_method);
1783 u_int32_t dvd_get_command (dvd_drive *dvd){
1784 return (dvd -> command);