nkeynes@138: /** nkeynes@561: * $Id$ nkeynes@138: * nkeynes@138: * Nero (NRG) CD file format. File information stolen shamelessly from nkeynes@138: * libcdio. nkeynes@138: * nkeynes@138: * Copyright (c) 2005 Nathan Keynes. nkeynes@138: * nkeynes@138: * This program is free software; you can redistribute it and/or modify nkeynes@138: * it under the terms of the GNU General Public License as published by nkeynes@138: * the Free Software Foundation; either version 2 of the License, or nkeynes@138: * (at your option) any later version. nkeynes@138: * nkeynes@138: * This program is distributed in the hope that it will be useful, nkeynes@138: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@138: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@138: * GNU General Public License for more details. nkeynes@138: */ nkeynes@138: nkeynes@138: #include nkeynes@138: #include nkeynes@138: #include nkeynes@1097: #include "drivers/cdrom/cdimpl.h" nkeynes@138: #include "dream.h" nkeynes@138: nkeynes@168: static gboolean nrg_image_is_valid( FILE *f ); nkeynes@1097: static gboolean nrg_image_read_toc( cdrom_disc_t disc, ERROR *err ); nkeynes@168: nkeynes@1097: struct cdrom_disc_factory nrg_disc_factory = { "Nero", "nrg", nkeynes@1097: nrg_image_is_valid, NULL, nrg_image_read_toc }; nkeynes@168: nkeynes@138: #define NERO_V55_ID 0x4e455235 nkeynes@138: #define NERO_V50_ID 0x4e45524f nkeynes@138: nkeynes@138: /* Courtesy of libcdio */ nkeynes@138: /* 5.0 or earlier */ nkeynes@138: #define NERO_ID 0x4e45524f /* Nero pre 5.5.x */ nkeynes@138: #define CUES_ID 0x43554553 /* Nero pre version 5.5.x-6.x */ nkeynes@138: #define DAOI_ID 0x44414f49 nkeynes@138: #define ETNF_ID 0x45544e46 nkeynes@138: #define SINF_ID 0x53494e46 /* Session information */ nkeynes@138: #define END_ID 0x454e4421 nkeynes@138: /* 5.5+ only */ nkeynes@138: #define NER5_ID 0x4e455235 /* Nero version 5.5.x */ nkeynes@138: #define CDTX_ID 0x43445458 /* CD TEXT */ nkeynes@138: #define CUEX_ID 0x43554558 /* Nero version 5.5.x-6.x */ nkeynes@138: #define DAOX_ID 0x44414f58 /* Nero version 5.5.x-6.x */ nkeynes@138: #define ETN2_ID 0x45544e32 nkeynes@138: #define MTYP_ID 0x4d545950 /* Disc Media type? */ nkeynes@138: nkeynes@138: nkeynes@138: union nrg_footer { nkeynes@138: struct nrg_footer_v50 { nkeynes@736: uint32_t dummy; nkeynes@736: uint32_t id; nkeynes@736: uint32_t offset; nkeynes@138: } v50; nkeynes@138: struct nrg_footer_v55 { nkeynes@736: uint32_t id; nkeynes@736: uint64_t offset; nkeynes@1178: } __attribute__((packed)) v55; nkeynes@138: }; nkeynes@138: nkeynes@138: struct nrg_chunk { nkeynes@138: uint32_t id; nkeynes@138: uint32_t length; nkeynes@138: }; nkeynes@138: nkeynes@138: struct nrg_etnf { nkeynes@138: uint32_t offset; nkeynes@138: uint32_t length; nkeynes@138: uint32_t mode; nkeynes@138: uint32_t lba; nkeynes@138: uint32_t padding; nkeynes@138: }; nkeynes@138: nkeynes@514: struct nrg_etn2 { nkeynes@514: uint64_t offset; nkeynes@514: uint64_t length; nkeynes@514: uint32_t mode; nkeynes@514: uint32_t lba; nkeynes@514: uint64_t padding; nkeynes@514: }; nkeynes@514: nkeynes@138: struct nrg_cues { nkeynes@138: uint8_t type; nkeynes@138: uint8_t track; nkeynes@138: uint8_t control; nkeynes@138: uint8_t pad; nkeynes@138: uint32_t addr; nkeynes@138: }; nkeynes@138: nkeynes@138: struct nrg_daoi { nkeynes@138: uint32_t length; nkeynes@138: char mcn[14]; nkeynes@138: uint8_t disc_mode; nkeynes@138: uint8_t unknown[2]; /* always 01 01? */ nkeynes@138: uint8_t track_count; nkeynes@138: struct nrg_daoi_track { nkeynes@736: char unknown[10]; nkeynes@736: uint32_t sector_size __attribute__((packed)); /* Always 0? */ nkeynes@736: uint8_t mode; nkeynes@736: uint8_t unknown2[3]; /* Always 00 00 01? */ nkeynes@736: uint32_t pregap __attribute__((packed)); nkeynes@736: uint32_t offset __attribute__((packed)); nkeynes@736: uint32_t end __attribute__((packed)); nkeynes@138: } track[0]; nkeynes@138: } __attribute__((packed)); nkeynes@138: nkeynes@514: struct nrg_daox { nkeynes@514: uint32_t length; nkeynes@514: char mcn[14]; nkeynes@514: uint8_t disc_mode; nkeynes@514: uint8_t unknown[2]; /* always 01 01? */ nkeynes@514: uint8_t track_count; nkeynes@514: struct nrg_daox_track { nkeynes@736: char unknown[10]; nkeynes@736: uint32_t sector_size __attribute__((packed)); /* Always 0? */ nkeynes@736: uint8_t mode; nkeynes@736: uint8_t unknown2[3]; /* Always 00 00 01? */ nkeynes@736: uint64_t pregap __attribute__((packed)); nkeynes@736: uint64_t offset __attribute__((packed)); nkeynes@736: uint64_t end __attribute__((packed)); nkeynes@514: } track[0]; nkeynes@514: } __attribute__((packed)); nkeynes@514: nkeynes@138: nkeynes@1097: sector_mode_t static nrg_track_mode( uint8_t mode ) nkeynes@138: { nkeynes@138: switch( mode ) { nkeynes@1097: case 0: return SECTOR_MODE1; nkeynes@1097: case 2: return SECTOR_MODE2_FORM1; nkeynes@1097: case 3: return SECTOR_SEMIRAW_MODE2; nkeynes@1097: case 7: return SECTOR_CDDA; nkeynes@1178: case 16: return SECTOR_CDDA_SUBCHANNEL; nkeynes@1298: default: return SECTOR_UNKNOWN; nkeynes@138: } nkeynes@138: } nkeynes@138: nkeynes@168: static gboolean nrg_image_is_valid( FILE *f ) nkeynes@138: { nkeynes@168: union nrg_footer footer; nkeynes@168: nkeynes@168: fseek( f, -12, SEEK_END ); nkeynes@168: fread( &footer, sizeof(footer), 1, f ); nkeynes@514: if( GUINT32_FROM_BE(footer.v50.id) == NERO_V50_ID || nkeynes@736: GUINT32_FROM_BE(footer.v55.id) == NERO_V55_ID ) { nkeynes@736: return TRUE; nkeynes@168: } else { nkeynes@736: return FALSE; nkeynes@168: } nkeynes@168: } nkeynes@168: nkeynes@1109: #define RETURN_PARSE_ERROR( ... ) do { SET_ERROR(err, LX_ERR_FILE_INVALID, __VA_ARGS__); return FALSE; } while(0) nkeynes@1097: nkeynes@1097: static gboolean nrg_image_read_toc( cdrom_disc_t disc, ERROR *err ) nkeynes@168: { nkeynes@138: union nrg_footer footer; nkeynes@138: struct nrg_chunk chunk; nkeynes@138: struct nrg_daoi *dao; nkeynes@514: struct nrg_daox *daox; nkeynes@514: struct nrg_etnf *etnf; nkeynes@514: struct nrg_etn2 *etn2; nkeynes@138: gboolean end = FALSE; nkeynes@514: uint32_t chunk_id; nkeynes@1097: int session_id = 1; nkeynes@138: int session_track_id = 0; nkeynes@138: int track_id = 0; nkeynes@138: int cue_track_id = 0, cue_track_count = 0; nkeynes@514: int i, count; nkeynes@138: nkeynes@1097: FILE *f = cdrom_disc_get_base_file(disc); nkeynes@1097: nkeynes@138: fseek( f, -12, SEEK_END ); nkeynes@138: fread( &footer, sizeof(footer), 1, f ); nkeynes@1178: uint32_t start = 0; nkeynes@514: if( GUINT32_FROM_BE(footer.v50.id) == NERO_V50_ID ) { nkeynes@1178: start = GUINT32_FROM_BE(footer.v50.offset); nkeynes@514: } else if( GUINT32_FROM_BE(footer.v55.id) == NERO_V55_ID ) { nkeynes@1178: start = (uint32_t)GUINT64_FROM_BE(footer.v55.offset); nkeynes@138: } else { nkeynes@1097: /* Not a (recognized) Nero image (should never happen) */ nkeynes@1097: RETURN_PARSE_ERROR("File is not an NRG image" ); nkeynes@138: } nkeynes@1178: if( fseek( f, start, SEEK_SET) != 0 ) { nkeynes@1178: RETURN_PARSE_ERROR("File is not a valid NRG image" ); nkeynes@1178: } nkeynes@138: nkeynes@138: do { nkeynes@736: fread( &chunk, sizeof(chunk), 1, f ); nkeynes@736: chunk.length = GUINT32_FROM_BE(chunk.length); nkeynes@736: char data[chunk.length]; nkeynes@736: fread( data, chunk.length, 1, f ); nkeynes@736: chunk_id = GUINT32_FROM_BE(chunk.id); nkeynes@736: switch( chunk_id ) { nkeynes@736: case CUES_ID: nkeynes@736: case CUEX_ID: nkeynes@736: cue_track_id = track_id; nkeynes@736: cue_track_count = ((chunk.length / sizeof(struct nrg_cues)) >> 1) - 1; nkeynes@736: track_id += cue_track_count; nkeynes@736: for( i=0; iaddr ); nkeynes@736: } else { nkeynes@1097: lba = BCD_MSFTOLBA( cue->addr ); nkeynes@736: } nkeynes@736: if( cue->track == 0 ) nkeynes@736: continue; /* Track 0. Leadin? always 0? */ nkeynes@736: if( cue->track == 0xAA ) { /* end of disc */ nkeynes@1097: disc->leadout = lba; nkeynes@736: } else { nkeynes@1097: track = BCDTOU8(cue->track) - 1; nkeynes@1097: if( (cue->control & 0x01) != 0 ) { nkeynes@1097: /* Track-start address */ nkeynes@1023: disc->track[track].lba = lba; nkeynes@1023: disc->track[track].flags = cue->type; nkeynes@736: } nkeynes@736: } nkeynes@736: } nkeynes@736: break; nkeynes@736: case DAOI_ID: nkeynes@736: dao = (struct nrg_daoi *)data; nkeynes@1097: count = dao->track_count - cue_track_id; nkeynes@1023: memcpy( disc->mcn, dao->mcn, 13 ); nkeynes@1023: disc->mcn[13] = '\0'; nkeynes@1097: if( dao->track_count != track_id || nkeynes@1097: count * 30 + 22 != chunk.length ) { nkeynes@1097: RETURN_PARSE_ERROR( "Invalid NRG image file (bad DAOI block)" ); nkeynes@1097: } nkeynes@1097: for( i=0; itrack[i].offset); nkeynes@1097: sector_mode_t mode = nrg_track_mode( dao->track[i].mode ); nkeynes@1298: if( mode == SECTOR_UNKNOWN ) { nkeynes@1097: RETURN_PARSE_ERROR("Unknown track mode in NRG image file (%d)", dao->track[i].mode); nkeynes@1097: } nkeynes@1097: if( CDROM_SECTOR_SIZE(mode) != GUINT32_FROM_BE(dao->track[i].sector_size) ) { nkeynes@1097: /* Sector size mismatch */ nkeynes@1097: RETURN_PARSE_ERROR("Invalid NRG image file (Bad sector size in DAOI block)"); nkeynes@1097: } nkeynes@1097: cdrom_count_t sector_count = nkeynes@736: (GUINT32_FROM_BE(dao->track[i].end) - GUINT32_FROM_BE(dao->track[i].offset))/ nkeynes@1097: CDROM_SECTOR_SIZE(mode); nkeynes@1097: disc->track[cue_track_id].source = file_sector_source_new_source( disc->base_source, mode, offset, sector_count ); nkeynes@736: cue_track_id++; nkeynes@736: } nkeynes@736: break; nkeynes@736: case DAOX_ID: nkeynes@736: daox = (struct nrg_daox *)data; nkeynes@1097: count = daox->track_count - cue_track_id; nkeynes@1023: memcpy( disc->mcn, daox->mcn, 13 ); nkeynes@1023: disc->mcn[13] = '\0'; nkeynes@1097: if( daox->track_count != track_id || nkeynes@1097: count * 42 + 22 != chunk.length ) { nkeynes@1097: RETURN_PARSE_ERROR( "Invalid NRG image file (bad DAOX block)" ); nkeynes@1097: } nkeynes@1097: for( i=0; itrack[i].offset); nkeynes@1097: sector_mode_t mode = nrg_track_mode( daox->track[i].mode ); nkeynes@1298: if( mode == SECTOR_UNKNOWN ) { nkeynes@1097: RETURN_PARSE_ERROR("Unknown track mode in NRG image file (%d)", daox->track[i].mode); nkeynes@1097: } nkeynes@1097: if( CDROM_SECTOR_SIZE(mode) != GUINT32_FROM_BE(daox->track[i].sector_size) ) { nkeynes@1097: /* Sector size mismatch */ nkeynes@1097: RETURN_PARSE_ERROR("Invalid NRG image file (Bad sector size in DAOX block)"); nkeynes@1097: } nkeynes@1097: cdrom_count_t sector_count = (cdrom_count_t) nkeynes@1097: ((GUINT64_FROM_BE(daox->track[i].end) - GUINT64_FROM_BE(daox->track[i].offset))/ nkeynes@1097: CDROM_SECTOR_SIZE(mode)); nkeynes@1097: disc->track[cue_track_id].source = file_sector_source_new_source( disc->base_source, mode, offset, sector_count ); nkeynes@736: cue_track_id++; nkeynes@736: } nkeynes@736: break; nkeynes@514: nkeynes@736: case SINF_ID: nkeynes@736: /* Data is a single 32-bit number representing number of tracks in session */ nkeynes@736: i = GUINT32_FROM_BE( *(uint32_t *)data ); nkeynes@736: while( i-- > 0 ) nkeynes@1097: disc->track[session_track_id++].sessionno = session_id; nkeynes@736: session_id++; nkeynes@736: break; nkeynes@736: case ETNF_ID: nkeynes@736: etnf = (struct nrg_etnf *)data; nkeynes@736: count = chunk.length / sizeof(struct nrg_etnf); nkeynes@736: for( i=0; i < count; i++, etnf++ ) { nkeynes@1097: uint32_t offset = GUINT32_FROM_BE(etnf->offset); nkeynes@1097: sector_mode_t mode = nrg_track_mode( GUINT32_FROM_BE(etnf->mode) ); nkeynes@1298: if( mode == SECTOR_UNKNOWN ) { nkeynes@1097: RETURN_PARSE_ERROR("Unknown track mode in NRG image file (%d)", etnf->mode); nkeynes@736: } nkeynes@1097: cdrom_count_t sector_count = GUINT32_FROM_BE(etnf->length) / nkeynes@1097: CDROM_SECTOR_SIZE(mode); nkeynes@1097: nkeynes@1097: disc->track[track_id].lba = GUINT32_FROM_BE(etnf->lba) + i*CDROM_PREGAP; nkeynes@1097: if( mode == SECTOR_CDDA ) nkeynes@1023: disc->track[track_id].flags = 0x01; nkeynes@736: else nkeynes@1097: disc->track[track_id].flags = 0x01 | TRACK_FLAG_DATA; nkeynes@1097: disc->track[track_id].source = file_sector_source_new_source( disc->base_source, mode, offset, sector_count ); nkeynes@736: track_id++; nkeynes@736: } nkeynes@736: break; nkeynes@736: case ETN2_ID: nkeynes@736: etn2 = (struct nrg_etn2 *)data; nkeynes@736: count = chunk.length / sizeof(struct nrg_etn2); nkeynes@736: for( i=0; i < count; i++, etn2++ ) { nkeynes@1097: uint32_t offset = (uint32_t)GUINT64_FROM_BE(etn2->offset); nkeynes@1097: sector_mode_t mode = nrg_track_mode( GUINT32_FROM_BE(etn2->mode) ); nkeynes@1298: if( mode == SECTOR_UNKNOWN ) { nkeynes@1097: RETURN_PARSE_ERROR("Unknown track mode in NRG image file (%d)", etn2->mode); nkeynes@736: } nkeynes@1097: cdrom_count_t sector_count = (uint32_t)(GUINT64_FROM_BE(etn2->length) / nkeynes@1097: CDROM_SECTOR_SIZE(mode)); nkeynes@1097: nkeynes@1097: disc->track[track_id].lba = GUINT32_FROM_BE(etn2->lba) + i*CDROM_PREGAP; nkeynes@1097: if( mode == SECTOR_CDDA ) nkeynes@1023: disc->track[track_id].flags = 0x01; nkeynes@736: else nkeynes@1097: disc->track[track_id].flags = 0x01 | TRACK_FLAG_DATA; nkeynes@1097: disc->track[track_id].source = file_sector_source_new_source( disc->base_source, mode, offset, sector_count ); nkeynes@736: track_id++; nkeynes@736: } nkeynes@736: break; nkeynes@736: nkeynes@736: case END_ID: nkeynes@736: end = TRUE; nkeynes@736: break; nkeynes@736: } nkeynes@138: } while( !end ); nkeynes@1097: nkeynes@1023: disc->track_count = track_id; nkeynes@1097: disc->session_count = session_id-1; nkeynes@1097: return TRUE; nkeynes@138: }