aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/memstream.h16
-rwxr-xr-xconfigure20
-rw-r--r--engines/mohawk/riven.cpp3
-rw-r--r--engines/saga/actor.cpp15
-rw-r--r--engines/saga/interface.cpp16
-rw-r--r--engines/saga/script.cpp19
-rw-r--r--engines/saga/sfuncs.cpp8
-rw-r--r--engines/sludge/sound.cpp151
-rw-r--r--engines/sludge/sound.h8
-rw-r--r--engines/sludge/sprites.cpp14
-rw-r--r--engines/titanic/carry/mouth.cpp5
-rw-r--r--engines/titanic/game/bridge_view.cpp5
-rw-r--r--engines/titanic/game/chicken_dispensor.cpp9
-rw-r--r--engines/titanic/game/fan_control.cpp2
-rw-r--r--engines/titanic/game_manager.cpp2
-rw-r--r--engines/titanic/main_game_window.cpp6
-rw-r--r--engines/titanic/npcs/titania.cpp13
-rw-r--r--engines/titanic/sound/qmixer.cpp22
-rw-r--r--engines/titanic/sound/sound_manager.cpp2
-rw-r--r--engines/titanic/sound/wave_file.cpp9
-rw-r--r--engines/titanic/sound/wave_file.h6
-rw-r--r--engines/titanic/support/credit_text.cpp2
-rw-r--r--engines/titanic/titanic.cpp62
-rw-r--r--engines/titanic/titanic.h10
-rw-r--r--engines/wage/detection_tables.h1
-rw-r--r--engines/wage/script.cpp11
-rw-r--r--graphics/macgui/mactextwindow.cpp3
-rw-r--r--graphics/macgui/macwindow.cpp4
-rw-r--r--graphics/macgui/macwindowmanager.cpp4
-rw-r--r--video/coktel_decoder.cpp240
-rw-r--r--video/coktel_decoder.h13
31 files changed, 400 insertions, 301 deletions
diff --git a/common/memstream.h b/common/memstream.h
index 25fdde91c7..0338d35378 100644
--- a/common/memstream.h
+++ b/common/memstream.h
@@ -212,13 +212,14 @@ public:
/**
* MemoryStream based on RingBuffer. Grows if has insufficient buffer size.
*/
-class MemoryReadWriteStream : public WriteStream {
+class MemoryReadWriteStream : public SeekableReadStream, public WriteStream {
private:
uint32 _capacity;
uint32 _size;
byte *_data;
uint32 _writePos, _readPos, _pos, _length;
DisposeAfterUse::Flag _disposeMemory;
+ bool _eos;
void ensureCapacity(uint32 new_len) {
if (new_len <= _capacity)
@@ -246,7 +247,7 @@ private:
}
}
public:
- MemoryReadWriteStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : _capacity(0), _size(0), _data(0), _writePos(0), _readPos(0), _pos(0), _length(0), _disposeMemory(disposeMemory) {}
+ MemoryReadWriteStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : _capacity(0), _size(0), _data(0), _writePos(0), _readPos(0), _pos(0), _length(0), _disposeMemory(disposeMemory), _eos(false) {}
~MemoryReadWriteStream() {
if (_disposeMemory)
@@ -271,8 +272,10 @@ public:
}
virtual uint32 read(void *dataPtr, uint32 dataSize) {
- uint32 length = _length;
- if (length < dataSize) dataSize = length;
+ if (_length < dataSize) {
+ dataSize = _length;
+ _eos = true;
+ }
if (dataSize == 0 || _capacity == 0) return 0;
if (_readPos + dataSize < _capacity) {
memcpy(dataPtr, _data + _readPos, dataSize);
@@ -287,7 +290,10 @@ public:
}
int32 pos() const { return _pos - _length; } //'read' position in the stream
- uint32 size() const { return _size; } //that's also 'write' position in the stream, as it's append-only
+ int32 size() const { return _size; } //that's also 'write' position in the stream, as it's append-only
+ bool seek(int32, int) { return false; }
+ bool eos() const { return _eos; }
+ void clearErr() { _eos = false; }
byte *getData() { return _data; }
};
diff --git a/configure b/configure
index 4d8c23501c..3f35902f78 100755
--- a/configure
+++ b/configure
@@ -164,6 +164,7 @@ _build_scalers=yes
_build_hq_scalers=yes
_enable_prof=no
_global_constructors=no
+_no_undefined_var_template=no
_bink=yes
_cloud=auto
# Default vkeybd/keymapper/eventrec options
@@ -2023,13 +2024,30 @@ echocheck "whether -Wglobal-constructors work"
cat > $TMPC << EOF
int main() { return 0; }
EOF
-cc_check -Wglobal-constructors && _global_constructors=yes
+cc_check -Wglobal-constructors -Werror && _global_constructors=yes
if test "$_global_constructors" = yes; then
append_var CXXFLAGS "-Wglobal-constructors"
fi
echo $_global_constructors
+# If the compiler supports the -Wundefined-var-template flag, silence that warning.
+# We get this warning a lot with regard to the Singleton class as we explicitly
+# instantiate each specialisation. An alternate way to deal with it would be to
+# change the way we instantiate the singleton classes as done in PR #967.
+# Note: we check the -Wundefined-var-template as gcc does not error out on unknown
+# -Wno-xxx flags.
+echocheck "whether -Wno-undefined-var-template work"
+cat > $TMPC << EOF
+int main() { return 0; }
+EOF
+cc_check -Wundefined-var-template -Werror && _no_undefined_var_template=yes
+
+if test "$_no_undefined_var_template" = yes; then
+ append_var CXXFLAGS "-Wno-undefined-var-template"
+fi
+echo $_no_undefined_var_template
+
echo_n "Checking for $_host_alias-strings... " >> "$TMPLOG"
if `which $_host_alias-strings >/dev/null 2>&1`; then
_strings=$_host_alias-strings
diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp
index c7f8d1c3d9..ea3e2ce7ec 100644
--- a/engines/mohawk/riven.cpp
+++ b/engines/mohawk/riven.cpp
@@ -510,7 +510,10 @@ void MohawkEngine_Riven::delay(uint32 ms) {
void MohawkEngine_Riven::runLoadDialog() {
GUI::SaveLoadChooser slc(_("Load game:"), _("Load"), false);
+ pauseEngine(true);
int slot = slc.runModalWithCurrentTarget();
+ pauseEngine(false);
+
if (slot >= 0) {
loadGameStateAndDisplayError(slot);
}
diff --git a/engines/saga/actor.cpp b/engines/saga/actor.cpp
index d8b115f9bd..8c45a2890e 100644
--- a/engines/saga/actor.cpp
+++ b/engines/saga/actor.cpp
@@ -1174,21 +1174,6 @@ void Actor::actorSpeech(uint16 actorId, const char **strings, int stringsCount,
_activeSpeech.speechBox.right = _vm->getDisplayInfo().width - 10;
}
- // HACK for the compact disk in Ellen's chapter
- // Once Ellen starts saying that "Something is different", bring the compact disk in the
- // scene. After speaking with AM, the compact disk is visible. She always says this line
- // when entering room 59, after speaking with AM, if the compact disk is not picked up yet
- // Check Script::sfDropObject for the other part of this hack
- if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 3 &&
- _vm->_scene->currentSceneNumber() == 59 && _activeSpeech.sampleResourceId == 286) {
- for (ObjectDataArray::iterator obj = _objs.begin(); obj != _objs.end(); ++obj) {
- if (obj->_id == 16385) { // the compact disk
- obj->_sceneNumber = 59;
- break;
- }
- }
- }
-
}
void Actor::nonActorSpeech(const Common::Rect &box, const char **strings, int stringsCount, int sampleResourceId, int speechFlags) {
diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp
index 9dcc8d9137..e6b196c4cd 100644
--- a/engines/saga/interface.cpp
+++ b/engines/saga/interface.cpp
@@ -2533,10 +2533,12 @@ void Interface::converseDisplayTextLines() {
char bullet[2] = {
(char)0xb7, 0
};
- Rect rect(8, _vm->getDisplayInfo().converseTextLines * _vm->getDisplayInfo().converseTextHeight);
- Point textPoint;
assert(_conversePanel.buttonsCount >= 6);
+ Rect rect(8, _vm->getDisplayInfo().converseTextLines * _vm->getDisplayInfo().converseTextHeight);
+ rect.moveTo(_conversePanel.x + _conversePanel.buttons[0].xOffset, _conversePanel.y + _conversePanel.buttons[0].yOffset);
+
+ Point textPoint;
if (_vm->getGameId() == GID_ITE) {
bulletForegnd = kITEColorGreen;
@@ -2547,13 +2549,11 @@ void Interface::converseDisplayTextLines() {
bullet[0] = '>'; // different bullet in IHNM
}
- rect.moveTo(_conversePanel.x + _conversePanel.buttons[0].xOffset,
- _conversePanel.y + _conversePanel.buttons[0].yOffset);
-
if (_vm->getGameId() == GID_ITE)
- _vm->_gfx->drawRect(rect, kITEColorDarkGrey); //fill bullet place
- else
- _vm->_gfx->drawRect(rect, _vm->KnownColor2ColorId(kKnownColorBlack)); //fill bullet place
+ _vm->_gfx->drawRect(rect, kITEColorDarkGrey); // fill bullet place
+ else if (_vm->getGameId() == GID_IHNM)
+ // TODO: Add these to IHNM_DisplayInfo?
+ _vm->_gfx->drawRect(Common::Rect(118, 345, 603, 463), _vm->KnownColor2ColorId(kKnownColorBlack)); // fill converse rect
for (int i = 0; i < _vm->getDisplayInfo().converseTextLines; i++) {
relPos = _converseStartPos + i;
diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp
index 3cc6586432..7a84944b17 100644
--- a/engines/saga/script.cpp
+++ b/engines/saga/script.cpp
@@ -1709,15 +1709,16 @@ void Script::whichObject(const Point& mousePoint) {
if (_vm->getGameId() == GID_IHNM && objectId == 8199)
newRightButtonVerb = getVerbType(kVerbLookAt);
- if ((_currentVerb == getVerbType(kVerbPickUp)) ||
- (_currentVerb == getVerbType(kVerbOpen)) ||
- (_currentVerb == getVerbType(kVerbClose)) ||
- ((_currentVerb == getVerbType(kVerbGive)) && !_firstObjectSet) ||
- ((_currentVerb == getVerbType(kVerbUse)) && !(actor->_flags & kFollower))) {
- if (_vm->getGameId() == GID_ITE) {
- objectId = ID_NOTHING;
- newObjectId = ID_NOTHING;
- }
+ bool actorIsFollower = (actor->_flags & kFollower);
+ bool actorCanBeUsed = (actor->_flags & kUsable);
+
+ if ( _currentVerb == getVerbType(kVerbPickUp) ||
+ _currentVerb == getVerbType(kVerbOpen) ||
+ _currentVerb == getVerbType(kVerbClose) ||
+ (_currentVerb == getVerbType(kVerbGive) && !_firstObjectSet) ||
+ (_currentVerb == getVerbType(kVerbUse) && !_firstObjectSet && !(actorIsFollower || actorCanBeUsed))) {
+ objectId = ID_NOTHING;
+ newObjectId = ID_NOTHING;
}
}
}
diff --git a/engines/saga/sfuncs.cpp b/engines/saga/sfuncs.cpp
index 2175d8f40a..6456daeb02 100644
--- a/engines/saga/sfuncs.cpp
+++ b/engines/saga/sfuncs.cpp
@@ -704,14 +704,6 @@ void Script::sfDropObject(SCRIPTFUNC_PARAMS) {
obj->_sceneNumber = _vm->_scene->currentSceneNumber();
- // HACK for the compact disk in Ellen's chapter
- // Change the scene number of the compact disk so that it's not shown. It will be shown
- // once Ellen says that there's something different (i.e. after speaking with AM)
- // See Actor::actorSpeech for the other part of this hack
- if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 3 &&
- _vm->_scene->currentSceneNumber() == 59 && obj->_id == 16385)
- obj->_sceneNumber = -1;
-
if (_vm->getGameId() == GID_IHNM) {
// Don't update _spriteListResourceId if spriteId is 0 and the object is not the
// psychic profile. If spriteId == 0, the object's sprite is incorrectly reset.
diff --git a/engines/sludge/sound.cpp b/engines/sludge/sound.cpp
index 79b9ab79d8..6820ae14be 100644
--- a/engines/sludge/sound.cpp
+++ b/engines/sludge/sound.cpp
@@ -52,10 +52,9 @@ SoundManager::SoundManager() {
_soundCache = nullptr;
_soundCache = new SoundThing[MAX_SAMPLES];
- #if 0
+
_modCache = nullptr;
_modCache = new SoundThing[MAX_MODS];
- #endif
_defVol = 128;
_defSoundVol = 255;
@@ -70,10 +69,8 @@ SoundManager::~SoundManager() {
delete []_soundCache;
_soundCache = nullptr;
- #if 0
delete []_modCache;
_modCache = nullptr;
- #endif
}
bool SoundManager::initSoundStuff() {
@@ -82,12 +79,13 @@ bool SoundManager::initSoundStuff() {
_soundCache[a].looping = false;
_soundCache[a].inSoundList = false;
}
-#if 0
+
for (int a = 0; a < MAX_MODS; a ++) {
- _modCache[a].stream = NULL;
- _modCache[a].playing = false;
+ _soundCache[a].fileLoaded = -1;
+ _soundCache[a].looping = false;
+ _soundCache[a].inSoundList = false;
}
-#endif
+
return _soundOK = true;
}
@@ -95,48 +93,24 @@ void SoundManager::killSoundStuff() {
if (!_soundOK)
return;
- _silenceIKillYou = true;
- for (int i = 0; i < MAX_SAMPLES; i ++) {
- if (g_sludge->_mixer->isSoundHandleActive(_soundCache[i].handle)) {
- g_sludge->_mixer->stopHandle(_soundCache[i].handle);
- }
- }
-#if 0
- for (int i = 0; i < MAX_MODS; i ++) {
- if (_modCache[i].playing) {
-
- if (! alureStopSource(modCache[i].playingOnSource, AL_TRUE)) {
- debugOut("Failed to stop source: %s\n",
- alureGetErrorString());
- }
-
- }
-
- if (_modCache[i].stream != NULL) {
-
- if (! alureDestroyStream(modCache[i].stream, 0, NULL)) {
- debugOut("Failed to destroy stream: %s\n",
- alureGetErrorString());
- }
+ for (int i = 0; i < MAX_SAMPLES; ++i)
+ freeSound(i);
- }
- }
-#endif
- _silenceIKillYou = false;
+ for (int i = 0; i < MAX_MODS; ++i)
+ stopMOD(i);
}
/*
* Some setters:
*/
-
void SoundManager::setMusicVolume(int a, int v) {
if (!_soundOK)
return;
-#if 0
- if (_modCache[a].playing) {
- alSourcef(modCache[a].playingOnSource, AL_GAIN, (float) _modLoudness * v / 256);
+
+ if (g_sludge->_mixer->isSoundHandleActive(_modCache[a].handle)) {
+ _modCache[a].vol = v;
+ g_sludge->_mixer->setChannelVolume(_modCache[a].handle, _modLoudness * v / 256);
}
-#endif
}
void SoundManager::setDefaultMusicVolume(int v) {
@@ -176,14 +150,11 @@ int SoundManager::findInSoundCache(int a) {
void SoundManager::stopMOD(int i) {
if (!_soundOK)
return;
-#if 0
- alGetError();
- if (modCache[i].playing) {
- if (! alureStopSource(modCache[i].playingOnSource, AL_TRUE)) {
- debugOut("Failed to stop source: %s\n", alureGetErrorString());
- }
+
+ if (g_sludge->_mixer->isSoundHandleActive(_modCache[i].handle)) {
+ g_sludge->_mixer->stopHandle(_modCache[i].handle);
}
-#endif
+ _modCache[i].fileLoaded = -1;
}
void SoundManager::huntKillSound(int filenum) {
@@ -191,15 +162,10 @@ void SoundManager::huntKillSound(int filenum) {
return;
int gotSlot = findInSoundCache(filenum);
- if (gotSlot == -1) return;
-
- _silenceIKillYou = true;
-
- if (g_sludge->_mixer->isSoundHandleActive(_soundCache[gotSlot].handle)) {
- g_sludge->_mixer->stopHandle(_soundCache[gotSlot].handle);
- }
+ if (gotSlot == -1)
+ return;
- _silenceIKillYou = false;
+ freeSound(gotSlot);
}
void SoundManager::freeSound(int a) {
@@ -214,6 +180,8 @@ void SoundManager::freeSound(int a) {
handleSoundLists();
}
+ _soundCache[a].inSoundList = false;
+ _soundCache[a].looping = false;
_soundCache[a].fileLoaded = -1;
_silenceIKillYou = false;
@@ -233,69 +201,33 @@ void SoundManager::huntKillFreeSound(int filenum) {
*/
bool SoundManager::playMOD(int f, int a, int fromTrack) {
#if 0
+ if (!_soundOK)
+ return true;
+ stopMOD(a);
+
// load sound
setResourceForFatal(f);
- uint32 length = openFileFromNum(f);
+ uint length = g_sludge->_resMan->openFileFromNum(f);
if (length == 0) {
- finishAccess();
+ g_sludge->_resMan->finishAccess();
setResourceForFatal(-1);
return false;
}
- Common::SeekableReadStream *memImage = bigDataFile->readStream(length);
- if (memImage->size() != length || bigDataFile->err())
+ // make audio stream
+ Common::SeekableReadStream *readStream = g_sludge->_resMan->getData();
+ Common::SeekableReadStream *memImage = readStream->readStream(length);
+ if (memImage->size() != (int)length || readStream->err())
debug("Sound reading failed");
Audio::AudioStream *stream = Audio::makeProtrackerStream(memImage);
- //TODO: replace by xm file decoders
+
if (!stream)
return false;
// play sound
- Audio::SoundHandle soundHandle;
- g_sludge->_mixer->playStream(Audio::Mixer::kSFXSoundType, &soundHandle,
+ g_sludge->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_modCache[a].handle,
stream, -1, Audio::Mixer::kMaxChannelVolume);
- if (!_soundOK)
- return true;
- stopMOD(a);
-
- setResourceForFatal(f);
- uint32 length = openFileFromNum(f);
- if (length == 0) {
- finishAccess();
- setResourceForFatal(-1);
- return false;
- }
-
- byte *memImage;
- memImage = (byte *) loadEntireFileToMemory(bigDataFile, length);
- if (! memImage) return fatal(ERROR_MUSIC_MEMORY_LOW);
-
- _modCache[a].stream = alureCreateStreamFromMemory(memImage, length, 19200, 0, NULL);
-
- delete memImage;
-
- if (_modCache[a].stream != NULL) {
- setMusicVolume(a, defVol);
-
- if (! alureSetStreamOrder(modCache[a].stream, fromTrack)) {
- debugOut("Failed to set stream order: %s\n",
- alureGetErrorString());
- }
-
- playStream(a, true, true);
-
- } else {
-
- debugOut("Failed to create stream from MOD: %s\n",
- alureGetErrorString());
-
- warning(ERROR_MUSIC_ODDNESS);
- _soundCache[a].stream = NULL;
- _soundCache[a].playing = false;
- _soundCache[a].playingOnSource = 0;
- }
- setResourceForFatal(-1);
#endif
return true;
}
@@ -363,20 +295,12 @@ int SoundManager::makeSoundAudioStream(int f, Audio::AudioStream *&audiostream,
return -1;
int a = findInSoundCache(f);
- if (a != -1) { // if this sound has been loaded before
- // still playing
- if (g_sludge->_mixer->isSoundHandleActive(_soundCache[a].handle)) {
- g_sludge->_mixer->stopHandle(_soundCache[a].handle); // stop it
- if (_soundCache[a].inSoundList) {
- handleSoundLists();
- }
- }
- } else {
+ if (a == -1) {
if (f == -2)
return -1;
a = findEmptySoundSlot();
- freeSound(a);
}
+ freeSound(a);
setResourceForFatal(f);
uint32 length = g_sludge->_resMan->openFileFromNum(f);
@@ -398,6 +322,7 @@ int SoundManager::makeSoundAudioStream(int f, Audio::AudioStream *&audiostream,
if (stream) {
audiostream = Audio::makeLoopingAudioStream(stream, loopy ? 0 : 1);
_soundCache[a].fileLoaded = f;
+ _soundCache[a].looping = loopy;
setResourceForFatal(-1);
} else {
audiostream = nullptr;
diff --git a/engines/sludge/sound.h b/engines/sludge/sound.h
index cdd76b33cc..1e1a2a47e4 100644
--- a/engines/sludge/sound.h
+++ b/engines/sludge/sound.h
@@ -83,9 +83,9 @@ private:
struct SoundThing {
Audio::SoundHandle handle;
- int fileLoaded, vol; //Used for sounds only. (sound saving/loading)
- bool looping; //Used for sounds only. (sound saving/loading)
- bool inSoundList;
+ int fileLoaded, vol; //Used for wav/ogg sounds only. (sound saving/loading)
+ bool looping; //Used for wav/ogg sounds only. (sound saving/loading)
+ bool inSoundList; //Used for wav/ogg sounds only
};
typedef Common::List<SoundList *> SoundListHandles;
@@ -97,9 +97,7 @@ private:
bool _isHandlingSoundList;
SoundThing *_soundCache;
- #if 0
SoundThing *_modCache;
- #endif
int _defVol;
int _defSoundVol;
diff --git a/engines/sludge/sprites.cpp b/engines/sludge/sprites.cpp
index 98d88a8337..c37c4a1905 100644
--- a/engines/sludge/sprites.cpp
+++ b/engines/sludge/sprites.cpp
@@ -271,6 +271,13 @@ bool GraphicsManager::loadSpriteBank(int fileNum, SpriteBank &loadhere, bool isF
// pasteSpriteToBackDrop uses the colour specified by the setPasteColour (or setPasteColor)
void GraphicsManager::pasteSpriteToBackDrop(int x1, int y1, Sprite &single, const SpritePalette &fontPal) {
+ // kill zBuffer
+ if (_zBuffer->originalNum >= 0 && _zBuffer->sprites) {
+ int num = _zBuffer->originalNum;
+ killZBuffer();
+ _zBuffer->originalNum = num;
+ }
+
//TODO: shader: useLightTexture
x1 -= single.xhot;
y1 -= single.yhot;
@@ -282,6 +289,13 @@ void GraphicsManager::pasteSpriteToBackDrop(int x1, int y1, Sprite &single, cons
// burnSpriteToBackDrop adds text in the colour specified by setBurnColour
// using the differing brightness levels of the font to achieve an anti-aliasing effect.
void GraphicsManager::burnSpriteToBackDrop(int x1, int y1, Sprite &single, const SpritePalette &fontPal) {
+ // kill zBuffer
+ if (_zBuffer->originalNum >= 0 && _zBuffer->sprites) {
+ int num = _zBuffer->originalNum;
+ killZBuffer();
+ _zBuffer->originalNum = num;
+ }
+
//TODO: shader: useLightTexture
x1 -= single.xhot;
y1 -= single.yhot - 1;
diff --git a/engines/titanic/carry/mouth.cpp b/engines/titanic/carry/mouth.cpp
index d750fc969e..1b2830b99e 100644
--- a/engines/titanic/carry/mouth.cpp
+++ b/engines/titanic/carry/mouth.cpp
@@ -74,6 +74,11 @@ bool CMouth::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
_field13C = true;
}
+ // WORKAROUND: If Mouth is removed from Titania after inserting,
+ // message the Titania control so it can be flagged as removed
+ CTakeHeadPieceMsg headpieceMsg(getName());
+ headpieceMsg.execute("TitaniaControl");
+
return true;
}
diff --git a/engines/titanic/game/bridge_view.cpp b/engines/titanic/game/bridge_view.cpp
index e8d70c8c43..14361b4e8c 100644
--- a/engines/titanic/game/bridge_view.cpp
+++ b/engines/titanic/game/bridge_view.cpp
@@ -55,6 +55,7 @@ bool CBridgeView::ActMsg(CActMsg *msg) {
} else if (msg->_action == "Go") {
_action = BA_GO;
setVisible(true);
+ hideMouse();
volumeMsg._volume = 100;
volumeMsg.execute("EngineSounds");
onMsg.execute("EngineSounds");
@@ -67,10 +68,13 @@ bool CBridgeView::ActMsg(CActMsg *msg) {
if (msg->_action == "Cruise") {
_action = BA_CRUISE;
setVisible(true);
+ hideMouse();
playMovie(MOVIE_NOTIFY_OBJECT);
} else if (msg->_action == "GoEnd") {
_action = BA_ENDING1;
setVisible(true);
+ hideMouse();
+
CChangeMusicMsg musicMsg;
musicMsg._flags = 1;
musicMsg.execute("BridgeAutoMusicPlayer");
@@ -90,6 +94,7 @@ bool CBridgeView::MovieEndMsg(CMovieEndMsg *msg) {
case BA_GO:
case BA_CRUISE:
setVisible(false);
+ showMouse();
decTransitions();
break;
diff --git a/engines/titanic/game/chicken_dispensor.cpp b/engines/titanic/game/chicken_dispensor.cpp
index 89873dcc4d..d44bc7157b 100644
--- a/engines/titanic/game/chicken_dispensor.cpp
+++ b/engines/titanic/game/chicken_dispensor.cpp
@@ -104,7 +104,7 @@ bool CChickenDispensor::StatusChangeMsg(CStatusChangeMsg *msg) {
}
bool CChickenDispensor::MovieEndMsg(CMovieEndMsg *msg) {
- int movieFrame = getMovieFrame();
+ int movieFrame = msg->_endFrame;
if (movieFrame == 16) {
// Dispensed a chicken
@@ -113,12 +113,7 @@ bool CChickenDispensor::MovieEndMsg(CMovieEndMsg *msg) {
CActMsg actMsg("Dispense Chicken");
actMsg.execute("Chicken");
- if (_dispenseMode == DISPENSE_HOT) {
- // A properly hot chicken is dispensed, no further ones will be
- // until the current one is used up, and the fuse in Titania's
- // fusebox is removed and replaced
- _dispenseMode = DISPENSE_NONE;
- } else {
+ if (_dispenseMode != DISPENSE_HOT) {
// WORKAROUND: If the fuse for the dispensor is removed in Titania's fusebox,
// make the dispensed chicken already cold
CChicken::_temperature = 0;
diff --git a/engines/titanic/game/fan_control.cpp b/engines/titanic/game/fan_control.cpp
index 7ed22fd560..fc99bd8b36 100644
--- a/engines/titanic/game/fan_control.cpp
+++ b/engines/titanic/game/fan_control.cpp
@@ -107,7 +107,7 @@ bool CFanControl::StatusChangeMsg(CStatusChangeMsg *msg) {
case 2:
// Fan Speed button
if (_fanOn) {
- _state = (_state + 1) % 4;
+ _state = (_state + 1) % 3;
switch (_state) {
case 0:
playMovie(18, 24, 0);
diff --git a/engines/titanic/game_manager.cpp b/engines/titanic/game_manager.cpp
index 13fe1d2872..65a2a278c8 100644
--- a/engines/titanic/game_manager.cpp
+++ b/engines/titanic/game_manager.cpp
@@ -81,8 +81,8 @@ void CGameManager::preLoad() {
_timers.destroyContents();
_soundMaker = nullptr;
- _trueTalkManager.preLoad();
_sound.preLoad();
+ _trueTalkManager.preLoad();
}
void CGameManager::postLoad(CProjectItem *project) {
diff --git a/engines/titanic/main_game_window.cpp b/engines/titanic/main_game_window.cpp
index 053712d412..cfea98cdf1 100644
--- a/engines/titanic/main_game_window.cpp
+++ b/engines/titanic/main_game_window.cpp
@@ -341,6 +341,12 @@ void CMainGameWindow::keyDown(Common::KeyState keyState) {
_gameManager->_gameState.changeView(newView, nullptr);
}
+ } else if (keyState.keycode == Common::KEYCODE_F5) {
+ // Show the GMM save dialog
+ g_vm->showScummVMSaveDialog();
+ } else if (keyState.keycode == Common::KEYCODE_F7) {
+ // Show the GMM load dialog
+ g_vm->showScummVMRestoreDialog();
} else if (_inputAllowed) {
_gameManager->_inputTranslator.keyDown(keyState);
}
diff --git a/engines/titanic/npcs/titania.cpp b/engines/titanic/npcs/titania.cpp
index 70ca4bace6..d3e3395fc8 100644
--- a/engines/titanic/npcs/titania.cpp
+++ b/engines/titanic/npcs/titania.cpp
@@ -149,9 +149,10 @@ bool CTitania::ActMsg(CActMsg *msg) {
playSound("z#47.wav", 100);
changeView("Titania.Node 7.S", "");
+ // Re-enable control, and reset bomb's volume back to normal 60%
petShow();
enableMouse();
- CSetFrameMsg frameMsg;
+ CSetFrameMsg frameMsg(60);
frameMsg.execute("Bomb");
} else if (msg->_action == "CheckHead") {
@@ -173,8 +174,9 @@ bool CTitania::ActMsg(CActMsg *msg) {
workingMsg3._value = _speechCentre ? "Working" : "Random";
}
- if (_centralCore && _eye1 && _eye2 && _ear1 && _ear2 && _nose && _mouth
- && _speechCentre && _olfactoryCentre && _auditoryCentre) {
+ if (_centralCore && _eye1 && _eye2 && _ear1 && _ear2 && _nose
+ && _mouth && _visionCentre && _speechCentre
+ && _olfactoryCentre && _auditoryCentre) {
CProximity prox(Audio::Mixer::kSpeechSoundType);
playSound("z#47.wav", prox);
@@ -203,11 +205,14 @@ bool CTitania::EnterViewMsg(CEnterViewMsg *msg) {
disableMouse();
petHide();
+ // The Bomb uses the CSetFrameMsg as a hack for setting the volume.
+ // In case it's currently active, set it to a quieter 25% so that
+ // it won't obscure Titania's speech.
CSetFrameMsg frameMsg;
frameMsg._frameNumber = 25;
frameMsg.execute("Bomb");
- playCutscene(0, 52);
+ playCutscene(0, 52);
setVisible(false);
CActMsg actMsg("TitaniaSpeech");
actMsg.execute("TitaniaSpeech");
diff --git a/engines/titanic/sound/qmixer.cpp b/engines/titanic/sound/qmixer.cpp
index 5c511c3cae..beb1502ab4 100644
--- a/engines/titanic/sound/qmixer.cpp
+++ b/engines/titanic/sound/qmixer.cpp
@@ -208,18 +208,13 @@ void QMixer::qsWaveMixPump() {
if (!channel._sounds.empty()) {
SoundEntry &sound = channel._sounds.front();
if (sound._started && !_mixer->isSoundHandleActive(sound._soundHandle)) {
- if (sound._loops == -1 || sound._loops-- > 0) {
- // Need to loop (replay) the sound again
- sound._soundHandle = sound._waveFile->play(channel.getRawVolume());
- } else {
- // Sound is finished
- if (sound._callback)
- // Call the callback to signal end
- sound._callback(iChannel, sound._waveFile, sound._userData);
-
- // Remove sound record from channel
- channel._sounds.erase(channel._sounds.begin());
- }
+ // Sound is finished
+ if (sound._callback)
+ // Call the callback to signal end
+ sound._callback(iChannel, sound._waveFile, sound._userData);
+
+ // Remove sound record from channel
+ channel._sounds.erase(channel._sounds.begin());
}
}
@@ -232,7 +227,8 @@ void QMixer::qsWaveMixPump() {
channel._distance = 0.0;
// Play the wave
- sound._soundHandle = sound._waveFile->play(channel.getRawVolume());
+ sound._soundHandle = sound._waveFile->play(
+ sound._loops, channel.getRawVolume());
sound._started = true;
}
}
diff --git a/engines/titanic/sound/sound_manager.cpp b/engines/titanic/sound/sound_manager.cpp
index 514618783b..4b5887b1e7 100644
--- a/engines/titanic/sound/sound_manager.cpp
+++ b/engines/titanic/sound/sound_manager.cpp
@@ -288,7 +288,7 @@ void QSoundManager::setVolume(int handle, uint volume, uint seconds) {
_channelsVolume[slot._channel] = volume;
updateVolume(slot._channel, seconds * 1000);
- if (volume) {
+ if (!volume) {
uint ticks = g_vm->_events->getTicksCount() + seconds * 1000;
if (!slot._ticks || ticks >= slot._ticks)
slot._ticks = ticks;
diff --git a/engines/titanic/sound/wave_file.cpp b/engines/titanic/sound/wave_file.cpp
index 8a4755ac97..c1aab42a7f 100644
--- a/engines/titanic/sound/wave_file.cpp
+++ b/engines/titanic/sound/wave_file.cpp
@@ -204,10 +204,15 @@ void CWaveFile::unlock(const int16 *ptr) {
// No implementation needed in ScummVM
}
-Audio::SoundHandle CWaveFile::play(byte volume) {
- Audio::SeekableAudioStream *stream = createAudioStream();
+Audio::SoundHandle CWaveFile::play(int numLoops, byte volume) {
+ Audio::SeekableAudioStream *audioStream = createAudioStream();
Audio::SoundHandle handle;
+ Audio::AudioStream *stream = audioStream;
+ if (numLoops != 0)
+ stream = new Audio::LoopingAudioStream(audioStream,
+ (numLoops == -1) ? 0 : numLoops);
+
_mixer->playStream(_soundType, &handle, stream, -1,
volume, 0, DisposeAfterUse::NO);
return handle;
diff --git a/engines/titanic/sound/wave_file.h b/engines/titanic/sound/wave_file.h
index 17c7b62f4b..c14891e2e4 100644
--- a/engines/titanic/sound/wave_file.h
+++ b/engines/titanic/sound/wave_file.h
@@ -126,8 +126,12 @@ public:
/**
* Plays the wave file
+ * @param numLoops Number of times to loop. 0 for none,
+ * -1 for infinite, and >0 for specified number of times
+ * @param volume Volume to play at
+ * @returns Audio handle for started sound
*/
- Audio::SoundHandle play(byte volume);
+ Audio::SoundHandle play(int numLoops, byte volume);
};
} // End of namespace Titanic
diff --git a/engines/titanic/support/credit_text.cpp b/engines/titanic/support/credit_text.cpp
index 98c3d9bf1d..6ee17a2b95 100644
--- a/engines/titanic/support/credit_text.cpp
+++ b/engines/titanic/support/credit_text.cpp
@@ -96,7 +96,7 @@ void CCreditText::setup() {
_screenManagerP->setFontNumber(oldFontNumber);
_groupIt = _groups.begin();
_lineIt = (*_groupIt)->_lines.begin();
- _yOffset = _objectP->getBounds().height() + _fontHeight * 2;
+ _yOffset = _objectP->_bounds.height() + _fontHeight * 2;
}
CString CCreditText::readLine(Common::SeekableReadStream *stream) {
diff --git a/engines/titanic/titanic.cpp b/engines/titanic/titanic.cpp
index 8a1b00c0fc..b6ee868877 100644
--- a/engines/titanic/titanic.cpp
+++ b/engines/titanic/titanic.cpp
@@ -25,9 +25,11 @@
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
+#include "common/translation.h"
#include "engines/util.h"
#include "graphics/scaler.h"
#include "graphics/thumbnail.h"
+#include "gui/saveload.h"
#include "titanic/titanic.h"
#include "titanic/debugger.h"
#include "titanic/carry/hose.h"
@@ -179,23 +181,30 @@ bool TitanicEngine::canLoadGameStateCurrently() {
CGameManager *gameManager = _window->_gameManager;
CScreenManager *screenMan = CScreenManager::_screenManagerPtr;
- if (!_window->_inputAllowed || !gameManager->_gameState._petActive)
+ if (!_window->_inputAllowed)
return false;
if (screenMan && screenMan->_inputHandler->isLocked())
return false;
CProjectItem *project = gameManager->_project;
if (project) {
- CPetControl *pet = project->getPetControl();
- if (pet && !pet->isAreaUnlocked())
- return false;
+ if (gameManager->_gameState._petActive) {
+ CPetControl *pet = project->getPetControl();
+ if (pet && !pet->isAreaUnlocked())
+ return false;
+ }
+ } else {
+ return false;
}
return true;
}
bool TitanicEngine::canSaveGameStateCurrently() {
- return canLoadGameStateCurrently();
+ CGameManager *gameManager = _window->_gameManager;
+
+ return gameManager->_gameState._petActive &&
+ canLoadGameStateCurrently();
}
Common::Error TitanicEngine::loadGameState(int slot) {
@@ -251,4 +260,47 @@ void TitanicEngine::GUIError(const char *msg, ...) {
GUIErrorMessage(buffer);
}
+
+void TitanicEngine::showScummVMSaveDialog() {
+ if (!canSaveGameStateCurrently())
+ return;
+
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
+
+ pauseEngine(true);
+ int slot = dialog->runModalWithCurrentTarget();
+ pauseEngine(false);
+
+ if (slot >= 0) {
+ Common::String desc = dialog->getResultString();
+
+ if (desc.empty()) {
+ // create our own description for the saved game, the user didn't enter it
+ desc = dialog->createDefaultSaveDescription(slot);
+ }
+
+ // Save the game
+ saveGameState(slot, desc);
+ }
+
+ delete dialog;
+}
+
+void TitanicEngine::showScummVMRestoreDialog() {
+ if (!canLoadGameStateCurrently())
+ return;
+
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
+
+ pauseEngine(true);
+ int slot = dialog->runModalWithCurrentTarget();
+ pauseEngine(false);
+
+ if (slot >= 0) {
+ loadGameState(slot);
+ }
+
+ delete dialog;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/titanic.h b/engines/titanic/titanic.h
index dc7fd156b8..0c15cf3e72 100644
--- a/engines/titanic/titanic.h
+++ b/engines/titanic/titanic.h
@@ -191,6 +191,16 @@ public:
* Displays an error message in a GUI dialog
*/
void GUIError(const char *msg, ...) GCC_PRINTF(2, 3);
+
+ /**
+ * Shows the ScummVM GMM save dialog
+ */
+ void showScummVMSaveDialog();
+
+ /**
+ * Shows the ScummVM GMM load dialog
+ */
+ void showScummVMRestoreDialog();
};
extern TitanicEngine *g_vm;
diff --git a/engines/wage/detection_tables.h b/engines/wage/detection_tables.h
index b207946882..bde10785df 100644
--- a/engines/wage/detection_tables.h
+++ b/engines/wage/detection_tables.h
@@ -158,7 +158,6 @@ static const ADGameDescription gameDescriptions[] = {
FANGAME("Star Trek", "3067332e6f0bb0314579f9bf102e1b56", 53320),
FANGAME("Strange Disappearance", "9d6e41b61c0fc90400e5da2fcb653a4a", 772282),
FANGAME("The Sultan's Palace", "fde31cbcc77b66969b4cfcd43075341e", 456855),
- // Code 0x03 in text
FANGAME("Swamp Witch", "bd8c8394be31f7845d55785b7ccfbbde", 739781), // Original file name "Swamp Witch†"
FANGAME("Swamp Witch", "07463c8b3b908b0c493a41b949ac1ff5", 740131), // alt version, normal file name
FANGAME("Sweetspace Now!", "574dc7dd25543f7a516d6524f0c5ab33", 123813), // Comes with Jumble
diff --git a/engines/wage/script.cpp b/engines/wage/script.cpp
index c4cf23fcb1..3405c8bf47 100644
--- a/engines/wage/script.cpp
+++ b/engines/wage/script.cpp
@@ -1161,12 +1161,19 @@ void Script::convertToText() {
break;
if (c < 0x80) {
- if (c < 0x20)
- error("convertToText: Unknown code 0x%02x at %d", c, _data->pos());
+ if (c < 0x20) {
+ warning("convertToText: Unknown code 0x%02x at %d", c, _data->pos());
+ c = ' ';
+ }
do {
scr->line += c;
c = _data->readByte();
+
+ if (c < 0x20) {
+ warning("convertToText: Unknown code 0x%02x at %d", c, _data->pos());
+ c = ' ';
+ }
} while (c < 0x80);
_data->seek(-1, SEEK_CUR);
diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index 9bbf2c8d10..85a07b975e 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -306,6 +306,9 @@ bool MacTextWindow::processEvent(Common::Event &event) {
}
}
+ if (hasAllFocus())
+ return MacWindow::processEvent(event); // Pass it to upstream
+
if (click == kBorderScrollUp || click == kBorderScrollDown) {
if (event.type == Common::EVENT_LBUTTONDOWN) {
int consoleHeight = getInnerDimensions().height();
diff --git a/graphics/macgui/macwindow.cpp b/graphics/macgui/macwindow.cpp
index 227f789d30..7b9afa4157 100644
--- a/graphics/macgui/macwindow.cpp
+++ b/graphics/macgui/macwindow.cpp
@@ -459,7 +459,9 @@ bool MacWindow::processEvent(Common::Event &event) {
_draggedY = event.mouse.y;
_wm->setFullRefresh(true);
- (*_callback)(click, event, _dataPtr);
+
+ if (_callback)
+ (*_callback)(click, event, _dataPtr);
}
break;
case Common::EVENT_LBUTTONDOWN:
diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp
index 9a3e711c19..8eaf8e9f5b 100644
--- a/graphics/macgui/macwindowmanager.cpp
+++ b/graphics/macgui/macwindowmanager.cpp
@@ -289,8 +289,10 @@ void MacWindowManager::draw() {
Common::Rect clip(w->getDimensions().left - 2, w->getDimensions().top - 2, w->getDimensions().right - 2, w->getDimensions().bottom - 2);
clip.clip(_screen->getBounds());
+ clip.clip(Common::Rect(0, 0, g_system->getWidth() - 1, g_system->getHeight() - 1));
- g_system->copyRectToScreen(_screen->getBasePtr(clip.left, clip.top), _screen->pitch, clip.left, clip.top, clip.width(), clip.height());
+ if (!clip.isEmpty())
+ g_system->copyRectToScreen(_screen->getBasePtr(clip.left, clip.top), _screen->pitch, clip.left, clip.top, clip.width(), clip.height());
}
}
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index 245319a931..981dd82e8b 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -1543,6 +1543,93 @@ Graphics::PixelFormat IMDDecoder::getPixelFormat() const {
return Graphics::PixelFormat::createFormatCLUT8();
}
+class DPCMStream : public Audio::AudioStream {
+public:
+ DPCMStream(Common::SeekableReadStream *stream, int rate, int channels, bool oldStereo) {
+ _stream = stream;
+ _rate = rate;
+ _channels = channels;
+ _oldStereo = oldStereo;
+ if (oldStereo) {
+ _buffer[0] = _buffer[1] = 0;
+ }
+ }
+
+ ~DPCMStream() {
+ delete _stream;
+ }
+
+ int readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return _channels == 2; }
+ int getRate() const { return _rate; }
+ bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); }
+
+private:
+ Common::SeekableReadStream *_stream;
+ int _channels;
+ int _rate;
+ int _buffer[2];
+ bool _oldStereo;
+};
+
+int DPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ static const uint16 tableDPCM[128] = {
+ 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
+ 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
+ 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
+ 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
+ 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
+ 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
+ 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
+ 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
+ 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
+ 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
+ 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
+ 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
+ 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+ };
+
+ assert((numSamples % _channels) == 0);
+
+ int samples = 0;
+
+ // Our starting position
+ if (!_oldStereo && _stream->pos() == 0) {
+ for (int i = 0; i < _channels; i++)
+ *buffer++ = _buffer[i] = _stream->readSint16LE();
+
+ samples += _channels;
+ }
+
+ while (!endOfData() && samples < numSamples) {
+ if (_channels == 2 && _stream->size() == 1) {
+ warning("Buffer underrun in DPCMStream");
+ break;
+ }
+
+ for (int i = 0; i < _channels; i++) {
+ byte data = _stream->readByte();
+
+ if (data & 0x80)
+ _buffer[i] -= tableDPCM[data & 0x7f];
+ else
+ _buffer[i] += tableDPCM[data];
+
+ // Emulating x86 16-bit signed register overflow
+ if (_buffer[i] > 32767) {
+ _buffer[i] -= 65536;
+ } else if (_buffer[i] < -32768) {
+ _buffer[i] += 65536;
+ }
+
+ *buffer++ = _buffer[i];
+ }
+
+ samples += _channels;
+ }
+
+ return samples;
+}
VMDDecoder::File::File() {
offset = 0;
@@ -1581,7 +1668,7 @@ VMDDecoder::VMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) :
_soundLastFilledFrame(0), _audioFormat(kAudioFormat8bitRaw),
_hasVideo(false), _videoCodec(0), _blitMode(0), _bytesPerPixel(0),
_firstFramePos(0), _videoBufferSize(0), _externalCodec(false), _codec(0),
- _subtitle(-1), _isPaletted(true), _autoStartSound(true) {
+ _subtitle(-1), _isPaletted(true), _autoStartSound(true), _oldStereoBuffer(nullptr) {
_videoBuffer [0] = 0;
_videoBuffer [1] = 0;
@@ -1625,7 +1712,7 @@ bool VMDDecoder::seek(int32 frame, int whence, bool restart) {
delete _audioStream;
_soundStage = kSoundLoaded;
- _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0);
+ createAudioStream();
}
_subtitle = -1;
@@ -1915,13 +2002,19 @@ bool VMDDecoder::assessAudioProperties() {
}
} else {
+ if (_soundStereo == 2) {
+ supportedFormat = false;
+ }
+
_soundBytesPerSample = 1;
- _audioFormat = kAudioFormat8bitRaw;
_soundHeaderSize = 0;
_soundDataSize = _soundSliceSize;
- if (_soundStereo > 0)
- supportedFormat = false;
+ if (_soundStereo == 1) {
+ _audioFormat = kAudioFormat16bitDPCM;
+ } else {
+ _audioFormat = kAudioFormat8bitRaw;
+ }
}
if (!supportedFormat) {
@@ -1930,14 +2023,12 @@ bool VMDDecoder::assessAudioProperties() {
return false;
}
- _frameRate = Common::Rational(_soundFreq, _soundSliceSize);
+ _frameRate = Common::Rational(_soundFreq, _soundSliceSize / (_soundStereo == 1 ? 2 : 1));
_hasSound = true;
_soundEnabled = true;
_soundStage = kSoundLoaded;
-
- _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0);
-
+ createAudioStream();
return true;
}
@@ -2074,6 +2165,7 @@ void VMDDecoder::close() {
_soundDataSize = 0;
_soundLastFilledFrame = 0;
_audioFormat = kAudioFormat8bitRaw;
+ _oldStereoBuffer = nullptr;
_hasVideo = false;
_videoCodec = 0;
@@ -2123,9 +2215,9 @@ void VMDDecoder::processFrame() {
bool startSound = false;
- for (uint16 i = 0; i < _partsPerFrame; i++) {
- uint32 pos = _stream->pos();
+ _stream->seek(_frames[_curFrame].offset, SEEK_SET);
+ for (uint16 i = 0; i < _partsPerFrame; i++) {
Part &part = _frames[_curFrame].parts[i];
if (part.type == kPartTypeAudio) {
@@ -2147,7 +2239,7 @@ void VMDDecoder::processFrame() {
if (_soundEnabled) {
uint32 mask = _stream->readUint32LE();
- filledSoundSlices(part.size - 4, mask);
+ filledSoundSlices(part.size - /* mask size */ 4, mask);
if (_soundStage == kSoundLoaded)
startSound = true;
@@ -2176,8 +2268,6 @@ void VMDDecoder::processFrame() {
_stream->skip(part.size);
}
- _stream->seek(pos + part.size);
-
} else if ((part.type == kPartTypeVideo) && !_hasVideo) {
warning("VMDDecoder::processFrame(): Header claims there's no video, but video found (%d)", part.size);
@@ -2470,6 +2560,14 @@ void VMDDecoder::blit24(const Graphics::Surface &srcSurf, Common::Rect &rect) {
}
void VMDDecoder::emptySoundSlice(uint32 size) {
+ if (_soundStereo == 1) {
+ // Technically an empty slice could be used at the very beginning of the
+ // stream, but anywhere else it would need to dynamically calculate the
+ // delta between the current sample and zero sample level and the steps
+ // to get a zero level
+ error("Old-style stereo cannot be filled with an empty slice");
+ }
+
byte *soundBuf = (byte *)malloc(size);
if (soundBuf) {
@@ -2488,6 +2586,17 @@ void VMDDecoder::filledSoundSlice(uint32 size) {
return;
}
+ if (_soundStereo == 1) {
+ void *buf = malloc(size);
+ assert(buf);
+ const uint32 numBytesRead = _stream->read(buf, size);
+ assert(numBytesRead == size);
+ const uint32 numBytesWritten = _oldStereoBuffer->write(buf, size);
+ assert(numBytesWritten == size);
+ free(buf);
+ return;
+ }
+
Common::SeekableReadStream *data = _stream->readStream(size);
Audio::AudioStream *sliceStream = 0;
@@ -2508,9 +2617,9 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) {
uint8 max;
uint8 n = evaluateMask(mask, fillInfo, max);
- int32 extraSize;
-
- extraSize = size - n * _soundDataSize;
+ // extraSize is needed by videos in some games (GK2) or audio data will be
+ // incomplete
+ int32 extraSize = size - n * _soundDataSize;
if (_soundSlicesCount > 32)
extraSize -= (_soundSlicesCount - 32) * _soundDataSize;
@@ -2518,6 +2627,11 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) {
if (n > 0)
extraSize /= n;
+ // extraSize cannot be negative or audio data will be incomplete in some
+ // games (old-style stereo videos in Lighthouse)
+ if (extraSize < 0)
+ extraSize = 0;
+
for (uint8 i = 0; i < max; i++)
if (fillInfo[i])
filledSoundSlice(_soundDataSize + extraSize);
@@ -2528,6 +2642,14 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) {
filledSoundSlice((_soundSlicesCount - 32) * _soundDataSize + _soundHeaderSize);
}
+void VMDDecoder::createAudioStream() {
+ _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0);
+ if (_soundStereo == 1) {
+ _oldStereoBuffer = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+ _audioStream->queueAudioStream(new DPCMStream(_oldStereoBuffer, _soundFreq, 2, true));
+ }
+}
+
uint8 VMDDecoder::evaluateMask(uint32 mask, bool *fillInfo, uint8 &max) {
max = MIN<int>(_soundSlicesCount - 1, 31);
@@ -2555,86 +2677,12 @@ Audio::AudioStream *VMDDecoder::create8bitRaw(Common::SeekableReadStream *stream
return Audio::makeRawStream(stream, _soundFreq, flags, DisposeAfterUse::YES);
}
-class DPCMStream : public Audio::AudioStream {
-public:
- DPCMStream(Common::SeekableReadStream *stream, int rate, int channels) {
- _stream = stream;
- _rate = rate;
- _channels = channels;
- }
-
- ~DPCMStream() {
- delete _stream;
- }
-
- int readBuffer(int16 *buffer, const int numSamples);
- bool isStereo() const { return _channels == 2; }
- int getRate() const { return _rate; }
- bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); }
-
-private:
- Common::SeekableReadStream *_stream;
- int _channels;
- int _rate;
- int _buffer[2];
-};
-
-int DPCMStream::readBuffer(int16 *buffer, const int numSamples) {
- static const uint16 tableDPCM[128] = {
- 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
- 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
- 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
- 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
- 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
- 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
- 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
- 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
- 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
- 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
- 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
- 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
- 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
- };
-
- assert((numSamples % _channels) == 0);
-
- int samples = 0;
-
- // Our starting position
- if (_stream->pos() == 0) {
- for (int i = 0; i < _channels; i++)
- *buffer++ = _buffer[i] = _stream->readSint16LE();
-
- samples += _channels;
- }
-
- while (!endOfData() && samples < numSamples) {
- for (int i = 0; i < _channels; i++) {
- byte data = _stream->readByte();
-
- if (data & 0x80)
- _buffer[i] -= tableDPCM[data & 0x7f];
- else
- _buffer[i] += tableDPCM[data];
-
- // Emulating x86 16-bit signed register overflow
- if (_buffer[i] > 32767) {
- _buffer[i] -= 65536;
- } else if (_buffer[i] < -32768) {
- _buffer[i] += 65536;
- }
-
- *buffer++ = _buffer[i];
- }
-
- samples += _channels;
- }
-
- return samples;
-}
-
Audio::AudioStream *VMDDecoder::create16bitDPCM(Common::SeekableReadStream *stream) {
- return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2);
+ // Old-style stereo audio blocks are not self-contained so cannot be played
+ // using this mechanism
+ assert(_soundStereo != 1);
+
+ return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2, false);
}
class VMD_ADPCMStream : public Audio::DVI_ADPCMStream {
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index b8faa99712..d1189c9394 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -43,6 +43,7 @@
namespace Common {
struct Rect;
+class MemoryReadWriteStream;
class SeekableReadStream;
}
namespace Audio {
@@ -503,6 +504,17 @@ private:
AudioFormat _audioFormat;
bool _autoStartSound;
+ /**
+ * Old stereo format packs a DPCM stream into audio packets without ensuring
+ * that each packet contains an even amount of samples. In order for the
+ * stream to play back correctly, all audio data needs to be pushed into a
+ * single data buffer and read from there.
+ *
+ * This buffer is owned by _audioStream and will be disposed when
+ * _audioStream is disposed.
+ */
+ Common::MemoryReadWriteStream *_oldStereoBuffer;
+
// Video properties
bool _hasVideo;
uint32 _videoCodec;
@@ -545,6 +557,7 @@ private:
void emptySoundSlice (uint32 size);
void filledSoundSlice (uint32 size);
void filledSoundSlices(uint32 size, uint32 mask);
+ void createAudioStream();
uint8 evaluateMask(uint32 mask, bool *fillInfo, uint8 &max);