Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/audiocd/audiocd-stream.h"
#include "common/textconsole.h"
AudioCDStream::AudioCDStream() : _buffer(), _frame(0), _bufferPos(0), _bufferFrame(0), _forceStop(false) {
}
AudioCDStream::~AudioCDStream() {
// Stop the timer; the subclass needs to do this,
// so this is just a last resort.
stopTimer();
// Clear any buffered frames
emptyQueue();
}
int AudioCDStream::readBuffer(int16 *buffer, const int numSamples) {
int samples = 0;
// See if any data is left first
while (_bufferPos < kSamplesPerFrame && samples < numSamples)
buffer[samples++] = _buffer[_bufferPos++];
// Bail out if done
if (endOfData())
return samples;
while (samples < numSamples && !endOfData()) {
if (!readNextFrame())
return samples;
// Copy the samples over
for (_bufferPos = 0; _bufferPos < kSamplesPerFrame && samples < numSamples;)
buffer[samples++] = _buffer[_bufferPos++];
}
return samples;
}
bool AudioCDStream::readNextFrame() {
// Fetch a frame from the queue
int16 *buffer;
{
Common::StackLock lock(_mutex);
// Nothing we can do if it's empty
if (_bufferQueue.empty())
return false;
buffer = _bufferQueue.pop();
}
memcpy(_buffer, buffer, kSamplesPerFrame * 2);
delete[] buffer;
_frame++;
return true;
}
bool AudioCDStream::endOfData() const {
return !shouldForceStop() && getStartFrame() + _frame >= getEndFrame() && _bufferPos == kSamplesPerFrame;
}
bool AudioCDStream::seek(const Audio::Timestamp &where) {
// Stop the timer
stopTimer();
// Clear anything out of the queue
emptyQueue();
// Convert to the frame number
// Really not much else needed
_bufferPos = kSamplesPerFrame;
_frame = where.convertToFramerate(kFramesPerSecond).totalNumberOfFrames();
_bufferFrame = _frame;
// Start the timer again
startTimer();
return true;
}
Audio::Timestamp AudioCDStream::getLength() const {
return Audio::Timestamp(0, getEndFrame() - getStartFrame(), kFramesPerSecond);
}
void AudioCDStream::timerProc(void *refCon) {
static_cast<AudioCDStream *>(refCon)->onTimer();
}
void AudioCDStream::onTimer() {
// The goal here is to do as much work in this timer instead
// of doing it in the readBuffer() call, which is the mixer.
// If we're done, bail.
if (shouldForceStop() || getStartFrame() + _bufferFrame >= getEndFrame())
return;
// Get a quick count of the number of items in the queue
// We don't care that much; we only need a quick estimate
_mutex.lock();
uint32 queueCount = _bufferQueue.size();
_mutex.unlock();
// If we have enough audio buffered, bail out
if (queueCount >= kBufferThreshold)
return;
while (!shouldForceStop() && queueCount < kBufferThreshold && getStartFrame() + _bufferFrame < getEndFrame()) {
int16 *buffer = new int16[kSamplesPerFrame];
// Figure out the MSF of the frame we're looking for
int frame = _bufferFrame + getStartFrame();
// Request to read that frame
if (!readFrame(frame, buffer)) {
warning("Failed to read CD audio");
forceStop();
return;
}
_bufferFrame++;
// Now push the buffer onto the queue
Common::StackLock lock(_mutex);
_bufferQueue.push(buffer);
queueCount = _bufferQueue.size();
}
}
void AudioCDStream::startTimer(bool fillBuffer) {
_forceStop = false;
if (fillBuffer) {
onTimer();
}
g_system->getTimerManager()->installTimerProc(timerProc, 10 * 1000, this, "AudioCDStream");
}
void AudioCDStream::stopTimer() {
forceStop();
g_system->getTimerManager()->removeTimerProc(timerProc);
}
void AudioCDStream::emptyQueue() {
while (!_bufferQueue.empty())
delete[] _bufferQueue.pop();
}
bool AudioCDStream::shouldForceStop() const {
Common::StackLock lock(_forceStopMutex);
return _forceStop;
}
void AudioCDStream::forceStop() {
Common::StackLock lock(_forceStopMutex);
_forceStop = true;
}

View File

@@ -0,0 +1,106 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_AUDIOCD_AUDIOCD_STREAM_H
#define BACKENDS_AUDIOCD_AUDIOCD_STREAM_H
#include "audio/audiostream.h"
#include "common/mutex.h"
#include "common/queue.h"
#include "common/timer.h"
class AudioCDStream : public Audio::SeekableAudioStream {
public:
AudioCDStream();
~AudioCDStream();
int readBuffer(int16 *buffer, const int numSamples);
bool isStereo() const { return true; }
int getRate() const { return 44100; }
bool endOfData() const;
bool seek(const Audio::Timestamp &where);
Audio::Timestamp getLength() const;
protected:
virtual uint getStartFrame() const = 0;
virtual uint getEndFrame() const = 0;
virtual bool readFrame(int frame, int16 *buffer) = 0;
void startTimer(bool fillBuffer = false);
void stopTimer();
enum {
kBytesPerFrame = 2352,
kSamplesPerFrame = kBytesPerFrame / 2
};
enum {
kSecondsPerMinute = 60,
kFramesPerSecond = 75
};
enum {
// Keep about a second's worth of audio in the buffer
kBufferThreshold = kFramesPerSecond
};
private:
int16 _buffer[kSamplesPerFrame];
int _frame;
uint _bufferPos;
Common::Queue<int16 *> _bufferQueue;
int _bufferFrame;
Common::Mutex _mutex;
bool _forceStop;
bool shouldForceStop() const;
void forceStop();
Common::Mutex _forceStopMutex;
bool readNextFrame();
static void timerProc(void *refCon);
void onTimer();
void emptyQueue();
};
#endif

135
backends/audiocd/audiocd.h Normal file
View File

