Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement XDVD security challenge responses #1659

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ sys:
eeprom_path: string
hdd_path: string
dvd_path: string
dvd_security_path: string

perf:
hard_fpu:
Expand Down
139 changes: 139 additions & 0 deletions hw/ide/atapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@
#define ATAPI_SECTOR_BITS (2 + BDRV_SECTOR_BITS)
#define ATAPI_SECTOR_SIZE (1 << ATAPI_SECTOR_BITS)

#ifdef XBOX
#include "ui/xemu-settings.h"
#include "hw/xbox/xdvd/xdvd.h"
#endif

// #define DEBUG
#ifdef DEBUG

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be moved inside the above #ifdef XBOX conditional block.

# define XBOX_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
#else
# define XBOX_DPRINTF(format, ...) do { } while (0)
#endif

static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret);

static void padstr8(uint8_t *buf, int buf_size, const char *src)
Expand Down Expand Up @@ -61,11 +73,27 @@ static inline int media_present(IDEState *s)
/* XXX: DVDs that could fit on a CD will be reported as a CD */
static inline int media_is_dvd(IDEState *s)
{
#ifdef XBOX
// We need to prevent small XISOs from being reported as CD-ROMs
// If the DVD security path is set, we can assume it is an XISO.
const char *dvd_security_path = g_config.sys.files.dvd_security_path;
Copy link
Member

@mborgerson mborgerson Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably make this a qdev property, like disc file path, media type, etc. so it can play nicely with other systems

if (strlen(dvd_security_path) > 0) {
mborgerson marked this conversation as resolved.
Show resolved Hide resolved
return media_present(s);
}
#endif
return (media_present(s) && s->nb_sectors > CD_MAX_SECTORS);
}

static inline int media_is_cd(IDEState *s)
{
#ifdef XBOX
// We need to prevent small XISOs from being reported as CD-ROMs
// If the DVD security path is set, we can assume it is an XISO.
const char *dvd_security_path = g_config.sys.files.dvd_security_path;
if (strlen(dvd_security_path) > 0) {
return 0;
}
#endif
return (media_present(s) && s->nb_sectors <= CD_MAX_SECTORS);
}

Expand Down Expand Up @@ -485,6 +513,24 @@ static int ide_dvd_read_structure(IDEState *s, int format,
{
int layer = packet[6];
uint64_t total_sectors;
#ifdef XBOX
int block_number = ldl_be_p(buf + 2);

// Challenge table is located on disk runout, on Xbox these locations are always set to these for this command it looks like
if (layer == XDVD_STRUCTURE_LAYER && block_number == XDVD_STRUCTURE_BLOCK_NUMBER)
{
if (xdvd_get_encrypted_challenge_table(s->xdvd_challenges_encrypted))
{
xdvd_get_decrypted_responses(s->xdvd_challenges_encrypted, s->xdvd_challenges_decrypted);
memcpy(buf, s->xdvd_challenges_encrypted, XDVD_STRUCTURE_LEN);
return XDVD_STRUCTURE_LEN;
}
}

// We deferred clearing this earlier so do it now
int max_len = lduw_be_p(buf + 8);
memset(buf, 0, MIN(max_len, IDE_DMA_BUF_SECTORS * BDRV_SECTOR_SIZE + 4));
#endif

if (layer != 0)
return -ASC_INV_FIELD_IN_CMD_PACKET;
Expand Down Expand Up @@ -855,6 +901,56 @@ static void cmd_get_configuration(IDEState *s, uint8_t *buf)
ide_atapi_cmd_reply(s, len, max_len);
}

#ifdef XBOX
static void cmd_mode_select_cb(void *opaque, int ret)
{
IDEState *s = opaque;
uint8_t *buf = s->io_buffer;

if (ret < 0) {
block_acct_failed(blk_get_stats(s->blk), &s->acct);
ide_set_inactive(s, false);
return;
}

if (s->bus->dma->ops->rw_buf(s->bus->dma, 0))
{
s->status = READY_STAT | SEEK_STAT;
s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
ide_set_irq(s->bus);
}
block_acct_done(blk_get_stats(s->blk), &s->acct);
ide_set_inactive(s, false);

int max_len = lduw_be_p(buf) + 2;
if (max_len == XDVD_SECURITY_PAGE_LEN && buf[8] == MODE_PAGE_XBOX_SECURITY)
{
memcpy(&s->xdvd_security, buf, XDVD_SECURITY_PAGE_LEN);
XBOX_DPRINTF("Authenticated: %d, Partition: %d\n", s->xdvd_security.page.Authenticated, s->xdvd_security.page.Partition);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that the drive requires the full unlock challenge-response sequence to happen before it will unlock and we are not yet fully modeling this state machine. If this is correct and you don't plan to implement this, can you leave a comment so we can remember to come back to it?

s->xdvd_security.page.Authenticated = 1;
ide_atapi_cmd_ok(s);
}
}

static void cmd_mode_select(IDEState *s, uint8_t *buf)
{
int parameter_list_len = lduw_be_p(buf + 7);

// Does MODE SELECT contain more data? Read it out
if (parameter_list_len > 0)
{
s->lba = -1;
s->packet_transfer_size = parameter_list_len;
s->io_buffer_size = parameter_list_len;
s->elementary_transfer_size = 0;

block_acct_start(blk_get_stats(s->blk), &s->acct, parameter_list_len, BLOCK_ACCT_WRITE);
s->status = READY_STAT | SEEK_STAT | DRQ_STAT;
ide_start_dma(s, cmd_mode_select_cb);
}
}
#endif

static void cmd_mode_sense(IDEState *s, uint8_t *buf)
{
int action, code;
Expand Down Expand Up @@ -941,6 +1037,32 @@ static void cmd_mode_sense(IDEState *s, uint8_t *buf)
buf[29] = 0;
ide_atapi_cmd_reply(s, 30, max_len);
break;
#ifdef XBOX
case MODE_PAGE_XBOX_SECURITY:
// If this is the first response, get the default security page
if (s->xdvd_security.page.PageCode != MODE_PAGE_XBOX_SECURITY) {
xdvd_get_default_security_page(&s->xdvd_security);
}

XBOX_DPRINTF("Got MODE_SENSE: PAGE_XBOX_SECURITY. Responding with ss ");

s->xdvd_security.page.ResponseValue =
xdvd_get_challenge_response(s->xdvd_challenges_decrypted,
s->xdvd_security.page.ChallengeID);

XBOX_DPRINTF("Challenge value %08x, ID %02x, Response %08x\n",
s->xdvd_security.page.ChallengeValue,
s->xdvd_security.page.ChallengeID,
s->xdvd_security.page.ResponseValue);
XBOX_DPRINTF("Authenticated: %d, Partition: %d\n",
s->xdvd_security.page.Authenticated,
s->xdvd_security.page.Partition);

memcpy(buf, &s->xdvd_security, sizeof(s->xdvd_security));

ide_atapi_cmd_reply(s, sizeof(XBOX_DVD_SECURITY), max_len);
#endif
break;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This break must reside in the above conditional block, not after it.

default:
goto error_cmd;
}
Expand Down Expand Up @@ -993,6 +1115,12 @@ static void cmd_read(IDEState *s, uint8_t* buf)
}

