aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine
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/sci/engine
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/sci/engine')
-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
13 files changed, 1504 insertions, 47 deletions
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 */