/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Generic CD-ROM drive core header.
 *
 * Authors: Miran Grca, <mgrca8@gmail.com>
 *
 *          Copyright 2016-2019 Miran Grca.
 */
#ifndef EMU_CDROM_H
#define EMU_CDROM_H

#ifndef EMU_VERSION_H
#include <86box/version.h>
#endif

#define CDROM_NUM                   8

#define CD_STATUS_EMPTY             0
#define CD_STATUS_DATA_ONLY         1
#define CD_STATUS_DVD               2
#define CD_STATUS_PAUSED            4
#define CD_STATUS_PLAYING           5
#define CD_STATUS_STOPPED           6
#define CD_STATUS_PLAYING_COMPLETED 7
#define CD_STATUS_HOLD              8
#define CD_STATUS_DVD_REJECTED      16
#define CD_STATUS_HAS_AUDIO         0xc
#define CD_STATUS_MASK              0x1f

/* Medium changed flag. */
#define CD_STATUS_TRANSITION     0x40
#define CD_STATUS_MEDIUM_CHANGED 0x80

#define CD_TRACK_UNK_DATA        0x04
#define CD_TRACK_NORMAL          0x00
#define CD_TRACK_AUDIO           0x08
#define CD_TRACK_CDI             0x10
#define CD_TRACK_XA              0x20
#define CD_TRACK_MODE_MASK       0x30
#define CD_TRACK_MODE2           0x04
#define CD_TRACK_MODE2_MASK      0x07

#define CD_TOC_NORMAL            0
#define CD_TOC_SESSION           1
#define CD_TOC_RAW               2

#define CD_IMAGE_HISTORY         10

#define CDROM_IMAGE              200

/* This is so that if/when this is changed to something else,
   changing this one define will be enough. */
#define CDROM_EMPTY              !dev->host_drive

#define DVD_LAYER_0_SECTORS      0x00210558ULL

#define RAW_SECTOR_SIZE          2352
#define COOKED_SECTOR_SIZE       2048

#define CD_BUF_SIZE              (16 * RAW_SECTOR_SIZE)

#define DATA_TRACK               0x14
#define AUDIO_TRACK              0x10

#define CD_FPS                   75

#define _LUT_SIZE                0x100

#define FRAMES_TO_MSF(f, M, S, F)                 \
    {                                             \
        uint64_t value = f;                       \
        *(F)           = (value % CD_FPS) & 0xff; \
        value /= CD_FPS;                          \
        *(S) = (value % 60) & 0xff;               \
        value /= 60;                              \
        *(M) = value & 0xff;                      \
    }
#define MSF_TO_FRAMES(M, S, F) ((M) *60 * CD_FPS + (S) *CD_FPS + (F))

typedef struct SMSF {
    uint16_t min;
    uint8_t  sec;
    uint8_t  fr;
} TMSF;

