nkeynes@1097: /** nkeynes@1097: * $Id$ nkeynes@1097: * nkeynes@1097: * Copyright (c) 2009 Nathan Keynes. nkeynes@1097: * nkeynes@1097: * This program is free software; you can redistribute it and/or modify nkeynes@1097: * it under the terms of the GNU General Public License as published by nkeynes@1097: * the Free Software Foundation; either version 2 of the License, or nkeynes@1097: * (at your option) any later version. nkeynes@1097: * nkeynes@1097: * This program is distributed in the hope that it will be useful, nkeynes@1097: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@1097: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@1097: * GNU General Public License for more details. nkeynes@1097: */ nkeynes@1097: nkeynes@1097: #include nkeynes@1097: #include nkeynes@1097: #include nkeynes@1097: #include nkeynes@1097: #include nkeynes@1097: #include nkeynes@1097: #include nkeynes@1097: #include "lxdream.h" nkeynes@1097: #include "drivers/cdrom/cdrom.h" nkeynes@1097: #include "drivers/cdrom/cdimpl.h" nkeynes@1108: #include "drivers/cdrom/isofs.h" nkeynes@1097: nkeynes@1097: extern struct cdrom_disc_factory linux_cdrom_drive_factory; nkeynes@1097: extern struct cdrom_disc_factory nrg_disc_factory; nkeynes@1097: extern struct cdrom_disc_factory cdi_disc_factory; nkeynes@1097: extern struct cdrom_disc_factory gdi_disc_factory; nkeynes@1097: nkeynes@1097: cdrom_disc_factory_t cdrom_disc_factories[] = { nkeynes@1097: #ifdef HAVE_LINUX_CDROM nkeynes@1097: &linux_cdrom_drive_factory, nkeynes@1097: #endif nkeynes@1097: &nrg_disc_factory, nkeynes@1097: &cdi_disc_factory, nkeynes@1097: &gdi_disc_factory, nkeynes@1097: NULL }; nkeynes@1097: nkeynes@1097: /********************* Implementation Support functions ************************/ nkeynes@1097: nkeynes@1097: cdrom_error_t default_image_read_blocks( sector_source_t source, cdrom_lba_t lba, cdrom_count_t count, nkeynes@1097: unsigned char *buf ) nkeynes@1097: { nkeynes@1097: assert( 0 && "read_blocks called on a cdrom disc" ); nkeynes@1097: return CDROM_ERROR_BADREAD; nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_error_t default_image_read_sectors( sector_source_t source, cdrom_lba_t lba, cdrom_count_t count, nkeynes@1097: cdrom_read_mode_t mode, unsigned char *buf, size_t *length ) nkeynes@1097: { nkeynes@1097: assert( IS_SECTOR_SOURCE_TYPE(source,DISC_SECTOR_SOURCE) ); nkeynes@1097: cdrom_disc_t disc = (cdrom_disc_t)source; nkeynes@1097: size_t len = 0, tmplen; nkeynes@1097: cdrom_count_t current = 0; nkeynes@1097: nkeynes@1097: while( current < count ) { nkeynes@1097: cdrom_track_t track = cdrom_disc_get_track_by_lba( disc, lba + current ); nkeynes@1097: if( track == NULL ) nkeynes@1097: return CDROM_ERROR_BADREAD; nkeynes@1097: uint32_t track_size = cdrom_disc_get_track_size( disc, track ); nkeynes@1097: cdrom_lba_t track_offset = lba + current - track->lba; nkeynes@1097: cdrom_count_t sub_count = count - current; nkeynes@1097: if( track_size - track_offset < sub_count ) nkeynes@1097: /* Read breaks across track boundaries. This will probably fail (due nkeynes@1097: * to inter-track gaps), but try it just in case nkeynes@1097: */ nkeynes@1097: sub_count = track_size - track_offset; nkeynes@1097: cdrom_error_t err = track->source->read_sectors( track->source, track_offset, sub_count, mode, &buf[len], &tmplen ); nkeynes@1097: if( err != CDROM_ERROR_OK ) nkeynes@1097: return err; nkeynes@1097: len += tmplen; nkeynes@1097: current += sub_count; nkeynes@1097: } nkeynes@1099: if( length != NULL ) nkeynes@1099: *length = len; nkeynes@1097: return CDROM_ERROR_OK; nkeynes@1097: } nkeynes@1097: nkeynes@1097: void default_cdrom_disc_destroy( sector_source_t source ) nkeynes@1097: { nkeynes@1097: assert( IS_SECTOR_SOURCE_TYPE(source,DISC_SECTOR_SOURCE) ); nkeynes@1097: cdrom_disc_t disc = (cdrom_disc_t)source; nkeynes@1097: int i; nkeynes@1097: nkeynes@1097: for( i=0; itrack_count; i++ ) { nkeynes@1097: sector_source_unref( disc->track[i].source ); nkeynes@1097: } nkeynes@1097: sector_source_unref( disc->base_source ); nkeynes@1097: g_free( (char *)disc->name ); nkeynes@1097: nkeynes@1097: default_sector_source_destroy( source ); nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_disc_t cdrom_disc_init( cdrom_disc_t disc, const char *filename ) nkeynes@1097: { nkeynes@1097: sector_source_init( &disc->source, DISC_SECTOR_SOURCE, SECTOR_UNKNOWN, 0, default_image_read_blocks, nkeynes@1097: default_cdrom_disc_destroy ); nkeynes@1097: disc->source.read_sectors = default_image_read_sectors; nkeynes@1097: disc->disc_type = CDROM_DISC_NONE; nkeynes@1097: disc->track_count = disc->session_count = 0; nkeynes@1097: for( int i=0; i<99; i++ ) { nkeynes@1097: disc->track[i].trackno = i+1; nkeynes@1097: } nkeynes@1097: if( filename != NULL ) nkeynes@1097: disc->name = g_strdup(filename); nkeynes@1097: return disc; nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_disc_t cdrom_disc_new( const char *name, ERROR *err ) nkeynes@1097: { nkeynes@1097: cdrom_disc_t disc = g_malloc0( sizeof(struct cdrom_disc) ); nkeynes@1097: if( disc != NULL ) { nkeynes@1097: cdrom_disc_init( disc, name ); nkeynes@1097: } else { nkeynes@1097: SET_ERROR(err, ENOMEM, "Unable to allocate memory for cdrom disc"); nkeynes@1097: } nkeynes@1097: return disc; nkeynes@1097: } nkeynes@1097: nkeynes@1097: /** nkeynes@1097: * Construct a new image-based disc using the given filename as the base source. nkeynes@1097: * TOC is initialized to the empty values. nkeynes@1097: */ nkeynes@1097: static cdrom_disc_t cdrom_disc_image_new( const char *filename, ERROR *err ) nkeynes@1097: { nkeynes@1097: cdrom_disc_t disc = cdrom_disc_new( filename, err ); nkeynes@1097: if( disc != NULL && filename != NULL ) { nkeynes@1097: disc->base_source = file_sector_source_new_filename( filename, SECTOR_UNKNOWN, 0, FILE_SECTOR_FULL_FILE ); nkeynes@1097: if( disc->base_source == NULL ) { nkeynes@1097: SET_ERROR( err, errno, "Unable to open cdrom file '%s': %s", filename, strerror(errno) ); nkeynes@1097: cdrom_disc_unref(disc); nkeynes@1097: disc = NULL; nkeynes@1097: } else { nkeynes@1097: sector_source_ref(disc->base_source); nkeynes@1097: } nkeynes@1097: nkeynes@1097: } nkeynes@1097: return disc; nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_lba_t cdrom_disc_compute_leadout( cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: if( disc->track_count == 0 ) { nkeynes@1097: disc->leadout = 0; nkeynes@1097: } else { nkeynes@1097: cdrom_track_t last_track = &disc->track[disc->track_count-1]; nkeynes@1097: if( last_track->source != NULL ) { nkeynes@1097: cdrom_lba_t leadout = last_track->lba + last_track->source->size; nkeynes@1097: if( leadout > disc->leadout ) nkeynes@1097: disc->leadout = leadout; nkeynes@1097: } nkeynes@1097: } nkeynes@1097: return disc->leadout; nkeynes@1097: } nkeynes@1097: nkeynes@1097: nkeynes@1097: void cdrom_disc_set_default_disc_type( cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: int type = CDROM_DISC_NONE, i; nkeynes@1097: for( i=0; itrack_count; i++ ) { nkeynes@1097: if( (disc->track[i].flags & TRACK_FLAG_DATA == 0) ) { nkeynes@1097: if( type == CDROM_DISC_NONE ) nkeynes@1097: type = CDROM_DISC_AUDIO; nkeynes@1097: } else if( disc->track[i].source != NULL && nkeynes@1097: (disc->track[i].source->mode == SECTOR_MODE1 || nkeynes@1097: disc->track[i].source->mode == SECTOR_RAW_NONXA) ) { nkeynes@1097: if( type != CDROM_DISC_XA ) nkeynes@1097: type = CDROM_DISC_NONXA; nkeynes@1097: } else { nkeynes@1097: type = CDROM_DISC_XA; nkeynes@1097: break; nkeynes@1097: } nkeynes@1097: } nkeynes@1097: disc->disc_type = type; nkeynes@1097: } nkeynes@1097: nkeynes@1097: void cdrom_disc_clear_toc( cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: disc->disc_type = CDROM_DISC_NONE; nkeynes@1097: disc->leadout = 0; nkeynes@1097: disc->track_count = 0; nkeynes@1097: disc->session_count = 0; nkeynes@1097: for( unsigned i=0; i< CDROM_MAX_TRACKS; i++ ) { nkeynes@1097: if( disc->track[i].source != NULL ) { nkeynes@1097: sector_source_unref( disc->track[i].source ); nkeynes@1097: disc->track[i].source = NULL; nkeynes@1097: } nkeynes@1097: } nkeynes@1097: } nkeynes@1097: nkeynes@1097: gboolean cdrom_disc_read_toc( cdrom_disc_t disc, ERROR *err ) nkeynes@1097: { nkeynes@1108: if( disc->read_toc != NULL ) { nkeynes@1108: /* First set the defaults for an empty disc */ nkeynes@1108: cdrom_disc_clear_toc(disc); nkeynes@1097: nkeynes@1108: if( disc->read_toc(disc, err ) ) { nkeynes@1108: /* Success - update disc type and leadout if the TOC read didn't set them */ nkeynes@1108: if( disc->disc_type == CDROM_DISC_NONE ) nkeynes@1108: cdrom_disc_set_default_disc_type(disc); nkeynes@1108: cdrom_disc_compute_leadout(disc); nkeynes@1108: return TRUE; nkeynes@1108: } else { nkeynes@1108: /* Reset to an empty disc in case the reader left things in an nkeynes@1108: * inconsistent state */ nkeynes@1108: cdrom_disc_clear_toc(disc); nkeynes@1108: return FALSE; nkeynes@1108: } nkeynes@1108: } else { nkeynes@1097: return TRUE; nkeynes@1097: } nkeynes@1097: } nkeynes@1097: nkeynes@1097: FILE *cdrom_disc_get_base_file( cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: return file_sector_source_get_file(disc->base_source); nkeynes@1097: } nkeynes@1097: nkeynes@1097: /*************************** Public functions ***************************/ nkeynes@1097: nkeynes@1097: cdrom_disc_t cdrom_disc_open( const char *inFilename, ERROR *err ) nkeynes@1097: { nkeynes@1097: const gchar *filename = inFilename; nkeynes@1097: const gchar *ext = strrchr(filename, '.'); nkeynes@1097: int i; nkeynes@1097: cdrom_disc_factory_t extclz = NULL; nkeynes@1097: nkeynes@1097: /* Ask the drive list if it recognizes the name first */ nkeynes@1097: cdrom_drive_t drive = cdrom_drive_find(inFilename); nkeynes@1097: if( drive != NULL ) { nkeynes@1097: return cdrom_drive_open(drive, err); nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_disc_t disc = cdrom_disc_image_new( filename, err ); nkeynes@1097: if( disc == NULL ) nkeynes@1097: return NULL; nkeynes@1097: nkeynes@1097: /* check file extensions first */ nkeynes@1097: FILE *f = file_sector_source_get_file(disc->base_source); nkeynes@1097: if( ext != NULL ) { nkeynes@1097: ext++; /* Skip the '.' */ nkeynes@1097: for( i=0; cdrom_disc_factories[i] != NULL; i++ ) { nkeynes@1097: if( cdrom_disc_factories[i]->extension != NULL && nkeynes@1097: strcasecmp( cdrom_disc_factories[i]->extension, ext ) == 0 ) { nkeynes@1097: extclz = cdrom_disc_factories[i]; nkeynes@1097: if( extclz->is_valid_file(f) ) { nkeynes@1097: disc->read_toc = extclz->read_toc; nkeynes@1097: } nkeynes@1097: break; nkeynes@1097: } nkeynes@1097: } nkeynes@1097: } nkeynes@1097: nkeynes@1097: if( disc->read_toc == NULL ) { nkeynes@1097: /* Okay, fall back to magic */ nkeynes@1097: for( i=0; cdrom_disc_factories[i] != NULL; i++ ) { nkeynes@1097: if( cdrom_disc_factories[i] != extclz && nkeynes@1097: cdrom_disc_factories[i]->is_valid_file(f) ) { nkeynes@1097: disc->read_toc = cdrom_disc_factories[i]->read_toc; nkeynes@1097: break; nkeynes@1097: } nkeynes@1097: } nkeynes@1097: } nkeynes@1097: nkeynes@1097: if( disc->read_toc != NULL && cdrom_disc_read_toc( disc, err ) ) { nkeynes@1097: /* All good */ nkeynes@1097: return disc; nkeynes@1097: } else { nkeynes@1097: /* No handler found for file */ nkeynes@1097: cdrom_disc_unref( disc ); nkeynes@1097: SET_ERROR( err, EINVAL, "File '%s' could not be recognized as any known image file or device type" ); nkeynes@1097: return NULL; nkeynes@1097: } nkeynes@1097: } nkeynes@1097: nkeynes@1097: /** nkeynes@1108: * Construct a disc around a source track. nkeynes@1108: * @param type Disc type, which must be compatible with the track mode nkeynes@1108: * @param track The source of data for the main track nkeynes@1108: * @param lba The position on disc of the main track. If non-zero, nkeynes@1108: * a filler track is added before it, in 2 separate sessions. nkeynes@1108: */ nkeynes@1108: cdrom_disc_t cdrom_disc_new_from_track( cdrom_disc_type_t type, sector_source_t track, cdrom_lba_t lba ) nkeynes@1108: { nkeynes@1108: cdrom_disc_t disc = cdrom_disc_new( NULL, NULL ); nkeynes@1108: if( disc != NULL ) { nkeynes@1108: disc->disc_type = type; nkeynes@1108: int trackno = 0; nkeynes@1108: if( lba != 0 ) { nkeynes@1108: cdrom_count_t size = lba - 150; nkeynes@1108: if( lba < 150 ) nkeynes@1108: size = lba; nkeynes@1108: disc->track[0].trackno = 1; nkeynes@1108: disc->track[0].sessionno = 1; nkeynes@1108: disc->track[0].lba = 0; nkeynes@1108: disc->track[0].flags = 0; nkeynes@1108: disc->track[0].source = null_sector_source_new( SECTOR_CDDA, size ); nkeynes@1108: sector_source_ref( disc->track[0].source ); nkeynes@1108: trackno++; nkeynes@1108: } nkeynes@1108: disc->track[trackno].trackno = trackno+1; nkeynes@1108: disc->track[trackno].sessionno = trackno+1; nkeynes@1108: disc->track[trackno].lba = lba; nkeynes@1108: disc->track[trackno].flags = (track->mode == SECTOR_CDDA ? 0 : TRACK_FLAG_DATA); nkeynes@1108: disc->track[trackno].source = track; nkeynes@1108: sector_source_ref(track); nkeynes@1108: nkeynes@1108: disc->track_count = trackno+1; nkeynes@1108: disc->session_count = trackno+1; nkeynes@1108: cdrom_disc_compute_leadout(disc); nkeynes@1108: } nkeynes@1108: return disc; nkeynes@1108: } nkeynes@1108: nkeynes@1108: /** nkeynes@1108: * Construct a disc around an IsoImage track (convenience function) nkeynes@1108: */ nkeynes@1108: cdrom_disc_t cdrom_disc_new_from_iso_image( cdrom_disc_type_t type, IsoImage *iso, cdrom_lba_t lba, nkeynes@1108: const char *bootstrap, ERROR *err ) nkeynes@1108: { nkeynes@1108: sector_mode_t mode = (type == CDROM_DISC_NONXA ? SECTOR_MODE1 : SECTOR_MODE2_FORM1 ); nkeynes@1108: sector_source_t source = iso_sector_source_new( iso, mode, lba, bootstrap, err ); nkeynes@1108: if( source != NULL ) { nkeynes@1108: cdrom_disc_t disc = cdrom_disc_new_from_track(type, source, lba); nkeynes@1108: if( disc == NULL ) { nkeynes@1108: sector_source_unref( source ); nkeynes@1108: } else { nkeynes@1108: return disc; nkeynes@1108: } nkeynes@1108: } nkeynes@1108: return NULL; nkeynes@1108: } nkeynes@1108: nkeynes@1108: /** nkeynes@1097: * Get the track information for the given track. If there is no such track, nkeynes@1097: * return NULL; nkeynes@1097: */ nkeynes@1097: cdrom_track_t cdrom_disc_get_track( cdrom_disc_t disc, cdrom_trackno_t track ) nkeynes@1097: { nkeynes@1097: if( track < 1 || track >= disc->track_count ) nkeynes@1097: return NULL; nkeynes@1097: return &disc->track[track-1]; nkeynes@1097: } nkeynes@1097: nkeynes@1097: /** nkeynes@1097: * Get the track information for the first track of the given session. If there nkeynes@1097: * is no such session, return NULL; nkeynes@1097: */ nkeynes@1097: cdrom_track_t cdrom_disc_get_session( cdrom_disc_t disc, cdrom_sessionno_t session ) nkeynes@1097: { nkeynes@1097: for( unsigned i=0; i< disc->track_count; i++ ) { nkeynes@1097: if( disc->track[i].sessionno == session ) nkeynes@1097: return &disc->track[i]; nkeynes@1097: } nkeynes@1097: return NULL; nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_count_t cdrom_disc_get_track_size( cdrom_disc_t disc, cdrom_track_t track ) nkeynes@1097: { nkeynes@1097: if( track->trackno == disc->track_count ) nkeynes@1097: return disc->leadout - track->lba; nkeynes@1097: else nkeynes@1097: return disc->track[track->trackno].lba - track->lba; nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_track_t cdrom_disc_get_last_track( cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: if( disc->track_count == 0 ) nkeynes@1097: return NULL; nkeynes@1097: return &disc->track[disc->track_count-1]; nkeynes@1097: } nkeynes@1097: nkeynes@1099: cdrom_track_t cdrom_disc_get_last_data_track( cdrom_disc_t disc ) nkeynes@1099: { nkeynes@1099: for( unsigned i=disc->track_count; i>0; i-- ) { nkeynes@1099: if( disc->track[i-1].flags & TRACK_FLAG_DATA ) { nkeynes@1099: return &disc->track[i-1]; nkeynes@1099: } nkeynes@1099: } nkeynes@1099: return NULL; nkeynes@1099: } nkeynes@1097: cdrom_track_t cdrom_disc_prev_track( cdrom_disc_t disc, cdrom_track_t track ) nkeynes@1097: { nkeynes@1097: if( track->trackno <= 1 ) nkeynes@1097: return NULL; nkeynes@1097: return cdrom_disc_get_track( disc, track->trackno-1 ); nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_track_t cdrom_disc_next_track( cdrom_disc_t disc, cdrom_track_t track ) nkeynes@1097: { nkeynes@1097: if( track->trackno >= disc->track_count ) nkeynes@1097: return NULL; nkeynes@1097: return cdrom_disc_get_track( disc, track->trackno+1 ); nkeynes@1097: } nkeynes@1097: nkeynes@1097: /** nkeynes@1097: * Find the track containing the sector specified by LBA. nkeynes@1097: * Note: this function does not check for media change. nkeynes@1097: * @return The track, or NULL if no track contains the sector. nkeynes@1097: */ nkeynes@1097: cdrom_track_t cdrom_disc_get_track_by_lba( cdrom_disc_t disc, cdrom_lba_t lba ) nkeynes@1097: { nkeynes@1097: if( disc->track_count == 0 || disc->track[0].lba > lba || lba >= disc->leadout ) nkeynes@1097: return NULL; /* LBA outside disc bounds */ nkeynes@1097: nkeynes@1097: for( unsigned i=1; i< disc->track_count; i++ ) { nkeynes@1097: if( lba < disc->track[i].lba ) nkeynes@1097: return &disc->track[i-1]; nkeynes@1097: } nkeynes@1097: return &disc->track[disc->track_count-1]; nkeynes@1097: } nkeynes@1097: nkeynes@1097: cdrom_error_t cdrom_disc_read_sectors( cdrom_disc_t disc, cdrom_lba_t lba, cdrom_count_t count, nkeynes@1097: cdrom_read_mode_t mode, unsigned char *buf, size_t *length ) nkeynes@1097: { nkeynes@1097: return disc->source.read_sectors( &disc->source, lba, count, mode, buf, length ); nkeynes@1097: } nkeynes@1097: nkeynes@1097: /** nkeynes@1097: * Check if the disc contains valid media. nkeynes@1097: * @return CDROM_ERROR_OK if disc is present, otherwise CDROM_ERROR_NODISC nkeynes@1097: */ nkeynes@1097: cdrom_error_t cdrom_disc_check_media( cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: if( disc == NULL ) nkeynes@1097: return CDROM_ERROR_NODISC; nkeynes@1097: if( disc->check_media != NULL ) nkeynes@1097: disc->check_media(disc); nkeynes@1097: return disc->disc_type == CDROM_DISC_NONE ? CDROM_ERROR_NODISC : CDROM_ERROR_OK; nkeynes@1097: } nkeynes@1097: nkeynes@1097: void cdrom_disc_print_toc( FILE *f, cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: int i; nkeynes@1097: int session = 0; nkeynes@1097: nkeynes@1097: if( disc == NULL || disc->track_count == 0 ) { nkeynes@1097: fprintf( f, "No disc\n" ); nkeynes@1097: return; nkeynes@1097: } nkeynes@1097: for( i=0; itrack_count; i++ ) { nkeynes@1097: cdrom_track_t track = &disc->track[i]; nkeynes@1097: if( track->sessionno != session ) { nkeynes@1097: session = disc->track[i].sessionno; nkeynes@1097: fprintf( f, "Session %d:\n", session ); nkeynes@1097: } nkeynes@1097: fprintf( f, " %02d. %6d %02x\n", track->trackno, track->lba, track->flags ); nkeynes@1097: } nkeynes@1097: } nkeynes@1097: nkeynes@1097: void cdrom_disc_dump_toc( cdrom_disc_t disc ) nkeynes@1097: { nkeynes@1097: cdrom_disc_print_toc( stderr, disc ); nkeynes@1097: }