@@ -0,0 +1,135 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_AUDIOCD_ABSTRACT_H
#define BACKENDS_AUDIOCD_ABSTRACT_H
#include "audio/mixer.h"
#include "common/scummsys.h"
#include "common/noncopyable.h"
/**
* Abstract Audio CD manager class. Subclasses implement the actual
* functionality.
*/
class AudioCDManager : Common::NonCopyable {
public:
virtual ~AudioCDManager() {}
/**
* A structure containing the current playback information
*/
struct Status {
bool playing;
int track;
int start;
int duration;
int numLoops;
int volume;
int balance;
};
/**
* Initialize the specified CD drive for audio playback.
* @return true if the CD drive was inited successfully
*/
virtual bool open() = 0;
/**
* Close the currently open CD drive
*/
virtual void close() = 0;
/**
* Start audio CD playback
* @param track the track to play.
* @param numLoops how often playback should be repeated (<=0 means infinitely often).
* @param startFrame the frame at which playback should start (75 frames = 1 second).
* @param duration the number of frames to play.
* @param onlyEmulate determines if the track should be emulated only
* @param soundType What sound type to play as. By default, it's as music
* @note The @c onlyEmulate parameter is deprecated.
* @return @c true if the track started playing, @c false otherwise
*/
virtual bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false,
Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType) = 0;
/**
* Start audio CD playback at a specific absolute timestamp
* @param startFrame the frame at which playback should start (75 frames = 1 second).
* @param numLoops how often playback should be repeated (<=0 means infinitely often).
* @param duration the number of frames to play.
* @param onlyEmulate determines if the track should be emulated only
* @param soundType What sound type to play as. By default, it's as music
* @param cuesheet The name of the cuesheet to use for timing data
* @note The @c onlyEmulate parameter is deprecated.
* @return @c true if the track started playing, @c false otherwise
*/
virtual bool playAbsolute(int startFrame, int numLoops, int duration, bool onlyEmulate = false,
Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType, const char *cuesheet = "disc.cue") = 0;
/**
* Get if audio is being played.
* @return true if CD audio is playing
*/
virtual bool isPlaying() const = 0;
/**
* Set the audio volume
*/
virtual void setVolume(byte volume) = 0;
/**
* Set the speakers balance
*/
virtual void setBalance(int8 balance) = 0;
/**
* Stop audio playback.
*/
virtual void stop() = 0;
/**
* Update audio status.
*/
virtual void update() = 0;
/**
* Get the playback status.
* @return a Status struct with playback data.
*/
virtual Status getStatus() const = 0;
/**
* Checks whether the extracted audio cd tracks exists as files in
* the search paths.
* @return true if audio files of the expected naming scheme are found, and supported by ScummVM.
*/
virtual bool existExtractedCDAudioFiles(uint track) = 0;
/**
* Checks if game data are read from the same CD drive which should also play game CD audio.
* @return true, if this case is applicable, and the system doesn't allow it.
*/
virtual bool isDataAndCDAudioReadFromSameCD() = 0;
};
#endif

View File

@@ -0,0 +1,231 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/audiocd/default/default-audiocd.h"
#include "audio/audiostream.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/system.h"
#include "common/util.h"
#include "common/formats/cue.h"
DefaultAudioCDManager::DefaultAudioCDManager() {
_cd.playing = false;
_cd.track = 0;
_cd.start = 0;
_cd.duration = 0;
_cd.numLoops = 0;
_cd.volume = Audio::Mixer::kMaxChannelVolume;
_cd.balance = 0;
_mixer = g_system->getMixer();
_emulating = false;
assert(_mixer);
}
DefaultAudioCDManager::~DefaultAudioCDManager() {
// Subclasses should call close as well
close();
}
bool DefaultAudioCDManager::open() {
// For emulation, opening is always valid
close();
return true;
}
void DefaultAudioCDManager::close() {
// Only need to stop for emulation
stop();
}
void DefaultAudioCDManager::fillPotentialTrackNames(Common::Array<Common::String> &trackNames, int track) const {
trackNames.reserve(4);
trackNames.push_back(Common::String::format("track%d", track));
trackNames.push_back(Common::String::format("track%02d", track));
trackNames.push_back(Common::String::format("track_%d", track));
trackNames.push_back(Common::String::format("track_%02d", track));
}
bool DefaultAudioCDManager::existExtractedCDAudioFiles(uint track) {
// keep this in sync with STREAM_FILEFORMATS
const char *extensions[] = {
#ifdef USE_VORBIS
"ogg",
#endif
#ifdef USE_FLAC
"fla", "flac",
#endif
#ifdef USE_MAD
"mp3",
#endif
"m4a",
"wav",
nullptr
};
Common::Array<Common::String> trackNames;
fillPotentialTrackNames(trackNames, track);
for (auto &trackName : trackNames) {
for (const char **ext = extensions; *ext; ++ext) {
const Common::String &filename = Common::String::format("%s.%s", trackName.c_str(), *ext);
if (Common::File::exists(Common::Path(filename, '/'))) {
return true;
}
}
}
return false;
}
bool DefaultAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) {
stop();
if (numLoops != 0 || startFrame != 0) {
_cd.track = track;
_cd.numLoops = numLoops;
_cd.start = startFrame;
_cd.duration = duration;
// Try to load the track from a compressed data file, and if found, use
// that. If not found, attempt to start regular Audio CD playback of
// the requested track.
Common::Array<Common::String> trackNames;
fillPotentialTrackNames(trackNames, track);
Audio::SeekableAudioStream *stream = nullptr;
for (auto &trackName : trackNames) {
stream = Audio::SeekableAudioStream::openStreamFile(Common::Path(trackName, '/'));
if (stream)
break;
}
if (stream != nullptr) {
Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
Audio::Timestamp end = duration ? Audio::Timestamp(0, startFrame + duration, 75) : stream->getLength();
/*
FIXME: Seems numLoops == 0 and numLoops == 1 both indicate a single repetition,
while all other positive numbers indicate precisely the number of desired
repetitions. Finally, -1 means infinitely many
*/
_emulating = true;
_mixer->playStream(soundType, &_handle,
Audio::makeLoopingAudioStream(stream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops), -1, _cd.volume, _cd.balance);
return true;
}
}
return false;
}
bool DefaultAudioCDManager::playAbsolute(int startFrame, int numLoops, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType, const char *cuesheet) {
Common::File cuefile;
if (!cuefile.open(cuesheet)) {
return false;
}
Common::String cuestring = cuefile.readString(0, cuefile.size());
Common::CueSheet cue(cuestring.c_str());
Common::CueSheet::CueTrack *track = cue.getTrackAtFrame(startFrame);
if (track == nullptr) {
warning("Unable to locate track for frame %i", startFrame);
return false;
} else {
warning("Playing from frame %i", startFrame);
}
int firstFrame = track->indices[0] == -1 ? track->indices[1] : track->indices[0];
return play(track->number, numLoops, startFrame - firstFrame, duration, onlyEmulate);
}
void DefaultAudioCDManager::stop() {
if (_emulating) {
// Audio CD emulation
_mixer->stopHandle(_handle);
_emulating = false;
}
}
bool DefaultAudioCDManager::isPlaying() const {
// Audio CD emulation
if (_emulating)
return _mixer->isSoundHandleActive(_handle);
// The default class only handles emulation
return false;
}
void DefaultAudioCDManager::setVolume(byte volume) {
_cd.volume = volume;
// Audio CD emulation
if (_emulating && isPlaying())
_mixer->setChannelVolume(_handle, _cd.volume);
}
void DefaultAudioCDManager::setBalance(int8 balance) {
_cd.balance = balance;
// Audio CD emulation
if (_emulating && isPlaying())
_mixer->setChannelBalance(_handle, _cd.balance);
}
void DefaultAudioCDManager::update() {
if (_emulating) {
// Check whether the audio track stopped playback
if (!_mixer->isSoundHandleActive(_handle)) {
// FIXME: We do not update the numLoops parameter here (and in fact,
// currently can't do that). Luckily, only one engine ever checks
// this part of the AudioCD status, namely the SCUMM engine; and it
// only checks whether the track is currently set to infinite looping
// or not.
_emulating = false;
}
}
}
DefaultAudioCDManager::Status DefaultAudioCDManager::getStatus() const {
Status info = _cd;
info.playing = isPlaying();
return info;
}
bool DefaultAudioCDManager::openRealCD() {
Common::String cdrom = ConfMan.get("cdrom");
// Try to parse it as an int
char *endPos;
int drive = strtol(cdrom.c_str(), &endPos, 0);
// If not an integer, treat as a drive path
if (endPos == cdrom.c_str())
return openCD(Common::Path::fromConfig(cdrom));
if (drive < 0)
return false;
return openCD(drive);
}

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_AUDIOCD_DEFAULT_H
#define BACKENDS_AUDIOCD_DEFAULT_H
#include "backends/audiocd/audiocd.h"
#include "audio/mixer.h"
namespace Common {
class String;
} // End of namespace Common
/**
* The default audio cd manager. Implements emulation of audio cd playback.
*/
class DefaultAudioCDManager : public AudioCDManager {
public:
DefaultAudioCDManager();
virtual ~DefaultAudioCDManager();
virtual bool open();
virtual void close();
virtual bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate = false,
Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType);
virtual bool playAbsolute(int startFrame, int numLoops, int duration, bool onlyEmulate = false,
Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType, const char *cuesheet = "disc.cue");
virtual void stop();
virtual bool isPlaying() const;
virtual void setVolume(byte volume);
virtual void setBalance(int8 balance);
virtual void update();
virtual Status getStatus() const; // Subclasses should override for better status results
virtual bool existExtractedCDAudioFiles(uint track);
virtual bool isDataAndCDAudioReadFromSameCD() { return false; }
private:
void fillPotentialTrackNames(Common::Array<Common::String> &trackNames, int track) const;
protected:
/**
* Open a CD using the cdrom config variable
*/
bool openRealCD();
/**
* Open a CD using the specified drive index
* @param drive The index of the drive
* @note The index is implementation-defined, but 0 is always the best choice
*/
virtual bool openCD(int drive) { return false; }
/**
* Open a CD from a specific drive
* @param drive The name of the drive/path
* @note The drive parameter is platform-specific
*/
virtual bool openCD(const Common::Path &drive) { return false; }
Audio::SoundHandle _handle;
bool _emulating;
Status _cd;
Audio::Mixer *_mixer;
};
#endif

