aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorColin Snover2017-01-26 13:18:41 -0600
committerColin Snover2017-04-21 19:00:27 -0500
commit3303a881397beff1753fba237a5da735de03edb5 (patch)
tree8c3a682ee12840bfeaa9cb8e80967440d2aad26e /engines
parentc2e9fee93264468d89de66eccdc0cc712cdb76fe (diff)
downloadscummvm-rg350-3303a881397beff1753fba237a5da735de03edb5.tar.gz
scummvm-rg350-3303a881397beff1753fba237a5da735de03edb5.tar.bz2
scummvm-rg350-3303a881397beff1753fba237a5da735de03edb5.zip
SCI: Improve audio volume & settings sync code
This patch includes enhancements to the ScummVM integration with SCI engine, with particular focus on SCI32 support. 1. Fixes audio volumes syncing erroneously to ScummVM in games that modify the audio volume without user action (e.g. SCI1.1 talkies that reduce music volume during speech playback). Now, volumes will only be synchronised when the user interacts with the game's audio settings. This mechanism works by looking for a known volume control object in the stack, and only syncing when the control object is present. (Ports and planes were researched and found unreliable.) 2. Fixes audio syncing in SCI32 games that do not set game volumes through kDoSoundMasterVolume/kDoAudioVolume, like GK1, GK2, Phant1, and Torin. 3. Fixes speech/subtitles syncing in SCI32 games that do not use global 90, like LSL6hires. 4. Fixes in-game volume controls in SCI32 games reflecting outdated audio volumes when a change is made during the game from the ScummVM launcher. 5. Fixes SCI32 games that would restore volumes from save games or reset volumes on startup, which caused game volumes to be out-of-sync with ScummVM when started. 6. ScummVM integration code for audio sync has been abstracted into a new GuestAdditions class. This keeps the ScummVM- specific code all in one place, with only small hooks into the engine code. ScummVM integrated save/load code should probably also go here in the future. Fixes Trac#9700.
Diffstat (limited to 'engines')
-rw-r--r--engines/sci/detection_tables.h65
-rw-r--r--engines/sci/engine/features.cpp74
-rw-r--r--engines/sci/engine/features.h44
-rw-r--r--engines/sci/engine/guest_additions.cpp866
-rw-r--r--engines/sci/engine/guest_additions.h245
-rw-r--r--engines/sci/engine/ksound.cpp18
-rw-r--r--engines/sci/engine/object.h4
-rw-r--r--engines/sci/engine/script_patches.cpp203
-rw-r--r--engines/sci/engine/selector.cpp14
-rw-r--r--engines/sci/engine/selector.h14
-rw-r--r--engines/sci/engine/state.cpp7
-rw-r--r--engines/sci/engine/state.h1
-rw-r--r--engines/sci/engine/vm.cpp41
-rw-r--r--engines/sci/engine/vm.h20
-rw-r--r--engines/sci/module.mk1
-rw-r--r--engines/sci/sci.cpp160
-rw-r--r--engines/sci/sci.h24
-rw-r--r--engines/sci/sound/audio32.cpp14
-rw-r--r--engines/sci/sound/audio32.h7
-rw-r--r--engines/sci/sound/soundcmd.cpp131
-rw-r--r--engines/sci/sound/soundcmd.h51
21 files changed, 1677 insertions, 327 deletions
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 02ec9aa55b..c37dd4fffb 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -724,7 +724,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
#define GUIO_GK1_FLOPPY GUIO2(GUIO_NOSPEECH, \
GAMEOPTION_ORIGINAL_SAVELOAD)
-#define GUIO_GK1_CD GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD, \
+#define GUIO_GK1_CD GUIO3(GUIO_LINKSPEECHTOSFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD, \
GAMEOPTION_HIGH_RESOLUTION_GRAPHICS)
#define GUIO_GK1_MAC GUIO_GK1_FLOPPY
@@ -840,13 +841,16 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#undef GUIO_GK1_CD
#undef GUIO_GK1_MAC
-#define GUIO_GK2_DEMO GUIO6(GUIO_NOSUBTITLES, \
+#define GUIO_GK2_DEMO GUIO7(GUIO_NOSUBTITLES, \
GUIO_NOMUSIC, \
+ GUIO_NOSFX, \
GUIO_NOSPEECH, \
GUIO_NOMIDI, \
GUIO_NOLAUNCHLOAD, \
GUIO_NOASPECT)
-#define GUIO_GK2 GUIO5(GUIO_NOSUBTITLES, \
+#define GUIO_GK2 GUIO7(GUIO_NOSUBTITLES, \
+ GUIO_NOSFX, \
+ GUIO_NOSPEECHVOLUME, \
GUIO_NOMIDI, \
GUIO_NOASPECT, \
GAMEOPTION_ORIGINAL_SAVELOAD, \
@@ -1134,8 +1138,10 @@ static const struct ADGameDescription SciGameDescriptions[] = {
Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) },
#ifdef ENABLE_SCI32
-#define GUIO_HOYLE5 GUIO3(GUIO_NOMIDI, \
+#define GUIO_HOYLE5 GUIO5(GUIO_NOMIDI, \
GUIO_NOLAUNCHLOAD, \
+ GUIO_LINKMUSICTOSFX, \
+ GUIO_LINKSPEECHTOSFX, \
GUIO_NOASPECT)
// Hoyle 5 (Hoyle Classic Games) - Windows demo
@@ -1803,11 +1809,14 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
-#define GUIO_KQ7_DEMO GUIO4(GUIO_NOSUBTITLES, \
- GUIO_NOSPEECH, \
+#define GUIO_KQ7_DEMO GUIO5(GUIO_NOSUBTITLES, \
GUIO_NOLAUNCHLOAD, \
+ GUIO_LINKMUSICTOSFX, \
+ GUIO_LINKSPEECHTOSFX, \
GUIO_NOASPECT)
-#define GUIO_KQ7 GUIO2(GUIO_NOASPECT, \
+#define GUIO_KQ7 GUIO4(GUIO_NOASPECT, \
+ GUIO_LINKMUSICTOSFX, \
+ GUIO_LINKSPEECHTOSFX, \
GAMEOPTION_ORIGINAL_SAVELOAD)
// King's Quest 7 - English Windows (from the King's Quest Collection)
@@ -2591,7 +2600,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
-#define GUIO_LSL6HIRES GUIO2(GUIO_NOASPECT, \
+#define GUIO_LSL6HIRES GUIO3(GUIO_NOASPECT, \
+ GUIO_LINKSPEECHTOSFX, \
GAMEOPTION_ORIGINAL_SAVELOAD)
// Larry 6 - English/German DOS CD - HIRES
@@ -2874,7 +2884,9 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
-#define GUIO_MOTHERGOOSEHIRES GUIO2(GUIO_NOASPECT, \
+#define GUIO_MOTHERGOOSEHIRES GUIO4(GUIO_NOSUBTITLES, \
+ GUIO_NOASPECT, \
+ GUIO_LINKSPEECHTOSFX, \
GAMEOPTION_ORIGINAL_SAVELOAD)
// Mixed-Up Mother Goose Deluxe - English Windows/DOS CD (supplied by markcoolio in bug report #2723810)
@@ -2907,9 +2919,10 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
-#define GUIO_PHANTASMAGORIA_DEMO GUIO4(GUIO_NOSUBTITLES, \
+#define GUIO_PHANTASMAGORIA_DEMO GUIO5(GUIO_NOSUBTITLES, \
GUIO_NOASPECT, \
GUIO_NOLAUNCHLOAD, \
+ GUIO_LINKSPEECHTOSFX, \
GAMEOPTION_ENABLE_BLACK_LINED_VIDEO)
#define GUIO_PHANTASMAGORIA GUIO_PHANTASMAGORIA_DEMO
#define GUIO_PHANTASMAGORIA_MAC GUIO_PHANTASMAGORIA_DEMO
@@ -3348,7 +3361,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#define GUIO_PQ4_FLOPPY GUIO2(GUIO_NOSPEECH, \
GAMEOPTION_ORIGINAL_SAVELOAD)
-#define GUIO_PQ4_CD GUIO2(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \
+#define GUIO_PQ4_CD GUIO3(GUIO_LINKSPEECHTOSFX, \
+ GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \
GAMEOPTION_ORIGINAL_SAVELOAD)
// Police Quest 4 - English DOS CD (from the Police Quest Collection)
@@ -3394,11 +3408,16 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#undef GUIO_PQ4_FLOPPY
#undef GUIO_PQ4_CD
-#define GUIO_PQSWAT_DEMO GUIO4(GUIO_NOSUBTITLES, \
- GUIO_NOSPEECH, \
+#define GUIO_PQSWAT_DEMO GUIO6(GUIO_NOSUBTITLES, \
+ GUIO_NOMIDI, \
+ GUIO_LINKMUSICTOSFX, \
+ GUIO_LINKSPEECHTOSFX, \
GUIO_NOASPECT, \
GUIO_NOLAUNCHLOAD)
-#define GUIO_PQSWAT GUIO4(GUIO_NOMIDI, \
+#define GUIO_PQSWAT GUIO7(GUIO_NOSUBTITLES, \
+ GUIO_NOMIDI, \
+ GUIO_LINKMUSICTOSFX, \
+ GUIO_LINKSPEECHTOSFX, \
GUIO_NOASPECT, \
GAMEOPTION_ORIGINAL_SAVELOAD, \
GAMEOPTION_ENABLE_BLACK_LINED_VIDEO)
@@ -3797,7 +3816,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#define GUIO_QFG4_FLOPPY GUIO2(GUIO_NOSPEECH, \
GAMEOPTION_ORIGINAL_SAVELOAD)
-#define GUIO_QFG4_CD GUIO1(GAMEOPTION_ORIGINAL_SAVELOAD)
+#define GUIO_QFG4_CD GUIO2(GUIO_LINKSPEECHTOSFX, \
+ GAMEOPTION_ORIGINAL_SAVELOAD)
// Quest for Glory 4 1.1 Floppy - English DOS (supplied by markcool in bug report #2723852)
// SCI interpreter version 2.000.000 (a guess?)
@@ -3920,12 +3940,15 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#endif // ENABLE_SCI3_GAMES
-#define GUIO_SHIVERS_DEMO GUIO5(GUIO_NOSUBTITLES, \
- GUIO_NOSPEECH, \
+#define GUIO_SHIVERS_DEMO GUIO6(GUIO_NOSUBTITLES, \
GUIO_NOMIDI, \
GUIO_NOLAUNCHLOAD, \
+ GUIO_LINKSPEECHTOSFX, \
+ GUIO_LINKMUSICTOSFX, \
GUIO_NOASPECT)
-#define GUIO_SHIVERS GUIO4(GUIO_NOMIDI, \
+#define GUIO_SHIVERS GUIO6(GUIO_NOMIDI, \
+ GUIO_LINKSPEECHTOSFX, \
+ GUIO_LINKMUSICTOSFX, \
GUIO_NOASPECT, \
GAMEOPTION_ORIGINAL_SAVELOAD, \
GAMEOPTION_ENABLE_BLACK_LINED_VIDEO)
@@ -4528,9 +4551,11 @@ static const struct ADGameDescription SciGameDescriptions[] = {
#ifdef ENABLE_SCI32
-#define GUIO_SQ6_DEMO GUIO2(GUIO_NOLAUNCHLOAD, \
+#define GUIO_SQ6_DEMO GUIO3(GUIO_NOLAUNCHLOAD, \
+ GUIO_LINKSPEECHTOSFX, \
GUIO_NOASPECT)
-#define GUIO_SQ6 GUIO3(GUIO_NOASPECT, \
+#define GUIO_SQ6 GUIO4(GUIO_LINKSPEECHTOSFX, \
+ GUIO_NOASPECT, \
GAMEOPTION_ORIGINAL_SAVELOAD, \
GAMEOPTION_ENABLE_BLACK_LINED_VIDEO)
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp
index 40d380195d..1085fec85b 100644
--- a/engines/sci/engine/features.cpp
+++ b/engines/sci/engine/features.cpp
@@ -534,6 +534,80 @@ SciVersion GameFeatures::detectSci21KernelType() {
}
#endif
+bool GameFeatures::supportsSpeechWithSubtitles() const {
+ switch (g_sci->getGameId()) {
+ case GID_SQ4:
+ case GID_FREDDYPHARKAS:
+ case GID_ECOQUEST:
+ case GID_LSL6:
+ case GID_LAURABOW2:
+ case GID_KQ6:
+#ifdef ENABLE_SCI32
+ // TODO: Hoyle5, SCI3
+ case GID_GK1:
+ case GID_KQ7:
+ case GID_LSL6HIRES:
+ case GID_PQ4:
+ case GID_QFG4:
+ case GID_SQ6:
+ case GID_TORIN:
+#endif
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool GameFeatures::audioVolumeSyncUsesGlobals() const {
+ switch (g_sci->getGameId()) {
+ case GID_GK1:
+ case GID_GK2:
+ case GID_LSL6HIRES:
+ case GID_PHANTASMAGORIA:
+ case GID_TORIN:
+ // TODO: SCI3
+ return true;
+ default:
+ return false;
+ }
+}
+
+MessageTypeSyncStrategy GameFeatures::getMessageTypeSyncStrategy() const {
+ if (getSciVersion() < SCI_VERSION_1_1) {
+ return kMessageTypeSyncStrategyNone;
+ }
+
+ if (getSciVersion() == SCI_VERSION_1_1 && g_sci->isCD()) {
+ return kMessageTypeSyncStrategyDefault;
+ }
+
+#ifdef ENABLE_SCI32
+ switch (g_sci->getGameId()) {
+ // TODO: Hoyle5, SCI3
+ case GID_GK1:
+ case GID_KQ7:
+ case GID_MOTHERGOOSEHIRES:
+ case GID_PHANTASMAGORIA:
+ case GID_PQ4:
+ case GID_QFG4:
+ case GID_TORIN:
+ return kMessageTypeSyncStrategyDefault;
+
+ case GID_LSL6HIRES:
+ return kMessageTypeSyncStrategyLSL6Hires;
+
+ case GID_SHIVERS:
+ return kMessageTypeSyncStrategyShivers;
+
+ default:
+ break;
+ }
+#endif
+
+ return kMessageTypeSyncStrategyNone;
+}
+
bool GameFeatures::autoDetectMoveCountType() {
// Look up the script address
reg_t addr = getDetectionAddr("Motion", SELECTOR(doit));
diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h
index 8f84bbacad..044111a43e 100644
--- a/engines/sci/engine/features.h
+++ b/engines/sci/engine/features.h
@@ -40,6 +40,16 @@ enum PseudoMouseAbilityType {
kPseudoMouseAbilityTrue
};
+enum MessageTypeSyncStrategy {
+ kMessageTypeSyncStrategyNone,
+ kMessageTypeSyncStrategyDefault
+#ifdef ENABLE_SCI32
+ ,
+ kMessageTypeSyncStrategyLSL6Hires,
+ kMessageTypeSyncStrategyShivers
+#endif
+};
+
class GameFeatures {
public:
GameFeatures(SegManager *segMan, Kernel *kernel);
@@ -148,6 +158,40 @@ public:
#endif
/**
+ * If true, the current game supports simultaneous speech & subtitles.
+ */
+ bool supportsSpeechWithSubtitles() const;
+
+ /**
+ * If true, the game supports changing text speed.
+ */
+ bool supportsTextSpeed() const {
+ switch (g_sci->getGameId()) {
+#ifdef ENABLE_SCI32
+ case GID_GK1:
+ case GID_SQ6:
+ return true;
+#endif
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ /**
+ * If true, audio volume sync between the game and ScummVM is done by
+ * monitoring and setting game global variables.
+ */
+ bool audioVolumeSyncUsesGlobals() const;
+
+ /**
+ * The strategy that should be used when synchronising the message type
+ * (text/speech/text+speech) between the game and ScummVM.
+ */
+ MessageTypeSyncStrategy getMessageTypeSyncStrategy() const;
+
+ /**
* Applies to all versions before 0.000.502
* Old SCI versions used to interpret the third DrawPic() parameter inversely,
* with the opposite default value (obviously).
diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
new file mode 100644
index 0000000000..a2c64239a5
--- /dev/null
+++ b/engines/sci/engine/guest_additions.cpp
@@ -0,0 +1,866 @@
+/* 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 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 "audio/mixer.h"
+#include "common/config-manager.h"
+#include "common/gui_options.h"
+#include "sci/engine/features.h"
+#include "sci/engine/guest_additions.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/state.h"
+#include "sci/engine/vm.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/frameout.h"
+#endif
+#include "sci/sound/music.h"
+#include "sci/sci.h"
+
+namespace Sci {
+
+enum {
+ kSoundsMusicType = 0,
+ kSoundsSoundType = 1
+};
+
+enum {
+ kMessageTypeSubtitles = 1,
+ kMessageTypeSpeech = 2
+};
+
+enum {
+ kLSL6HiresUIVolumeMax = 13,
+ kLSL6HiresSubtitleFlag = 105
+};
+
+
+GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features) :
+ _state(state),
+ _features(features),
+ _segMan(state->_segMan),
+ _messageTypeSynced(false) {}
+
+#pragma mark -
+
+void GuestAdditions::syncSoundSettings() const {
+#ifdef ENABLE_SCI32
+ if (_features->audioVolumeSyncUsesGlobals())
+ syncAudioVolumeGlobalsFromScummVM();
+ else
+#endif
+ syncMasterVolumeFromScummVM();
+}
+
+void GuestAdditions::syncAudioOptionsFromScummVM() const {
+#ifdef ENABLE_SCI32
+ if (_features->supportsTextSpeed()) {
+ syncTextSpeedFromScummVM();
+ }
+#endif
+ syncMessageTypeFromScummVM();
+}
+
+void GuestAdditions::reset() {
+ _messageTypeSynced = false;
+}
+
+void GuestAdditions::invokeSelector(const reg_t objId, const Selector selector, const int argc, const StackPtr argv) const {
+ ::Sci::invokeSelector(_state, objId, selector, 0, _state->_executionStack.back().sp, argc, argv);
+}
+
+bool GuestAdditions::shouldSyncAudio() const {
+ const SciGameId gameId = g_sci->getGameId();
+ Common::List<ExecStack>::const_iterator it;
+ for (it = _state->_executionStack.begin(); it != _state->_executionStack.end(); ++it) {
+ const ExecStack &call = *it;
+ const Common::String objName = _segMan->getObjectName(call.sendp);
+
+ if (getSciVersion() < SCI_VERSION_2 && (objName == "TheMenuBar" ||
+ objName == "MenuBar")) {
+ // SCI16 with menu bar
+ return true;
+ } else if (objName == "volumeSlider") {
+ // SCI16 with icon bar, QFG4, Hoyle5
+ return true;
+ } else if (gameId == GID_MOTHERGOOSE256 && objName == "soundBut") {
+ return true;
+ } else if (gameId == GID_SLATER && objName == "volButton") {
+ return true;
+ } else if (gameId == GID_LSL6 && objName == "menuBar") {
+ return true;
+#ifdef ENABLE_SCI32
+ } else if ((gameId == GID_GK1 || gameId == GID_SQ6) && (objName == "musicBar" ||
+ objName == "soundBar")) {
+ return true;
+ } else if (gameId == GID_PQ4 && (objName == "increaseVolume" ||
+ objName == "decreaseVolume")) {
+ return true;
+ } else if (gameId == GID_KQ7 && (objName == "volumeUp" ||
+ objName == "volumeDown")) {
+ return true;
+ } else if (gameId == GID_LSL6HIRES && (objName == "hiResMenu" ||
+ objName == "volumeDial")) {
+ return true;
+ } else if (gameId == GID_MOTHERGOOSEHIRES && objName == "MgButtonBar") {
+ return true;
+ } else if (gameId == GID_PQSWAT && (objName == "volumeDownButn" ||
+ objName == "volumeUpButn")) {
+ return true;
+ } else if (gameId == GID_SHIVERS && objName == "spVolume") {
+ return true;
+ } else if (gameId == GID_GK2 && objName == "soundSlider") {
+ return true;
+ } else if (gameId == GID_PHANTASMAGORIA && (objName == "midiVolDown" ||
+ objName == "midiVolUp" ||
+ objName == "dacVolDown" ||
+ objName == "dacVolUp")) {
+ return true;
+ } else if (gameId == GID_TORIN && (objName == "oMusicScroll" ||
+ objName == "oSFXScroll" ||
+ objName == "oAudioScroll")) {
+ return true;
+#endif
+ }
+ }
+
+ return false;
+}
+
+#pragma mark -
+#pragma mark Hooks
+
+void GuestAdditions::sciEngineRunGameHook() {
+ _messageTypeSynced = true;
+}
+
+void GuestAdditions::writeVarHook(const int type, const int index, const reg_t value) {
+ if (type == VAR_GLOBAL) {
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ if (_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) {
+ syncAudioVolumeGlobalsToScummVM(index, value);
+ } else if (g_sci->getGameId() == GID_GK1) {
+ syncGK1StartupVolumeFromScummVM(index, value);
+ }
+
+ if (_features->supportsTextSpeed()) {
+ syncTextSpeedToScummVM(index, value);
+ }
+ }
+#endif
+ syncMessageTypeToScummVM(index, value);
+ }
+}
+
+bool GuestAdditions::kDoSoundMasterVolumeHook(const int volume) const {
+ if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) {
+ syncMasterVolumeToScummVM(volume);
+ return true;
+ }
+ return false;
+}
+
+#ifdef ENABLE_SCI32
+void GuestAdditions::sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp) {
+ if (_features->getMessageTypeSyncStrategy() == kMessageTypeSyncStrategyLSL6Hires) {
+ syncMessageTypeToScummVMUsingLSL6HiresStrategy(sendObj, selector, argp);
+ }
+}
+
+bool GuestAdditions::audio32SetVolumeHook(const int16 channelIndex, int16 volume) const {
+ if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) {
+ volume = volume * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume;
+ if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) {
+ ConfMan.setInt("music_volume", volume);
+ }
+ ConfMan.setInt("sfx_volume", volume);
+ ConfMan.setInt("speech_volume", volume);
+ g_engine->syncSoundSettings();
+ return true;
+ }
+
+ return false;
+}
+
+void GuestAdditions::kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const {
+ if (g_sci->getGameId() == GID_GK1 && shouldSyncAudio()) {
+ syncGK1AudioVolumeToScummVM(soundObj, volume);
+ }
+}
+#endif
+
+#pragma mark -
+#pragma mark Message type sync
+
+void GuestAdditions::syncMessageTypeFromScummVM() const {
+ switch (_features->getMessageTypeSyncStrategy()) {
+ case kMessageTypeSyncStrategyDefault:
+ syncMessageTypeFromScummVMUsingDefaultStrategy();
+ break;
+
+#ifdef ENABLE_SCI32
+ case kMessageTypeSyncStrategyShivers:
+ syncMessageTypeFromScummVMUsingShiversStrategy();
+ break;
+
+ case kMessageTypeSyncStrategyLSL6Hires:
+ syncMessageTypeFromScummVMUsingLSL6HiresStrategy();
+ break;
+#endif
+ case kMessageTypeSyncStrategyNone:
+ break;
+ }
+}
+
+void GuestAdditions::syncMessageTypeFromScummVMUsingDefaultStrategy() const {
+ uint8 value = 0;
+ if (ConfMan.getBool("subtitles")) {
+ value |= kMessageTypeSubtitles;
+ }
+ if (!ConfMan.getBool(("speech_mute"))) {
+ value |= kMessageTypeSpeech;
+ }
+
+ if (value == kMessageTypeSubtitles + kMessageTypeSpeech && !_features->supportsSpeechWithSubtitles()) {
+ value &= ~kMessageTypeSubtitles;
+ }
+
+ if (value) {
+ _state->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, value);
+ }
+
+ if (g_sci->getGameId() == GID_GK1) {
+ if (value == kMessageTypeSubtitles) {
+ _state->variables[VAR_GLOBAL][kGlobalVarGK1NarratorMode] = NULL_REG;
+ } else if (value == kMessageTypeSpeech) {
+ _state->variables[VAR_GLOBAL][kGlobalVarGK1NarratorMode] = TRUE_REG;
+ }
+ }
+}
+
+#ifdef ENABLE_SCI32
+void GuestAdditions::syncMessageTypeFromScummVMUsingShiversStrategy() const {
+ if (ConfMan.getBool("subtitles")) {
+ _state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] |= 256;
+ } else {
+ _state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] &= ~256;
+ }
+}
+
+void GuestAdditions::syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const {
+ // LSL6hires synchronisation happens in send_selector, except when
+ // restoring a game, where it happens here
+ if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags].isNull()) {
+ return;
+ }
+
+ reg_t params[] = { make_reg(0, kLSL6HiresSubtitleFlag) };
+ Selector selector;
+ reg_t restore;
+
+ if (ConfMan.getBool("subtitles")) {
+ restore = TRUE_REG;
+ selector = SELECTOR(clear);
+ } else {
+ restore = NULL_REG;
+ selector = SELECTOR(set);
+ }
+
+ // Attempting to show or hide the ScrollWindow used for subtitles
+ // directly (by invoking `show` or `hide`) causes the game to crash with
+ // an error about passing an invalid ScrollWindow ID. Fortunately, the
+ // game scripts store a flag that restores the window when a game is
+ // restored
+ _state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresRestoreTextWindow] = restore;
+ invokeSelector(_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags], selector, 1, params);
+}
+#endif
+
+void GuestAdditions::syncMessageTypeToScummVM(const int index, const reg_t value) {
+ switch (_features->getMessageTypeSyncStrategy()) {
+ case kMessageTypeSyncStrategyDefault:
+ syncMessageTypeToScummVMUsingDefaultStrategy(index, value);
+ break;
+
+#ifdef ENABLE_SCI32
+ case kMessageTypeSyncStrategyShivers:
+ syncMessageTypeToScummVMUsingShiversStrategy(index, value);
+ break;
+
+ case kMessageTypeSyncStrategyLSL6Hires:
+ // LSL6hires synchronisation happens via send_selector
+#endif
+ case kMessageTypeSyncStrategyNone:
+ break;
+ }
+}
+
+void GuestAdditions::syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value) {
+ if (index == kGlobalVarMessageType) {
+ // ScummVM audio options haven't been applied yet. Use this set call
+ // as a trigger to apply defaults from ScummVM, ignoring the default
+ // value that was just received from the game scripts
+ if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
+ _messageTypeSynced = true;
+ syncAudioOptionsFromScummVM();
+ return;
+ }
+
+ ConfMan.setBool("subtitles", value.toSint16() & kMessageTypeSubtitles);
+ ConfMan.setBool("speech_mute", !(value.toSint16() & kMessageTypeSpeech));
+ }
+}
+
+#ifdef ENABLE_SCI32
+void GuestAdditions::syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value) {
+ if (index == kGlobalVarShiversFlags) {
+ // ScummVM audio options haven't been applied yet, so apply them
+ // and ignore the default value that was just received from the
+ // game scripts
+ if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
+ _messageTypeSynced = true;
+ syncAudioOptionsFromScummVM();
+ return;
+ }
+
+ ConfMan.setBool("subtitles", value.toUint16() & 256);
+ }
+}
+
+void GuestAdditions::syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp) {
+ if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags] == sendObj &&
+ (selector == SELECTOR(clear) || selector == SELECTOR(set))) {
+
+ if (argp[1].toUint16() == kLSL6HiresSubtitleFlag) {
+ if (_messageTypeSynced) {
+ ConfMan.setBool("subtitles", selector == SELECTOR(clear));
+ } else if (ConfMan.getBool("subtitles")) {
+ selector = SELECTOR(clear);
+ argp[-1].setOffset(selector);
+ _messageTypeSynced = true;
+ } else {
+ selector = SELECTOR(set);
+ argp[-1].setOffset(selector);
+ _messageTypeSynced = true;
+ }
+ }
+ }
+}
+#endif
+
+#pragma mark -
+#pragma mark Master volume sync
+
+void GuestAdditions::syncMasterVolumeFromScummVM() const {
+ const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
+
+ // When the volume changes from the ScummVM launcher, ScummVM automatically
+ // adjusts the software mixer in Engine::syncSoundSettings, but MIDI may not
+ // run through the ScummVM mixer so its master volume must be adjusted
+ // explicitly
+ if (g_sci->_soundCmd) {
+ g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : musicVolume);
+ }
+
+#ifdef ENABLE_SCI32
+ const int16 sfxVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
+
+ // Volume was changed from ScummVM during the game, so resync the
+ // in-game UI
+ syncInGameUI(musicVolume, sfxVolume);
+#endif
+}
+
+void GuestAdditions::syncMasterVolumeToScummVM(const int16 masterVolume) const {
+ const int scummVMVolume = masterVolume * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
+ ConfMan.setInt("music_volume", scummVMVolume);
+
+ if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) {
+ ConfMan.setInt("sfx_volume", scummVMVolume);
+ if (Common::checkGameGUIOption(GUIO_LINKSPEECHTOSFX, ConfMan.get("guioptions"))) {
+ ConfMan.setInt("speech_volume", scummVMVolume);
+ }
+ }
+
+ // In SCI32, digital audio volume is controlled separately by
+ // kDoAudioVolume
+ // TODO: In SCI16, the volume slider only changed the music volume.
+ // Is this non-standard behavior better, or just wrong?
+ if (getSciVersion() < SCI_VERSION_2) {
+ ConfMan.setInt("sfx_volume", scummVMVolume);
+ ConfMan.setInt("speech_volume", scummVMVolume);
+ }
+ g_engine->syncSoundSettings();
+}
+
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Globals volume sync
+
+void GuestAdditions::syncAudioVolumeGlobalsFromScummVM() const {
+ // On muting: Setting the music volume to zero when mute is enabled is done
+ // only for the games that use MIDI for music playback, since MIDI playback
+ // does not always run through the ScummVM mixer. Games that use digital
+ // audio for music do not need any extra code since that always runs
+ // straight through the audio mixer, which gets muted directly
+ switch (g_sci->getGameId()) {
+ case GID_GK1: {
+ const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
+ const int16 dacVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
+ syncGK1VolumeFromScummVM(musicVolume, dacVolume);
+ syncGK1UI();
+ break;
+ }
+
+ case GID_GK2: {
+ const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
+ syncGK2VolumeFromScummVM(musicVolume);
+ syncGK2UI();
+ break;
+ }
+
+ case GID_LSL6HIRES: {
+ const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * kLSL6HiresUIVolumeMax / Audio::Mixer::kMaxMixerVolume;
+ syncLSL6HiresVolumeFromScummVM(musicVolume);
+ syncLSL6HiresUI(musicVolume);
+ break;
+ }
+
+ case GID_PHANTASMAGORIA: {
+ reg_t &musicGlobal = _state->variables[VAR_GLOBAL][kGlobalVarPhant1MusicVolume];
+ reg_t &dacGlobal = _state->variables[VAR_GLOBAL][kGlobalVarPhant1DACVolume];
+
+ const int16 oldMusicVolume = musicGlobal.toSint16();
+ const int16 oldDacVolume = dacGlobal.toSint16();
+
+ const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
+ const int16 dacVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
+
+ g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : musicVolume);
+
+ // Phant1 has a fragile volume UI. Global volumes need to be set during
+ // UI updates to move the volume bars to the correct position
+ syncPhant1UI(oldMusicVolume, musicVolume, musicGlobal, oldDacVolume, dacVolume, dacGlobal);
+ break;
+ }
+
+ case GID_TORIN: {
+ const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume;
+ const int16 sfxVolume = (ConfMan.getInt("sfx_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume;
+ const int16 speechVolume = (ConfMan.getInt("speech_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume;
+ syncTorinVolumeFromScummVM(musicVolume, sfxVolume, speechVolume);
+ syncTorinUI(musicVolume, sfxVolume, speechVolume);
+ break;
+ }
+
+ default:
+ error("Trying to sync audio volume globals in a game with no implementation");
+ }
+}
+
+void GuestAdditions::syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const {
+ if (index == kGlobalVarGK1Music1 || index == kGlobalVarGK1Music2 ||
+ index == kGlobalVarGK1DAC1 || index == kGlobalVarGK1DAC2 ||
+ index == kGlobalVarGK1DAC3) {
+
+ int16 volume;
+ Selector selector;
+
+ switch (readSelectorValue(_segMan, value, SELECTOR(type))) {
+ case kSoundsMusicType: {
+ volume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
+ selector = SELECTOR(musicVolume);
+ break;
+ }
+
+ case kSoundsSoundType: {
+ volume = (ConfMan.getInt("sound_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
+ selector = SELECTOR(soundVolume);
+ break;
+ }
+
+ default:
+ error("Unknown sound type");
+ }
+
+ writeSelectorValue(_segMan, value, selector, volume);
+ writeSelectorValue(_segMan, value, selector, volume);
+ }
+}
+
+void GuestAdditions::syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const {
+ const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds];
+ if (!soundsId.isNull()) {
+ List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements)));
+ reg_t soundId = sounds->first;
+ while (!soundId.isNull()) {
+ Node *sound = _segMan->lookupNode(soundId);
+ const int16 type = readSelectorValue(_segMan, sound->value, SELECTOR(type));
+ int16 volume;
+
+ if (type == kSoundsMusicType) {
+ volume = ConfMan.getBool("mute") ? 0 : musicVolume;
+ writeSelectorValue(_segMan, sound->value, SELECTOR(musicVolume), musicVolume);
+ } else if (type == kSoundsSoundType) {
+ volume = dacVolume;
+ writeSelectorValue(_segMan, sound->value, SELECTOR(soundVolume), dacVolume);
+ } else {
+ error("Unknown sound type %d", type);
+ }
+
+ // `setVolume` will set the `vol` property on the sound object;
+ // if it did not do this, an invocation of the `setVol` selector
+ // would need to be here (though doing so would result in
+ // recursion, so don't)
+ g_sci->_soundCmd->setVolume(sound->value, volume);
+ soundId = sound->succ;
+ }
+ }
+}
+
+void GuestAdditions::syncGK2VolumeFromScummVM(const int16 musicVolume) const {
+ _state->variables[VAR_GLOBAL][kGlobalVarGK2MusicVolume] = make_reg(0, musicVolume);
+
+ // Calling `setVol` on all sounds is necessary to propagate the volume
+ // change to existing sounds, and matches how game scripts propagate
+ // volume changes when the in-game music slider is moved
+ const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds];
+ if (!soundsId.isNull()) {
+ List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements)));
+ reg_t soundId = sounds->first;
+ while (!soundId.isNull()) {
+ Node *sound = _segMan->lookupNode(soundId);
+ reg_t params[] = { make_reg(0, musicVolume) };
+ invokeSelector(sound->value, SELECTOR(setVol), 1, params);
+ soundId = sound->succ;
+ }
+ }
+}
+
+void GuestAdditions::syncLSL6HiresVolumeFromScummVM(const int16 musicVolume) const {
+ _state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresMusicVolume] = make_reg(0, musicVolume);
+ g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : (musicVolume * MUSIC_MASTERVOLUME_MAX / kLSL6HiresUIVolumeMax));
+}
+
+void GuestAdditions::syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const {
+ _state->variables[VAR_GLOBAL][kGlobalVarTorinMusicVolume] = make_reg(0, musicVolume);
+ _state->variables[VAR_GLOBAL][kGlobalVarTorinSFXVolume] = make_reg(0, sfxVolume);
+ _state->variables[VAR_GLOBAL][kGlobalVarTorinSpeechVolume] = make_reg(0, speechVolume);
+
+ // Calling `reSyncVol` on all sounds is necessary to propagate the
+ // volume change to existing sounds, and matches how game scripts
+ // propagate volume changes when the in-game volume sliders are moved
+ const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds];
+ if (!soundsId.isNull()) {
+ const Selector selector = SELECTOR(reSyncVol);
+ List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements)));
+ reg_t soundId = sounds->first;
+ while (!soundId.isNull()) {
+ Node *sound = _segMan->lookupNode(soundId);
+ const reg_t &soundObj = sound->value;
+
+ if (_segMan->isHeapObject(soundObj) && lookupSelector(_segMan, soundObj, selector, nullptr, nullptr) != kSelectorNone) {
+ invokeSelector(sound->value, SELECTOR(reSyncVol));
+ }
+ soundId = sound->succ;
+ }
+ }
+}
+
+void GuestAdditions::syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const {
+ switch (g_sci->getGameId()) {
+ case GID_GK2:
+ if (index == kGlobalVarGK2MusicVolume) {
+ const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume;
+ ConfMan.setInt("music_volume", musicVolume);
+ }
+ break;
+
+ case GID_LSL6HIRES:
+ if (index == kGlobalVarLSL6HiresMusicVolume) {
+ const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / kLSL6HiresUIVolumeMax;
+ ConfMan.setInt("music_volume", musicVolume);
+ }
+ break;
+
+ case GID_PHANTASMAGORIA:
+ if (index == kGlobalVarPhant1MusicVolume) {
+ const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
+ ConfMan.setInt("music_volume", musicVolume);
+ } else if (index == kGlobalVarPhant1DACVolume) {
+ const int16 dacVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume;
+ ConfMan.setInt("sfx_volume", dacVolume);
+ ConfMan.setInt("speech_volume", dacVolume);
+ }
+ break;
+
+ case GID_TORIN:
+ if (index == kGlobalVarTorinMusicVolume ||
+ index == kGlobalVarTorinSFXVolume ||
+ index == kGlobalVarTorinSpeechVolume) {
+
+ const int16 volume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / 100;
+
+ switch (index) {
+ case kGlobalVarTorinMusicVolume:
+ ConfMan.setInt("music_volume", volume);
+ break;
+ case kGlobalVarTorinSFXVolume:
+ ConfMan.setInt("sfx_volume", volume);
+ break;
+ case kGlobalVarTorinSpeechVolume:
+ ConfMan.setInt("speech_volume", volume);
+ break;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void GuestAdditions::syncGK1AudioVolumeToScummVM(const reg_t soundObj, int16 volume) const {
+ const Common::String objName = _segMan->getObjectName(soundObj);
+ volume = volume * Audio::Mixer::kMaxMixerVolume / MUSIC_VOLUME_MAX;
+
+ // Using highest-numbered sound objects to sync only after all slots
+ // have been set by the volume slider
+ if (objName == "gkMusic2") {
+ ConfMan.setInt("music_volume", volume);
+ g_engine->syncSoundSettings();
+ } else if (objName == "gkSound3") {
+ ConfMan.setInt("sfx_volume", volume);
+ ConfMan.setInt("speech_volume", volume);
+ g_engine->syncSoundSettings();
+ }
+}
+
+#pragma mark -
+#pragma mark Audio UI sync
+
+void GuestAdditions::syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const {
+ if (_state->abortScriptProcessing != kAbortNone) {
+ // Attempting to update a UI that is in the process of being destroyed
+ // will result in a crash
+ return;
+ }
+
+ switch (g_sci->getGameId()) {
+ case GID_PQ4:
+ syncPQ4UI(musicVolume);
+ break;
+
+ case GID_PQSWAT:
+ syncPQSWATUI();
+ break;
+
+ case GID_QFG4:
+ syncQFG4UI(musicVolume);
+ break;
+
+ case GID_SHIVERS:
+ syncShivers1UI(sfxVolume);
+ break;
+
+ case GID_SQ6:
+ syncSQ6UI();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void GuestAdditions::syncGK1UI() const {
+ const reg_t bars[] = { _segMan->findObjectByName("musicBar"),
+ _segMan->findObjectByName("soundBar") };
+
+ for (int i = 0; i < ARRAYSIZE(bars); ++i) {
+ const reg_t barId = bars[i];
+ if (!barId.isNull()) {
+ // Resetting the position to 0 causes the bar to refresh its
+ // position when it next draws
+ writeSelectorValue(_segMan, barId, SELECTOR(position), 0);
+
+ // The `signal` property indicates bar visibility (for some
+ // reason, the normal `-info-` flag is not used)
+ if (readSelectorValue(_segMan, barId, SELECTOR(signal)) & 0x20) {
+ // `show` pulls a new value from the underlying sound object
+ // and refreshes the bar rendering
+ invokeSelector(barId, SELECTOR(show));
+ }
+ }
+ }
+}
+
+void GuestAdditions::syncGK2UI() const {
+ const reg_t sliderId = _segMan->findObjectByName("soundSlider");
+ if (!sliderId.isNull() && _segMan->getObject(sliderId)->isInserted()) {
+ const reg_t oldAcc = _state->r_acc;
+ invokeSelector(sliderId, SELECTOR(initialOff));
+ writeSelector(_segMan, sliderId, SELECTOR(x), _state->r_acc);
+ _state->r_acc = oldAcc;
+ }
+}
+
+void GuestAdditions::syncLSL6HiresUI(const int16 musicVolume) const {
+ const reg_t musicDialId = _segMan->findObjectByName("volumeDial");
+ if (!musicDialId.isNull()) {
+ writeSelectorValue(_segMan, musicDialId, SELECTOR(curPos), musicVolume);
+ writeSelectorValue(_segMan, musicDialId, SELECTOR(cel), musicVolume);
+ reg_t params[] = { make_reg(0, musicVolume) };
+ invokeSelector(musicDialId, SELECTOR(update), 1, params);
+ if (_segMan->getObject(musicDialId)->isInserted()) {
+ g_sci->_gfxFrameout->kernelUpdateScreenItem(musicDialId);
+ }
+ }
+}
+
+void GuestAdditions::syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const {
+ const reg_t buttonId = _segMan->findObjectByName("dacVolUp");
+ if (buttonId.isNull() || !_segMan->getObject(buttonId)->isInserted()) {
+ // No inserted dacVolUp button means the control panel with the
+ // volume controls is not visible and we can just update the values
+ // and leave
+ musicGlobal.setOffset(musicVolume);
+ dacGlobal.setOffset(dacVolume);
+ return;
+ }
+
+ reg_t thermo = _segMan->findObjectByName("midiVolThermo");
+ if (!thermo.isNull()) {
+ int count = ABS(musicVolume - oldMusicVolume);
+ const int stepSize = (musicVolume > oldMusicVolume ? 1 : -1);
+ while (count--) {
+ musicGlobal.incOffset(stepSize);
+ invokeSelector(thermo, SELECTOR(doit));
+ }
+ }
+
+ thermo = _segMan->findObjectByName("dacVolThermo");
+ if (!thermo.isNull()) {
+ int count = ABS(dacVolume - oldDacVolume) / 8;
+ const int stepSize = (dacVolume > oldDacVolume ? 8 : -8);
+ while (count--) {
+ dacGlobal.incOffset(stepSize);
+ invokeSelector(thermo, SELECTOR(doit));
+ }
+ }
+}
+
+void GuestAdditions::syncPQ4UI(const int16 musicVolume) const {
+ const SegmentId segment = _segMan->getScriptSegment(9, SCRIPT_GET_DONT_LOAD);
+ if (segment != 0 && _segMan->getScript(segment)->getLocalsCount() > 2) {
+ const reg_t barId = _segMan->getScript(segment)->getLocalsBegin()[2];
+ if (!barId.isNull()) {
+ reg_t params[] = { make_reg(0, musicVolume) };
+ invokeSelector(barId, SELECTOR(setSize), 1, params);
+ }
+ }
+}
+
+void GuestAdditions::syncPQSWATUI() const {
+ const reg_t barId = _segMan->findObjectByName("volumeLed");
+ if (!barId.isNull() && _segMan->getObject(barId)->isInserted()) {
+ invokeSelector(barId, SELECTOR(displayValue));
+ }
+}
+
+void GuestAdditions::syncQFG4UI(const int16 musicVolume) const {
+ const reg_t sliderId = _segMan->findObjectByName("volumeSlider");
+ if (!sliderId.isNull()) {
+ const int16 yPosition = 84 - musicVolume * 34 / 10;
+ writeSelectorValue(_segMan, sliderId, SELECTOR(y), yPosition);
+
+ // There does not seem to be any good way to learn whether the
+ // volume slider is visible (and thus eligible for
+ // kUpdateScreenItem)
+ const reg_t planeId = readSelector(_segMan, sliderId, SELECTOR(plane));
+ if (g_sci->_gfxFrameout->getPlanes().findByObject(planeId) != nullptr) {
+ g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId);
+ }
+ }
+}
+
+void GuestAdditions::syncShivers1UI(const int16 dacVolume) const {
+ const reg_t sliderId = _segMan->findObjectByName("spVolume");
+ if (!sliderId.isNull()) {
+ const int16 xPosition = dacVolume * 78 / Audio32::kMaxVolume + 32;
+ writeSelectorValue(_segMan, sliderId, SELECTOR(x), xPosition);
+ if (_segMan->getObject(sliderId)->isInserted()) {
+ g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId);
+ }
+ }
+}
+
+void GuestAdditions::syncSQ6UI() const {
+ const reg_t bars[] = { _segMan->findObjectByName("musicBar"),
+ _segMan->findObjectByName("soundBar") };
+ for (int i = 0; i < ARRAYSIZE(bars); ++i) {
+ const reg_t barId = bars[i];
+ if (!barId.isNull()) {
+ invokeSelector(barId, SELECTOR(show));
+ }
+ }
+}
+
+void GuestAdditions::syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const {
+ const reg_t sliders[] = { _segMan->findObjectByName("oMusicScroll"),
+ _segMan->findObjectByName("oSFXScroll"),
+ _segMan->findObjectByName("oAudioScroll") };
+ const int16 values[] = { musicVolume, sfxVolume, speechVolume };
+ for (int i = 0; i < ARRAYSIZE(sliders); ++i) {
+ const reg_t sliderId = sliders[i];
+ if (!sliderId.isNull()) {
+ reg_t params[] = { make_reg(0, values[i]) };
+ invokeSelector(sliderId, SELECTOR(setPos), 1, params);
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Talk speed sync
+
+void GuestAdditions::syncTextSpeedFromScummVM() const {
+ const int16 textSpeed = 8 - (ConfMan.getInt("talkspeed") + 1) * 8 / 255;
+
+ _state->variables[VAR_GLOBAL][kGlobalVarTextSpeed] = make_reg(0, textSpeed);
+
+ if (g_sci->getGameId() == GID_GK1) {
+ const reg_t textBarId = _segMan->findObjectByName("textBar");
+ if (!textBarId.isNull()) {
+ // Resetting the bar position to 0 causes the game to retrieve the
+ // new text speed value and re-render
+ writeSelectorValue(_segMan, textBarId, SELECTOR(position), 0);
+ }
+ }
+}
+
+void GuestAdditions::syncTextSpeedToScummVM(const int index, const reg_t value) const {
+ if (index == kGlobalVarTextSpeed) {
+ ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8);
+ }
+}
+
+#endif
+
+} // End of namespace Sci
diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h
new file mode 100644
index 0000000000..a6875d43ad
--- /dev/null
+++ b/engines/sci/engine/guest_additions.h
@@ -0,0 +1,245 @@
+/* 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 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 SCI_ENGINE_GUEST_ADDITIONS_H
+#define SCI_ENGINE_GUEST_ADDITIONS_H
+
+#include "sci/engine/vm_types.h"
+
+namespace Sci {
+
+struct EngineState;
+class GameFeatures;
+class SegManager;
+
+/**
+ * The GuestAdditions class hooks into the SCI virtual machine to provide
+ * enhanced interactions between the ScummVM GUI and the game engine. Currently,
+ * this enhanced functionality encompasses synchronisation of audio volumes and
+ * other audio-related settings.
+ *
+ * Some parts of the audio sync are applied as script patches using the normal
+ * ScriptPatcher mechanism. These patches are designed to prevent the game from
+ * resetting to a default volume when starting up or loading a save.
+ */
+class GuestAdditions {
+public:
+ GuestAdditions(EngineState *state, GameFeatures *features);
+
+ /**
+ * Synchronises audio volume settings from ScummVM to the game. Called
+ * whenever the ScummVM launcher is dismissed.
+ */
+ void syncSoundSettings() const;
+
+ /**
+ * Synchronises all audio settings from ScummVM to the game. Called when the
+ * game is first started, and when save games are loaded.
+ */
+ void syncAudioOptionsFromScummVM() const;
+
+ /**
+ * Clears audio settings synchronisation state.
+ */
+ void reset();
+
+private:
+ EngineState *_state;
+ GameFeatures *_features;
+ SegManager *_segMan;
+
+ /**
+ * Convenience function for invoking selectors that reduces boilerplate code
+ * required by Sci::invokeSelector.
+ */
+ void invokeSelector(const reg_t objId, const Selector selector, const int argc = 0, const StackPtr argv = nullptr) const;
+
+ /**
+ * Determines whether the current stack contains calls from audio controls
+ * that indicate a user-initiated change of audio settings.
+ */
+ bool shouldSyncAudio() const;
+
+#pragma mark -
+#pragma mark Hooks
+
+public:
+ /**
+ * Guest additions hook for SciEngine::runGame.
+ */
+ void sciEngineRunGameHook();
+
+ /**
+ * Guest additions hook for write_var.
+ */
+ void writeVarHook(const int type, const int index, const reg_t value);
+
+ /**
+ * Guest additions hook for kDoSoundMasterVolume.
+ *
+ * @returns true if the default action should be prevented
+ */
+ bool kDoSoundMasterVolumeHook(const int volume) const;
+
+#ifdef ENABLE_SCI32
+ /**
+ * Guest additions hook for send_selector.
+ */
+ void sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp);
+
+ /**
+ * Guest additions hook for Audio32::setVolume.
+ *
+ * @returns true if the default action should be prevented
+ */
+ bool audio32SetVolumeHook(const int16 channelIndex, const int16 volume) const;
+
+ /**
+ * Guest additions hook for kDoSoundSetVolume.
+ */
+ void kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const;
+#endif
+
+#pragma mark -
+#pragma mark Message type sync
+
+private:
+ /**
+ * true if the message type (text/speech/text+speech) has been synchronised
+ * from ScummVM to the game.
+ */
+ bool _messageTypeSynced;
+
+ /**
+ * Synchronises the message type (speech/text/speech+text) from a ScummVM to
+ * a game.
+ */
+ void syncMessageTypeFromScummVM() const;
+
+ void syncMessageTypeFromScummVMUsingDefaultStrategy() const;
+#ifdef ENABLE_SCI32
+ void syncMessageTypeFromScummVMUsingShiversStrategy() const;
+ void syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const;
+#endif
+
+ /**
+ * Synchronises the message type (speech/text/speech+text) from a game to
+ * ScummVM.
+ */
+ void syncMessageTypeToScummVM(const int index, const reg_t value);
+
+ void syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value);
+#ifdef ENABLE_SCI32
+ void syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value);
+ void syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp);
+#endif
+
+#pragma mark -
+#pragma mark Master volume sync
+
+private:
+ /**
+ * Synchronises audio volume settings from ScummVM to the game, for games
+ * that do not store volume themselves and just call to the kernel.
+ */
+ void syncMasterVolumeFromScummVM() const;
+
+ /**
+ * Synchronises audio volume settings from the game to ScummVM, for games
+ * that do not store volume themselves and just call to the kernel.
+ */
+ void syncMasterVolumeToScummVM(const int16 masterVolume) const;
+
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Globals volume sync
+
+private:
+ /**
+ * Synchronises audio volume settings from ScummVM to the game, for games
+ * that store volumes in globals.
+ */
+ void syncAudioVolumeGlobalsFromScummVM() const;
+
+ /**
+ * Synchronises audio volume settings from ScummVM to GK1 at game startup
+ * time.
+ */
+ void syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const;
+
+ void syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const;
+ void syncGK2VolumeFromScummVM(const int16 musicVolume) const;
+ void syncLSL6HiresVolumeFromScummVM(const int16 musicVolume) const;
+ void syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const;
+
+ /**
+ * Synchronises audio volume settings from a game to ScummVM, for games
+ * that store volumes in globals.
+ */
+ void syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const;
+
+ /**
+ * Synchronises audio volume settings from GK1 to ScummVM.
+ */
+ void syncGK1AudioVolumeToScummVM(const reg_t soundObj, const int16 volume) const;
+
+#pragma mark -
+#pragma mark Audio UI sync
+
+private:
+ /**
+ * Synchronises the in-game control panel UI in response to a change of
+ * volume from the ScummVM GUI. The values of the volume parameters passed
+ * to this function are game-specific.
+ */
+ void syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const;
+
+ void syncGK1UI() const;
+ void syncGK2UI() const;
+ void syncLSL6HiresUI(const int16 musicVolume) const;
+ void syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const;
+ void syncPQ4UI(const int16 musicVolume) const;
+ void syncPQSWATUI() const;
+ void syncQFG4UI(const int16 musicVolume) const;
+ void syncShivers1UI(const int16 dacVolume) const;
+ void syncSQ6UI() const;
+ void syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const;
+
+#pragma mark -
+#pragma mark Talk speed sync
+
+private:
+ /**
+ * Synchronises text speed settings from ScummVM to a game.
+ */
+ void syncTextSpeedFromScummVM() const;
+
+ /**
+ * Synchronises text speed settings from a game to ScummVM.
+ */
+ void syncTextSpeedToScummVM(const int index, const reg_t value) const;
+#endif
+};
+
+} // End of namespace Sci
+
+#endif // SCI_ENGINE_GUEST_ADDITIONS_H
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index 4a5e4f3df6..e7cad21d67 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -46,7 +46,7 @@ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) {
error("not supposed to call this");
}
-#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(argc, argv, s->r_acc); }
+#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(s, argc, argv); }
CREATE_DOSOUND_FORWARD(DoSoundInit)
CREATE_DOSOUND_FORWARD(DoSoundPlay)
@@ -77,21 +77,21 @@ reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) {
switch (argv[0].toUint16()) {
case 0:
- return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundMasterVolume(s, argc - 1, argv + 1);
case 2:
- return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundInit(s, argc - 1, argv + 1);
case 3:
- return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundDispose(s, argc - 1, argv + 1);
case 4:
- return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundPlay(s, argc - 1, argv + 1);
case 5:
- return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundStop(s, argc - 1, argv + 1);
case 8:
- return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundSetVolume(s, argc - 1, argv + 1);
case 9:
- return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundSetLoop(s, argc - 1, argv + 1);
case 10:
- return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc);
+ return g_sci->_soundCmd->kDoSoundUpdateCues(s, argc - 1, argv + 1);
}
error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16());
diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h
index 61f942c04a..dca7e97e81 100644
--- a/engines/sci/engine/object.h
+++ b/engines/sci/engine/object.h
@@ -179,6 +179,10 @@ public:
_infoSelectorSci3 &= ~flag;
}
}
+
+ bool isInserted() const {
+ return getInfoSelector().toUint16() & kInfoFlagViewInserted;
+ }
#endif
reg_t getNameSelector() const {
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index f5ec2d300c..97e53a0742 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -1079,12 +1079,29 @@ static const uint16 gk2InvScrollPatch[] = {
PATCH_END
};
+// The init code that runs when GK2 starts up unconditionally resets the
+// music volume to 63, but the game should always use the volume stored in
+// ScummVM.
+// Applies to at least: English 1.00 CD
+static const uint16 gk2VolumeResetSignature[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x3f, // ldi $3f
+ 0xa1, 0x4c, // sag $4c (music volume)
+ SIG_END
+};
+
+static const uint16 gk2VolumeResetPatch[] = {
+ 0x33, 0x02, // jmp 2 [past volume changes]
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry gk2Signatures[] = {
+ { true, 0, "disable volume reset on startup", 1, gk2VolumeResetSignature, gk2VolumeResetPatch },
+ { true, 23, "inventory starts scroll down in the wrong direction", 1, gk2InvScrollSignature, gk2InvScrollPatch },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
{ true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
- { true, 23, "inventory starts scroll down in the wrong direction", 1, gk2InvScrollSignature, gk2InvScrollPatch },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -2295,8 +2312,30 @@ static const uint16 larry6HiresPatchSetScale[] = {
PATCH_END
};
+// The init code that runs when LSL6hires starts up unconditionally resets the
+// master music volume to 12 (and the volume dial to 11), but the game should
+// always use the volume stored in ScummVM.
+// Applies to at least: English CD
+static const uint16 larry6HiresSignatureVolumeReset[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x221), // pushi $221 (masterVolume)
+ 0x78, // push1
+ 0x39, 0x0c, // push $0c
+ 0x81, 0x01, // lag $01
+ 0x4a, SIG_UINT16(0x06), // send $6
+ 0x35, 0x0b, // ldi $0b
+ 0xa1, 0xc2, // sag $c2
+ SIG_END
+};
+
+static const uint16 larry6HiresPatchVolumeReset[] = {
+ 0x32, PATCH_UINT16(12), // jmp 12 [past volume changes]
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry larry6HiresSignatures[] = {
+ { true, 71, "disable volume reset on startup", 1, larry6HiresSignatureVolumeReset, larry6HiresPatchVolumeReset },
{ true, 270, "fix incorrect setScale call", 1, larry6HiresSignatureSetScale, larry6HiresPatchSetScale },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
@@ -3151,9 +3190,42 @@ static const SciScriptPatcherEntry mothergooseHiresSignatures[] = {
#pragma mark -
#pragma mark Phantasmagoria
+// Phantasmagoria persists audio volumes in the save games, but ScummVM manages
+// game volumes through the launcher, so stop the game from overwriting the
+// ScummVM volumes with volumes from save games.
+// Applies to at least: English CD
+static const uint16 phant1SignatureSavedVolume[] = {
+ 0x7a, // push2
+ 0x39, 0x08, // pushi 8
+ 0x38, SIG_UINT16(0x20b), // push $20b (readWord)
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x13c), // lofsa $13c (PREF.DAT)
+ 0x4a, SIG_UINT16(0x04), // send 4
+ SIG_MAGICDWORD,
+ 0xa1, 0xbc, // sag $bc
+ 0x36, // push
+ 0x43, 0x76, SIG_UINT16(0x04), // callk DoAudio[76], 4
+ 0x7a, // push2
+ 0x76, // push0
+ 0x38, SIG_UINT16(0x20b), // push $20b (readWord)
+ 0x76, // push0
+ 0x72, SIG_UINT16(0x13c), // lofsa $13c (PREF.DAT)
+ 0x4a, SIG_UINT16(0x04), // send 4
+ 0xa1, 0xbb, // sag $bb
+ 0x36, // push
+ 0x43, 0x75, SIG_UINT16(0x04), // callk DoSound[75], 4
+ SIG_END
+};
+
+static const uint16 phant1PatchSavedVolume[] = {
+ 0x32, PATCH_UINT16(36), // jmp [to prefFile::close]
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry phantasmagoriaSignatures[] = {
{ true, 901, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
+ { true, 1111, "ignore audio settings from save game", 1, phant1SignatureSavedVolume, phant1PatchSavedVolume },
SCI_SIGNATUREENTRY_TERMINATOR
};
@@ -3312,6 +3384,50 @@ static const SciScriptPatcherEntry pq4Signatures[] = {
SCI_SIGNATUREENTRY_TERMINATOR
};
+#pragma mark -
+#pragma mark Police Quest: SWAT
+
+// The init code that runs when PQ:SWAT starts up unconditionally resets the
+// master sound volume to 127, but the game should always use the volume stored
+// in ScummVM.
+// Applies to at least: English CD
+static const uint16 pqSwatSignatureVolumeReset1[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x21a), // pushi $21a (masterVolume)
+ 0x78, // push1
+ 0x39, 0x7f, // push $7f
+ 0x54, SIG_UINT16(0x06), // self 6
+ SIG_END
+};
+
+static const uint16 pqSwatPatchVolumeReset1[] = {
+ 0x32, PATCH_UINT16(6), // jmp 6 [past volume reset]
+ PATCH_END
+};
+
+// pqInitCode::doit
+static const uint16 pqSwatSignatureVolumeReset2[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x21a), // pushi $21a (masterVolume)
+ 0x78, // push1
+ 0x39, 0x0f, // pushi $f
+ 0x81, 0x01, // lag 1
+ 0x4a, SIG_UINT16(0x06), // send 6
+ SIG_END
+};
+
+static const uint16 pqSwatPatchVolumeReset2[] = {
+ 0x32, PATCH_UINT16(8), // jmp 8 [past volume reset]
+ PATCH_END
+};
+
+// script, description, signature patch
+static const SciScriptPatcherEntry pqSwatSignatures[] = {
+ { true, 0, "disable volume reset on startup", 1, pqSwatSignatureVolumeReset1, pqSwatPatchVolumeReset1 },
+ { true, 1, "disable volume reset on startup", 1, pqSwatSignatureVolumeReset2, pqSwatPatchVolumeReset2 },
+ SCI_SIGNATUREENTRY_TERMINATOR
+};
+
#endif
// ===========================================================================
@@ -4284,8 +4400,41 @@ static const SciScriptPatcherEntry qfg3Signatures[] = {
#pragma mark -
#pragma mark Quest for Glory 4
+// The init code that runs when QFG4 starts up unconditionally resets the
+// master music volume to 15, but the game should always use the volume stored
+// in ScummVM.
+// Applies to at least: English floppy
+static const uint16 qfg4SignatureVolumeReset[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x215), // pushi $215 (masterVolume)
+ 0x78, // push1
+ 0x39, 0x0f, // pushi $f
+ 0x81, 0x01, // lag 1 (Glory object)
+ 0x4a, SIG_UINT16(0x06), // send 6
+ SIG_END
+};
+
+// Same as above, but with a different masterVolume selector.
+// Applies to at least: English CD
+static const uint16 qfg4CDSignatureVolumeReset[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x217), // pushi $217 (masterVolume)
+ 0x78, // push1
+ 0x39, 0x0f, // pushi $f
+ 0x81, 0x01, // lag 1 (Glory object)
+ 0x4a, SIG_UINT16(0x06), // send 6
+ SIG_END
+};
+
+static const uint16 qfg4PatchVolumeReset[] = {
+ 0x32, PATCH_UINT16(8), // jmp 8 [past volume changes]
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry qfg4Signatures[] = {
+ { true, 1, "disable volume reset on startup (floppy)", 1, qfg4SignatureVolumeReset, qfg4PatchVolumeReset },
+ { true, 1, "disable volume reset on startup (CD)", 1, qfg4CDSignatureVolumeReset, qfg4PatchVolumeReset },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
{ true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
@@ -4948,8 +5097,57 @@ static const SciScriptPatcherEntry sq6Signatures[] = {
#pragma mark -
#pragma mark Torins Passage
+// The init code that runs when Torin starts up unconditionally resets the
+// master music volume to defaults, but the game should always use the volume
+// stored in ScummVM.
+// Applies to at least: English CD
+static const uint16 torinVolumeResetSignature1[] = {
+ SIG_MAGICDWORD,
+ 0x35, 0x28, // ldi $28
+ 0xa1, 0xe3, // sag $e3 (music volume)
+ 0x35, 0x3c, // ldi $3c
+ 0xa1, 0xe4, // sag $e4 (sfx volume)
+ 0x35, 0x64, // ldi $64
+ 0xa1, 0xe5, // sag $e5 (speech volume)
+ SIG_END
+};
+
+static const uint16 torinVolumeResetPatch1[] = {
+ 0x33, 0x0a, // jmp [past volume resets]
+ PATCH_END
+};
+
+// The init code that runs when Torin starts up unconditionally resets the
+// master music volume to values stored in torin.prf, but the game should always
+// use the volume stored in ScummVM.
+// Applies to at least: English CD
+static const uint16 torinVolumeResetSignature2[] = {
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x20b), // pushi $020b
+ 0x76, // push0
+ SIG_ADDTOOFFSET(6), // advance file stream
+ 0xa1, 0xe3, // sag $e3 (music volume)
+ SIG_ADDTOOFFSET(10), // advance file stream
+ 0xa1, 0xe4, // sag $e4 (sfx volume)
+ SIG_ADDTOOFFSET(10), // advance file stream
+ 0xa1, 0xe5, // sag $e5 (speech volume)
+ SIG_END
+};
+
+static const uint16 torinVolumeResetPatch2[] = {
+ PATCH_ADDTOOFFSET(10), // advance file stream
+ 0x18, 0x18, // waste bytes
+ PATCH_ADDTOOFFSET(10), // advance file stream
+ 0x18, 0x18, // waste bytes
+ PATCH_ADDTOOFFSET(10), // advance file stream
+ 0x18, 0x18, // waste bytes
+ PATCH_END
+};
+
// script, description, signature patch
static const SciScriptPatcherEntry torinSignatures[] = {
+ { true, 64000, "disable volume reset on startup 1/2", 1, torinVolumeResetSignature1, torinVolumeResetPatch1 },
+ { true, 64000, "disable volume reset on startup 2/2", 1, torinVolumeResetSignature2, torinVolumeResetPatch2 },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
{ true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
SCI_SIGNATUREENTRY_TERMINATOR
@@ -5469,6 +5667,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, SciSpan<byte> scriptData) {
case GID_PQ4:
signatureTable = pq4Signatures;
break;
+ case GID_PQSWAT:
+ signatureTable = pqSwatSignatures;
+ break;
#endif
case GID_QFG1:
signatureTable = qfg1egaSignatures;
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 2bc4051a79..8a1a9e76d4 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -202,6 +202,20 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(magnifier);
FIND_SELECTOR(frameOut);
FIND_SELECTOR(casts);
+ FIND_SELECTOR(setVol);
+ FIND_SELECTOR(reSyncVol);
+ FIND_SELECTOR(set);
+ FIND_SELECTOR(clear);
+ FIND_SELECTOR(curPos);
+ FIND_SELECTOR(update);
+ FIND_SELECTOR(show);
+ FIND_SELECTOR(position);
+ FIND_SELECTOR(musicVolume);
+ FIND_SELECTOR(soundVolume);
+ FIND_SELECTOR(initialOff);
+ FIND_SELECTOR(setPos);
+ FIND_SELECTOR(setSize);
+ FIND_SELECTOR(displayValue);
#endif
}
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index 8d1edeb489..d8c0bf711b 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -158,6 +158,20 @@ struct SelectorCache {
Selector magnifier;
Selector frameOut;
Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts)
+ Selector setVol; // for GK2 volume sync on restore
+ Selector reSyncVol; // for Torin volume sync on restore
+ Selector set; // for LSL6hires subtitle sync
+ Selector clear; // for LSL6hires subtitle sync
+ Selector curPos; // for LSL6hires volume sync
+ Selector update; // for LSL6hires volume sync
+ Selector show; // for GK1 volume sync
+ Selector position; // for GK1 volume sync
+ Selector musicVolume; // for GK1 volume sync
+ Selector soundVolume; // for GK1 volume sync
+ Selector initialOff; // for GK2 volume sync
+ Selector setPos; // for Torin volume sync
+ Selector setSize; // for PQ4 volume sync
+ Selector displayValue; // for PQ:SWAT volume sync
#endif
};
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index 76df322b30..6e2229727f 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -19,14 +19,12 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
-
#include "common/system.h"
#include "sci/sci.h" // for INCLUDE_OLDGFX
#include "sci/debug.h" // for g_debug_sleeptime_factor
-#include "sci/event.h"
-
#include "sci/engine/file.h"
+#include "sci/engine/guest_additions.h"
#include "sci/engine/kernel.h"
#include "sci/engine/state.h"
#include "sci/engine/selector.h"
@@ -84,6 +82,8 @@ void EngineState::reset(bool isRestoring) {
_memorySegmentSize = 0;
_fileHandles.resize(5);
abortScriptProcessing = kAbortNone;
+ } else {
+ g_sci->_guestAdditions->reset();
}
// reset delayed restore game functionality
@@ -120,7 +120,6 @@ void EngineState::reset(bool isRestoring) {
scriptGCInterval = GC_INTERVAL;
_videoState.reset();
- _syncedAudioOptions = false;
}
void EngineState::speedThrottler(uint32 neededSleep) {
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index 5297a176d3..dd6b728716 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -214,7 +214,6 @@ public:
// TODO: Excise video code from the state manager
VideoState _videoState;
- bool _syncedAudioOptions;
/**
* Resets the engine state.
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 3535b278c0..7e514a2dc6 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -27,7 +27,13 @@
#include "sci/sci.h"
#include "sci/console.h"
#include "sci/resource.h"
+#ifdef ENABLE_SCI32
+#include "audio/mixer.h"
+#include "sci/sound/audio32.h"
+#include "sci/sound/music.h"
+#endif
#include "sci/engine/features.h"
+#include "sci/engine/guest_additions.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/object.h"
@@ -199,36 +205,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
s->variables[type][index] = value;
-#ifdef ENABLE_SCI32
- if (type == VAR_GLOBAL && getSciVersion() >= SCI_VERSION_2 && g_sci->getEngineState()->_syncedAudioOptions) {
-
- switch (g_sci->getGameId()) {
- case GID_LSL6HIRES:
- if (index == kGlobalVarLSL6HiresTextSpeed) {
- ConfMan.setInt("talkspeed", (14 - value.toSint16()) * 255 / 13);
- }
- break;
- default:
- if (index == kGlobalVarTextSpeed) {
- ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8);
- }
- }
- }
-#endif
-
- if (type == VAR_GLOBAL && index == kGlobalVarMessageType) {
- // The game is trying to change its speech/subtitle settings
- if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
- // ScummVM audio options haven't been applied yet, so apply them.
- // We also force the ScummVM audio options when loading a game from
- // the launcher.
- g_sci->syncIngameAudioOptions();
- g_sci->getEngineState()->_syncedAudioOptions = true;
- } else {
- // Update ScummVM's audio options
- g_sci->updateScummVMAudioOptions();
- }
- }
+ g_sci->_guestAdditions->writeVarHook(type, index, value);
}
}
@@ -311,6 +288,10 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt
if (argc > 0x800) // More arguments than the stack could possibly accomodate for
error("send_selector(): More than 0x800 arguments to function call");
+#ifdef ENABLE_SCI32
+ g_sci->_guestAdditions->sendSelectorHook(send_obj, selector, argp);
+#endif
+
SelectorType selectorType = lookupSelector(s->_segMan, send_obj, selector, &varp, &funcp);
if (selectorType == kSelectorNone)
error("Send to invalid selector 0x%x of object at %04x:%04x", 0xffff & selector, PRINT_REG(send_obj));
diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h
index 0444cf5ffc..1ac6fed0b6 100644
--- a/engines/sci/engine/vm.h
+++ b/engines/sci/engine/vm.h
@@ -142,16 +142,32 @@ enum GlobalVar {
kGlobalVarCurrentRoom = 2,
kGlobalVarSpeed = 3, // SCI16
kGlobalVarQuit = 4,
+ kGlobalVarSounds = 8,
kGlobalVarPlanes = 10, // SCI32
kGlobalVarCurrentRoomNo = 11,
kGlobalVarPreviousRoomNo = 12,
kGlobalVarNewRoomNo = 13,
kGlobalVarScore = 15,
+ kGlobalVarGK2MusicVolume = 76, // 0 to 127
kGlobalVarFastCast = 84, // SCI16
kGlobalVarMessageType = 90,
kGlobalVarTextSpeed = 94, // SCI32; 0 is fastest, 8 is slowest
- kGlobalVarLSL6HiresTextSpeed = 167, // 1 is fastest, 14 is slowest
- kGlobalVarShivers1Score = 349
+ kGlobalVarGK1Music1 = 102, // 0 to 127
+ kGlobalVarGK1Music2 = 103, // 0 to 127
+ kGlobalVarLSL6HiresGameFlags = 137,
+ kGlobalVarGK1NarratorMode = 166, // 0 for text, 1 for speech
+ kGlobalVarPhant1MusicVolume = 187, // 0 to 15
+ kGlobalVarPhant1DACVolume = 188, // 0 to 127
+ kGlobalVarLSL6HiresMusicVolume = 194, // 0 to 13
+ kGlobalVarGK1DAC1 = 207, // 0 to 127
+ kGlobalVarGK1DAC2 = 208, // 0 to 127
+ kGlobalVarLSL6HiresRestoreTextWindow = 210,
+ kGlobalVarGK1DAC3 = 211, // 0 to 127
+ kGlobalVarShiversFlags = 211,
+ kGlobalVarTorinMusicVolume = 227, // 0 to 100
+ kGlobalVarTorinSFXVolume = 228, // 0 to 100
+ kGlobalVarTorinSpeechVolume = 229, // 0 to 100
+ kGlobalVarShivers1Score = 349
};
/** Number of kernel calls in between gcs; should be < 50000 */
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index eb2c6a148b..7decff6e7a 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -12,6 +12,7 @@ MODULE_OBJS := \
engine/features.o \
engine/file.o \
engine/gc.o \
+ engine/guest_additions.o \
engine/kernel.o \
engine/kevent.o \
engine/kfile.o \
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 34930a648e..c8e500b700 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -33,6 +33,7 @@
#include "sci/event.h"
#include "sci/engine/features.h"
+#include "sci/engine/guest_additions.h"
#include "sci/engine/message.h"
#include "sci/engine/object.h"
#include "sci/engine/state.h"
@@ -94,6 +95,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
_video32 = nullptr;
_gfxCursor32 = nullptr;
#endif
+ _guestAdditions = nullptr;
_features = 0;
_resMan = 0;
_gamestate = 0;
@@ -202,6 +204,7 @@ SciEngine::~SciEngine() {
delete _kernel;
delete _vocabulary;
delete _console;
+ delete _guestAdditions;
delete _features;
delete _gfxMacIconBar;
@@ -280,6 +283,7 @@ Common::Error SciEngine::run() {
_vocabulary = new Vocabulary(_resMan, false);
_gamestate = new EngineState(segMan);
+ _guestAdditions = new GuestAdditions(_gamestate, _features);
_eventMan = new EventManager(_resMan->detectFontExtended());
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
@@ -331,7 +335,7 @@ Common::Error SciEngine::run() {
_soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, _features->detectDoSoundType());
syncSoundSettings();
- syncIngameAudioOptions();
+ _guestAdditions->syncAudioOptionsFromScummVM();
// Patch in our save/restore code, so that dialogs are replaced
patchGameSaveRestore();
@@ -834,14 +838,14 @@ void SciEngine::runGame() {
if (DebugMan.isDebugChannelEnabled(kDebugLevelOnStartup))
_console->attach();
- _gamestate->_syncedAudioOptions = false;
+ _guestAdditions->reset();
do {
_gamestate->_executionStackPosChanged = false;
run_vm(_gamestate);
exitGame();
- _gamestate->_syncedAudioOptions = true;
+ _guestAdditions->sciEngineRunGameHook();
if (_gamestate->abortScriptProcessing == kAbortRestartGame) {
_gamestate->_segMan->resetSegMan();
@@ -854,7 +858,7 @@ void SciEngine::runGame() {
if (_gfxMenu)
_gfxMenu->reset();
_gamestate->abortScriptProcessing = kAbortNone;
- _gamestate->_syncedAudioOptions = false;
+ _guestAdditions->reset();
} else if (_gamestate->abortScriptProcessing == kAbortLoadGame) {
_gamestate->abortScriptProcessing = kAbortNone;
_gamestate->_executionStack.clear();
@@ -865,8 +869,7 @@ void SciEngine::runGame() {
_gamestate->abortScriptProcessing = kAbortNone;
syncSoundSettings();
- syncIngameAudioOptions();
- // Games do not set their audio settings when loading
+ _guestAdditions->syncAudioOptionsFromScummVM();
} else {
break; // exit loop
}
@@ -1055,150 +1058,7 @@ void SciEngine::pauseEngineIntern(bool pause) {
void SciEngine::syncSoundSettings() {
Engine::syncSoundSettings();
-
- bool mute = false;
- if (ConfMan.hasKey("mute"))
- mute = ConfMan.getBool("mute");
-
- int soundVolumeMusic = (mute ? 0 : ConfMan.getInt("music_volume"));
-
- if (_gamestate && _soundCmd) {
- int vol = (soundVolumeMusic + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
- _soundCmd->setMasterVolume(vol);
- }
-}
-
-void SciEngine::syncIngameAudioOptions() {
- bool useGlobal90 = false;
-
- // Sync the in-game speech/subtitles settings for SCI1.1 CD games
- if (isCD()) {
- switch (getSciVersion()) {
- case SCI_VERSION_1_1:
- // All SCI1.1 CD games use global 90
- useGlobal90 = true;
- break;
-#ifdef ENABLE_SCI32
- case SCI_VERSION_2:
- case SCI_VERSION_2_1_EARLY:
- case SCI_VERSION_2_1_MIDDLE:
- case SCI_VERSION_2_1_LATE:
- // Only use global 90 for some specific games, not all SCI32 games used this method
- switch (_gameId) {
- case GID_KQ7: // SCI2.1
- case GID_GK1: // SCI2
- case GID_GK2: // SCI2.1
- case GID_SQ6: // SCI2.1
- case GID_TORIN: // SCI2.1
- case GID_QFG4: // SCI2.1
- case GID_PQ4: // SCI2
- case GID_PHANTASMAGORIA: // SCI2.1
- case GID_MOTHERGOOSEHIRES: // SCI2.1
- useGlobal90 = true;
- break;
- case GID_LSL6: // SCI2.1
- // TODO: Uses gameFlags array
- break;
- // Shivers does not use global 90
- // Police Quest: SWAT does not use global 90
- //
- // TODO: Unknown at the moment:
- // LSL7, Lighthouse, RAMA, Phantasmagoria 2
- default:
- return;
- }
- break;
-#endif // ENABLE_SCI32
- default:
- return;
- }
-
- bool subtitlesOn = ConfMan.getBool("subtitles");
- bool speechOn = !ConfMan.getBool("speech_mute");
-
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2) {
- GlobalVar index;
- uint16 textSpeed;
-
- switch (g_sci->getGameId()) {
- case GID_LSL6HIRES:
- index = kGlobalVarLSL6HiresTextSpeed;
- textSpeed = 14 - ConfMan.getInt("talkspeed") * 14 / 255 + 1;
- break;
- default:
- index = kGlobalVarTextSpeed;
- textSpeed = 8 - ConfMan.getInt("talkspeed") * 8 / 255;
- }
-
- _gamestate->variables[VAR_GLOBAL][index] = make_reg(0, textSpeed);
- }
-#endif
-
- if (useGlobal90) {
- if (subtitlesOn && !speechOn) {
- _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 1); // subtitles
- } else if (!subtitlesOn && speechOn) {
- _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2); // speech
- } else if (subtitlesOn && speechOn) {
- // Is it a game that supports simultaneous speech and subtitles?
- switch (_gameId) {
- case GID_SQ4:
- case GID_FREDDYPHARKAS:
- case GID_ECOQUEST:
- case GID_LSL6:
- case GID_LAURABOW2:
- case GID_KQ6:
-#ifdef ENABLE_SCI32
- // Unsure about Gabriel Knight 2
- case GID_KQ7: // SCI2.1
- case GID_GK1: // SCI2
- case GID_SQ6: // SCI2.1, SQ6 seems to always use subtitles anyway
- case GID_TORIN: // SCI2.1
- case GID_QFG4: // SCI2.1
- case GID_PQ4: // SCI2
- // Phantasmagoria does not support simultaneous speech + subtitles
- // Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles
-#endif // ENABLE_SCI32
- _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 3); // speech + subtitles
- break;
- default:
- // Game does not support speech and subtitles, set it to speech
- _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2); // speech
- }
- }
- }
- }
-}
-
-void SciEngine::updateScummVMAudioOptions() {
- // Update ScummVM's speech/subtitles settings for SCI1.1 CD games,
- // depending on the in-game settings
- if ((isCD() && getSciVersion() == SCI_VERSION_1_1) ||
- getSciVersion() >= SCI_VERSION_2) {
-
- uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType].getOffset();
-
- switch (ingameSetting) {
- case 1:
- // subtitles
- ConfMan.setBool("subtitles", true);
- ConfMan.setBool("speech_mute", true);
- break;
- case 2:
- // speech
- ConfMan.setBool("subtitles", false);
- ConfMan.setBool("speech_mute", false);
- break;
- case 3:
- // speech + subtitles
- ConfMan.setBool("subtitles", true);
- ConfMan.setBool("speech_mute", false);
- break;
- default:
- break;
- }
- }
+ _guestAdditions->syncSoundSettings();
}
void SciEngine::loadMacExecutable() {
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index f5ccc19d7c..744a1b4aed 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -62,6 +62,7 @@ class Vocabulary;
class ResourceManager;
class Kernel;
class GameFeatures;
+class GuestAdditions;
class Console;
class AudioPlayer;
class SoundCommandParser;
@@ -263,28 +264,6 @@ public:
uint32 getTickCount();
void setTickCount(const uint32 ticks);
- /**
- * Syncs the audio options of the ScummVM launcher (speech, subtitles or
- * both) with the in-game audio options of certain CD game versions. For
- * some games, this allows simultaneous playing of speech and subtitles,
- * even if the original games didn't support this feature.
- *
- * SCI1.1 games which support simultaneous speech and subtitles:
- * - EcoQuest 1 CD
- * - Leisure Suit Larry 6 CD
- * SCI1.1 games which don't support simultaneous speech and subtitles,
- * and we add this functionality in ScummVM:
- * - Space Quest 4 CD
- * - Freddy Pharkas CD
- * - Laura Bow 2 CD
- * SCI1.1 games which don't support simultaneous speech and subtitles,
- * and we haven't added any extra functionality in ScummVM because extra
- * script patches are needed:
- * - King's Quest 6 CD
- */
- void syncIngameAudioOptions();
- void updateScummVMAudioOptions();
-
const SciGameId &getGameId() const { return _gameId; }
const char *getGameIdStr() const;
int getResourceVersion() const;
@@ -398,6 +377,7 @@ public:
Sync *_sync;
SoundCommandParser *_soundCmd;
GameFeatures *_features;
+ GuestAdditions *_guestAdditions;
opcode_format (*_opcode_formats)[4];
diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
index 69a83ebc8e..e9e90e41a7 100644
--- a/engines/sci/sound/audio32.cpp
+++ b/engines/sci/sound/audio32.cpp
@@ -36,6 +36,8 @@
#include "common/types.h" // for Flag::NO
#include "engine.h" // for Engine, g_engine
#include "sci/engine/features.h" // for GameFeatures
+#include "sci/engine/guest_additions.h" // for GuestAdditions
+#include "sci/engine/state.h" // for EngineState
#include "sci/engine/vm_types.h" // for reg_t, make_reg, NULL_REG
#include "sci/resource.h" // for ResourceId, ResourceType::kResour...
#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
@@ -118,6 +120,9 @@ Audio32::Audio32(ResourceManager *resMan) :
}
_useModifiedAttenuation = g_sci->_features->usesModifiedAudioAttenuation();
+ // The mixer stream type is given as `kSFXSoundType` so that audio from
+ // Audio32 will be mixed at the same standard volume as the video players
+ // (which must use `kSFXSoundType` as well).
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
@@ -980,7 +985,7 @@ reg_t Audio32::kernelPlay(const bool autoPlay, const int argc, const reg_t *cons
int16 Audio32::getVolume(const int16 channelIndex) const {
if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
- return _mixer->getChannelVolume(_handle) * kMaxVolume / Audio::Mixer::kMaxChannelVolume;
+ return (_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) + 1) * kMaxVolume / Audio::Mixer::kMaxMixerVolume;
}
Common::StackLock lock(_mutex);
@@ -990,10 +995,9 @@ int16 Audio32::getVolume(const int16 channelIndex) const {
void Audio32::setVolume(const int16 channelIndex, int16 volume) {
volume = MIN<int16>(kMaxVolume, volume);
if (channelIndex == kAllChannels) {
- ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
- ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
- _mixer->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
- g_engine->syncSoundSettings();
+ if (!g_sci->_guestAdditions->audio32SetVolumeHook(channelIndex, volume)) {
+ setMasterVolume(volume);
+ }
} else if (channelIndex != kNoExistingChannel) {
Common::StackLock lock(_mutex);
getChannel(channelIndex).volume = volume;
diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h
index 9130cbe687..8a1d8cfb43 100644
--- a/engines/sci/sound/audio32.h
+++ b/engines/sci/sound/audio32.h
@@ -504,6 +504,13 @@ public:
}
/**
+ * Sets the master volume for digital audio playback.
+ */
+ void setMasterVolume(const int16 volume) {
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ }
+
+ /**
* Initiate an immediate fade of the given channel.
*/
bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade);
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index b9a764c93a..e0cbb97d19 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -29,6 +29,7 @@
#include "sci/sound/soundcmd.h"
#include "sci/engine/features.h"
+#include "sci/engine/guest_additions.h"
#include "sci/engine/kernel.h"
#include "sci/engine/object.h"
#include "sci/engine/selector.h"
@@ -57,10 +58,10 @@ SoundCommandParser::~SoundCommandParser() {
delete _music;
}
-reg_t SoundCommandParser::kDoSoundInit(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundInit(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelSound, "kDoSound(init): %04x:%04x", PRINT_REG(argv[0]));
processInitSound(argv[0]);
- return acc;
+ return s->r_acc;
}
int SoundCommandParser::getSoundResourceId(reg_t obj) {
@@ -153,13 +154,13 @@ void SoundCommandParser::processInitSound(reg_t obj) {
}
}
-reg_t SoundCommandParser::kDoSoundPlay(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundPlay(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelSound, "kDoSound(play): %04x:%04x", PRINT_REG(argv[0]));
bool playBed = false;
if (argc >= 2 && !argv[1].isNull())
playBed = true;
processPlaySound(argv[0], playBed);
- return acc;
+ return s->r_acc;
}
void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) {
@@ -225,10 +226,10 @@ void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) {
musicSlot->fadeStep = 0;
}
-reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundDispose(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelSound, "kDoSound(dispose): %04x:%04x", PRINT_REG(argv[0]));
processDisposeSound(argv[0]);
- return acc;
+ return s->r_acc;
}
void SoundCommandParser::processDisposeSound(reg_t obj) {
@@ -248,10 +249,10 @@ void SoundCommandParser::processDisposeSound(reg_t obj) {
writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundStopped);
}
-reg_t SoundCommandParser::kDoSoundStop(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundStop(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelSound, "kDoSound(stop): %04x:%04x", PRINT_REG(argv[0]));
processStopSound(argv[0], false);
- return acc;
+ return s->r_acc;
}
void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying) {
@@ -282,7 +283,7 @@ void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying)
_music->soundStop(musicSlot);
}
-reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundPause(EngineState *s, int argc, reg_t *argv) {
if (argc == 1)
debugC(kDebugLevelSound, "kDoSound(pause): %04x:%04x", PRINT_REG(argv[0]));
else
@@ -333,7 +334,7 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
if (!musicSlot) {
// This happens quite frequently
debugC(kDebugLevelSound, "kDoSound(pause): Slot not found (%04x:%04x)", PRINT_REG(obj));
- return acc;
+ return s->r_acc;
}
#ifdef ENABLE_SCI32
@@ -354,17 +355,17 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
#endif
_music->soundToggle(musicSlot, shouldPause);
}
- return acc;
+ return s->r_acc;
}
// SCI0 only command
// It's called right after restoring a game - it's responsible to kick off playing music again
// we don't need this at all, so we don't do anything here
-reg_t SoundCommandParser::kDoSoundResumeAfterRestore(int argc, reg_t *argv, reg_t acc) {
- return acc;
+reg_t SoundCommandParser::kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv) {
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundMute(EngineState *s, int argc, reg_t *argv) {
uint16 previousState = _music->soundGetSoundOn();
if (argc > 0) {
debugC(kDebugLevelSound, "kDoSound(mute): %d", argv[0].toUint16());
@@ -374,37 +375,33 @@ reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) {
return make_reg(0, previousState);
}
-reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc) {
- acc = make_reg(0, _music->soundGetMasterVolume());
+reg_t SoundCommandParser::kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv) {
+ s->r_acc = make_reg(0, _music->soundGetMasterVolume());
if (argc > 0) {
debugC(kDebugLevelSound, "kDoSound(masterVolume): %d", argv[0].toSint16());
int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX);
- vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
- ConfMan.setInt("music_volume", vol);
- // In SCI32, digital audio volume is controlled separately by
- // kDoAudioVolume
- if (_soundVersion < SCI_VERSION_2_1_EARLY) {
- ConfMan.setInt("sfx_volume", vol);
+
+ if (!g_sci->_guestAdditions->kDoSoundMasterVolumeHook(vol)) {
+ setMasterVolume(vol);
}
- g_engine->syncSoundSettings();
}
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundFade(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
// The object can be null in several SCI0 games (e.g. Camelot, KQ1, KQ4, MUMG).
// Check bugs #3035149, #3036942 and #3578335.
// In this case, we just ignore the call.
if (obj.isNull() && argc == 1)
- return acc;
+ return s->r_acc;
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
debugC(kDebugLevelSound, "kDoSound(fade): Slot not found (%04x:%04x)", PRINT_REG(obj));
- return acc;
+ return s->r_acc;
}
int volume = musicSlot->volume;
@@ -412,7 +409,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
#ifdef ENABLE_SCI32
if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
g_sci->_audio32->fadeChannel(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, argv[1].toSint16(), argv[2].toSint16(), argv[3].toSint16(), argc > 4 ? (bool)argv[4].toSint16() : false);
- return acc;
+ return s->r_acc;
}
#endif
@@ -420,7 +417,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
if (musicSlot->status != kSoundPlaying) {
debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj));
writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET);
- return acc;
+ return s->r_acc;
}
switch (argc) {
@@ -439,7 +436,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
// Check if the song is already at the requested volume. If it is, don't
// perform any fading. Happens for example during the intro of Longbow.
if (musicSlot->fadeTo == musicSlot->volume)
- return acc;
+ return s->r_acc;
// Sometimes we get objects in that position, so fix the value (refer to workarounds.cpp)
if (!argv[1].getSegment())
@@ -464,14 +461,14 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
}
debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x to %d, step %d, ticker %d", PRINT_REG(obj), musicSlot->fadeTo, musicSlot->fadeStep, musicSlot->fadeTickerStep);
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, _music->soundGetVoices()); // Get the number of voices
}
-reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundUpdate(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
debugC(kDebugLevelSound, "kDoSound(update): %04x:%04x", PRINT_REG(argv[0]));
@@ -479,7 +476,7 @@ reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) {
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
warning("kDoSound(update): Slot not found (%04x:%04x)", PRINT_REG(obj));
- return acc;
+ return s->r_acc;
}
musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop));
@@ -489,12 +486,12 @@ reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) {
int16 objPrio = readSelectorValue(_segMan, obj, SELECTOR(priority));
if (objPrio != musicSlot->priority)
_music->soundSetPriority(musicSlot, objPrio);
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv) {
processUpdateCues(argv[0]);
- return acc;
+ return s->r_acc;
}
void SoundCommandParser::processUpdateCues(reg_t obj) {
@@ -608,7 +605,7 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {
}
}
-reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv) {
// The 4 parameter variant of this call is used in at least LSL1VGA, room
// 110 (Lefty's bar), to distort the music when Larry is drunk and stands
// up - bug #3614447.
@@ -637,13 +634,13 @@ reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) {
// if so, allow it
//_music->sendMidiCommand(_midiCommand);
warning("kDoSound(sendMidi): Slot not found (%04x:%04x)", PRINT_REG(obj));
- return acc;
+ return s->r_acc;
}
_music->sendMidiCommand(musicSlot, midiCommand);
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv) {
byte prevReverb = _music->getCurrentReverb();
byte reverb = argv[0].toUint16() & 0xF;
@@ -656,7 +653,7 @@ reg_t SoundCommandParser::kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc)
return make_reg(0, prevReverb);
}
-reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundSetHold(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
debugC(kDebugLevelSound, "doSoundSetHold: %04x:%04x, %d", PRINT_REG(argv[0]), argv[1].toUint16());
@@ -664,24 +661,24 @@ reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) {
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
warning("kDoSound(setHold): Slot not found (%04x:%04x)", PRINT_REG(obj));
- return acc;
+ return s->r_acc;
}
// Set the special hold marker ID where the song should be looped at.
musicSlot->hold = argv[1].toSint16();
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv) {
// Tests for digital audio support
return make_reg(0, 1);
}
-reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundStopAll(EngineState *s, int argc, reg_t *argv) {
// TODO: this can't be right, this gets called in kq1 - e.g. being in witch house, getting the note
// now the point jingle plays and after a messagebox they call this - and would stop the background effects with it
// this doesn't make sense, so i disable it for now
- return acc;
+ return s->r_acc;
Common::StackLock(_music->_mutex);
@@ -697,10 +694,10 @@ reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) {
(*i)->dataInc = 0;
_music->soundStop(*i);
}
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
int16 value = argv[1].toSint16();
@@ -711,7 +708,7 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {
// the drum sounds of the energizer bunny at the beginning), so this is
// normal behavior.
//warning("cmdSetSoundVolume: Slot not found (%04x:%04x)", PRINT_REG(obj));
- return acc;
+ return s->r_acc;
}
debugC(kDebugLevelSound, "kDoSound(setVolume): %d", value);
@@ -724,15 +721,20 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {
_music->soundSetVolume(musicSlot, value);
}
#endif
+
if (musicSlot->volume != value) {
musicSlot->volume = value;
_music->soundSetVolume(musicSlot, value);
writeSelectorValue(_segMan, obj, SELECTOR(vol), value);
+#ifdef ENABLE_SCI32
+ g_sci->_guestAdditions->kDoSoundSetVolumeHook(obj, value);
+#endif
}
- return acc;
+
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
int16 value = argv[1].toSint16();
@@ -741,7 +743,7 @@ reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc)
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
debugC(kDebugLevelSound, "kDoSound(setPriority): Slot not found (%04x:%04x)", PRINT_REG(obj));
- return acc;
+ return s->r_acc;
}
if (value == -1) {
@@ -759,10 +761,10 @@ reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc)
_music->soundSetPriority(musicSlot, value);
}
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
int16 value = argv[1].toSint16();
@@ -780,7 +782,7 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
} else {
// Doesn't really matter
}
- return acc;
+ return s->r_acc;
}
#ifdef ENABLE_SCI32
@@ -805,13 +807,13 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
}
#endif
- return acc;
+ return s->r_acc;
}
-reg_t SoundCommandParser::kDoSoundSuspend(int argc, reg_t *argv, reg_t acc) {
+reg_t SoundCommandParser::kDoSoundSuspend(EngineState *s, int argc, reg_t *argv) {
// TODO
warning("kDoSound(suspend): STUB");
- return acc;
+ return s->r_acc;
}
void SoundCommandParser::updateSci0Cues() {
@@ -879,6 +881,17 @@ void SoundCommandParser::setMasterVolume(int vol) {
_music->soundSetMasterVolume(vol);
}
+#ifdef ENABLE_SCI32
+void SoundCommandParser::setVolume(const reg_t obj, const int volume) {
+ MusicEntry *slot = _music->getSlot(obj);
+ if (slot != nullptr) {
+ slot->volume = volume;
+ writeSelectorValue(_segMan, obj, SELECTOR(vol), volume);
+ _music->soundSetVolume(slot, volume);
+ }
+}
+#endif
+
void SoundCommandParser::pauseAll(bool pause) {
_music->pauseAll(pause);
}
diff --git a/engines/sci/sound/soundcmd.h b/engines/sci/sound/soundcmd.h
index 5bb7cf2cb1..928c9b1acb 100644
--- a/engines/sci/sound/soundcmd.h
+++ b/engines/sci/sound/soundcmd.h
@@ -46,7 +46,7 @@ public:
SoundCommandParser(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, AudioPlayer *audio, SciVersion soundVersion);
~SoundCommandParser();
- //reg_t parseCommand(int argc, reg_t *argv, reg_t acc);
+ //reg_t parseCommand(EngineState *s, int argc, reg_t *argv);
// Functions used for game state loading
void clearPlayList();
@@ -56,6 +56,9 @@ public:
// Functions used for the ScummVM menus
void setMasterVolume(int vol);
void pauseAll(bool pause);
+#ifdef ENABLE_SCI32
+ void setVolume(const reg_t obj, const int vol);
+#endif
// Debug console functions
void startNewSound(int number);
@@ -78,29 +81,29 @@ public:
*/
void updateSci0Cues();
- reg_t kDoSoundInit(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundPlay(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundRestore(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundMute(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundPause(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundResumeAfterRestore(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundStop(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundStopAll(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundDispose(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundFade(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundUpdate(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundSetHold(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundDummy(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc);
- reg_t kDoSoundSuspend(int argc, reg_t *argv, reg_t acc);
+ reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundRestore(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundPause(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundStopAll(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundFade(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundUpdate(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv);
+ reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv);
private:
//typedef Common::Array<MusicEntryCommand *> SoundCommandContainer;