DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/dos/cdrom_image.cpp
00001 /*
00002  *  Copyright (C) 2002-2020  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017  */
00018 
00019 #include "cdrom.h"
00020 #include <cassert>
00021 #include <cctype>
00022 #include <chrono>
00023 #include <cmath>
00024 #include <cstdio>
00025 #include <fstream>
00026 #include <iostream>
00027 #include <iterator>
00028 #include <limits>
00029 #include <sstream>
00030 #include <vector>
00031 #include <sys/stat.h>
00032 
00033 #if !defined(WIN32)
00034 #include <libgen.h>
00035 #else
00036 #include <cstring>
00037 #endif
00038 
00039 #include "drives.h"
00040 #include "support.h"
00041 #include "setup.h"
00042 #include "src/libs/decoders/SDL_sound.c"
00043 #include "src/libs/decoders/vorbis.c"
00044 #include "src/libs/decoders/flac.c"
00045 #include "src/libs/decoders/wav.c"
00046 #include "src/libs/decoders/mp3_seek_table.cpp"
00047 #include "src/libs/decoders/mp3.cpp"
00048 
00049 using namespace std;
00050 
00051 // String maximums, local to this file
00052 #define MAX_LINE_LENGTH 512
00053 #define MAX_FILENAME_LENGTH 256
00054 
00055 std::string get_basename(const std::string& filename) {
00056         // Guard against corner cases: '', '/', '\', 'a'
00057         if (filename.length() <= 1)
00058                 return filename;
00059 
00060         // Find the last slash, but if not is set to zero
00061         size_t slash_pos = filename.find_last_of("/\\");
00062 
00063         // If the slash is the last character
00064         if (slash_pos == filename.length() - 1)
00065                 slash_pos = 0;
00066 
00067         // Otherwise if the slash is found mid-string
00068         else if (slash_pos > 0)
00069                 slash_pos++;
00070         return filename.substr(slash_pos);
00071 }
00072 
00073 // STL type shorteners, local to this file
00074 using track_iter       = vector<CDROM_Interface_Image::Track>::iterator;
00075 using track_const_iter = vector<CDROM_Interface_Image::Track>::const_iterator;
00076 using tracks_size_t    = vector<CDROM_Interface_Image::Track>::size_type;
00077 
00078 // Report bad seeks that would go beyond the end of the track
00079 bool CDROM_Interface_Image::TrackFile::offsetInsideTrack(const uint32_t offset)
00080 {
00081         if (static_cast<int>(offset) >= getLength()) {
00082                 LOG_MSG("CDROM: attempted to seek to byte %u, beyond the "
00083                         "track's %d byte-length",
00084                         offset, length_redbook_bytes);
00085                 return false;
00086         }
00087         return true;
00088 }
00089 
00090 // Trim requested read sizes that will spill beyond the track ending
00091 uint32_t CDROM_Interface_Image::TrackFile::adjustOverRead(const uint32_t offset,
00092                                                           const uint32_t requested_bytes)
00093 {
00094         // The most likely scenario is read is valid and no adjustment is needed
00095         uint32_t adjusted_bytes = requested_bytes;
00096 
00097         // If we seek beyond the end of the track, then we can't read any bytes...
00098         if (static_cast<int>(offset) >= getLength()) {
00099                 adjusted_bytes = 0;
00100                 LOG_MSG("CDROM: can't read audio because requested offset %u "
00101                         "is beyond the track length, %u",
00102                         offset, getLength());
00103         }
00104         // Otherwise if our seek + requested bytes goes beyond, then prune back
00105         // the request
00106         else if (static_cast<int>(offset + requested_bytes) > getLength()) {
00107                 adjusted_bytes = static_cast<uint32_t>(getLength()) - offset;
00108                 LOG_MSG("CDROM: reducing read-length by %u bytes to avoid "
00109                         "reading beyond track.",
00110                         requested_bytes - adjusted_bytes);
00111         }
00112         return adjusted_bytes;
00113 }
00114 
00115 CDROM_Interface_Image::BinaryFile::BinaryFile(const char *filename, bool &error)
00116         : TrackFile(BYTES_PER_RAW_REDBOOK_FRAME),
00117           file(nullptr)
00118 {
00119         file = new ifstream(filename, ios::in | ios::binary);
00120         // If new fails, an exception is generated and scope leaves this constructor
00121         error = file->fail();
00122 }
00123 
00124 CDROM_Interface_Image::BinaryFile::~BinaryFile()
00125 {
00126         // Guard: only cleanup if needed
00127         if (file == nullptr)
00128                 return;
00129 
00130         delete file;
00131         file = nullptr;
00132 }
00133 
00134 bool CDROM_Interface_Image::BinaryFile::read(uint8_t *buffer,
00135                                              const uint32_t offset,
00136                                              const uint32_t requested_bytes)
00137 {
00138         // Check for logic bugs and illegal values
00139         assertm(file && buffer, "The file and/or buffer pointer is invalid");
00140         assertm(offset <= MAX_REDBOOK_BYTES, "Requested offset exceeds CDROM size");
00141         assertm(requested_bytes <= MAX_REDBOOK_BYTES, "Requested bytes exceeds CDROM size");
00142 
00143         if (!seek(offset))
00144                 return false;
00145 
00146         const uint32_t adjusted_bytes = adjustOverRead(offset, requested_bytes);
00147         if (adjusted_bytes == 0) // no work to do!
00148                 return true;
00149 
00150         file->read((char *)buffer, adjusted_bytes);
00151         return !file->fail();
00152 }
00153 
00154 int CDROM_Interface_Image::BinaryFile::getLength()
00155 {
00156         // Return our cached result if we've already been asked before
00157         if (length_redbook_bytes < 0 && file) {
00158                 file->seekg(0, ios::end);
00164                 length_redbook_bytes = static_cast<int>(file->tellg());
00165 
00166                 assertm(length_redbook_bytes >= 0,
00167                         "Track length could not be determined");
00168                 assertm(static_cast<uint32_t>(length_redbook_bytes) <= MAX_REDBOOK_BYTES,
00169                         "Track length exceeds the maximum CDROM size");
00170         }
00171         return length_redbook_bytes;
00172 }
00173 
00174 Bit16u CDROM_Interface_Image::BinaryFile::getEndian()
00175 {
00176         // Image files are always little endian
00177         return AUDIO_S16LSB;
00178 }
00179 
00180 bool CDROM_Interface_Image::BinaryFile::seek(const uint32_t offset)
00181 {
00182         // Check for logic bugs and illegal values
00183         assertm(file, "The file pointer needs to be valid, but is the nullptr");
00184         assertm(offset <= MAX_REDBOOK_BYTES, "Requested offset exceeds CDROM size");
00185 
00186         if (!offsetInsideTrack(offset))
00187                 return false;
00188 
00189         file->seekg(offset, ios::beg);
00190 
00191         // If the first seek attempt failed, then try harder
00192         if (file->fail()) {
00193                 file->clear();                 // clear fail and eof bits
00194                 file->seekg(0, std::ios::beg); // "I have returned."
00195                 file->seekg(offset, ios::beg); // "It will be done."
00196         }
00197         return !file->fail();
00198 }
00199 
00200 uint32_t CDROM_Interface_Image::BinaryFile::decode(int16_t *buffer,
00201                                                    const uint32_t desired_track_frames)
00202 {
00203         // Guard against logic bugs and illegal values
00204         assertm(buffer && file, "The file pointer or buffer are invalid");
00205         assertm(desired_track_frames <= MAX_REDBOOK_FRAMES,
00206                 "Requested number of frames exceeds the maximum for a CDROM");
00207 
00208         file->read((char*)buffer, desired_track_frames * BYTES_PER_REDBOOK_PCM_FRAME);
00214         const uint32_t bytes_read = static_cast<uint32_t>(file->gcount());
00215 
00216         // Return the number of decoded Redbook frames
00217         return ceil_udivide(bytes_read, BYTES_PER_REDBOOK_PCM_FRAME);
00218 }
00219 
00220 CDROM_Interface_Image::AudioFile::AudioFile(const char *filename, bool &error)
00221         : TrackFile(4096)
00222 {
00223         // Use the audio file's sample rate and number of channels as-is
00224         Sound_AudioInfo desired = {AUDIO_S16, 0, 0};
00225         sample = Sound_NewSampleFromFile(filename, &desired);
00226         const std::string filename_only = get_basename(filename);
00227         if (sample) {
00228                 error = false;
00229                 LOG_MSG("CDROM: Loaded %s [%d Hz, %d-channel, %2.1f minutes]",
00230                         filename_only.c_str(), getRate(), getChannels(),
00231                         getLength() / static_cast<double>(REDBOOK_PCM_BYTES_PER_MIN));
00232         } else {
00233                 LOG_MSG("CDROM: Failed adding '%s' as CDDA track!", filename_only.c_str());
00234                 error = true;
00235         }
00236 }
00237 
00238 CDROM_Interface_Image::AudioFile::~AudioFile()
00239 {
00240         // Guard to prevent double-free or nullptr free
00241         if (sample == nullptr)
00242                 return;
00243 
00244         Sound_FreeSample(sample);
00245         sample = nullptr;
00246 }
00247 
00260 bool CDROM_Interface_Image::AudioFile::seek(const uint32_t requested_pos)
00261 {
00262         // Check for logic bugs and if the track is already positioned as requested
00263         assertm(sample, "Audio sample needs to be valid, but is the nullptr");
00264         assertm(requested_pos <= MAX_REDBOOK_BYTES, "Requested offset exceeds CDROM size");
00265 
00266         if (!offsetInsideTrack(requested_pos))
00267                 return false;
00268 
00269         if (track_pos == requested_pos) {
00270                 return true;
00271         }
00272 
00273         // Convert the position from a byte offset to time offset, in milliseconds.
00274         const uint32_t ms_per_s = 1000;
00275         const uint32_t pos_in_frames = ceil_udivide(requested_pos, BYTES_PER_RAW_REDBOOK_FRAME);
00276         const uint32_t pos_in_ms = ceil_udivide(pos_in_frames * ms_per_s, REDBOOK_FRAMES_PER_SECOND);
00277 
00278         // Perform the seek
00279         const bool result = Sound_Seek(sample, pos_in_ms);
00280 
00281         // Only store the track's new position if the seek was successful
00282         if (result)
00283                 track_pos = requested_pos;
00284         return result;
00285 }
00286 
00287 bool CDROM_Interface_Image::AudioFile::read(uint8_t *buffer,
00288                                             const uint32_t requested_pos,
00289                                             const uint32_t requested_bytes)
00290 {
00291         // Guard again logic bugs and the no-op case
00292         assertm(buffer != nullptr, "buffer needs to be allocated but is the nullptr");
00293         assertm(sample != nullptr, "Audio sample needs to be valid, but is the nullptr");
00294         assertm(requested_pos <= MAX_REDBOOK_BYTES, "Requested offset exceeds CDROM size");
00295         assertm(requested_bytes <= MAX_REDBOOK_BYTES,
00296                 "Requested bytes exceeds CDROM size");
00297 
00304         if (getRate() != REDBOOK_PCM_FRAMES_PER_SECOND) {
00305                 static uint8_t dae_attempts = 0;
00306                 if (dae_attempts++ > 10) {
00307                         E_Exit("\n"
00308                                "CDROM: Digital Audio Extration (DAE) was attempted with a %u kHz\n"
00309                                "       track, but DAE is only compatible with %u kHz tracks.",
00310                                getRate(),
00311                                REDBOOK_PCM_FRAMES_PER_SECOND);
00312                 }
00313                 return false; // we always correctly return false to the application in this case.
00314         }
00315 
00316         if (!seek(requested_pos))
00317                 return false;
00318 
00319         const uint32_t adjusted_bytes = adjustOverRead(requested_pos, requested_bytes);
00320         if (adjusted_bytes == 0) // no work to do!
00321                 return true;
00322 
00323         // Setup characteristics about our track and the request
00324         const uint8_t channels = getChannels();
00325         const uint8_t bytes_per_frame = channels * REDBOOK_BPS;
00326         const uint32_t requested_frames = ceil_udivide(adjusted_bytes,
00327                                                        BYTES_PER_REDBOOK_PCM_FRAME);
00328 
00329         uint32_t decoded_bytes = 0;
00330         uint32_t decoded_frames = 0;
00331         while (decoded_frames < requested_frames) {
00332                 const uint32_t decoded = Sound_Decode_Direct(sample,
00333                                                              buffer + decoded_bytes,
00334                                                              requested_frames - decoded_frames);
00335                 if (sample->flags & (SOUND_SAMPLEFLAG_ERROR | SOUND_SAMPLEFLAG_EOF) || !decoded)
00336                         break;
00337                 decoded_frames += decoded;
00338                 decoded_bytes = decoded_frames * bytes_per_frame;
00339         }
00340         // Zero out any remainining frames that we didn't fill
00341         if (decoded_frames < requested_frames)
00342                 memset(buffer + decoded_bytes, 0, adjusted_bytes - decoded_bytes);
00343 
00344         // If the track is mono, convert to stereo
00345         if (channels == 1 && decoded_frames) {
00367                 int16_t* pcm_buffer = reinterpret_cast<int16_t*>(buffer);
00368                 const uint32_t mono_samples = decoded_frames;
00369                 for (uint32_t i = mono_samples - 1; i > 0; --i) {
00370                         pcm_buffer[i * REDBOOK_CHANNELS + 1] = pcm_buffer[i]; // right
00371                         pcm_buffer[i * REDBOOK_CHANNELS + 0] = pcm_buffer[i]; // left
00372                 }
00373 
00374                 // Taken into account that we've now fill both (stereo) channels
00375                 decoded_bytes *= REDBOOK_CHANNELS;
00376         }
00377         // Increment the track's position by the Redbook-sized decoded bytes
00378         track_pos += decoded_bytes;
00379         return !(sample->flags & SOUND_SAMPLEFLAG_ERROR);
00380 }
00381 
00382 uint32_t CDROM_Interface_Image::AudioFile::decode(int16_t *buffer,
00383                                                   const uint32_t desired_track_frames)
00384 {
00385         // Sound_Decode_Direct returns frames (agnostic of bitrate and channels)
00386         const uint32_t frames_decoded =
00387             Sound_Decode_Direct(sample, (void*)buffer, desired_track_frames);
00388 
00389         // Increment the track's in terms of Redbook-equivalent bytes
00390         const uint32_t redbook_bytes = frames_decoded * BYTES_PER_REDBOOK_PCM_FRAME;
00391         track_pos += redbook_bytes;
00392 
00393         return frames_decoded;
00394 }
00395 
00396 Bit16u CDROM_Interface_Image::AudioFile::getEndian()
00397 {
00398         return sample ? sample->actual.format : AUDIO_S16SYS;
00399 }
00400 
00401 Bit32u CDROM_Interface_Image::AudioFile::getRate()
00402 {
00403         return sample ? sample->actual.rate : 0;
00404 }
00405 
00406 Bit8u CDROM_Interface_Image::AudioFile::getChannels()
00407 {
00408         return sample ? sample->actual.channels : 0;
00409 }
00410 
00411 int CDROM_Interface_Image::AudioFile::getLength()
00412 {
00413         if (length_redbook_bytes < 0 && sample) {
00414                 /*
00415                  *  Sound_GetDuration returns milliseconds but getLength()
00416                  *  needs to return bytes, so we covert using PCM bytes/s
00417                  */
00418                 length_redbook_bytes = static_cast<int>(static_cast<float>(Sound_GetDuration(sample)) * REDBOOK_PCM_BYTES_PER_MS);
00419         }
00420         assertm(length_redbook_bytes >= 0,
00421                 "Track length could not be determined");
00422         assertm(static_cast<uint32_t>(length_redbook_bytes) <= MAX_REDBOOK_BYTES,
00423                 "Track length exceeds the maximum CDROM size");
00424         return length_redbook_bytes;
00425 }
00426 
00427 // initialize static members
00428 int CDROM_Interface_Image::refCount = 0;
00429 CDROM_Interface_Image* CDROM_Interface_Image::images[26] = {};
00430 CDROM_Interface_Image::imagePlayer CDROM_Interface_Image::player;
00431 
00432 CDROM_Interface_Image::CDROM_Interface_Image(Bit8u subUnit)
00433         : tracks({}),
00434           mcn("")
00435           //subUnit(_subUnit)
00436 {
00437         images[subUnit] = this;
00438         if (refCount == 0) {
00439                 if (!player.mutex)
00440                         player.mutex = SDL_CreateMutex();
00441 
00442                 if (!player.channel) {
00443                         player.channel = player.mixerChannel.Install(&CDAudioCallBack, 0, "CDAUDIO");
00444                         player.channel->Enable(false); // only enabled during playback periods
00445                 }
00446         }
00447         refCount++;
00448 }
00449 
00450 CDROM_Interface_Image::~CDROM_Interface_Image()
00451 {
00452         refCount--;
00453         tracks.clear();
00454         // Stop playback before wiping out the CD Player
00455         if (refCount == 0 && player.cd) {
00456                 StopAudio();
00457                 SDL_DestroyMutex(player.mutex);
00458                 player.mutex = nullptr;
00459                 if (player.channel) {
00460                         player.channel->Enable(false);
00461                         MIXER_DelChannel(player.channel);
00462                         player.channel = nullptr;
00463                 }
00464         }
00465         if (player.cd == this) {
00466                 player.cd = nullptr;
00467         }
00468 }
00469 
00470 bool CDROM_Interface_Image::SetDevice(char* path, int forceCD)
00471 {
00472         (void)forceCD;//UNUSED
00473         const bool result = LoadCueSheet(path) || LoadIsoFile(path);
00474         if (!result) {
00475                 // print error message on dosbox console
00476                 char buf[MAX_LINE_LENGTH];
00477                 snprintf(buf, MAX_LINE_LENGTH, "Could not load image file: %s\r\n", path);
00478                 Bit16u size = (Bit16u)strlen(buf);
00479                 DOS_WriteFile(STDOUT, (Bit8u*)buf, &size);
00480         }
00481         return result;
00482 }
00483 
00484 bool CDROM_Interface_Image::GetUPC(unsigned char& attr, char* upc)
00485 {
00486         attr = 0;
00487         strcpy(upc, this->mcn.c_str());
00488         return true;
00489 }
00490 
00491 bool CDROM_Interface_Image::GetAudioTracks(int& start_track_num,
00492                                            int& end_track_num,
00493                                            TMSF& lead_out_msf)
00494 {
00499         if (tracks.size() < MIN_REDBOOK_TRACKS) {
00500                 return false;
00501         }
00502         start_track_num = tracks.front().number;
00503         end_track_num = next(tracks.crbegin())->number; // next(crbegin) == [vec.size - 2]
00504         lead_out_msf = frames_to_msf(tracks.back().start + REDBOOK_FRAME_PADDING);
00505         return true;
00506 }
00507 
00508 bool CDROM_Interface_Image::GetAudioTrackInfo(int requested_track_num,
00509                                               TMSF& start_msf,
00510                                               unsigned char& attr)
00511 {
00512         if (tracks.size() < MIN_REDBOOK_TRACKS
00513             || requested_track_num < 1
00514             || requested_track_num > 99
00515             || (unsigned int)requested_track_num >= tracks.size()) {
00516                 return false;
00517         }
00518 
00519         const int requested_track_index = static_cast<int>(requested_track_num) - 1;
00520         track_const_iter track = tracks.begin() + requested_track_index;
00521         start_msf = frames_to_msf(track->start + REDBOOK_FRAME_PADDING);
00522         attr = track->attr;
00523         return true;
00524 }
00525 
00526 bool CDROM_Interface_Image::GetAudioSub(unsigned char& attr,
00527                                         unsigned char& track_num,
00528                                         unsigned char& index,
00529                                         TMSF& relative_msf,
00530                                         TMSF& absolute_msf) {
00531         // Setup valid defaults to handle all scenarios
00532         attr = 0;
00533         track_num = 1;
00534         index = 1;
00535         uint32_t absolute_sector = 0;
00536         uint32_t relative_sector = 0;
00537 
00538         if (!tracks.empty()) {  // We have a useable CD; get a valid play-position
00539                 track_iter track = tracks.begin();
00540                 // the CD's current track is valid
00541 
00542                  // reserve the track_file as a shared_ptr to avoid deletion in another thread
00543                 const auto track_file = player.trackFile.lock();
00544                 if (track_file) {
00545                         const uint32_t sample_rate = track_file->getRate();
00546                         const uint32_t played_frames = ceil_udivide(player.playedTrackFrames
00547                                                        * REDBOOK_FRAMES_PER_SECOND, sample_rate);
00548                         absolute_sector = player.startSector + played_frames;
00549                         track_iter current_track = GetTrack(absolute_sector);
00550                         if (current_track != tracks.end()) {
00551                                 track = current_track;
00552                                 relative_sector = absolute_sector >= track->start ?
00553                                                   absolute_sector - track->start : 0;
00554                         } else { // otherwise fallback to the beginning track
00555                                 absolute_sector = track->start;
00556                                 // relative_sector is zero because we're at the start of the track
00557                         }
00558                 // the CD hasn't been played yet or has an invalid track_pos
00559                 } else {
00560                         for (track_iter it = tracks.begin(); it != tracks.end(); ++it) {
00561                                 if (it->attr == 0) {    // Found an audio track
00562                                         track = it;
00563                                         absolute_sector = it->start;
00564                                         break;
00565                                 } // otherwise fallback to the beginning track
00566                         }
00567                 }
00568                 attr = track->attr;
00569                 track_num = track->number;
00570         }
00571         absolute_msf = frames_to_msf(absolute_sector + REDBOOK_FRAME_PADDING);
00572         relative_msf = frames_to_msf(relative_sector);
00573         return true;
00574 }
00575 
00576 bool CDROM_Interface_Image::GetAudioStatus(bool& playing, bool& pause)
00577 {
00578         playing = player.isPlaying;
00579         pause = player.isPaused;
00580 
00581         return true;
00582 }
00583 
00584 bool CDROM_Interface_Image::GetMediaTrayStatus(bool& mediaPresent, bool& mediaChanged, bool& trayOpen)
00585 {
00586         mediaPresent = true;
00587         mediaChanged = false;
00588         trayOpen = false;
00589         return true;
00590 }
00591 
00592 bool CDROM_Interface_Image::PlayAudioSector(unsigned long start, unsigned long len)
00593 {
00594         // Find the track that holds the requested sector
00595         track_const_iter track = GetTrack(start);
00596         std::shared_ptr<TrackFile> track_file;
00597         if (track != tracks.end())
00598                 track_file = track->file;
00599 
00600         // Guard: sanity check the request beyond what GetTrack already checks
00601         if (len == 0
00602            || track == tracks.end()
00603            || !track_file
00604            || track->attr == 0x40
00605            || !player.channel
00606            || !player.mutex) {
00607                 StopAudio();
00608                 return false;
00609         }
00615         if (start < track->start)
00616                 len -= (track->start - start);
00617 
00618         // Seek to the calculated byte offset, bounded to the valid byte offsets
00619         const uint32_t offset = (track->skip
00620                                 + clamp((uint32_t)start - static_cast<uint32_t>(track->start),
00621                                         0u, track->length - 1)
00622                                 * track->sectorSize);
00623 
00624         // Guard: Bail if our track could not be seeked
00625         if (!track_file->seek(offset)) {
00626                 LOG_MSG("CDROM: Track %d failed to seek to byte %u, so cancelling playback",
00627                         track->number,
00628                         offset);
00629                 StopAudio();
00630                 return false;
00631         }
00632 
00633         // Get properties about the current track
00634         const uint8_t track_channels = track_file->getChannels();
00635         const uint32_t track_rate = track_file->getRate();
00636 
00642         if (SDL_LockMutex(player.mutex) < 0) {
00643                 LOG_MSG("CDROM: PlayAudioSector couldn't lock our player for exclusive access");
00644                 StopAudio();
00645                 return false;
00646         }
00647 
00648         // Update our player with properties about this playback sequence
00649         player.cd = this;
00650         player.trackFile = track_file;
00651         player.startSector = start;
00652         player.totalRedbookFrames = len;
00653         player.isPlaying = true;
00654         player.isPaused = false;
00655 
00656         // Assign the mixer function associated with this track's content type
00657         if (track_file->getEndian() == AUDIO_S16SYS) {
00658                 player.addFrames = track_channels ==  2  ? &MixerChannel::AddSamples_s16 \
00659                                                          : &MixerChannel::AddSamples_m16;
00660         } else {
00661                 player.addFrames = track_channels ==  2  ? &MixerChannel::AddSamples_s16_nonnative \
00662                                                          : &MixerChannel::AddSamples_m16_nonnative;
00663         }
00664 
00671         player.playedTrackFrames = 0;
00672         player.totalTrackFrames = ceil_udivide(track_rate * player.totalRedbookFrames,
00673                                               REDBOOK_FRAMES_PER_SECOND);
00674 
00675         // start the channel!
00676         player.channel->SetFreq(track_rate);
00677         player.channel->Enable(true);
00678 
00679         // Guard: release the lock in this data
00680     if (SDL_UnlockMutex(player.mutex) < 0) {
00681         LOG_MSG("CDROM: PlayAudioSector couldn't unlock this thread");
00682                 StopAudio();
00683                 return false;
00684     }
00685         return true;
00686 }
00687 
00688 bool CDROM_Interface_Image::PauseAudio(bool resume)
00689 {
00690         player.isPaused = !resume;
00691         if (player.channel)
00692                 player.channel->Enable(resume);
00693         return true;
00694 }
00695 
00696 bool CDROM_Interface_Image::StopAudio(void)
00697 {
00698         player.isPlaying = false;
00699         player.isPaused = false;
00700         if (player.channel)
00701                 player.channel->Enable(false);
00702         return true;
00703 }
00704 
00705 void CDROM_Interface_Image::ChannelControl(TCtrl ctrl)
00706 {
00707         // Guard: Bail if our mixer channel hasn't been allocated
00708         if (!player.channel)
00709                 return;
00710 
00711         player.ctrlUsed = (ctrl.out[0]!=0 || ctrl.out[1]!=1 || ctrl.vol[0]<0xfe || ctrl.vol[1]<0xfe);
00712         player.ctrlData = ctrl;
00713 
00714         // Adjust the volume of our mixer channel as defined by the application
00715         player.channel->SetScale(static_cast<float>(ctrl.vol[0]),  // left vol
00716                                  static_cast<float>(ctrl.vol[1])); // right vol
00717 }
00718 
00719 bool CDROM_Interface_Image::ReadSectors(PhysPt buffer,
00720                                         const bool raw,
00721                                         unsigned long sector,
00722                                         unsigned long num)
00723 {
00724         const uint16_t sectorSize = (raw ? BYTES_PER_RAW_REDBOOK_FRAME
00725                                          : BYTES_PER_COOKED_REDBOOK_FRAME);
00726         const uint32_t requested_bytes = num * sectorSize;
00727 
00728         // Resize our underlying vector if it's not big enough
00729         if (readBuffer.size() < requested_bytes)
00730                 readBuffer.resize(requested_bytes);
00731 
00732         // Setup state-tracking variables to be used in the read-loop
00733         bool success = true; //Gobliiins reads 0 sectors
00734         uint32_t bytes_read = 0;
00735         uint32_t current_sector = sector;
00736         uint8_t* buffer_position = readBuffer.data();
00737 
00738         // Read until we have enough or fail
00739         while (bytes_read < requested_bytes) {
00740                 success = ReadSector(buffer_position, raw, current_sector);
00741                 if (!success)
00742                         break;
00743                 current_sector++;
00744                 bytes_read += sectorSize;
00745                 buffer_position += sectorSize;
00746         }
00747         // Write only the successfully read bytes
00748         MEM_BlockWrite(buffer, readBuffer.data(), bytes_read);
00749         return success;
00750 }
00751 
00752 bool CDROM_Interface_Image::ReadSectorsHost(void *buffer, bool raw, unsigned long sector, unsigned long num)
00753 {
00754         unsigned int sectorSize = raw ? RAW_SECTOR_SIZE : COOKED_SECTOR_SIZE;
00755         bool success = true; //Gobliiins reads 0 sectors
00756         for(unsigned long i = 0; i < num; i++) {
00757                 success = ReadSector((Bit8u*)buffer + (i * (Bitu)sectorSize), raw, sector + i);
00758                 if (!success) break;
00759         }
00760 
00761         return success;
00762 }
00763 
00764 bool CDROM_Interface_Image::LoadUnloadMedia(bool unload)
00765 {
00766         (void)unload; // unused by part of the API
00767         return true;
00768 }
00769 
00770 track_iter CDROM_Interface_Image::GetTrack(const uint32_t sector)
00771 {
00772         // Guard if we have no tracks or the sector is beyond the lead-out
00773         if (sector > MAX_REDBOOK_SECTOR ||
00774             tracks.size() < MIN_REDBOOK_TRACKS ||
00775             sector >= tracks.back().start) {
00776                 LOG_MSG("CDROM: GetTrack at sector %u is outside the"
00777                         " playable range", sector);
00778                 return tracks.end();
00779         }
00780 
00786         track_iter track = tracks.begin();
00787         uint32_t lower_bound = track->start;
00788         while (track != tracks.end()) {
00789                 const uint32_t upper_bound = track->start + track->length;
00790                 if (lower_bound <= sector && sector < upper_bound) {
00791                         break;
00792                 }
00793                 ++track;
00794                 lower_bound = upper_bound;
00795         }
00796 
00797         return track;
00798 }
00799 
00800 bool CDROM_Interface_Image::ReadSector(uint8_t *buffer, bool raw, const uint32_t sector)
00801 {
00802         track_const_iter track = GetTrack(sector);
00803 
00804         // Guard: Bail if the requested sector fell outside our tracks
00805         if (track == tracks.end() || track->file == nullptr) {
00806                 return false;
00807         }
00808         uint32_t offset = track->skip + (sector - track->start) * track->sectorSize;
00809         const uint16_t length = (raw ? BYTES_PER_RAW_REDBOOK_FRAME : BYTES_PER_COOKED_REDBOOK_FRAME);
00810         if (track->sectorSize != BYTES_PER_RAW_REDBOOK_FRAME && raw)
00811                 return false;
00812         if (!raw && !(track->attr & 0x40)) /* non-raw (data) reads must fail against non-data (Windows 95 CDPLAYER.EXE fix) */
00813                 return false;
00814         if (track->sectorSize == BYTES_PER_RAW_REDBOOK_FRAME && !track->mode2 && !raw)
00815                 offset += 16;
00816         if (track->mode2 && !raw)
00817                 offset += 24;
00818 
00819         return track->file->read(buffer, offset, length);
00820 }
00821 
00822 
00823 void CDROM_Interface_Image::CDAudioCallBack(Bitu desired_track_frames)
00824 {
00830         std::shared_ptr<TrackFile> track_file = player.trackFile.lock();
00831 
00832         // Guards: Bail if the request or our player is invalid
00833         if (desired_track_frames == 0
00834            || !player.cd
00835            || !player.mutex
00836            || !track_file) {
00837 
00838                 if (player.cd)
00839                         player.cd->StopAudio();
00840                 return;
00841         }
00842 
00843         // Ensure we have exclusive access to update our player members
00844         if (SDL_LockMutex(player.mutex) < 0) {
00845                 LOG_MSG("CDROM: CDAudioCallBack couldn't lock this thread");
00846                 return;
00847         }
00848 
00849         const uint32_t decoded_track_frames = track_file->decode(player.buffer,
00850                                                                  static_cast<uint32_t>(desired_track_frames));
00851         player.playedTrackFrames += decoded_track_frames;
00852 
00857         (player.channel->*player.addFrames)(decoded_track_frames, player.buffer);
00858 
00859         if (player.playedTrackFrames >= player.totalTrackFrames) {
00860                 player.cd->StopAudio();
00861 
00862         } else if (decoded_track_frames == 0) {
00863                 // Our track has run dry but we still have more music left to play!
00864                 const double percent_played = static_cast<double>(
00865                                               player.playedTrackFrames)
00866                                               / player.totalTrackFrames;
00867                 const Bit32u played_redbook_frames = static_cast<Bit32u>(ceil(
00868                                                      percent_played
00869                                                      * player.totalRedbookFrames));
00870                 const Bit32u new_redbook_start_frame = player.startSector
00871                                                        + played_redbook_frames;
00872                 const Bit32u remaining_redbook_frames = player.totalRedbookFrames
00873                                                         - played_redbook_frames;
00874                 if (SDL_UnlockMutex(player.mutex) < 0) {
00875                         LOG_MSG("CDROM: CDAudioCallBack couldn't unlock to move to next track");
00876                         return;
00877                 }
00878                 player.cd->PlayAudioSector(new_redbook_start_frame, remaining_redbook_frames);
00879                 return;
00880         }
00881         if (SDL_UnlockMutex(player.mutex) < 0) {
00882                 LOG_MSG("CDROM: CDAudioCallBack couldn't unlock our player before returning");
00883         }
00884 }
00885 
00886 bool CDROM_Interface_Image::LoadIsoFile(char* filename)
00887 {
00888         tracks.clear();
00889 
00890         // data track (track 1)
00891         Track track;
00892         bool error;
00893         track.file = make_shared<BinaryFile>(filename, error);
00894 
00895         if (error) {
00896                 return false;
00897         }
00898         track.number = 1;
00899         track.attr = 0x40;//data
00900 
00901         // try to detect iso type
00902         if (CanReadPVD(track.file.get(), BYTES_PER_COOKED_REDBOOK_FRAME, false)) {
00903                 track.sectorSize = BYTES_PER_COOKED_REDBOOK_FRAME;
00904                 assert(track.mode2 == false);
00905         } else if (CanReadPVD(track.file.get(), BYTES_PER_RAW_REDBOOK_FRAME, false)) {
00906                 track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
00907                 assert(track.mode2 == false);
00908         } else if (CanReadPVD(track.file.get(), 2336, true)) {
00909                 track.sectorSize = 2336;
00910                 track.mode2 = true;
00911         } else if (CanReadPVD(track.file.get(), BYTES_PER_RAW_REDBOOK_FRAME, true)) {
00912                 track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
00913                 track.mode2 = true;
00914         } else {
00915                 return false;
00916         }
00917         const int32_t track_bytes = track.file->getLength();
00918         if (track_bytes < 0)
00919                 return false;
00920 
00921         track.length = static_cast<uint32_t>(track_bytes) / track.sectorSize;
00922 
00923         tracks.push_back(track);
00924 
00925         // lead-out track (track 2)
00926         Track leadout_track;
00927         leadout_track.number = 2;
00928         leadout_track.start = track.length;
00929         tracks.push_back(leadout_track);
00930         return true;
00931 }
00932 
00933 bool CDROM_Interface_Image::CanReadPVD(TrackFile *file,
00934                                        const uint16_t sectorSize,
00935                                        const bool mode2)
00936 {
00937         // Guard: Bail if our file pointer is empty
00938         if (file == nullptr) return false;
00939 
00940         // Initialize our array in the event file->read() doesn't fully write it
00941         Bit8u pvd[BYTES_PER_COOKED_REDBOOK_FRAME] = {0};
00942 
00943         uint32_t seek = 16 * sectorSize;  // first vd is located at sector 16
00944         if (sectorSize == BYTES_PER_RAW_REDBOOK_FRAME && !mode2) seek += 16;
00945         if (mode2) seek += 24;
00946         file->read(pvd, seek, BYTES_PER_COOKED_REDBOOK_FRAME);
00947         // pvd[0] = descriptor type, pvd[1..5] = standard identifier,
00948         // pvd[6] = iso version (+8 for High Sierra)
00949         return ((pvd[0] == 1 && !strncmp((char*)(&pvd[1]), "CD001", 5) && pvd[6]  == 1) ||
00950                 (pvd[8] == 1 && !strncmp((char*)(&pvd[9]), "CDROM", 5) && pvd[14] == 1));
00951 }
00952 
00953 #if defined(WIN32)
00954 static string dirname(char * file) {
00955         char * sep = strrchr(file, '\\');
00956         if (sep == nullptr)
00957                 sep = strrchr(file, '/');
00958         if (sep == nullptr)
00959                 return "";
00960         else {
00961                 int len = (int)(sep - file);
00962                 char tmp[MAX_FILENAME_LENGTH];
00963                 safe_strncpy(tmp, file, len+1);
00964                 return tmp;
00965         }
00966 }
00967 #endif
00968 
00969 bool CDROM_Interface_Image::LoadCueSheet(char *cuefile)
00970 {
00971         tracks.clear();
00972 
00973         Track track;
00974         uint32_t shift = 0;
00975         uint32_t currPregap = 0;
00976         uint32_t totalPregap = 0;
00977         int32_t prestart = -1;
00978         int track_number;
00979         bool success;
00980         bool canAddTrack = false;
00981         char tmp[MAX_FILENAME_LENGTH];  // dirname can change its argument
00982         safe_strncpy(tmp, cuefile, MAX_FILENAME_LENGTH);
00983         string pathname(dirname(tmp));
00984         ifstream in;
00985         in.open(cuefile, ios::in);
00986         if (in.fail()) {
00987                 return false;
00988         }
00989 
00990         while (!in.eof()) {
00991                 // get next line
00992                 char buf[MAX_LINE_LENGTH];
00993                 in.getline(buf, MAX_LINE_LENGTH);
00994                 if (in.fail() && !in.eof()) {
00995                         return false;  // probably a binary file
00996                 }
00997                 istringstream line(buf);
00998 
00999                 string command;
01000                 GetCueKeyword(command, line);
01001 
01002                 if (command == "TRACK") {
01003                         if (canAddTrack) success = AddTrack(track, shift, prestart, totalPregap, currPregap);
01004                         else success = true;
01005 
01006                         track.start = 0;
01007                         track.skip = 0;
01008                         currPregap = 0;
01009                         prestart = -1;
01010 
01011                         line >> track_number; // (cin) read into a true int first
01012                         track.number = static_cast<uint8_t>(track_number);
01013                         string type;
01014                         GetCueKeyword(type, line);
01015 
01016                         if (type == "AUDIO") {
01017                                 track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
01018                                 track.attr = 0;
01019                                 track.mode2 = false;
01020                         } else if (type == "MODE1/2048") {
01021                                 track.sectorSize = BYTES_PER_COOKED_REDBOOK_FRAME;
01022                                 track.attr = 0x40;
01023                                 track.mode2 = false;
01024                         } else if (type == "MODE1/2352") {
01025                                 track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
01026                                 track.attr = 0x40;
01027                                 track.mode2 = false;
01028                         } else if (type == "MODE2/2336") {
01029                                 track.sectorSize = 2336;
01030                                 track.attr = 0x40;
01031                                 track.mode2 = true;
01032                         } else if (type == "MODE2/2352") {
01033                                 track.sectorSize = BYTES_PER_RAW_REDBOOK_FRAME;
01034                                 track.attr = 0x40;
01035                                 track.mode2 = true;
01036                         } else success = false;
01037 
01038                         canAddTrack = true;
01039                 }
01040                 else if (command == "INDEX") {
01041                         int index;
01042                         line >> index;
01043                         uint32_t frame;
01044                         success = GetCueFrame(frame, line);
01045 
01046                         if (index == 1) track.start = frame;
01047                         else if (index == 0) prestart = static_cast<int32_t>(frame);
01048                         // ignore other indices
01049                 }
01050                 else if (command == "FILE") {
01051                         if (canAddTrack) success = AddTrack(track, shift, prestart, totalPregap, currPregap);
01052                         else success = true;
01053                         canAddTrack = false;
01054 
01055                         string filename;
01056                         GetCueString(filename, line);
01057                         GetRealFileName(filename, pathname);
01058                         string type;
01059                         GetCueKeyword(type, line);
01060 
01061                         bool error = true;
01062                         if (type == "BINARY") {
01063                                 track.file = make_shared<BinaryFile>(filename.c_str(), error);
01064                         }
01065                         else {
01066                                 track.file = make_shared<AudioFile>(filename.c_str(), error);
01072                         }
01073                         if (error) {
01074                                 success = false;
01075                         }
01076                 }
01077                 else if (command == "PREGAP") success = GetCueFrame(currPregap, line);
01078                 else if (command == "CATALOG") success = GetCueString(mcn, line);
01079                 // ignored commands
01080                 else if (command == "CDTEXTFILE" || command == "FLAGS"   || command == "ISRC" ||
01081                          command == "PERFORMER"  || command == "POSTGAP" || command == "REM" ||
01082                          command == "SONGWRITER" || command == "TITLE"   || command.empty()) {
01083                         success = true;
01084                 }
01085                 // failure
01086                 else {
01087                         success = false;
01088                 }
01089                 if (!success) {
01090                         return false;
01091                 }
01092         }
01093         // add last track
01094         if (!AddTrack(track, shift, prestart, totalPregap, currPregap)) {
01095                 return false;
01096         }
01097 
01098         // add lead-out track
01099         track.number++;
01100         track.attr = 0;//sync with load iso
01101         track.start = 0;
01102         track.length = 0;
01103         track.file.reset();
01104         if (!AddTrack(track, shift, -1, totalPregap, 0)) {
01105                 return false;
01106         }
01107         return true;
01108 }
01109 
01110 
01111 
01112 bool CDROM_Interface_Image::AddTrack(Track &curr,
01113                                      uint32_t &shift,
01114                                      const int32_t prestart,
01115                                      uint32_t &totalPregap,
01116                                      uint32_t currPregap)
01117 {
01118         uint32_t skip = 0;
01119 
01120         // frames between index 0(prestart) and 1(curr.start) must be skipped
01121         if (prestart >= 0) {
01122                 if (prestart > static_cast<int>(curr.start)) {
01123                         LOG_MSG("CDROM: AddTrack => prestart %d cannot be > curr.start %u",
01124                                 prestart, curr.start);
01125                         return false;
01126                 }
01127                 skip = static_cast<uint32_t>(static_cast<int>(curr.start) - prestart);
01128         }
01129 
01130         // Add the first track, if our vector is empty
01131         if (tracks.empty()) {
01132                 assertm(curr.number == 1, "The first track must be labelled number 1 [BUG!]");
01133                 curr.skip = skip * curr.sectorSize;
01134                 curr.start += currPregap;
01135                 totalPregap = currPregap;
01136                 tracks.push_back(curr);
01137                 return true;
01138         }
01139 
01140         // Guard against undefined behavior in subsequent tracks.back() call
01141         assert(!tracks.empty());
01142         Track &prev = tracks.back();
01143 
01144         // current track consumes data from the same file as the previous
01145         if (prev.file == curr.file) {
01146                 curr.start += shift;
01147                 if (!prev.length) {
01148                         prev.length = curr.start + totalPregap - prev.start - skip;
01149                 }
01150                 curr.skip += prev.skip + prev.length * prev.sectorSize + skip * curr.sectorSize;
01151                 totalPregap += currPregap;
01152                 curr.start += totalPregap;
01153         // current track uses a different file as the previous track
01154         } else {
01155                 const uint32_t tmp = static_cast<uint32_t>
01156                                      (prev.file->getLength()) - prev.skip;
01157                 prev.length = tmp / prev.sectorSize;
01158                 if (tmp % prev.sectorSize != 0)
01159                         prev.length++; // padding
01160 
01161                 curr.start += prev.start + prev.length + currPregap;
01162                 curr.skip = skip * curr.sectorSize;
01163                 shift += prev.start + prev.length;
01164                 totalPregap = currPregap;
01165         }
01166         // error checks
01167         if (curr.number <= 1
01168             || prev.number + 1 != curr.number
01169             || curr.start < prev.start + prev.length) {
01170                 LOG_MSG("AddTrack: failed consistency checks\n"
01171                 "\tcurr.number (%d) <= 1\n"
01172                 "\tprev.number (%d) + 1 != curr.number (%d)\n"
01173                 "\tcurr.start (%d) < prev.start (%d) + prev.length (%d)\n",
01174                 curr.number, prev.number, curr.number,
01175                 curr.start, prev.start, prev.length);
01176                 return false;
01177         }
01178 
01179         tracks.push_back(curr);
01180         return true;
01181 }
01182 
01183 bool CDROM_Interface_Image::HasDataTrack(void)
01184 {
01185         //Data track has attribute 0x40
01186         for (const auto &track : tracks) {
01187                 if (track.attr == 0x40) {
01188                         return true;
01189                 }
01190         }
01191         return false;
01192 }
01193 
01194 
01195 bool CDROM_Interface_Image::GetRealFileName(string &filename, string &pathname)
01196 {
01197         // check if file exists
01198         struct stat test;
01199         if (stat(filename.c_str(), &test) == 0) {
01200                 return true;
01201         }
01202 
01203         // check if file with path relative to cue file exists
01204         string tmpstr(pathname + "/" + filename);
01205         if (stat(tmpstr.c_str(), &test) == 0) {
01206                 filename = tmpstr;
01207                 return true;
01208         }
01209         // finally check if file is in a dosbox local drive
01210         char fullname[CROSS_LEN];
01211         char tmp[CROSS_LEN];
01212         safe_strncpy(tmp, filename.c_str(), CROSS_LEN);
01213         Bit8u drive;
01214         if (!DOS_MakeName(tmp, fullname, &drive)) {
01215                 return false;
01216         }
01217 
01218         localDrive *ldp = dynamic_cast<localDrive*>(Drives[drive]);
01219         if (ldp) {
01220                 ldp->GetSystemFilename(tmp, fullname);
01221                 if (stat(tmp, &test) == 0) {
01222                         filename = tmp;
01223                         return true;
01224                 }
01225         }
01226 
01227 #if !defined (WIN32)
01228 
01233         string copy = filename;
01234         size_t l = copy.size();
01235         for (size_t i = 0; i < l;i++) {
01236                 if (copy[i] == '\\') copy[i] = '/';
01237         }
01238 
01239         if (stat(copy.c_str(), &test) == 0) {
01240                 filename = copy;
01241                 return true;
01242         }
01243 
01244         tmpstr = pathname + "/" + copy;
01245         if (stat(tmpstr.c_str(), &test) == 0) {
01246                 filename = tmpstr;
01247                 return true;
01248         }
01249 #endif
01250         return false;
01251 }
01252 
01253 bool CDROM_Interface_Image::GetCueKeyword(string &keyword, istream &in)
01254 {
01255         in >> keyword;
01256         for (Bitu i = 0; i < keyword.size(); i++) {
01257                 keyword[i] = static_cast<char>(toupper(keyword[i]));
01258         }
01259         return true;
01260 }
01261 
01262 bool CDROM_Interface_Image::GetCueFrame(uint32_t &frames, istream &in)
01263 {
01264         std::string msf;
01265         in >> msf;
01266         TMSF tmp = {0, 0, 0};
01267         bool success = sscanf(msf.c_str(), "%hhu:%hhu:%hhu", &tmp.min, &tmp.sec, &tmp.fr) == 3;
01268         frames = (int)MSF_TO_FRAMES(tmp.min, tmp.sec, tmp.fr);
01269         return success;
01270 }
01271 
01272 bool CDROM_Interface_Image::GetCueString(string &str, istream &in)
01273 {
01274         int pos = (int)in.tellg();
01275         in >> str;
01276         if (str[0] == '\"') {
01277                 if (str[str.size() - 1] == '\"') {
01278                         str.assign(str, 1, str.size() - 2);
01279                 } else {
01280                         in.seekg(pos, ios::beg);
01281                         char buffer[MAX_FILENAME_LENGTH];
01282                         in.getline(buffer, MAX_FILENAME_LENGTH, '\"');  // skip
01283                         in.getline(buffer, MAX_FILENAME_LENGTH, '\"');
01284                         str = buffer;
01285                 }
01286         }
01287         return true;
01288 }
01289 
01290 void CDROM_Image_ShutDown(Section* /*sec*/) {
01291         Sound_Quit();
01292 }
01293 
01294 void CDROM_Image_Init() {
01295         Sound_Init();
01296         AddExitFunction(AddExitFunctionFuncPair(CDROM_Image_ShutDown));
01297 }