From 1923f3ec66723451d1cbdc8efa49768a29767bc9 Mon Sep 17 00:00:00 2001 From: OBattler Date: Thu, 24 Jul 2025 12:08:58 +0200 Subject: [PATCH] Added MDS support. --- src/cdrom/cdrom_image.c | 529 ++++++++++++++++++++++++++++++++++++++-- src/qt/qt_mediamenu.cpp | 2 +- 2 files changed, 510 insertions(+), 21 deletions(-) diff --git a/src/cdrom/cdrom_image.c b/src/cdrom/cdrom_image.c index 6bc284fd3..6f1137b19 100644 --- a/src/cdrom/cdrom_image.c +++ b/src/cdrom/cdrom_image.c @@ -19,6 +19,7 @@ #define __STDC_FORMAT_MACROS #include #include +// #define ENABLE_IMAGE_LOG 1 #ifdef ENABLE_IMAGE_LOG #include #endif @@ -26,6 +27,7 @@ #include #include #include +#include #include #ifndef _WIN32 # include @@ -84,17 +86,146 @@ typedef struct track_t { track_index_t idx[3]; } track_t; +/* + MDS for DVD has the disc structure table - 4 byte pointer to BCA, + followed by the copyright, DMI, and layer pages. +*/ +#pragma pack(push, 1) +typedef struct +{ + uint8_t f1[4]; + uint8_t f4[2048]; + uint8_t f0[2048]; +} layer_t; + +typedef struct +{ + layer_t layers[2]; +} mds_disc_struct_t; +#pragma pack(pop) + +#define dstruct_t mds_disc_struct_t + typedef struct cd_image_t { cdrom_t *dev; void *log; int is_dvd; int has_audio; + int has_dstruct; int32_t tracks_num; uint32_t bad_sectors_num; track_t *tracks; uint32_t *bad_sectors; + dstruct_t dstruct; } cd_image_t; +typedef enum +{ + CD = 0x00, /* CD-ROM */ + CD_R = 0x01, /* CD-R */ + CD_RW = 0x02, /* CD-RW */ + DVD = 0x10, /* DVD-ROM */ + DVD_MINUS_R = 0x12 /* DVD-R */ +} mds_medium_type_t; + +typedef enum +{ + UNKNOWN = 0x00, + AUDIO = 0xa9, /* sector size = 2352 */ + MODE1 = 0xaa, /* sector size = 2048 */ + MODE2 = 0xab, /* sector size = 2336 */ + MODE2_FORM1 = 0xac, /* sector size = 2048 */ + MODE2_FORM2 = 0xad /* sector size = 2324 (+4) */ +} mds_trk_mode_t; + +typedef enum +{ + NONE = 0x00, /* no subchannel */ + PW_INTERLEAVED = 0x08 /* 96-byte PW subchannel, interleaved */ +} mds_subch_mode_t; + +typedef struct +{ + uint8_t file_sig[16]; + uint8_t file_ver[2]; + uint16_t medium_type; + uint16_t sess_num; + uint16_t pad[2]; + uint16_t bca_data_len; + uint32_t pad0[2]; + uint32_t bca_data_offs_offs; + uint32_t pad1[6]; + uint32_t disc_struct_offs; + uint32_t pad2[3]; + uint32_t sess_blocks_offs; + uint32_t dpm_blocks_offs; +} mds_hdr_t; /* 88 bytes */ + +typedef struct +{ + int32_t sess_start; + int32_t sess_end; + uint16_t sess_id; + uint8_t all_blocks_num; + uint8_t non_track_blocks_num; + uint16_t first_trk; + uint16_t last_trk; + uint32_t pad; + uint32_t trk_blocks_offs; +} mds_sess_block_t; /* 24 bytes */ + +typedef struct +{ + uint8_t trk_mode; + /* DiscImageCreator says this is the number of subchannels. */ + uint8_t subch_mode; + uint8_t adr_ctl; + uint8_t track_id; + uint8_t point; + uint8_t m; + uint8_t s; + uint8_t f; + uint8_t zero; + uint8_t pm; + uint8_t ps; + uint8_t pf; + /* DiscImageCreator calls this the index offset. */ + uint32_t ex_offs; + uint16_t sector_len; + /* DiscImageCreator says unknown1 followed by 17x zero. */ + uint8_t pad0[18]; + uint32_t start_sect; + uint64_t start_offs; + uint32_t files_num; + uint32_t footer_offs; + uint8_t pad1[24]; +} mds_trk_block_t; /* 80 bytes */ + +/* + DiscImageCreator's interpretation here makes sense and essentially + matches libmirage's - Index 0 sectors followed by Index 1 sectors. + */ +typedef struct +{ + uint32_t pregap; + uint32_t trk_sectors; +} mds_trk_ex_block_t; /* 8 bytes */ + +typedef struct +{ + uint32_t fn_offs; + uint32_t fn_is_wide; + uint32_t pad; + uint32_t pad0; +} mds_footer_t; /* 16 bytes */ + +typedef struct +{ + uint32_t type; + uint32_t pad[2]; + uint32_t entries; +} mds_dpm_block_t; + #ifdef ENABLE_IMAGE_LOG int image_do_log = ENABLE_IMAGE_LOG; @@ -284,6 +415,9 @@ bin_close(void *priv) memset(tf->fn, 0x00, sizeof(tf->fn)); + log_close(tf->log); + tf->log = NULL; + free(priv); } @@ -298,6 +432,11 @@ bin_init(const uint8_t id, const char *filename, int *error) return NULL; } + char n[1024] = { 0 }; + + sprintf(n, "CD-ROM %i Bin ", id + 1); + tf->log = log_open(n); + memset(tf->fn, 0x00, sizeof(tf->fn)); strncpy(tf->fn, filename, sizeof(tf->fn) - 1); tf->fp = plat_fopen64(tf->fn, "rb"); @@ -314,11 +453,6 @@ bin_init(const uint8_t id, const char *filename, int *error) tf->read = bin_read; tf->get_length = bin_get_length; tf->close = bin_close; - - char n[1024] = { 0 }; - - sprintf(n, "CD-ROM %i Bin ", id + 1); - tf->log = log_open(n); } else { /* From the check above, error may still be non-zero if opening a directory. * The error is set for viso to try and open the directory following this function. @@ -326,8 +460,12 @@ bin_init(const uint8_t id, const char *filename, int *error) if ((tf->fp != NULL) && ((stats.st_mode & S_IFMT) == S_IFDIR)) { /* tf is freed by bin_close */ bin_close(tf); - } else + } else { + log_close(tf->log); + tf->log = NULL; + free(tf); + } tf = NULL; } @@ -1183,7 +1321,7 @@ image_process(cd_image_t *img) ct->point, j, cit[ci->type + 2], ci->file_start * ct->sector_size); image_log(img->log, " TOC data: %02X %02X %02X " - "%%02X %02X %02X %02X 02X %02X %02X %02X\n", + "%02X %02X %02X %02X %02X %02X %02X %02X\n", ct->session, ct->attr, ct->tno, ct->point, ct->extra[0], ct->extra[1], ct->extra[2], ct->extra[3], (uint32_t) ((ci->start / 75) / 60), @@ -1446,9 +1584,9 @@ image_load_cue(cd_image_t *img, const char *cuefile) if (last_t != -1) { /* Important: This has to be done like this because pointers - change due to realloc. + change due to realloc. */ - ct = &(img->tracks[t]); + ct = &(img->tracks[img->tracks_num - 1]); for (int i = 2; i >= 0; i--) { if (ct->idx[i].file == NULL) @@ -1662,6 +1800,321 @@ image_load_cue(cd_image_t *img, const char *cuefile) return success; } +static int +image_load_mds(cd_image_t *img, const char *mdsfile) +{ + track_t *ct = NULL; + track_index_t *ci = NULL; + track_file_t *tf = NULL; + int is_viso = 0; + int last_t = -1; + int error; + char pathname[MAX_FILENAME_LENGTH]; + + mds_hdr_t mds_hdr = { 0 }; + mds_sess_block_t mds_sess_block = { 0 }; + mds_trk_block_t mds_trk_block = { 0 }; + mds_trk_ex_block_t mds_trk_ex_block = { 0 }; + mds_footer_t mds_footer = { 0 }; + mds_dpm_block_t mds_dpm_block = { 0 }; + uint32_t mds_dpm_blocks_num = 0x00000000; + uint32_t mds_dpm_block_offs = 0x00000000; + + img->tracks = NULL; + img->tracks_num = 0; + + /* Get a copy of the filename into pathname, we need it later. */ + memset(pathname, 0, MAX_FILENAME_LENGTH * sizeof(char)); + path_get_dirname(pathname, mdsfile); + + /* Open the file. */ + FILE *fp = plat_fopen(mdsfile, "rb"); + if (fp == NULL) + return 0; + + int success = 0; + + /* + Pass 1 - loading the MDS sheet. + */ + image_log(img->log, "Pass 1 (loading the Media Descriptor Sheet)...\n"); + img->tracks_num = 0; + success = 2; + + fseek(fp, 0, SEEK_SET); + fread(&mds_hdr, 1, sizeof(mds_hdr_t), fp); + if (memcmp(mds_hdr.file_sig, "MEDIA DESCRIPTOR", 16)) { + image_log(img->log, " [MDS ] Not an actual MDF file \"%s\"\n", mdsfile); + fclose(fp); + return 0; + } + + if (mds_hdr.file_ver[0] == 0x02) { + image_log(img->log, " [MDS ] Daemon tools encrypted MDS is not supported in file \"%s\"\n", mdsfile); + fclose(fp); + return 0; + } + + img->is_dvd = (mds_hdr.medium_type >= 0x10); + + if (img->is_dvd) { + if (mds_hdr.disc_struct_offs != 0x00) { + fseek(fp, mds_hdr.disc_struct_offs, SEEK_SET); + fread(&(img->dstruct.layers[0]), 1, sizeof(layer_t), fp); + img->has_dstruct = 1; + + if (((img->dstruct.layers[0].f0[2] & 0x60) >> 4) == 0x01) { + fseek(fp, mds_hdr.disc_struct_offs, SEEK_SET); + fread(&(img->dstruct.layers[1]), 1, sizeof(layer_t), fp); + img->has_dstruct++; + } + } + + for (int t = 0; t < 3; t++) { + ct = image_insert_track(img, 1, 0xa0 + t); + + ct->attr = DATA_TRACK; + ct->mode = 0; + ct->form = 0; + ct->tno = 0; + ct->subch_type = 0; + memset(ct->extra, 0x00, 4); + + for (int i = 0; i < 3; i++) { + ci = &(ct->idx[i]); + ci->type = INDEX_NONE; + ci->start = 0; + ci->length = 0; + ci->file_start = 0; + ci->file_length = 0; + ci->file = NULL; + } + + ci = &(ct->idx[1]); + + if (t < 2) + ci->start = (0x01 * 60 * 75) + (0 * 75) + 0; + } + } + + if (mds_hdr.dpm_blocks_offs != 0x00) { + fseek(fp, mds_hdr.dpm_blocks_offs, SEEK_SET); + fread(&mds_dpm_blocks_num, 1, sizeof(uint32_t), fp); + + if (mds_dpm_blocks_num > 0) for (int b = 0; b < mds_dpm_blocks_num; b++) { + fseek(fp, mds_hdr.dpm_blocks_offs + 4 + (b * 4), SEEK_SET); + fread(&mds_dpm_block_offs, 1, sizeof(uint32_t), fp); + + fseek(fp, mds_dpm_block_offs, SEEK_SET); + fread(&mds_dpm_block, 1, 4 * sizeof(mds_dpm_block_t), fp); + + /* We currently only support the bad sectors block and not (yet) actual DPM. */ + if (mds_dpm_block.type == 0x00000002) { + /* Bad sectors. */ + img->bad_sectors_num = mds_dpm_block.entries; + img->bad_sectors = (uint32_t *) malloc(img->bad_sectors_num * sizeof(uint32_t)); + fseek(fp, mds_dpm_block_offs + sizeof(mds_dpm_block_t), SEEK_SET); + fread(img->bad_sectors, 1, img->bad_sectors_num * sizeof(uint32_t), fp); + break; + } + } + } + + for (int s = 0; s < mds_hdr.sess_num; s++) { + fseek(fp, mds_hdr.sess_blocks_offs + (s * sizeof(mds_sess_block_t)), SEEK_SET); + fread(&mds_sess_block, 1, sizeof(mds_sess_block_t), fp); + + for (int t = 0; t < mds_sess_block.all_blocks_num; t++) { + fseek(fp, mds_sess_block.trk_blocks_offs + (t * sizeof(mds_trk_block_t)), SEEK_SET); + fread(&mds_trk_block, 1, sizeof(mds_trk_block_t), fp); + + if (last_t != -1) { + /* + Important: This has to be done like this because pointers + change due to realloc. + */ + ct = &(img->tracks[img->tracks_num - 1]); + + for (int i = 2; i >= 0; i--) { + if (ct->idx[i].file == NULL) + ct->idx[i].file = tf; + else + break; + } + } + + last_t = mds_trk_block.point; + ct = image_insert_track(img, mds_sess_block.sess_id, mds_trk_block.point); + + tf = NULL; + + if (img->is_dvd) { + /* DVD images have no extra block - the extra block offset is the track length. */ + memset(&mds_trk_ex_block, 0x00, sizeof(mds_trk_ex_block_t)); + mds_trk_ex_block.pregap = 0x00000000; + mds_trk_ex_block.trk_sectors = mds_trk_block.ex_offs; + } else if (mds_trk_block.ex_offs != 0ULL) { + fseek(fp, mds_trk_block.ex_offs, SEEK_SET); + fread(&mds_trk_ex_block, 1, sizeof(mds_trk_ex_block), fp); + } + + uint32_t astart = mds_trk_block.start_sect - mds_trk_ex_block.pregap; + uint32_t aend = astart + mds_trk_ex_block.pregap; + uint32_t aend2 = aend + mds_trk_ex_block.trk_sectors; + uint32_t astart2 = mds_trk_block.start_sect + mds_trk_ex_block.trk_sectors; + + if (mds_trk_block.footer_offs != 0ULL) for (uint32_t ff = 0; ff < mds_trk_block.files_num; ff++) { + fseek(fp, mds_trk_block.footer_offs + (ff * sizeof(mds_footer_t)), SEEK_SET); + fread(&mds_footer, 1, sizeof(mds_footer_t), fp); + + wchar_t wfn[2048] = { 0 }; + char fn[2048] = { 0 }; + fseek(fp, mds_footer.fn_offs, SEEK_SET); + if (mds_footer.fn_is_wide) { + fgetws(wfn, 256, fp); + wcstombs(fn, wfn, 256); + } else + fgets(fn, 512, fp); + if (!stricmp(fn, "*.mdf")) { + strcpy(fn, mdsfile); + fn[strlen(mdsfile) - 3] = 'm'; + fn[strlen(mdsfile) - 2] = 'd'; + fn[strlen(mdsfile) - 1] = 'f'; + } + + char filename[2048] = { 0 }; + if (!path_abs(fn)) + path_append_filename(filename, pathname, fn); + else + strcpy(filename, fn); + + tf = index_file_init(img->dev->id, filename, &error, &is_viso); + } + + ct->sector_size = mds_trk_block.sector_len; + ct->form = 0; + ct->tno = mds_trk_block.track_id; + ct->subch_type = mds_trk_block.subch_mode; + ct->extra[0] = mds_trk_block.m; + ct->extra[1] = mds_trk_block.s; + ct->extra[2] = mds_trk_block.f; + ct->extra[3] = mds_trk_block.zero; + /* + Note from DiscImageCreator: + + I hexedited the track mode field with various values and fed it to Alchohol; + it seemed that high part of byte had no effect at all; only the lower one + affected the mode, in the following manner: + 00: Mode 2, 01: Audio, 02: Mode 1, 03: Mode 2, 04: Mode 2 Form 1, + 05: Mode 2 Form 2, 06: UKNONOWN, 07: Mode 2 + 08: Mode 2, 09: Audio, 0A: Mode 1, 0B: Mode 2, 0C: Mode 2 Form 1, + 0D: Mode 2 Form 2, 0E: UKNONOWN, 0F: Mode 2 + */ + ct->attr = ((mds_trk_block.trk_mode & 0x07) == 0x01) ? + AUDIO_TRACK : DATA_TRACK; + ct->mode = 0; + ct->form = 0; + if (((mds_trk_block.trk_mode & 0x07) != 0x01) && + ((mds_trk_block.trk_mode & 0x07) != 0x06)) + ct->mode = ((mds_trk_block.trk_mode & 0x07) != 0x02) + 1; + if ((mds_trk_block.trk_mode & 0x06) == 0x04) + ct->form = (mds_trk_block.trk_mode & 0x07) - 0x03; + if (ct->attr == AUDIO_TRACK) + success = 1; + + if (((ct->sector_size == 2336) || (ct->sector_size == 2332)) && (ct->mode == 2) && (ct->form == 1)) + ct->skip = 8; + + ci = &(ct->idx[0]); + if (ct->point < 0xa0) { + ci->start = astart + 150; + ci->length = mds_trk_ex_block.pregap; + } + ci->type = (ci->length > 0) ? INDEX_ZERO : INDEX_NONE; + ci->file_start = 0; + ci->file_length = 0; + ci->file = NULL; + + ci = &(ct->idx[1]); + if ((mds_trk_block.point >= 1) && (mds_trk_block.point <= 99)) { + ci->start = aend + 150; + ci->length = mds_trk_ex_block.trk_sectors; + ci->type = INDEX_NORMAL; + ci->file_start = mds_trk_block.start_offs / ct->sector_size; + ci->file_length = ci->length; + ci->file = tf; + } else { + ci->start = (mds_trk_block.pm * 60 * 75) + (mds_trk_block.ps * 75) + mds_trk_block.pf; + ci->type = INDEX_ZERO; + ci->file_start = 0; + ci->file_length = 0; + ci->file = NULL; + } + + ci = &(ct->idx[2]); + if (ct->point < 0xa0) { + ci->start = aend2 + 150; + ci->length = astart2 - aend2; + } + ci->type = (ci->length > 0) ? INDEX_ZERO : INDEX_NONE; + ci->file_start = 0; + ci->file_length = 0; + ci->file = NULL; + + if (img->is_dvd) { + ci = &(ct->idx[1]); + uint32_t total = ci->start + ci->length; + + ci = &(img->tracks[2].idx[1]); + ci->start = total; + } + } + + for (int i = 2; i >= 0; i--) { + if (ct->point >= 0xa0) + ci->type = INDEX_SPECIAL; + + if (ct->idx[i].file == NULL) + ct->idx[i].file = tf; + else + break; + } + } + + tf = NULL; + + fclose(fp); + + if (success) { + // image_process(img); + +#ifdef ENABLE_IMAGE_LOG + image_log(img->log, "Final tracks list:\n"); + for (int i = 0; i < img->tracks_num; i++) { + ct = &(img->tracks[i]); + for (int j = 0; j < 3; j++) { + ci = &(ct->idx[j]); + image_log(img->log, " [TRACK ] %02X INDEX %02X: [%8s, %016" PRIX64 "]\n", + ct->point, j, + cit[ci->type + 2], ci->file_start * ct->sector_size); + image_log(img->log, " TOC data: %02X %02X %02X " + "%02X %02X %02X %02X %02X %02X %02X %02X\n", + ct->session, ct->attr, ct->tno, ct->point, + ct->extra[0], ct->extra[1], ct->extra[2], ct->extra[3], + (uint32_t) ((ci->start / 75) / 60), + (uint32_t) ((ci->start / 75) % 60), + (uint32_t) (ci->start % 75)); + } + } +#endif + } else { + image_log(img->log, " [MDS ] Unable to open MDS sheet \"%s\"\n", mdsfile); + return 0; + } + + return success; +} + /* Root functions. */ static void image_clear_tracks(cd_image_t *img) @@ -1945,7 +2398,27 @@ static int image_read_dvd_structure(const void *local, const uint8_t layer, const uint8_t format, uint8_t *buffer, uint32_t *info) { - return 0; + const cd_image_t *img = (const cd_image_t *) local; + int ret = 0; + + if ((img->has_dstruct > 0) && ((layer + 1) > img->has_dstruct)) { + switch (format) { + case 0x00: + memcpy(buffer + 4, img->dstruct.layers[layer].f0, 2048); + ret = 2048 + 2; + break; + case 0x01: + memcpy(buffer + 4, img->dstruct.layers[layer].f1, 4); + ret = 4 + 2; + break; + case 0x04: + memcpy(buffer + 4, img->dstruct.layers[layer].f4, 2048); + ret = 2048 + 2; + break; + } + } + + return ret; } static int @@ -1977,6 +2450,9 @@ image_close(void *local) log_close(img->log); img->log = NULL; + if (img->bad_sectors != NULL) + free(img->bad_sectors); + free(img); } } @@ -2005,11 +2481,27 @@ image_open(cdrom_t *dev, const char *path) if (img != NULL) { int ret; - const int is_cue = ((ext == 4) && !stricmp(path + strlen(path) - ext + 1, "CUE")); + const int is_cue = ((ext == 4) && !stricmp(path + strlen(path) - ext + 1, "CUE")); + const int is_mds = ((ext == 4) && !stricmp(path + strlen(path) - ext + 1, "MDS")); + char n[1024] = { 0 }; + + sprintf(n, "CD-ROM %i Image", dev->id + 1); + img->log = log_open(n); img->dev = dev; - if (is_cue) { + if (is_mds) { + ret = image_load_mds(img, path); + + if (ret >= 2) + img->has_audio = 0; + else if (ret) + img->has_audio = 1; + else { + image_close(img); + img = NULL; + } + } else if (is_cue) { ret = image_load_cue(img, path); if (ret >= 2) @@ -2030,15 +2522,12 @@ image_open(cdrom_t *dev, const char *path) img->has_audio = 0; } - if (ret) { - char n[1024] = { 0 }; - - sprintf(n, "CD-ROM %i Image", dev->id + 1); - img->log = log_open(n); - + if (ret) dev->ops = &image_ops; - } else - warning("Unable to load CD-ROM image: %s\n", path); + else { + log_warning(img->log, "Unable to load CD-ROM image: %s\n", path); + log_close(img->log); + } } return img; diff --git a/src/qt/qt_mediamenu.cpp b/src/qt/qt_mediamenu.cpp index cf14d7826..2a0b3bbfd 100644 --- a/src/qt/qt_mediamenu.cpp +++ b/src/qt/qt_mediamenu.cpp @@ -597,7 +597,7 @@ MediaMenu::cdromMount(int i, int dir, const QString &arg) else { filename = QFileDialog::getOpenFileName(parentWidget, QString(), QString(), - tr("CD-ROM images") % util::DlgFilter({ "iso", "cue" }) % tr("All files") % util::DlgFilter({ "*" }, true)); + tr("CD-ROM images") % util::DlgFilter({ "iso", "cue", "mds" }) % tr("All files") % util::DlgFilter({ "*" }, true)); } if (filename.isEmpty())