+ 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);