lba = ldl_be_p(buf + 2);

#ifdef XBOX
lba = xdvd_get_lba_offset(&s->xdvd_security, total_sectors, lba);
total_sectors = xdvd_get_sector_cnt(&s->xdvd_security, total_sectors);
#endif

if (lba >= total_sectors || lba + nb_sectors - 1 >= total_sectors) {
ide_atapi_cmd_error(s, ILLEGAL_REQUEST, ASC_LOGICAL_BLOCK_OOR);
return;
Expand Down Expand Up @@ -1149,6 +1277,10 @@ static void cmd_read_cdvd_capacity(IDEState *s, uint8_t* buf)
{
uint64_t total_sectors = s->nb_sectors >> 2;

#ifdef XBOX
total_sectors = xdvd_get_sector_cnt(&s->xdvd_security, total_sectors);
#endif

/* NOTE: it is really the number of sectors minus 1 */
stl_be_p(buf, total_sectors - 1);
stl_be_p(buf + 4, 2048);
Expand Down Expand Up @@ -1206,8 +1338,12 @@ static void cmd_read_dvd_structure(IDEState *s, uint8_t* buf)
}
}

// Still some important info we don't want to lose so we disable clearing

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is one of the few "not defined" conditionals, it's perhaps nicer to change it into an #ifdef XBOX then the comment lines, and then an #else line before the code that has to be skipped under an Xbox build.

// on Xbox for now
#ifndef XBOX
memset(buf, 0, max_len > IDE_DMA_BUF_SECTORS * BDRV_SECTOR_SIZE + 4 ?
IDE_DMA_BUF_SECTORS * BDRV_SECTOR_SIZE + 4 : max_len);
#endif

switch (format) {
case 0x00 ... 0x7f:
Expand Down Expand Up @@ -1291,6 +1427,9 @@ static const struct AtapiCmd {
[ 0x46 ] = { cmd_get_configuration, ALLOW_UA },
[ 0x4a ] = { cmd_get_event_status_notification, ALLOW_UA },
[ 0x51 ] = { cmd_read_disc_information, CHECK_READY },
#ifdef XBOX
[ 0x55 ] = { cmd_mode_select,/* (10) */ 0 },
#endif
[ 0x5a ] = { cmd_mode_sense, /* (10) */ 0 },
[ 0xa8 ] = { cmd_read, /* (12) */ CHECK_READY },
[ 0xad ] = { cmd_read_dvd_structure, CHECK_READY },
Expand Down
14 changes: 14 additions & 0 deletions hw/ide/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,13 @@ static void ide_cd_change_cb(void *opaque, bool load, Error **errp)
s->cdrom_changed = 1;
s->events.new_media = true;
s->events.eject_request = false;

#ifdef XBOX
memset(s->xdvd_challenges_encrypted, 0, sizeof(s->xdvd_challenges_encrypted));
memset(s->xdvd_challenges_decrypted, 0, sizeof(s->xdvd_challenges_decrypted));
memset(&s->xdvd_security, 0, sizeof(s->xdvd_security));
#endif

ide_set_irq(s->bus);
}

Expand Down Expand Up @@ -1398,6 +1405,13 @@ static void ide_reset(IDEState *s)
s->end_transfer_func = ide_dummy_transfer_stop;
ide_dummy_transfer_stop(s);
s->media_changed = 0;

#ifdef XBOX
memset(s->xdvd_challenges_encrypted, 0, sizeof(s->xdvd_challenges_encrypted));
memset(s->xdvd_challenges_decrypted, 0, sizeof(s->xdvd_challenges_decrypted));
memset(&s->xdvd_security, 0, sizeof(s->xdvd_security));
#endif

}

static bool cmd_nop(IDEState *s, uint8_t cmd)
Expand Down
1 change: 1 addition & 0 deletions hw/xbox/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ specific_ss.add(files(
))
subdir('nv2a')
subdir('mcpx')
subdir('xdvd')
6 changes: 6 additions & 0 deletions hw/xbox/xdvd/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
xdvd_ss = ss.source_set()
xdvd_ss.add(files(
'xdvd.c',
))

specific_ss.add_all(xdvd_ss)
Loading
Loading