View File

@@ -0,0 +1,473 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Enable all forbidden symbols to allow us to include and use necessary APIs.
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/audiocd/linux/linux-audiocd.h"
#ifdef USE_LINUXCD
#include "backends/audiocd/audiocd-stream.h"
#include "backends/audiocd/default/default-audiocd.h"
#include "common/array.h"
#include "common/config-manager.h"
#include "common/str.h"
#include "common/debug.h"
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
enum {
kLeadoutTrack = 0xAA
};
enum {
kBytesPerFrame = 2352,
kSamplesPerFrame = kBytesPerFrame / 2
};
enum {
kSecondsPerMinute = 60,
kFramesPerSecond = 75
};
enum {
// Keep about a second's worth of audio in the buffer
kBufferThreshold = kFramesPerSecond
};
static int getFrameCount(const cdrom_msf0 &msf) {
int time = msf.minute;
time *= kSecondsPerMinute;
time += msf.second;
time *= kFramesPerSecond;
time += msf.frame;
return time;
}
// Helper function to convert an error code into a human-readable message
static Common::String getErrorMessage(int errorCode) {
char buf[256];
buf[0] = 0;
#ifdef _GNU_SOURCE
// glibc sucks
return Common::String(strerror_r(errorCode, buf, sizeof(buf)));
#else
strerror_r(errorCode, buf, sizeof(buf));
return Common::String(buf);
#endif
}
class LinuxAudioCDStream : public AudioCDStream {
public:
LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry);
~LinuxAudioCDStream();
protected:
uint getStartFrame() const;
uint getEndFrame() const;
bool readFrame(int frame, int16 *buffer);
private:
int _fd;
const cdrom_tocentry &_startEntry, &_endEntry;
};
LinuxAudioCDStream::LinuxAudioCDStream(int fd, const cdrom_tocentry &startEntry, const cdrom_tocentry &endEntry) :
_fd(fd), _startEntry(startEntry), _endEntry(endEntry) {
// We fill the buffer here already to prevent any out of sync issues due
// to the CD not yet having spun up.
startTimer(true);
}
LinuxAudioCDStream::~LinuxAudioCDStream() {
stopTimer();
}
bool LinuxAudioCDStream::readFrame(int frame, int16 *buffer) {
// Create the argument
union {
cdrom_msf msf;
char buffer[kBytesPerFrame];
} arg;
int seconds = frame / kFramesPerSecond;
frame %= kFramesPerSecond;
int minutes = seconds / kSecondsPerMinute;
seconds %= kSecondsPerMinute;
// Request to read that frame
// We don't use CDROMREADAUDIO, as it seems to cause kernel
// panics on ejecting discs. Probably bad to eject the disc
// while playing, but at least let's try to prevent that case.
arg.msf.cdmsf_min0 = minutes;
arg.msf.cdmsf_sec0 = seconds;
arg.msf.cdmsf_frame0 = frame;
// The "end" part is irrelevant (why isn't cdrom_msf0 the type
// instead?)
if (ioctl(_fd, CDROMREADRAW, &arg) < 0) {
warning("Failed to CD read audio: %s", getErrorMessage(errno).c_str());
return false;
}
memcpy(buffer, arg.buffer, kBytesPerFrame);
return true;
}
uint LinuxAudioCDStream::getStartFrame() const {
return getFrameCount(_startEntry.cdte_addr.msf);
}
uint LinuxAudioCDStream::getEndFrame() const {
return getFrameCount(_endEntry.cdte_addr.msf);
}
class LinuxAudioCDManager : public DefaultAudioCDManager {
public:
LinuxAudioCDManager();
~LinuxAudioCDManager();
bool open() override;
void close() override;
bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) override;
protected:
bool openCD(int drive) override;
bool openCD(const Common::Path &drive) override;
private:
struct Device {
Device(const Common::Path &n, dev_t d) : name(n), device(d) {}
Common::Path name;
dev_t device;
};
typedef Common::Array<Device> DeviceList;
DeviceList scanDevices();
bool tryAddDrive(DeviceList &devices, const Common::Path &drive);
bool tryAddDrive(DeviceList &devices, const Common::Path &drive, dev_t device);
bool tryAddDrive(DeviceList &devices, dev_t device);
bool tryAddPath(DeviceList &devices, const Common::Path &path);
bool tryAddGamePath(DeviceList &devices);
bool loadTOC();
static bool hasDevice(const DeviceList &devices, dev_t device);
int _fd;
cdrom_tochdr _tocHeader;
Common::Array<cdrom_tocentry> _tocEntries;
};
static bool isTrayEmpty(int errorNumber) {
switch (errorNumber) {
case EIO:
case ENOENT:
case EINVAL:
#ifdef ENOMEDIUM
case ENOMEDIUM:
#endif
return true;
}
return false;
}
LinuxAudioCDManager::LinuxAudioCDManager() {
_fd = -1;
memset(&_tocHeader, 0, sizeof(_tocHeader));
}
LinuxAudioCDManager::~LinuxAudioCDManager() {
close();
}
bool LinuxAudioCDManager::open() {
close();
if (openRealCD())
return true;
return DefaultAudioCDManager::open();
}
void LinuxAudioCDManager::close() {
DefaultAudioCDManager::close();
if (_fd < 0)
return;
::close(_fd);
_fd = -1;
memset(&_tocHeader, 0, sizeof(_tocHeader));
_tocEntries.clear();
}
bool LinuxAudioCDManager::openCD(int drive) {
DeviceList devices = scanDevices();
if (drive >= (int)devices.size())
return false;
_fd = ::open(devices[drive].name.toString('/').c_str(), O_RDONLY | O_NONBLOCK, 0);
if (_fd < 0)
return false;
if (!loadTOC()) {
close();
return false;
}
return true;
}
bool LinuxAudioCDManager::openCD(const Common::Path &drive) {
DeviceList devices;
if (!tryAddDrive(devices, drive) && !tryAddPath(devices, drive))
return false;
_fd = ::open(devices[0].name.toString('/').c_str(), O_RDONLY | O_NONBLOCK, 0);
if (_fd < 0)
return false;
if (!loadTOC()) {
close();
return false;
}
return true;
}
bool LinuxAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) {
// Prefer emulation
if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate, soundType))
return true;
// If we're set to only emulate, or have no CD drive, return here
if (onlyEmulate || _fd < 0)
return false;
// HACK: For now, just assume that track number is right
// That only works because ScummVM uses the wrong track number anyway
if (track >= (int)_tocEntries.size() - 1) {
warning("No such track %d", track);
return false;
}
// Bail if the track isn't an audio track
if ((_tocEntries[track].cdte_ctrl & 0x04) != 0) {
warning("Track %d is not audio", track);
return false;
}
// Create the AudioStream and play it
debug(1, "Playing CD track %d", track);
Audio::SeekableAudioStream *audioStream = new LinuxAudioCDStream(_fd, _tocEntries[track], _tocEntries[track + 1]);
Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
Audio::Timestamp end = (duration == 0) ? audioStream->getLength() : Audio::Timestamp(0, startFrame + duration, 75);
// Fake emulation since we're really playing an AudioStream
_emulating = true;
_mixer->playStream(
soundType,
&_handle,
Audio::makeLoopingAudioStream(audioStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops),
-1,
_cd.volume,
_cd.balance,
DisposeAfterUse::YES,
true);
return true;
}
LinuxAudioCDManager::DeviceList LinuxAudioCDManager::scanDevices() {
DeviceList devices;
// Try to use the game's path first as the device
tryAddGamePath(devices);
// Try adding the default CD-ROM
tryAddDrive(devices, "/dev/cdrom");
// TODO: Try others?
return devices;
}
bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, const Common::Path &drive) {
struct stat stbuf;
if (stat(drive.toString('/').c_str(), &stbuf) < 0)
return false;
// Must be a character or block device
if (!S_ISCHR(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode))
return false;
return tryAddDrive(devices, drive, stbuf.st_rdev);
}
bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, const Common::Path &drive, dev_t device) {
if (hasDevice(devices, device))
return true;
// Try opening the device and seeing if it is a CD-ROM drve
int fd = ::open(drive.toString('/').c_str(), O_RDONLY | O_NONBLOCK, 0);
if (fd >= 0) {
cdrom_subchnl info;
info.cdsc_format = CDROM_MSF;
bool isCD = ioctl(fd, CDROMSUBCHNL, &info) == 0 || isTrayEmpty(errno);
::close(fd);
if (isCD) {
devices.push_back(Device(drive, device));
return true;
}
}
return false;
}
bool LinuxAudioCDManager::tryAddDrive(DeviceList &devices, dev_t device) {
// Construct the block name
// TODO: libblkid's blkid_devno_to_devname is exactly what we look for.
// This requires an external dependency though.
Common::Path name(Common::String::format("/dev/block/%d:%d", major(device), minor(device)), '/');
return tryAddDrive(devices, name, device);
}
bool LinuxAudioCDManager::tryAddPath(DeviceList &devices, const Common::Path &path) {
struct stat stbuf;
if (stat(path.toString(Common::Path::kNativeSeparator).c_str(), &stbuf) < 0)
return false;
return tryAddDrive(devices, stbuf.st_dev);
}
bool LinuxAudioCDManager::tryAddGamePath(DeviceList &devices) {
if (!ConfMan.hasKey("path"))
return false;
return tryAddPath(devices, ConfMan.getPath("path"));
}
bool LinuxAudioCDManager::loadTOC() {
if (_fd < 0)
return false;
if (ioctl(_fd, CDROMREADTOCHDR, &_tocHeader) < 0)
return false;
debug(4, "CD: Start Track: %d, End Track %d", _tocHeader.cdth_trk0, _tocHeader.cdth_trk1);
for (int i = _tocHeader.cdth_trk0; i <= _tocHeader.cdth_trk1; i++) {
cdrom_tocentry entry;
memset(&entry, 0, sizeof(entry));
entry.cdte_track = i;
entry.cdte_format = CDROM_MSF;
if (ioctl(_fd, CDROMREADTOCENTRY, &entry) < 0)
return false;
#if 0
debug("Entry:");
debug("\tTrack: %d", entry.cdte_track);
debug("\tAdr: %d", entry.cdte_adr);
debug("\tCtrl: %d", entry.cdte_ctrl);
debug("\tFormat: %d", entry.cdte_format);
debug("\tMSF: %d:%d:%d", entry.cdte_addr.msf.minute, entry.cdte_addr.msf.second, entry.cdte_addr.msf.frame);
debug("\tMode: %d\n", entry.cdte_datamode);
#endif
_tocEntries.push_back(entry);
}
// Fetch the leadout so we can get the length of the last frame
cdrom_tocentry entry;
memset(&entry, 0, sizeof(entry));
entry.cdte_track = kLeadoutTrack;
entry.cdte_format = CDROM_MSF;
if (ioctl(_fd, CDROMREADTOCENTRY, &entry) < 0)
return false;
#if 0
debug("Lead out:");
debug("\tTrack: %d", entry.cdte_track);
debug("\tAdr: %d", entry.cdte_adr);
debug("\tCtrl: %d", entry.cdte_ctrl);
debug("\tFormat: %d", entry.cdte_format);
debug("\tMSF: %d:%d:%d", entry.cdte_addr.msf.minute, entry.cdte_addr.msf.second, entry.cdte_addr.msf.frame);
debug("\tMode: %d\n", entry.cdte_datamode);
#endif
_tocEntries.push_back(entry);
return true;
}
bool LinuxAudioCDManager::hasDevice(const DeviceList &devices, dev_t device) {
for (DeviceList::const_iterator it = devices.begin(); it != devices.end(); it++)
if (it->device == device)
return true;
return false;
}
AudioCDManager *createLinuxAudioCDManager() {
return new LinuxAudioCDManager();
}
#endif // USE_LINUXCD

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_AUDIOCD_LINUX_H
#define BACKENDS_AUDIOCD_LINUX_H
#include "common/scummsys.h"
#ifdef USE_LINUXCD
class AudioCDManager;
/**
* Create an audio CD manager using the Linux CDROM API
*/
AudioCDManager *createLinuxAudioCDManager();
#endif
#endif

