DOSBox-X
|
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 }