X-Git-Url: https://git.jtryba.com/browse/friidump.git/blobdiff_plain/d44030dfa440b1967ec7dcd3ccfe46fb69934486..29417fff9ebac65b1cf60ea8951c687696302c55:/libfriidump/dvd_drive.c?ds=sidebyside diff --git a/libfriidump/dvd_drive.c b/libfriidump/dvd_drive.c index 036d38b..fe71f80 100644 --- a/libfriidump/dvd_drive.c +++ b/libfriidump/dvd_drive.c @@ -44,12 +44,14 @@ #include "dvd_drive.h" #include "disc.h" #include "sha1.h" +#include "xbox_ref/xbox_ref_log.h" #ifdef WIN32 #include #include #else #include +#include #include #include #include @@ -99,6 +101,9 @@ struct dvd_drive_s { 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 @@ -509,6 +514,41 @@ void dvd_init_command (mmc_command *mmc, u_int8_t *buf, int len, req_sense *sens } +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 */ @@ -516,6 +556,8 @@ 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; @@ -533,39 +575,34 @@ int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { else sptd -> DataIn = SCSI_IOCTL_DATA_IN; sptd -> DataBuffer = mmc -> buffer; - // Quick hack: Windows hates sptd->DataTransferLength = 1, so we set it to 2 and ignore the second byte. - if (mmc -> buflen == 1) // TODO + /* 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); - //fprintf (stdout,"mmc->cmd[00] = %d \n",mmc->cmd[00]); - //Set streaming hack - if (mmc->cmd[00]==0xB6) { + if (mmc -> cmd[0] == 0xB6) { sptd -> DataIn = SCSI_IOCTL_DATA_OUT; sptd -> DataTransferLength = 28; } - { - BOOL ioctl_ok = DeviceIoControl (dvd -> fd, IOCTL_SCSI_PASS_THROUGH_DIRECT, - sptd, sizeof (*sptd) + 18, sptd, sizeof (*sptd) + 18, &bytes, NULL); - /* DeviceIoControl may succeed while the drive returns CHECK CONDITION. - * Treat any non-GOOD SCSI status as command failure. ignore_errors only - * suppresses diagnostics; it must not turn failed commands into success. */ - if (!ioctl_ok || sptd -> ScsiStatus != 0) { - out = -1; /* Failure */ - if (!ignore_errors) { - error ("Execution of MMC command failed: Win32=%lu SCSI=0x%02X", - (unsigned long) GetLastError (), 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; + 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) { @@ -573,8 +610,10 @@ int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { 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); + return out; } #else @@ -588,6 +627,7 @@ int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { */ 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; @@ -596,8 +636,8 @@ int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { hex_and_ascii_print ("", mmc -> cmd, sizeof (mmc -> cmd)); #endif - /* Init Linux-format MMC command */ - memset (&cgc, 0, sizeof (struct cdrom_generic_command)); + 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; @@ -607,17 +647,19 @@ int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { cgc.data_direction = CGC_DATA_NONE; else cgc.data_direction = CGC_DATA_READ; - cgc.timeout = MMC_CMD_TIMEOUT * 1000; /* Linux uses milliseconds */ + cgc.timeout = MMC_CMD_TIMEOUT * 1000; cgc.sense = &sense; if (ioctl (dvd -> fd, CDROM_SEND_PACKET, &cgc) < 0) { - out = -1; /* Failure */ + saved_errno = errno; + out = -1; if (!ignore_errors) { - error ("Execution of MMC command failed: %s", strerror (errno)); + 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; } @@ -626,8 +668,220 @@ int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { 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); + 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 @@ -1012,6 +1266,20 @@ 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) @@ -1031,28 +1299,59 @@ 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) + 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) + 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. */ + * 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; } @@ -1324,10 +1623,13 @@ int dvd_read_dvd_structure (dvd_drive *dvd, u_int8_t format, u_int8_t layer, u_i 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.cmd[11] = format; mmc.cmdlen = 12; return dvd_execute_cmd (dvd, &mmc, false); @@ -1430,47 +1732,109 @@ int dvd_xbox_vendor_unlock_wxripper (dvd_drive *dvd, u_int32_t *unlocked_sectors } -int dvd_xbox_recovery_kick (dvd_drive *dvd, bool auth_recovery) { - mmc_command mmc; - u_int8_t dummy[2048]; - u_int8_t auth_probe_buf[8]; - int i; +#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; - /* Mirrors the proven dumper recovery cadence: after the GDR-8050L - * authentication/view switch, re-trigger the Xbox media auth command, set - * maximum speed, then issue a few harmless READ(10) probes. All commands - * deliberately ignore errors because the point is to wake/re-prime firmware - * state, not to make any one probe authoritative. - */ - if (auth_recovery) { - /* GDR-8050L / HLDS auth kick: READ DVD STRUCTURE C0 magic trigger. */ - dvd_init_command (&mmc, auth_probe_buf, sizeof (auth_probe_buf), NULL); - mmc.cmd[0] = MMC_READ_DVD_STRUCTURE; - mmc.cmd[2] = 0xFF; - mmc.cmd[3] = 0x02; - mmc.cmd[4] = 0xFD; - mmc.cmd[11] = 0xC0; - mmc.cmdlen = 12; - dvd_execute_cmd (dvd, &mmc, true); - - /* Hitachi/Kreon-style fallback auth kick: FF 08 01. */ - dvd_init_command (&mmc, NULL, 0, NULL); - mmc.cmd[0] = 0xFF; - mmc.cmd[1] = 0x08; - mmc.cmd[2] = 0x01; - mmc.cmdlen = 10; - mmc.direction = DVD_DATA_NONE; - dvd_execute_cmd (dvd, &mmc, true); + /* 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); - for (i = 0; i < 10; i++) - dvd_read_10 (dvd, 0, 1, NULL, dummy, sizeof (dummy)); - + if (sectors) + *sectors = observed_sectors; + if (sector_size) + *sector_size = observed_sector_size; return 0; } @@ -1496,11 +1860,16 @@ int dvd_lock_volume (dvd_drive *dvd) { #ifdef WIN32 DWORD bytesReturned = 0; if (!dvd) - return -1; - return DeviceIoControl (dvd -> fd, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL) ? 0 : -1; + 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 0; + return DVD_VOLUME_LOCK_UNAVAILABLE; #endif } @@ -1519,6 +1888,7 @@ static int dvd_xbox_read_host_challenge_table (dvd_drive *dvd, u_int8_t *table, 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; @@ -1543,14 +1913,65 @@ static int dvd_xbox_read_host_challenge_table (dvd_drive *dvd, u_int8_t *table, 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]; @@ -1567,8 +1988,15 @@ int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) { /* 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, §ors, §or_size, NULL); dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL); +#else + dvd_xbox_exact_read_capacity ( + dvd, "1-initial-read-capacity", §ors, §or_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) { @@ -1610,8 +2038,15 @@ int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) { /* 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)); @@ -1620,8 +2055,15 @@ int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) { 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)); @@ -1634,14 +2076,24 @@ int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) { 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 @@ -1649,12 +2101,24 @@ int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) { * prime the drive before the required media-change event. */ sectors = 0; sector_size = 0; - if (dvd_read_capacity_10 (dvd, §ors, §or_size, NULL) == 0) { +#ifdef WIN32 + out = dvd_read_capacity_10 (dvd, §ors, §or_size, NULL); +#else + out = dvd_xbox_exact_read_capacity ( + dvd, "10-final-read-capacity", §ors, §or_size); +#endif + if (out == 0) { if (unlocked_sectors) *unlocked_sectors = sectors; - fprintf (stderr, "[XBOX] GDR-8050L handshake complete; READ CAPACITY reports %u sectors of %u bytes.\n", sectors, sector_size); + xbox_ref_log_fprintf ( + stderr, + "[XBOX] GDR-8050L handshake complete; READ CAPACITY reports %u sectors of %u bytes.\n", + sectors, + sector_size); } else { - fprintf (stderr, "[XBOX] GDR-8050L handshake sent; final READ CAPACITY verify failed, continuing like original dumper.\n"); + xbox_ref_log_fprintf ( + stderr, + "[XBOX] GDR-8050L handshake sent; final READ CAPACITY verify failed, continuing like original dumper.\n"); } return 0;