View File

@@ -0,0 +1,310 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef MACOSX
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include "common/scummsys.h"
#include "audio/audiostream.h"
#include "audio/decoders/aiff.h"
#include "audio/timestamp.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/fs.h"
#include "common/hashmap.h"
#include "common/textconsole.h"
#include "backends/audiocd/default/default-audiocd.h"
#include "backends/audiocd/macosx/macosx-audiocd.h"
#include "backends/fs/stdiostream.h"
// Partially based on SDL's code
/**
* The macOS audio cd manager. Implements real audio cd playback.
*/
class MacOSXAudioCDManager : public DefaultAudioCDManager {
public:
MacOSXAudioCDManager() {}
~MacOSXAudioCDManager();
bool open() override;
void close() override;
bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) override;
protected:
bool openCD(int drive) override;
bool openCD(const Common::Path &drive) override;
private:
struct Drive {
Drive(const Common::Path &m, const Common::Path &d, const Common::String &f) :
mountPoint(m), deviceName(d), fsType(f) {}
Common::Path mountPoint;
Common::Path deviceName;
Common::String fsType;
};
typedef Common::Array<Drive> DriveList;
DriveList detectAllDrives();
bool findTrackNames(const Common::Path &drivePath);
Common::HashMap<uint, Common::Path> _trackMap;
};
MacOSXAudioCDManager::~MacOSXAudioCDManager() {
close();
}
bool MacOSXAudioCDManager::open() {
close();
if (openRealCD())
return true;
return DefaultAudioCDManager::open();
}
/**
* Find the base disk number of device name.
* Returns -1 if mount point is not /dev/disk*
*/
static int findBaseDiskNumber(const Common::Path &diskPath) {
Common::String diskName(diskPath.toString('/'));
if (!diskName.hasPrefix("/dev/disk"))
return -1;
const char *startPtr = diskName.c_str() + 9;
char *endPtr;
int baseDiskNumber = strtol(startPtr, &endPtr, 10);
if (startPtr == endPtr)
return -1;
return baseDiskNumber;
}
bool MacOSXAudioCDManager::openCD(int drive) {
DriveList allDrives = detectAllDrives();
if (allDrives.empty())
return false;
DriveList cddaDrives;
// Try to get the volume related to the game's path
if (ConfMan.hasKey("path")) {
Common::String gamePath = ConfMan.getPath("path").toString(Common::Path::kNativeSeparator);
struct statfs gamePathStat;
if (statfs(gamePath.c_str(), &gamePathStat) == 0) {
int baseDiskNumber = findBaseDiskNumber(gamePathStat.f_mntfromname);
if (baseDiskNumber >= 0) {
// Look for a CDDA drive with the same base disk number
for (uint32 i = 0; i < allDrives.size(); i++) {
if (allDrives[i].fsType == "cddafs" && findBaseDiskNumber(allDrives[i].deviceName) == baseDiskNumber) {
debug(1, "Preferring drive '%s'", allDrives[i].mountPoint.toString(Common::Path::kNativeSeparator).c_str());
cddaDrives.push_back(allDrives[i]);
allDrives.remove_at(i);
break;
}
}
}
}
}
// Add the remaining CDDA drives to the CDDA list
for (uint32 i = 0; i < allDrives.size(); i++)
if (allDrives[i].fsType == "cddafs")
cddaDrives.push_back(allDrives[i]);
if (drive >= (int)cddaDrives.size())
return false;
debug(1, "Using '%s' as the CD drive", cddaDrives[drive].mountPoint.toString(Common::Path::kNativeSeparator).c_str());
return findTrackNames(cddaDrives[drive].mountPoint);
}
bool MacOSXAudioCDManager::openCD(const Common::Path &drive) {
DriveList drives = detectAllDrives();
for (uint32 i = 0; i < drives.size(); i++) {
if (drives[i].fsType != "cddafs")
continue;
if (drives[i].mountPoint == drive || drives[i].deviceName == drive) {
debug(1, "Using '%s' as the CD drive", drives[i].mountPoint.toString(Common::Path::kNativeSeparator).c_str());
return findTrackNames(drives[i].mountPoint);
}
}
return false;
}
void MacOSXAudioCDManager::close() {
DefaultAudioCDManager::close();
_trackMap.clear();
}
MacOSXAudioCDManager::DriveList MacOSXAudioCDManager::detectAllDrives() {
int foundDrives = getfsstat(nullptr, 0, MNT_WAIT);
if (foundDrives <= 0)
return DriveList();
// Fetch the lists of drives
struct statfs *driveStats = (struct statfs *)malloc(sizeof(struct statfs) * foundDrives);
foundDrives = getfsstat(driveStats, sizeof(struct statfs) * foundDrives, MNT_NOWAIT);
if (foundDrives <= 0) {
free(driveStats);
return DriveList();
}
DriveList drives;
for (int i = 0; i < foundDrives; i++)
drives.push_back(Drive(Common::Path(driveStats[i].f_mntonname, Common::Path::kNativeSeparator),
Common::Path(driveStats[i].f_mntfromname, Common::Path::kNativeSeparator), driveStats[i].f_fstypename));
free(driveStats);
return drives;
}
bool MacOSXAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) {
// Prefer emulation
if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate, soundType))
return true;
// If we're set to only emulate, or have no CD drive, return here
if (onlyEmulate || !_trackMap.contains(track))
return false;
if (!numLoops && !startFrame)
return false;
// Now load the AIFF track from the name
Common::Path fileName = _trackMap[track];
Common::SeekableReadStream *stream = StdioStream::makeFromPath(fileName.toString(Common::Path::kNativeSeparator).c_str(), StdioStream::WriteMode_Read);
if (!stream) {
warning("Failed to open track '%s'", fileName.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
Audio::AudioStream *audioStream = Audio::makeAIFFStream(stream, DisposeAfterUse::YES);
if (!audioStream) {
warning("Track '%s' is not an AIFF track", fileName.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(audioStream);
if (!seekStream) {
warning("Track '%s' is not seekable", fileName.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
Audio::Timestamp end = duration ? Audio::Timestamp(0, startFrame + duration, 75) : seekStream->getLength();
// Fake emulation since we're really playing an AIFF file
_emulating = true;
_mixer->playStream(soundType, &_handle,
Audio::makeLoopingAudioStream(seekStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops), -1, _cd.volume, _cd.balance);
return true;
}
bool MacOSXAudioCDManager::findTrackNames(const Common::Path &drivePath) {
Common::FSNode directory(drivePath);
if (!directory.exists()) {
warning("Directory '%s' does not exist", drivePath.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
if (!directory.isDirectory()) {
warning("'%s' is not a directory", drivePath.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
Common::FSList children;
if (!directory.getChildren(children, Common::FSNode::kListFilesOnly) || children.empty()) {
warning("Failed to find children for '%s'", drivePath.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
for (uint32 i = 0; i < children.size(); i++) {
if (!children[i].isDirectory()) {
Common::String fileName = children[i].getFileName();
if (fileName.hasSuffix(".aiff") || fileName.hasSuffix(".cdda")) {
uint j = 0;
// Search for the track ID in the file name.
for (; j < fileName.size() && !Common::isDigit(fileName[j]); j++)
;
const char *trackIDString = fileName.c_str() + j;
char *endPtr = nullptr;
long trackID = strtol(trackIDString, &endPtr, 10);
if (trackIDString != endPtr && trackID > 0 && (unsigned long)trackID < UINT_MAX) {
_trackMap[trackID - 1] = drivePath.appendComponent(fileName);
} else {
warning("Invalid track file name: '%s'", fileName.c_str());
}
}
}
}
return true;
}
AudioCDManager *createMacOSXAudioCDManager() {
return new MacOSXAudioCDManager();
}
#endif // MACOSX

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_AUDIOCD_MACOSX_H
#define BACKENDS_AUDIOCD_MACOSX_H
#include "common/scummsys.h"
#ifdef MACOSX
class AudioCDManager;
/**
* Create an audio CD manager for macOS
*/
AudioCDManager *createMacOSXAudioCDManager();
#endif
#endif //

View File

@@ -0,0 +1,172 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#if defined(SDL_BACKEND)
#include "backends/audiocd/sdl/sdl-audiocd.h"
#if !SDL_VERSION_ATLEAST(2, 0, 0)
#include "common/textconsole.h"
SdlAudioCDManager::SdlAudioCDManager()
:
_cdrom(0),
_cdTrack(0),
_cdNumLoops(0),
_cdStartFrame(0),
_cdDuration(0),
_cdEndTime(0),
_cdStopTime(0) {
}
SdlAudioCDManager::~SdlAudioCDManager() {
close();
}
bool SdlAudioCDManager::open() {
close();
if (openRealCD())
return true;
return DefaultAudioCDManager::open();
}
bool SdlAudioCDManager::openCD(int drive) {
if (SDL_InitSubSystem(SDL_INIT_CDROM) == -1)
_cdrom = NULL;
else {
_cdrom = SDL_CDOpen(drive);
// Did it open? Check if _cdrom is NULL
if (!_cdrom) {
warning("Couldn't open drive: %s", SDL_GetError());
} else {
_cdNumLoops = 0;
_cdStopTime = 0;
_cdEndTime = 0;
}
}
return (_cdrom != NULL);
}
void SdlAudioCDManager::close() {
DefaultAudioCDManager::close();
if (_cdrom) {
SDL_CDStop(_cdrom);
SDL_CDClose(_cdrom);
_cdrom = 0;
}
}
void SdlAudioCDManager::stop() {
DefaultAudioCDManager::stop();
// Stop CD Audio in 1/10th of a second
_cdStopTime = SDL_GetTicks() + 100;
_cdNumLoops = 0;
}
bool SdlAudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) {
// Prefer emulation
if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate, soundType))
return true;
// If we're set to only emulate, or have no CD, return here
if (onlyEmulate || !_cdrom)
return false;
if (!numLoops && !startFrame)
return false;
// FIXME: Explain this.
if (duration > 0)
duration += 5;
_cdTrack = track;
_cdNumLoops = numLoops;
_cdStartFrame = startFrame;
SDL_CDStatus(_cdrom);
if (startFrame == 0 && duration == 0)
SDL_CDPlayTracks(_cdrom, track, 0, 1, 0);
else
SDL_CDPlayTracks(_cdrom, track, startFrame, 0, duration);
_cdDuration = duration;
_cdStopTime = 0;
_cdEndTime = SDL_GetTicks() + _cdrom->track[track].length * 1000 / CD_FPS;
return true;
}
bool SdlAudioCDManager::isPlaying() const {
if (DefaultAudioCDManager::isPlaying())
return true;
if (!_cdrom)
return false;
return (_cdNumLoops != 0 && (SDL_GetTicks() < _cdEndTime || SDL_CDStatus(_cdrom) == CD_PLAYING));
}
void SdlAudioCDManager::update() {
DefaultAudioCDManager::update();
if (!_cdrom)
return;
if (_cdStopTime != 0 && SDL_GetTicks() >= _cdStopTime) {
SDL_CDStop(_cdrom);
_cdNumLoops = 0;
_cdStopTime = 0;
return;
}
if (_cdNumLoops == 0 || SDL_GetTicks() < _cdEndTime)
return;
if (_cdNumLoops != 1 && SDL_CDStatus(_cdrom) != CD_STOPPED) {
// Wait another second for it to be done
_cdEndTime += 1000;
return;
}
if (_cdNumLoops > 0)
_cdNumLoops--;
if (_cdNumLoops != 0) {
if (_cdStartFrame == 0 && _cdDuration == 0)
SDL_CDPlayTracks(_cdrom, _cdTrack, 0, 1, 0);
else
SDL_CDPlayTracks(_cdrom, _cdTrack, _cdStartFrame, 0, _cdDuration);
_cdEndTime = SDL_GetTicks() + _cdrom->track[_cdTrack].length * 1000 / CD_FPS;
}
}
#endif // !SDL_VERSION_ATLEAST(2, 0, 0)
#endif

View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_AUDIOCD_SDL_H
#define BACKENDS_AUDIOCD_SDL_H
#include "backends/audiocd/default/default-audiocd.h"
#include "backends/platform/sdl/sdl-sys.h"
#if !SDL_VERSION_ATLEAST(2, 0, 0)
/**
* The SDL audio cd manager. Implements real audio cd playback.
*/
class SdlAudioCDManager : public DefaultAudioCDManager {
public:
SdlAudioCDManager();
virtual ~SdlAudioCDManager();
bool open() override;
void close() override;
bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) override;
void stop() override;
bool isPlaying() const override;
void update() override;
protected:
bool openCD(int drive) override;
SDL_CD *_cdrom;
int _cdTrack, _cdNumLoops, _cdStartFrame, _cdDuration;
uint32 _cdEndTime, _cdStopTime;
};
#endif // !SDL_VERSION_ATLEAST(2, 0, 0)
#endif

View File

@@ -0,0 +1,415 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "backends/audiocd/win32/win32-audiocd.h"
#include "audio/audiostream.h"
#include "backends/audiocd/audiocd-stream.h"
#include "backends/audiocd/default/default-audiocd.h"
#include "backends/platform/sdl/win32/win32_wrapper.h"
#include "common/array.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/fs.h"
#include "common/mutex.h"
#include "common/queue.h"
#include "common/str.h"
#include "common/timer.h"
#include <winioctl.h>
#if defined(__MINGW32_VERSION)
// Classic MinGW uses non standard paths for DDK headers.
// __MINGW32_VERSION is not defined by the not classic Ming-w64
#include <ddk/ntddcdrm.h>
#else
#include <ntddcdrm.h>
#endif
class Win32AudioCDStream final : public AudioCDStream {
public:
Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry);
~Win32AudioCDStream();
protected:
uint getStartFrame() const override;
uint getEndFrame() const override;
bool readFrame(int frame, int16 *buffer) override;
private:
HANDLE _driveHandle;
const TRACK_DATA &_startEntry, &_endEntry;
enum {
// The CD-ROM pre-gap is 2s
kPreGapFrames = kFramesPerSecond * 2
};
static int getFrameCount(const TRACK_DATA &data) {
int time = data.Address[1];
time *= kSecondsPerMinute;
time += data.Address[2];
time *= kFramesPerSecond;
time += data.Address[3];
return time;
}
};
Win32AudioCDStream::Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry) :
_driveHandle(handle), _startEntry(startEntry), _endEntry(endEntry) {
// We fill the buffer here already to prevent any out of sync issues due
// to the CD not yet having spun up.
startTimer(true);
}
Win32AudioCDStream::~Win32AudioCDStream() {
stopTimer();
}
uint Win32AudioCDStream::getStartFrame() const {
return getFrameCount(_startEntry);
}
uint Win32AudioCDStream::getEndFrame() const {
return getFrameCount(_endEntry);
}
bool Win32AudioCDStream::readFrame(int frame, int16 *buffer) {
// Request to read that frame
RAW_READ_INFO readAudio;
memset(&readAudio, 0, sizeof(readAudio));
readAudio.DiskOffset.QuadPart = (frame - kPreGapFrames) * 2048;
readAudio.SectorCount = 1;
readAudio.TrackMode = CDDA;
DWORD bytesReturned;
return DeviceIoControl(
_driveHandle,
IOCTL_CDROM_RAW_READ,
&readAudio,
sizeof(readAudio),
buffer,
kBytesPerFrame,
&bytesReturned,
nullptr);
}
class Win32AudioCDManager final : public DefaultAudioCDManager {
public:
Win32AudioCDManager();
~Win32AudioCDManager();
bool open() override;
void close() override;
bool play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) override;
bool isDataAndCDAudioReadFromSameCD() override;
protected:
bool openCD(int drive) override;
bool openCD(const Common::Path &drive) override;
private:
bool loadTOC();
typedef Common::Array<char> DriveList;
DriveList detectDrives();
bool tryAddDrive(char drive, DriveList &drives);
HANDLE _driveHandle;
int _firstTrack, _lastTrack;
Common::Array<TRACK_DATA> _tocEntries;
};
Win32AudioCDManager::Win32AudioCDManager() {
_driveHandle = INVALID_HANDLE_VALUE;
_firstTrack = _lastTrack = 0;
}
Win32AudioCDManager::~Win32AudioCDManager() {
close();
}
bool Win32AudioCDManager::open() {
close();
if (openRealCD())
return true;
return DefaultAudioCDManager::open();
}
bool Win32AudioCDManager::openCD(int drive) {
// Fetch the drive list
DriveList drives = detectDrives();
if (drive >= (int)drives.size())
return false;
debug(1, "Opening CD drive %c:\\", drives[drive]);
// Construct the drive path and try to open it
Common::String drivePath = Common::String::format("\\\\.\\%c:", drives[drive]);
TCHAR *tDrivePath = Win32::stringToTchar(drivePath);
_driveHandle = CreateFile(tDrivePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
free(tDrivePath);
if (_driveHandle == INVALID_HANDLE_VALUE) {
warning("Failed to open drive %c:\\, error %d", drives[drive], (int)GetLastError());
return false;
}
if (!loadTOC()) {
close();
return false;
}
return true;
}
bool Win32AudioCDManager::openCD(const Common::Path &drive) {
// Just some bounds checking
if (drive.empty())
return false;
Common::String driveS(drive.toString(Common::Path::kNativeSeparator));
if (driveS.size() > 3)
return false;
if (!Common::isAlpha(driveS[0]) || driveS[1] != ':')
return false;
if (driveS[2] != 0 && driveS[2] != '\\')
return false;
DriveList drives;
if (!tryAddDrive(toupper(driveS[0]), drives))
return false;
// Construct the drive path and try to open it
Common::String drivePath = Common::String::format("\\\\.\\%c:", drives[0]);
TCHAR *tDrivePath = Win32::stringToTchar(drivePath);
_driveHandle = CreateFile(tDrivePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
free(tDrivePath);
if (_driveHandle == INVALID_HANDLE_VALUE) {
warning("Failed to open drive %c:\\, error %d", drives[0], (int)GetLastError());
return false;
}
if (!loadTOC()) {
close();
return false;
}
return true;
}
void Win32AudioCDManager::close() {
DefaultAudioCDManager::close();
if (_driveHandle != INVALID_HANDLE_VALUE) {
CloseHandle(_driveHandle);
_driveHandle = INVALID_HANDLE_VALUE;
}
_firstTrack = _lastTrack = 0;
_tocEntries.clear();
}
bool Win32AudioCDManager::play(int track, int numLoops, int startFrame, int duration, bool onlyEmulate,
Audio::Mixer::SoundType soundType) {
// Prefer emulation
if (DefaultAudioCDManager::play(track, numLoops, startFrame, duration, onlyEmulate, soundType))
return true;
// If we're set to only emulate, or have no CD drive, return here
if (onlyEmulate || _driveHandle == INVALID_HANDLE_VALUE)
return false;
// HACK: For now, just assume that track number is right
// That only works because ScummVM uses the wrong track number anyway
if (track >= (int)_tocEntries.size() - 1) {
warning("No such track %d", track);
return false;
}
// Bail if the track isn't an audio track
if ((_tocEntries[track].Control & 0x04) != 0) {
warning("Track %d is not audio", track);
return false;
}
// Create the AudioStream and play it
debug(1, "Playing CD track %d", track);
Audio::SeekableAudioStream *audioStream = new Win32AudioCDStream(_driveHandle, _tocEntries[track], _tocEntries[track + 1]);
Audio::Timestamp start = Audio::Timestamp(0, startFrame, 75);
Audio::Timestamp end = (duration == 0) ? audioStream->getLength() : Audio::Timestamp(0, startFrame + duration, 75);
// Fake emulation since we're really playing an AudioStream
_emulating = true;
_mixer->playStream(
soundType,
&_handle,
Audio::makeLoopingAudioStream(audioStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops),
-1,
_cd.volume,
_cd.balance,
DisposeAfterUse::YES,
true);
return true;
}
bool Win32AudioCDManager::loadTOC() {
CDROM_READ_TOC_EX tocRequest;
memset(&tocRequest, 0, sizeof(tocRequest));
tocRequest.Format = CDROM_READ_TOC_EX_FORMAT_TOC;
tocRequest.Msf = 1;
tocRequest.SessionTrack = 0;
DWORD bytesReturned;
CDROM_TOC tocData;
bool result = DeviceIoControl(
_driveHandle,
IOCTL_CDROM_READ_TOC_EX,
&tocRequest,
sizeof(tocRequest),
&tocData,
sizeof(tocData),
&bytesReturned,
nullptr);
if (!result) {
debug("Failed to query the CD TOC: %d", (int)GetLastError());
return false;
}
_firstTrack = tocData.FirstTrack;
_lastTrack = tocData.LastTrack;
#if 0
debug("First Track: %d", tocData.FirstTrack);
debug("Last Track: %d", tocData.LastTrack);
#endif
for (uint32 i = 0; i < (bytesReturned - 4) / sizeof(TRACK_DATA); i++)
_tocEntries.push_back(tocData.TrackData[i]);
#if 0
for (uint32 i = 0; i < _tocEntries.size(); i++) {
const TRACK_DATA &entry = _tocEntries[i];
debug("Entry:");
debug("\tTrack: %d", entry.TrackNumber);
debug("\tAdr: %d", entry.Adr);
debug("\tCtrl: %d", entry.Control);
debug("\tMSF: %d:%d:%d\n", entry.Address[1], entry.Address[2], entry.Address[3]);
}
#endif
return true;
}
Win32AudioCDManager::DriveList Win32AudioCDManager::detectDrives() {
DriveList drives;
// Try to get the game path's drive
char gameDrive = 0;
if (ConfMan.hasKey("path")) {
Common::Path gamePath = ConfMan.getPath("path");
TCHAR *tGamePath = Win32::stringToTchar(gamePath.toString(Common::Path::kNativeSeparator));
TCHAR fullPath[MAX_PATH];
DWORD result = GetFullPathName(tGamePath, MAX_PATH, fullPath, nullptr);
free(tGamePath);
if (result > 0 && result < sizeof(fullPath) && Common::isAlpha(fullPath[0]) && fullPath[1] == ':' && tryAddDrive(toupper(fullPath[0]), drives))
gameDrive = drives[0];
}
// Try adding the rest of the drives
for (char drive = 'A'; drive <= 'Z'; drive++)
if (drive != gameDrive)
tryAddDrive(drive, drives);
return drives;
}
bool Win32AudioCDManager::tryAddDrive(char drive, DriveList &drives) {
if (!Win32::isDriveCD(drive)) {
return false;
}
debug(2, "Detected drive %c:\\ as a CD drive", drive);
drives.push_back(drive);
return true;
}
bool Win32AudioCDManager::isDataAndCDAudioReadFromSameCD() {
// It is a known bug under Windows that games that play CD audio cause
// ScummVM to crash if the data files are read from the same CD.
char driveLetter;
Common::Path gameDataDir(ConfMan.getPath("path"));
if (!gameDataDir.empty()) {
driveLetter = gameDataDir.toString(Common::Path::kNativeSeparator)[0];
} else {
// That's it! I give up!
Common::FSNode currentDir(".");
gameDataDir = currentDir.getPath();
if (!gameDataDir.empty()) {
driveLetter = gameDataDir.toString(Common::Path::kNativeSeparator)[0];
} else {
return false;
}
}
return Win32::isDriveCD(driveLetter);
}
AudioCDManager *createWin32AudioCDManager() {
return new Win32AudioCDManager();
}
#endif // WIN32

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Original license header:
*
* Cabal - Legacy Game Implementations
*
* Cabal is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BACKENDS_AUDIOCD_WIN32_H
#define BACKENDS_AUDIOCD_WIN32_H
#ifdef WIN32
class AudioCDManager;
/**
* Create an AudioCDManager using the Win32 API
*/
AudioCDManager *createWin32AudioCDManager();
#endif
#endif