#ifdef __cplusplus
extern "C" {
#endif

enum {
    CDROM_BUS_DISABLED =  0,
    CDROM_BUS_PHILIPS  =  1,
    CDROM_BUS_SONY     =  2,
    CDROM_BUS_HITACHI  =  3,
    CDROM_BUS_MKE      =  4,
    CDROM_BUS_MITSUMI  =  5,
    CDROM_BUS_LPT      =  6,
    CDROM_BUS_ATAPI    =  8,
    CDROM_BUS_SCSI     =  9,
    CDROM_BUS_USB      = 10
};

#define BUS_TYPE_MKE                CDROM_BUS_MKE
#define BUS_TYPE_IDE                CDROM_BUS_ATAPI
#define BUS_TYPE_SCSI               CDROM_BUS_SCSI
#define BUS_TYPE_BOTH              -2
#define BUS_TYPE_NONE              -1

#define CDV                         EMU_VERSION_EX

static const struct cdrom_drive_types_s {
    const char   *vendor;
    const char   *model;
    const char   *revision;
    const char   *internal_name;
    const int     bus_type;
    /* SCSI standard for SCSI (or both) devices, early for IDE. */
    const int     scsi_std;
    const int     speed;
    const int     inquiry_len;
    const int     caddy;
    const int     is_dvd;
    const int     transfer_max[4];
} cdrom_drive_types[] = {
    { EMU_NAME,   "86B_CD",           CDV,    "86cd",           BUS_TYPE_BOTH, 2, -1, 36, 0, 0, {  4,  2,  2,  5 } },
    /* SCSI-1 / early ATAPI generic - second on purpose so the later variant is the default. */
    { EMU_NAME,   "86B_CD",           "1.00", "86cd100",        BUS_TYPE_BOTH, 1, -1, 36, 1, 0, {  0, -1, -1, -1 } },
    /* No difference from 86BOX CD-ROM, other than name - but enough people have requested such a name to warrant it. */
    { EMU_NAME,   "86B_DVD",          "5.00", "86dvd",          BUS_TYPE_BOTH, 2, -1, 36, 0, 1, {  4,  2,  2,  5 } },
    { "ASUS",     "CD-S500/A",        "1.41", "asus_500",       BUS_TYPE_IDE,  0, 50, 36, 0, 0, {  4,  2,  2,  2 } },
    { "ASUS",     "CD-S520/A4",       "1.32", "asus_520",       BUS_TYPE_IDE,  0, 52, 36, 0, 0, {  4,  2,  2,  2 } },
    { "AZT",      "CDA46802I",        "1.15", "azt_cda",        BUS_TYPE_IDE,  0,  4, 36, 0, 0, {  3,  0,  0,  0 } },
    { "BTC",      "CD-ROM BCD36XH",   "U1.0", "btc_36xh",       BUS_TYPE_IDE,  0, 36, 36, 0, 0, {  4,  2,  2, -1 } },
    { "GOLDSTAR", "CRD-8160B",        "3.14", "goldstar",       BUS_TYPE_IDE,  0, 16, 36, 0, 0, {  4,  2,  2, -1 } },
    { "GOLDSTAR", "CRD-8400B",        "1.03", "goldstar_8400b", BUS_TYPE_IDE,  0, 40, 36, 0, 0, {  4,  2,  2, -1 } },
    /* TODO: Find an IDENTIFY and/or INQUIRY dump. */
    { "GOLDSTAR", "GCD-R560B",        "1.00", "goldstar_r560b", BUS_TYPE_IDE,  0,  6, 36, 0, 0, {  4,  2,  2, -1 } },
    { "HITACHI",  "CDR-8130",         "0020", "hitachi_r8130",  BUS_TYPE_IDE,  0, 16, 36, 0, 0, {  4,  2,  2, -1 } },
    { "HITACHI",  "CDR-8435",         "0010", "hitachi_r8435",  BUS_TYPE_IDE,  0, 32, 36, 0, 0, {  4,  2,  2, -1 } },
    { "HITACHI",  "GD-7500",          "A1  ", "hitachi_7500",   BUS_TYPE_IDE,  0, 40, 36, 0, 1, {  4,  2,  2,  2 } }, /* DVD. */
    { "HL-DT-ST", "CD-ROM GCR-8526B", "1.01", "hldtst_8526b",   BUS_TYPE_IDE,  0, 52, 36, 0, 0, {  4,  2,  2,  2 } },
    { "HL-DT-ST", "DVDRAM GSA-4160",  "A302", "hldtst_4160",    BUS_TYPE_IDE,  0, 40, 36, 0, 1, {  4,  2,  2,  2 } },
    { "KENWOOD",  "CD-ROM UCR-421",   "208E", "kenwood_421",    BUS_TYPE_IDE,  0, 72, 36, 0, 0, {  4,  2,  2,  4 } },
    /*
       This is a laptop/notebook drive, as is also evident from the name:
           CRN = Notebook, CRD = Desktop.
     */
    { "LG",       "CD-ROM CRN-8245B", "1.30", "lg_8245b",       BUS_TYPE_IDE,  0, 24, 36, 0, 0, {  4,  2,  2, -1 } },
    { "LG",       "CD-ROM CRD-8322B", "1.06", "lg_8322b",       BUS_TYPE_IDE,  0, 32, 36, 0, 0, {  4,  2,  2, -1 } },
    /* Nothing on Google, deduced 48x from the name. */
    { "LITE-ON",  "LTN48125S",        "1S07", "liteon_48125s",  BUS_TYPE_IDE,  0, 48, 36, 0, 0, {  4,  2,  2,  2 } },
    /* Confirmed to be 52x, was the basis for deducing the other one's speed. */
    { "LITE-ON",  "LTN526D",          "YSR5", "liteon_526d",    BUS_TYPE_IDE,  0, 52, 36, 0, 0, {  4,  2,  2,  2 } },
    /* TODO: Version is from the CR-581-M, find an IDENTIFY and/or INQUIRY dup of te CR-581-M. */
    { "MATSHITA", "CD-ROM CR-581-B",  "1.05", "matshita_581",   BUS_TYPE_IDE,  0,  4, 36, 0, 0, {  4,  2,  2, -1 } },
    { "MATSHITA", "CD-ROM CR-583",    "1.07", "matshita_583",   BUS_TYPE_IDE,  0,  8, 36, 0, 0, {  4,  2,  2, -1 } },
    { "MATSHITA", "CD-ROM CR-585",    "Z18P", "matshita_585",   BUS_TYPE_IDE,  0, 24, 36, 0, 0, {  4,  2,  2, -1 } },
    { "MATSHITA", "CD-ROM CR-587",    "7S13", "matshita_587",   BUS_TYPE_IDE,  0, 24, 36, 0, 0, {  4,  2,  2, -1 } },
    { "MATSHITA", "CD-ROM CR-588",    "LS15", "matshita_588",   BUS_TYPE_IDE,  0, 32, 36, 0, 0, {  4,  2,  2, -1 } },
    { "MATSHITA", "CR-571",           "1.0e", "matshita_571",   BUS_TYPE_IDE,  0,  2, 36, 0, 0, {  0, -1, -1, -1 } },
    { "MATSHITA", "CR-572",           "1.0j", "matshita_572",   BUS_TYPE_IDE,  0,  4, 36, 0, 0, {  0, -1, -1, -1 } },
    { "MITSUMI",  "CRMC-FX4820T",     "D02A", "mitsumi_4820t",  BUS_TYPE_IDE,  0, 48, 36, 0, 0, {  4,  2,  2,  2 } },
    /* TODO: Find an IDENTIFY and/or INQUIRY dump. */
    { "MITSUMI",  "CRMC-FX810T4",     "a01 ", "mitsumi_810t4",  BUS_TYPE_IDE,  0,  8, 36, 0, 0, {  4,  2,  2, -1 } },
    { "NEC",      "CD-ROM DRIVE:260", "1.00", "nec_260_early",  BUS_TYPE_IDE,  1,  2, 36, 1, 0, {  0, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:260", "1.01", "nec_260",        BUS_TYPE_IDE,  1,  4, 36, 1, 0, {  0, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:273", "4.20", "nec_273",        BUS_TYPE_IDE,  0,  4, 36, 0, 0, {  0, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:280", "1.05", "nec_280_early",  BUS_TYPE_IDE,  0,  6, 36, 1, 0, {  4,  2,  2, -1 } },
    { "NEC",      "CD-ROM DRIVE:280", "3.08", "nec_280",        BUS_TYPE_IDE,  0,  8, 36, 1, 0, {  4,  2,  2, -1 } },
    { "NEC",      "CDR-1300A",        "1.05", "nec_1300a",      BUS_TYPE_IDE,  0,  6, 36, 0, 0, {  4,  2,  2, -1 } },
    { "NEC",      "CDR-1900A",        "1.00", "nec_1900a",      BUS_TYPE_IDE,  0, 32, 36, 0, 0, {  4,  2,  2, -1 } },
    { "PHILIPS",  "CD-ROM PCA403CD",  "U31P", "philips_403",    BUS_TYPE_IDE,  0, 40, 36, 0, 0, {  4,  2,  2, -1 } },
    { "SONY",     "CD-ROM CDU76",     "1.0i", "sony_76",        BUS_TYPE_IDE,  0,  4, 36, 0, 0, {  2, -1, -1, -1 } },
    { "SONY",     "CD-ROM CDU311",    "3.0h", "sony_311",       BUS_TYPE_IDE,  0,  8, 36, 0, 0, {  3,  2,  1, -1 } },
    { "SONY",     "CD-ROM CDU611",    "2.2c", "sony_611",       BUS_TYPE_IDE,  0, 24, 36, 0, 0, {  3,  2,  2, -1 } },
    { "SONY",     "CD-ROM CDU5225",   "NYS4", "sony_5225",      BUS_TYPE_IDE,  0, 52, 36, 0, 0, {  4,  2,  2,  4 } },
    { "TEAC",     "CD-516E",          "1.0G", "teac_516e",      BUS_TYPE_IDE,  0, 16, 36, 0, 0, {  3,  2,  2, -1 } },
    { "TEAC",     "CD-524EA",         "3.0D", "teac_524ea",     BUS_TYPE_IDE,  0, 24, 36, 0, 0, {  3,  2,  2, -1 } },
    { "TEAC",     "CD-532E",          "2.0A", "teac_532e",      BUS_TYPE_IDE,  0, 32, 36, 0, 0, {  3,  2,  2, -1 } },
    { "TOSHIBA",  "CD-ROM XM-5302TA", "0305", "toshiba_5302ta", BUS_TYPE_IDE,  0,  4, 96, 0, 0, {  0, -1, -1, -1 } },
    { "TOSHIBA",  "CD-ROM XM-5702B",  "TA70", "toshiba_5702b",  BUS_TYPE_IDE,  0, 12, 96, 0, 0, {  3,  2,  1, -1 } },
    { "TOSHIBA",  "CD-ROM XM-6102B",  "WA70", "toshiba_6102b",  BUS_TYPE_IDE,  0, 24, 96, 0, 0, {  4,  2,  2, -1 } },
    { "TOSHIBA",  "CD-ROM XM-6202B",  "1512", "toshiba_6202b",  BUS_TYPE_IDE,  0, 32, 96, 0, 0, {  4,  2,  2, -1 } },
    { "TOSHIBA",  "CD-ROM XM-6402B",  "1008", "toshiba_6402b",  BUS_TYPE_IDE,  0, 32, 96, 0, 0, {  4,  2,  2,  2 } },
    { "TOSHIBA",  "CD-ROM XM-6702B",  "1007", "toshiba_6720b",  BUS_TYPE_IDE,  0, 48, 96, 0, 0, {  4,  2,  2,  2 } },
    { "TOSHIBA",  "DVD-ROM SD-M1202", "1020", "toshiba_m1202",  BUS_TYPE_IDE,  0, 32, 96, 0, 1, {  4,  2,  2,  2 } },
    { "TOSHIBA",  "DVD-ROM SD-M1802", "1051", "toshiba_m1802",  BUS_TYPE_IDE,  0, 48, 96, 0, 1, {  4,  2,  2,  2 } },
    { "CHINON",   "CD-ROM CDS-431",   "H42 ", "chinon_431",     BUS_TYPE_SCSI, 1,  1, 36, 1, 0, { -1, -1, -1, -1 } },
    { "CHINON",   "CD-ROM CDX-435",   "M62 ", "chinon_435",     BUS_TYPE_SCSI, 1,  2, 36, 1, 0, { -1, -1, -1, -1 } },
    { "DEC",      "RRD42   (C) DEC",  "4.5d", "dec_42",         BUS_TYPE_SCSI, 1,  1, 36, 0, 0, { -1, -1, -1, -1 } },
    { "DEC",      "RRD45   (C) DEC",  "0436", "dec_45",         BUS_TYPE_SCSI, 1,  4, 36, 0, 0, { -1, -1, -1, -1 } },
    { "MATSHITA", "CD-ROM CR-501",    "1.0b", "matshita_501",   BUS_TYPE_SCSI, 1,  1, 36, 1, 0, { -1, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:25",  "1.0a", "nec_25",         BUS_TYPE_SCSI, 1,  2, 36, 0, 0, { -1, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:38",  "1.00", "nec_38",         BUS_TYPE_SCSI, 2,  1, 36, 0, 0, { -1, -1, -1, -1 } },
    /* The speed of the following two is guesswork based on the CDR-74. */
    { "NEC",      "CD-ROM DRIVE:75",  "1.03", "nec_75",         BUS_TYPE_SCSI, 1,  1, 36, 1, 0, { -1, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:77",  "1.06", "nec_77",         BUS_TYPE_SCSI, 1,  1, 36, 1, 0, { -1, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:211", "1.00", "nec_211",        BUS_TYPE_SCSI, 2,  3, 36, 0, 0, { -1, -1, -1, -1 } },
    /* The speed of the following two is guesswork based on the CDR-400. */
    { "NEC",      "CD-ROM DRIVE:464", "1.05", "nec_464",        BUS_TYPE_SCSI, 2,  3, 36, 0, 0, { -1, -1, -1, -1 } },
    { "NEC",      "CD-ROM DRIVE:900", "2.5a", "nec_900",        BUS_TYPE_SCSI, 2,  4, 36, 0, 0, { -1, -1, -1, -1 } },
    /* The speed of the following two is guesswork based on the name. */
    { "ShinaKen", "CD-ROM DM-3x1S",   "1.04", "shinaken_3x1s",  BUS_TYPE_SCSI, 1,  3, 36, 0, 0, { -1, -1, -1, -1 } },
    { "SONY",     "CD-ROM CDU-541",   "1.0i", "sony_541",       BUS_TYPE_SCSI, 1,  1, 36, 1, 0, { -1, -1, -1, -1 } },
    { "SONY",     "CD-ROM CDU-561",   "1.8k", "sony_561",       BUS_TYPE_SCSI, 2,  2, 36, 1, 0, { -1, -1, -1, -1 } },
    { "SONY",     "CD-ROM CDU-76S",   "1.00", "sony_76s",       BUS_TYPE_SCSI, 2,  4, 36, 0, 0, { -1, -1, -1, -1 } },
    { "PHILIPS",  "CDD2600",          "1.07", "philips_2600",   BUS_TYPE_SCSI, 2,  6, 36, 0, 0, { -1, -1, -1, -1 } },
    /* NOTE: The real thing is a CD changer drive! */
    { "PIONEER",  "CD-ROM DRM-604X",  "2403", "pioneer_604x",   BUS_TYPE_SCSI, 2,  4, 47, 0, 0, { -1, -1, -1, -1 } },
    { "PLEXTOR",  "CD-ROM PX-12CS",   "1.04", "plextor_12cs",   BUS_TYPE_SCSI, 2, 12, 36, 1, 0, { -1, -1, -1, -1 } },
    { "PLEXTOR",  "CD-ROM PX-12TS",   "1.01", "plextor_12ts",   BUS_TYPE_SCSI, 2, 12, 36, 0, 0, { -1, -1, -1, -1 } },
    { "PLEXTOR",  "CD-ROM PX-32TS",   "1.03", "plextor_32ts",   BUS_TYPE_SCSI, 2, 32, 36, 0, 0, { -1, -1, -1, -1 } },
    { "PLEXTOR",  "CD-ROM PX-83CS",   "1.01", "plextor_83cs",   BUS_TYPE_SCSI, 2,  8, 36, 1, 0, { -1, -1, -1, -1 } },
    /* The speed of the following two is guesswork based on the R55S. */
    { "TEAC",     "CD 50",            "1.00", "teac_50",        BUS_TYPE_SCSI, 2,  4, 36, 1, 0, { -1, -1, -1, -1 } },
    { "TEAC",     "CD-ROM R55S",      "1.0R", "teac_55s",       BUS_TYPE_SCSI, 2,  4, 36, 0, 0, { -1, -1, -1, -1 } },
    /* Texel is Plextor according to Plextor's own EU website. */
    { "TEXEL",    "CD-ROM DM-3024",   "1.00", "texel_3024",     BUS_TYPE_SCSI, 2,  2, 36, 1, 0, { -1, -1, -1, -1 } },
    /*
       Unusual 2.23x according to Google, I'm rounding it upwards to 3x.
       Assumed caddy based on the DM-3024.
     */
    { "TEXEL",    "CD-ROM DM-3028",   "1.06", "texel_3028",     BUS_TYPE_SCSI, 2,  3, 36, 1, 0, { -1, -1, -1, -1 } }, /* Caddy. */
    /*
       The characteristics are a complete guesswork because I can't find
       this one on Google.

       Also, INQUIRY length is always 96 on these Toshiba drives.
     */
    { "TOSHIBA",  "CD-ROM DRIVE:XM",  "3433", "toshiba_xm",     BUS_TYPE_SCSI, 2,  2, 96, 0, 0, { -1, -1, -1, -1 } }, /* Tray.  */
    { "TOSHIBA",  "CD-ROM XM-3201B",  "3232", "toshiba_3201b",  BUS_TYPE_SCSI, 1,  1, 96, 1, 0, { -1, -1, -1, -1 } }, /* Caddy. */
    { "TOSHIBA",  "CD-ROM XM-3301TA", "0272", "toshiba_3301ta", BUS_TYPE_SCSI, 2,  2, 96, 0, 0, { -1, -1, -1, -1 } }, /* Tray.  */
    { "TOSHIBA",  "CD-ROM XM-3701B",  "0236", "toshiba_3701b",  BUS_TYPE_SCSI, 2,  6, 96, 0, 0, { -1, -1, -1, -1 } }, /* Tray.  */
    { "TOSHIBA",  "CD-ROM XM-5701TA", "3136", "toshiba_5701a",  BUS_TYPE_SCSI, 2, 12, 96, 0, 0, { -1, -1, -1, -1 } }, /* Tray.  */
    { "TOSHIBA",  "DVD-ROM SD-M1401", "1008", "toshiba_m1401",  BUS_TYPE_SCSI, 2, 40, 96, 0, 1, { -1, -1, -1, -1 } }, /* Tray.  */
    { "MATSHITA", "CR-562",           "0.75", "cr562",          BUS_TYPE_MKE , 0,  2,  0, 0, 0, { -1, -1, -1, -1 } },
    { "MATSHITA", "CR-562",           "0.76", "cr562_076",      BUS_TYPE_MKE , 0,  2,  0, 0, 0, { -1, -1, -1, -1 } },
    { "MATSHITA", "CR-562",           "0.80", "cr562_080",      BUS_TYPE_MKE , 0,  2,  0, 0, 0, { -1, -1, -1, -1 } },
    { "MATSHITA", "CR-562",           "081k", "cr562_081k",     BUS_TYPE_MKE , 0,  2,  0, 0, 0, { -1, -1, -1, -1 } },
    { "MATSHITA", "CR-563",           "0.74", "cr563",          BUS_TYPE_MKE , 0,  2,  0, 0, 0, { -1, -1, -1, -1 } },
    { "MATSHITA", "CR-563",           "0.75", "cr563_075",      BUS_TYPE_MKE , 0,  2,  0, 0, 0, { -1, -1, -1, -1 } },
    { "MATSHITA", "CR-563",           "0.80", "cr563_080",      BUS_TYPE_MKE , 0,  2,  0, 0, 0, { -1, -1, -1, -1 } },
    { "",         "",                 "",     "",               BUS_TYPE_NONE, 0, -1,  0, 0, 0, { -1, -1, -1, -1 } }
};

/* To shut up the GCC compilers. */
struct cdrom;

typedef struct subchannel_t {
    uint8_t attr;
    uint8_t track;
    uint8_t index;
    uint8_t abs_m;
    uint8_t abs_s;
    uint8_t abs_f;
    uint8_t rel_m;
    uint8_t rel_s;
    uint8_t rel_f;
} subchannel_t;

typedef struct track_info_t {
    int     number;
    uint8_t attr;
    uint8_t m;
    uint8_t s;
    uint8_t f;
} track_info_t;

typedef struct raw_track_info_t {
    uint8_t  session;
    uint8_t  adr_ctl;
    uint8_t  tno;
    uint8_t  point;
    uint8_t  m;
    uint8_t  s;
    uint8_t  f;
    uint8_t  zero;
    uint8_t  pm;
    uint8_t  ps;
    uint8_t  pf;
} raw_track_info_t;

/* Define the various CD-ROM drive operations (ops). */
typedef struct cdrom_ops_t {
    int      (*get_track_info)(const void *local, const uint32_t track,
                               const int end, track_info_t *ti);
    void     (*get_raw_track_info)(const void *local, int *num,
                                   uint8_t *rti);
    int      (*is_track_pre)(const void *local, const uint32_t sector);
    int      (*read_sector)(const void *local, uint8_t *buffer,
                            const uint32_t sector);
    uint8_t  (*get_track_type)(const void *local, const uint32_t sector);
    uint32_t (*get_last_block)(const void *local);
    int      (*read_dvd_structure)(const void *local, const uint8_t layer,
                                   const uint8_t format, uint8_t *buffer,
                                   uint32_t *info);
    int      (*is_dvd)(const void *local);
    int      (*has_audio)(const void *local);
    int      (*is_empty)(const void *local);
    void     (*close)(void *local);
    void     (*load)(const void *local);
} cdrom_ops_t;

typedef struct cdrom {
    uint8_t           id;

    union {
        uint8_t           res;
        uint8_t           res0;      /* Reserved for other ID's. */
        uint8_t           mke_channel;
        uint8_t           ide_channel;
        uint8_t           scsi_device_id;
    };

    uint8_t            bus_type;     /* 0 = ATAPI, 1 = SCSI */
    uint8_t            bus_mode;     /* Bit 0 = PIO suported;
                                        Bit 1 = DMA supportd. */
    uint8_t            cd_status;    /* Struct variable reserved for
                                        media status. */
    uint8_t            speed;
    uint8_t            cur_speed;

    void              *priv;

    char               image_path[MAX_IMAGE_PATH_LEN];
    char               prev_image_path[MAX_IMAGE_PATH_LEN + 256];

    uint32_t           sound_on;
    uint32_t           cdrom_capacity;
    uint32_t           seek_pos;
    uint32_t           seek_diff;
    uint32_t           cd_end;
    uint32_t           type;
    uint32_t           sector_size;

    uint32_t           inv_field;
    int32_t            cached_sector;
    int32_t            cd_buflen;
    int32_t            sony_msf;
    int32_t            real_speed;
    int32_t            is_early;
    int32_t            is_nec;
    int32_t            is_bcd;

    int32_t            cdrom_sector_size;

    const cdrom_ops_t *ops;

    char              *image_history[CD_IMAGE_HISTORY];

    void              *local;
    void              *log;

    void               (*insert)(void *priv);
    void               (*close)(void *priv);
    uint32_t           (*get_volume)(void *p, int channel);
    uint32_t           (*get_channel)(void *p, int channel);

    int16_t            cd_buffer[CD_BUF_SIZE];

    uint8_t            subch_buffer[96];

    /* Needs some extra breathing space in case of overflows. */
    uint8_t            raw_buffer[2][4096];
    uint8_t            extra_buffer[296];

    int32_t            is_chinon;
    int32_t            is_pioneer;
    int32_t            is_plextor;
    int32_t            is_sony;
    int32_t            is_toshiba;

    int32_t            c2_first;
    int32_t            cur_buf;

    /* Only used on Windows hosts for disc change notifications. */
    uint8_t            host_letter;
    uint8_t            mode2;

    int                no_check;

    uint8_t            _F_LUT[_LUT_SIZE];
    uint8_t            _B_LUT[_LUT_SIZE];

    uint8_t            p_parity[172];
    uint8_t            q_parity[104];
} cdrom_t;

extern cdrom_t cdrom[CDROM_NUM];

#define MSFtoLBA(m, s, f)  ((((m * 60) + s) * 75) + f)

static __inline int
bin2bcd(int x)
{
    return (x % 10) | ((x / 10) << 4);
}

static __inline int
bcd2bin(int x)
{
    return (x >> 4) * 10 + (x & 0x0f);
}

extern char           *cdrom_get_vendor(const int type);
extern void            cdrom_get_model(const int type, char *name, const int id);
extern char           *cdrom_get_revision(const int type);
extern int             cdrom_get_scsi_std(const int type);
extern int             cdrom_is_early(const int type);
extern int             cdrom_is_dvd(const int type);
extern int             cdrom_is_generic(const int type);
extern int             cdrom_is_caddy(const int type);
extern int             cdrom_get_speed(const int type);
extern int             cdrom_get_inquiry_len(const int type);
extern int             cdrom_has_dma(const int type);
extern int             cdrom_get_transfer_max(const int type, const int mode);
extern int             cdrom_get_type_count(void);
extern void            cdrom_generate_name_mke(const int type, char *name);
extern void            cdrom_get_identify_model(const int type, char *name, const int id);
extern void            cdrom_get_name(const int type, char *name);
extern char           *cdrom_get_internal_name(const int type);
extern int             cdrom_get_from_internal_name(const char *s);
/* TODO: Configuration migration, remove when no longer needed. */
extern int             cdrom_get_from_name(const char *s);
extern void            cdrom_set_type(const int model, const int type);
extern int             cdrom_get_type(const int model);

extern int             cdrom_lba_to_msf_accurate(const int lba);
extern void            cdrom_interleave_subch(uint8_t *d, const uint8_t *s);
extern double          cdrom_seek_time(const cdrom_t *dev);
extern void            cdrom_stop(cdrom_t *dev);
extern void            cdrom_seek(cdrom_t *dev, const uint32_t pos, const uint8_t vendor_type);
extern int             cdrom_is_pre(const cdrom_t *dev, const uint32_t lba);

extern int             cdrom_audio_callback(cdrom_t *dev, int16_t *output, const int len);
extern uint8_t         cdrom_audio_play(cdrom_t *dev, const uint32_t pos, const uint32_t len, const int ismsf);
extern uint8_t         cdrom_audio_track_search(cdrom_t *dev, const uint32_t pos,
                                                const int type, const uint8_t playbit);
extern uint8_t         cdrom_audio_track_search_pioneer(cdrom_t *dev, const uint32_t pos, const uint8_t playbit);
extern uint8_t         cdrom_audio_play_pioneer(cdrom_t *dev, const uint32_t pos);
extern uint8_t         cdrom_audio_play_toshiba(cdrom_t *dev, const uint32_t pos, const int type);
extern uint8_t         cdrom_audio_scan(cdrom_t *dev, const uint32_t pos);
extern void            cdrom_audio_pause_resume(cdrom_t *dev, const uint8_t resume);

extern uint8_t         cdrom_get_current_status(const cdrom_t *dev);
extern void            cdrom_get_current_subchannel(cdrom_t *dev, uint8_t *b, const int msf);
extern void            cdrom_get_current_subchannel_sony(cdrom_t *dev, uint8_t *b, const int msf);
extern uint8_t         cdrom_get_audio_status_pioneer(cdrom_t *dev, uint8_t *b);
extern uint8_t         cdrom_get_audio_status_sony(cdrom_t *dev, uint8_t *b, const int msf);
extern void            cdrom_get_current_subcodeq(cdrom_t *dev, uint8_t *b);
extern uint8_t         cdrom_get_current_subcodeq_playstatus(cdrom_t *dev, uint8_t *b);
extern int             cdrom_read_toc(const cdrom_t *dev, uint8_t *b, const int type,
                                      const uint8_t start_track, const int msf, const int max_len);
extern int             cdrom_read_toc_sony(const cdrom_t *dev, uint8_t *b, const uint8_t start_track,
                                           const int msf, const int max_len);
#ifdef USE_CDROM_MITSUMI
extern void            cdrom_get_track_buffer(cdrom_t *dev, uint8_t *buf);
extern void            cdrom_get_q(cdrom_t *dev, uint8_t *buf, int *curtoctrk, uint8_t mode);
extern uint8_t         cdrom_mitsumi_audio_play(cdrom_t *dev, uint32_t pos, uint32_t len);
#endif
extern uint8_t         cdrom_read_disc_info_toc(cdrom_t *dev, uint8_t *b,
                                                const uint8_t track, const int type);
extern int             cdrom_is_track_audio(cdrom_t *dev, const int sector, const int ismsf,
                                            int cdrom_sector_type, const uint8_t vendor_type);
extern int             cdrom_readsector_raw(cdrom_t *dev, uint8_t *buffer, const int sector, const int ismsf,
                                            int cdrom_sector_type, const int cdrom_sector_flags,
                                            int *len, const uint8_t vendor_type);
extern int             cdrom_read_dvd_structure(const cdrom_t *dev, const uint8_t layer, const uint8_t format,
                                                uint8_t *buffer, uint32_t *info);
extern void            cdrom_read_disc_information(const cdrom_t *dev, uint8_t *buffer);
extern int             cdrom_read_track_information(cdrom_t *dev, const uint8_t *cdb, uint8_t *buffer);
extern uint8_t         cdrom_get_current_mode(cdrom_t *dev);
extern void            cdrom_set_empty(cdrom_t *dev);
extern void            cdrom_update_status(cdrom_t *dev);
extern int             cdrom_load(cdrom_t *dev, const char *fn, const int skip_insert);

extern void            cdrom_global_init(void);
extern void            cdrom_hard_reset(void);
extern void            cdrom_close(void);
extern void            cdrom_insert(const uint8_t id);
extern void            cdrom_exit(const uint8_t id);
extern int             cdrom_is_empty(const uint8_t id);
extern int             cdrom_is_playing(const uint8_t id);
extern int             cdrom_is_paused(const uint8_t id);
extern void            cdrom_eject(const uint8_t id);
extern void            cdrom_reload(const uint8_t id);

extern void            cdrom_compute_ecc_block(cdrom_t *dev, uint8_t *parity, const uint8_t *data,
                                               uint32_t major_count, uint32_t minor_count,
                                               uint32_t major_mult, uint32_t minor_inc, int m2f1);
extern unsigned long   cdrom_crc32(unsigned long crc, const unsigned char *buf,
                                   size_t len);

extern int             cdrom_assigned_letters;

#ifdef __cplusplus
}
#endif

#endif /*EMU_CDROM_H*/
