aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backends/audiocd/win32/win32-audiocd.cpp363
-rw-r--r--backends/audiocd/win32/win32-audiocd.h38
-rw-r--r--backends/module.mk1
-rw-r--r--backends/platform/sdl/win32/win32.cpp5
-rw-r--r--backends/platform/sdl/win32/win32.h4
5 files changed, 411 insertions, 0 deletions
diff --git a/backends/audiocd/win32/win32-audiocd.cpp b/backends/audiocd/win32/win32-audiocd.cpp
new file mode 100644
index 0000000000..fdde61cc45
--- /dev/null
+++ b/backends/audiocd/win32/win32-audiocd.cpp
@@ -0,0 +1,363 @@
+/* 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "backends/audiocd/win32/win32-audiocd.h"
+
+#include "audio/audiostream.h"
+#include "backends/audiocd/default/default-audiocd.h"
+#include "common/array.h"
+#include "common/config-manager.h"
+#include "common/endian.h"
+#include "common/str.h"
+#include "common/debug.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#ifdef _MSC_VER
+#include <winioctl.h>
+#include <ntddcdrm.h>
+#else
+#include <ddk/ntddcdrm.h>
+#endif
+
+enum {
+ kLeadoutTrack = 0xAA
+};
+
+enum {
+ kBytesPerFrame = 2352,
+ kSamplesPerFrame = kBytesPerFrame / 2
+};
+
+enum {
+ kSecondsPerMinute = 60,
+ kFramesPerSecond = 75
+};
+
+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;
+}
+
+class Win32AudioCDStream : public Audio::SeekableAudioStream {
+public:
+ Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry);
+
+ 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;
+
+private:
+ HANDLE _driveHandle;
+ const TRACK_DATA &_startEntry, &_endEntry;
+ int16 _buffer[kSamplesPerFrame];
+ int _frame;
+ uint _bufferPos;
+
+ bool readNextFrame();
+};
+
+Win32AudioCDStream::Win32AudioCDStream(HANDLE handle, const TRACK_DATA &startEntry, const TRACK_DATA &endEntry) :
+ _driveHandle(handle), _startEntry(startEntry), _endEntry(endEntry), _buffer(), _frame(0), _bufferPos(kSamplesPerFrame) {
+ // Read the first frame here. This should hopefully keep games more
+ // in sync instead of potentially waiting in the mixer thread for the
+ // disc to seek to the first frame.
+ readNextFrame();
+}
+
+int Win32AudioCDStream::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 Win32AudioCDStream::readNextFrame() {
+ // Figure out the MSF of the frame we're looking for
+ int frame = _frame + getFrameCount(_startEntry);
+
+ // Request to read that frame. Subtract the pre-gap frames
+ // so that we get to the right sector.
+ RAW_READ_INFO readAudio;
+ memset(&readAudio, 0, sizeof(readAudio));
+ readAudio.DiskOffset.QuadPart = (frame - kPreGapFrames) * 2048;
+ readAudio.SectorCount = 1;
+ readAudio.TrackMode = CDDA;
+
+ DWORD bytesReturned;
+ bool result = DeviceIoControl(
+ _driveHandle,
+ IOCTL_CDROM_RAW_READ,
+ &readAudio,
+ sizeof(readAudio),
+ &_buffer,
+ sizeof(_buffer),
+ &bytesReturned,
+ NULL);
+ if (!result) {
+ warning("Failed to retrieve CD sector %d: %d", frame, (int)GetLastError());
+ _frame = getFrameCount(_endEntry);
+ return false;
+ }
+
+ _frame++;
+ return true;
+}
+
+bool Win32AudioCDStream::endOfData() const {
+ return getFrameCount(_startEntry) + _frame >= getFrameCount(_endEntry) && _bufferPos == kSamplesPerFrame;
+}
+
+bool Win32AudioCDStream::seek(const Audio::Timestamp &where) {
+ // Convert to the frame number
+ // Really not much else needed
+ _bufferPos = kSamplesPerFrame;
+ _frame = where.convertToFramerate(kFramesPerSecond).totalNumberOfFrames();
+ return true;
+}
+
+Audio::Timestamp Win32AudioCDStream::getLength() const {
+ return Audio::Timestamp(0, getFrameCount(_endEntry) - getFrameCount(_startEntry), 75);
+}
+
+class Win32AudioCDManager : public DefaultAudioCDManager {
+public:
+ Win32AudioCDManager();
+ ~Win32AudioCDManager();
+
+ bool openCD(int drive);
+ void closeCD();
+ void playCD(int track, int numLoops, int startFrame, int duration);
+
+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() {
+ closeCD();
+}
+
+bool Win32AudioCDManager::openCD(int drive) {
+ closeCD();
+
+ // 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]);
+ _driveHandle = CreateFileA(drivePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+ if (_driveHandle == INVALID_HANDLE_VALUE) {
+ warning("Failed to open drive %c:\\, error %d", drives[drive], (int)GetLastError());
+ return false;
+ }
+
+ if (!loadTOC()) {
+ closeCD();
+ return false;
+ }
+
+ return false;
+}
+
+void Win32AudioCDManager::closeCD() {
+ // Stop any previous track
+ stop();
+
+ if (_driveHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(_driveHandle);
+ _driveHandle = INVALID_HANDLE_VALUE;
+ }
+
+ _firstTrack = _lastTrack = 0;
+ _tocEntries.clear();
+}
+
+void Win32AudioCDManager::playCD(int track, int numLoops, int startFrame, int duration) {
+ // Stop any previous track
+ stop();
+
+ // 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;
+ }
+
+ // Bail if the track isn't an audio track
+ if ((_tocEntries[track].Control & 0x04) != 0) {
+ warning("Track %d is not audio", track);
+ return;
+ }
+
+ // 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(
+ Audio::Mixer::kMusicSoundType,
+ &_handle,
+ Audio::makeLoopingAudioStream(audioStream, start, end, (numLoops < 1) ? numLoops + 1 : numLoops),
+ -1,
+ _cd.volume,
+ _cd.balance,
+ DisposeAfterUse::YES,
+ 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,
+ NULL);
+ 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::String gamePath = ConfMan.get("path");
+ char fullPath[MAX_PATH];
+ DWORD result = GetFullPathNameA(gamePath.c_str(), sizeof(fullPath), fullPath, 0);
+
+ 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) {
+ Common::String drivePath = Common::String::format("%c:\\", drive);
+
+ // Ensure it's an actual CD drive
+ if (GetDriveTypeA(drivePath.c_str()) != DRIVE_CDROM)
+ return false;
+
+ debug(2, "Detected drive %c:\\ as a CD drive", drive);
+ drives.push_back(drive);
+ return true;
+}
+
+AudioCDManager *createWin32AudioCDManager() {
+ return new Win32AudioCDManager();
+}
diff --git a/backends/audiocd/win32/win32-audiocd.h b/backends/audiocd/win32/win32-audiocd.h
new file mode 100644
index 0000000000..d2a5b257a7
--- /dev/null
+++ b/backends/audiocd/win32/win32-audiocd.h
@@ -0,0 +1,38 @@
+/* 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#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
+
diff --git a/backends/module.mk b/backends/module.mk
index 40b63e2aa4..8abab65f14 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -106,6 +106,7 @@ endif
ifdef WIN32
MODULE_OBJS += \
+ audiocd/win32/win32-audiocd.o \
fs/windows/windows-fs.o \
fs/windows/windows-fs-factory.o \
midi/windows.o \
diff --git a/backends/platform/sdl/win32/win32.cpp b/backends/platform/sdl/win32/win32.cpp
index 0f70c00b40..fbab7eb782 100644
--- a/backends/platform/sdl/win32/win32.cpp
+++ b/backends/platform/sdl/win32/win32.cpp
@@ -35,6 +35,7 @@
#include "common/error.h"
#include "common/textconsole.h"
+#include "backends/audiocd/win32/win32-audiocd.h"
#include "backends/platform/sdl/win32/win32.h"
#include "backends/platform/sdl/win32/win32-window.h"
#include "backends/saves/windows/windows-saves.h"
@@ -318,4 +319,8 @@ void OSystem_Win32::addSysArchivesToSearchSet(Common::SearchSet &s, int priority
OSystem_SDL::addSysArchivesToSearchSet(s, priority);
}
+AudioCDManager *OSystem_Win32::createAudioCDManager() {
+ return createWin32AudioCDManager();
+}
+
#endif
diff --git a/backends/platform/sdl/win32/win32.h b/backends/platform/sdl/win32/win32.h
index 473e78ff0b..ca0843e834 100644
--- a/backends/platform/sdl/win32/win32.h
+++ b/backends/platform/sdl/win32/win32.h
@@ -49,6 +49,10 @@ protected:
virtual Common::String getDefaultConfigFileName();
virtual Common::WriteStream *createLogFile();
+
+ // Override createAudioCDManager() to get our Mac-specific
+ // version.
+ virtual AudioCDManager *createAudioCDManager();
};
#endif