[Scummvm-git-logs] scummvm master -> a2fb53790b85050e2bac1af5e7a3ab626a6dc3db

csnover csnover at users.noreply.github.com
Sat Apr 22 20:37:28 CEST 2017


This automated email contains information about 15 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
3303a88139 SCI: Improve audio volume & settings sync code
ec12c5a342 SCI: Move ScummVM save/restore to GuestAdditions and reimplement for SCI32
c8486395fa SCI: Clean up unnecessary delayed restore flags
4c0f2a3738 SCI: Move ScummVM kernel calls to 0xe0
6f75bed90c SCI32: Remove original save/load option from games without ScummVM save integration
1ed185bbbb SCI: Implement delayed restore for SCI32 and move implementations to GuestAdditions
a2c48eff4c SCI32: Only activate the Load button in the launcher when it is usable
2862e51068 SCI32: Fix launcher load for Shivers
f8703ae48d SCI32: Fix infinite recursion when loading invalid save from launcher
2df939d33b SCI32: Add workaround for SQ6 invalid read after a failed save game restore
a22e461208 SCI: Improve comments in GuestAdditions
93b337b833 SCI: Do not sync objects when saving games
399551af09 SCI: Fix compilation when SCI32 is disabled
e504efe4da SCI32: Add palette code for late SCI2.1mid+ games
a2fb53790b SCI32: Make version 41 the first supported save game version


Commit: 3303a881397beff1753fba237a5da735de03edb5
    https://github.com/scummvm/scummvm/commit/3303a881397beff1753fba237a5da735de03edb5
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-21T19:00:27-05:00

Commit Message:
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.

Changed paths:
  A engines/sci/engine/guest_additions.cpp
  A engines/sci/engine/guest_additions.h
    engines/sci/detection_tables.h
    engines/sci/engine/features.cpp
    engines/sci/engine/features.h
    engines/sci/engine/ksound.cpp
    engines/sci/engine/object.h
    engines/sci/engine/script_patches.cpp
    engines/sci/engine/selector.cpp
    engines/sci/engine/selector.h
    engines/sci/engine/state.cpp
    engines/sci/engine/state.h
    engines/sci/engine/vm.cpp
    engines/sci/engine/vm.h
    engines/sci/module.mk
    engines/sci/sci.cpp
    engines/sci/sci.h
    engines/sci/sound/audio32.cpp
    engines/sci/sound/audio32.h
    engines/sci/sound/soundcmd.cpp
    engines/sci/sound/soundcmd.h


diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 02ec9aa..c37dd4f 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 40d3801..1085fec 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 8f84bba..044111a 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 0000000..a2c6423
--- /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 0000000..a6875d4
--- /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 4a5e4f3..e7cad21 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 61f942c..dca7e97 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 f5ec2d3..97e53a0 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 2bc4051..8a1a9e7 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 8d1edeb..d8c0bf7 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 76df322..6e22297 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 5297a17..dd6b728 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 3535b27..7e514a2 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 0444cf5..1ac6fed 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 eb2c6a1..7decff6 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 34930a6..c8e500b 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 f5ccc19..744a1b4 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 69a83eb..e9e90e4 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 9130cbe..8a1d8cf 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 b9a764c..e0cbb97 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 5bb7cf2..928c9b1 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;


Commit: ec12c5a342519c51a1db521111ee19d4a56970f6
    https://github.com/scummvm/scummvm/commit/ec12c5a342519c51a1db521111ee19d4a56970f6
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:16-05:00

Commit Message:
SCI: Move ScummVM save/restore to GuestAdditions and reimplement for SCI32

Changed paths:
    engines/sci/engine/guest_additions.cpp
    engines/sci/engine/guest_additions.h
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kfile.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/seg_manager.cpp
    engines/sci/graphics/frameout.cpp
    engines/sci/graphics/frameout.h
    engines/sci/sci.cpp
    engines/sci/sci.h


diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
index a2c6423..6477e32 100644
--- a/engines/sci/engine/guest_additions.cpp
+++ b/engines/sci/engine/guest_additions.cpp
@@ -29,6 +29,8 @@
 #include "sci/engine/state.h"
 #include "sci/engine/vm.h"
 #ifdef ENABLE_SCI32
+#include "common/translation.h"
+#include "gui/saveload.h"
 #include "sci/graphics/frameout.h"
 #endif
 #include "sci/sound/music.h"
@@ -52,9 +54,10 @@ enum {
 };
 
 
-GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features) :
+GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features, Kernel *kernel) :
 	_state(state),
 	_features(features),
+	_kernel(kernel),
 	_segMan(state->_segMan),
 	_messageTypeSynced(false) {}
 
@@ -205,6 +208,178 @@ void GuestAdditions::kDoSoundSetVolumeHook(const reg_t soundObj, const int16 vol
 		syncGK1AudioVolumeToScummVM(soundObj, volume);
 	}
 }
+
+void GuestAdditions::instantiateScriptHook(Script &script) const {
+	if (getSciVersion() < SCI_VERSION_2) {
+		return;
+	}
+
+	// 64990 is the system script containing SRDialog. This script is normally
+	// used by the main Game object, but it is not loaded immediately, so we
+	// wait for it to be loaded before patching it
+	if (!ConfMan.getBool("originalsaveload") && script.getScriptNumber() == 64990) {
+		patchGameSaveRestoreSCI32(script);
+	}
+}
+
+#endif
+
+#pragma mark -
+#pragma mark Save & restore
+
+void GuestAdditions::patchGameSaveRestore() const {
+	if (ConfMan.getBool("originalsaveload") || getSciVersion() >= SCI_VERSION_2)
+		return;
+
+	patchGameSaveRestoreSCI16();
+}
+
+static const byte kSaveRestorePatch[] = {
+	0x39, 0x03,        // pushi 03
+	0x76,              // push0
+	0x38, 0xff, 0xff,  // pushi -1
+	0x76,              // push0
+	0x43, 0xff, 0x06,  // callk kRestoreGame/kSaveGame (will get changed afterwards)
+	0x48               // ret
+};
+
+static void patchKSaveRestore(SegManager *segMan, reg_t methodAddress, byte id) {
+	Script *script = segMan->getScript(methodAddress.getSegment());
+	byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
+	memcpy(patchPtr, kSaveRestorePatch, sizeof(kSaveRestorePatch));
+	patchPtr[8] = id;
+}
+
+void GuestAdditions::patchGameSaveRestoreSCI16() const {
+	const Object *gameObject = _segMan->getObject(g_sci->getGameObject());
+	const Object *gameSuperObject = _segMan->getObject(gameObject->getSuperClassSelector());
+	if (!gameSuperObject)
+		gameSuperObject = gameObject;	// happens in KQ5CD, when loading saved games before r54510
+	byte kernelIdRestore = 0;
+	byte kernelIdSave = 0;
+
+	switch (g_sci->getGameId()) {
+	case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all
+	case GID_HOYLE2: // gets confused, see hoyle1
+	case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required
+	case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
+	case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
+		return;
+	default:
+		break;
+	}
+
+	uint16 kernelNamesSize = _kernel->getKernelNamesSize();
+	for (uint16 kernelNr = 0; kernelNr < kernelNamesSize; kernelNr++) {
+		Common::String kernelName = _kernel->getKernelName(kernelNr);
+		if (kernelName == "RestoreGame")
+			kernelIdRestore = kernelNr;
+		if (kernelName == "SaveGame")
+			kernelIdSave = kernelNr;
+		if (kernelName == "Save")
+			kernelIdSave = kernelIdRestore = kernelNr;
+	}
+
+	// Search for gameobject superclass ::restore
+	uint16 gameSuperObjectMethodCount = gameSuperObject->getMethodCount();
+	for (uint16 methodNr = 0; methodNr < gameSuperObjectMethodCount; methodNr++) {
+		uint16 selectorId = gameSuperObject->getFuncSelector(methodNr);
+		Common::String methodName = _kernel->getSelectorName(selectorId);
+		if (methodName == "restore") {
+				patchKSaveRestore(_segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore);
+		} else if (methodName == "save") {
+			if (g_sci->getGameId() != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
+					patchKSaveRestore(_segMan, gameSuperObject->getFunction(methodNr), kernelIdSave);
+			}
+		}
+	}
+
+	// Patch gameobject ::save for now for SCI0 - SCI1.1
+	// TODO: It seems this was never adjusted to superclass, but adjusting it now may cause
+	// issues with some game. Needs to get checked and then possibly changed.
+	const Object *patchObjectSave = gameObject;
+
+	// Search for gameobject ::save, if there is one patch that one too
+	uint16 patchObjectMethodCount = patchObjectSave->getMethodCount();
+	for (uint16 methodNr = 0; methodNr < patchObjectMethodCount; methodNr++) {
+		uint16 selectorId = patchObjectSave->getFuncSelector(methodNr);
+		Common::String methodName = _kernel->getSelectorName(selectorId);
+		if (methodName == "save") {
+			if (g_sci->getGameId() != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
+					patchKSaveRestore(_segMan, patchObjectSave->getFunction(methodNr), kernelIdSave);
+			}
+			break;
+		}
+	}
+}
+
+#ifdef ENABLE_SCI32
+static const byte SRDialogPatch[] = {
+	0x76,                   // push0
+	0x59, 0x00,             // &rest 0
+	0x43, 0x57, 0x00, 0x00, // callk kScummVMSaveLoad, 0
+	0x48                    // ret
+};
+
+void GuestAdditions::patchGameSaveRestoreSCI32(Script &script) const {
+	ObjMap &objMap = script.getObjectMap();
+	for (ObjMap::iterator it = objMap.begin(); it != objMap.end(); ++it) {
+		Object &obj = it->_value;
+		if (obj.getNameSelector().isNull()) {
+			continue;
+		}
+
+		if (Common::String(_segMan->derefString(obj.getNameSelector())) != "SRDialog") {
+			continue;
+		}
+
+		const uint16 methodCount = obj.getMethodCount();
+		for (uint16 methodNr = 0; methodNr < methodCount; ++methodNr) {
+			const uint16 selectorId = obj.getFuncSelector(methodNr);
+			const Common::String methodName = _kernel->getSelectorName(selectorId);
+			if (methodName == "doit") {
+				const reg_t methodAddress = obj.getFunction(methodNr);
+				byte *patchPtr = const_cast<byte *>(script.getBuf(methodAddress.getOffset()));
+				memcpy(patchPtr, SRDialogPatch, sizeof(SRDialogPatch));
+				break;
+			}
+		}
+	}
+}
+
+reg_t GuestAdditions::kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) const {
+	const bool isSave = (bool)argv[0].toSint16();
+	int saveNo;
+
+	if (isSave) {
+		GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
+		saveNo = dialog.runModalWithCurrentTarget();
+		if (saveNo != -1) {
+			reg_t descriptionId;
+			if (_segMan->isObject(argv[1])) {
+				descriptionId = readSelector(_segMan, argv[1], SELECTOR(data));
+			} else {
+				descriptionId = argv[1];
+			}
+			SciArray &description = *_segMan->lookupArray(descriptionId);
+			description.fromString(dialog.getResultString());
+		}
+	} else {
+		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
+		saveNo = dialog.runModalWithCurrentTarget();
+	}
+
+	if (saveNo > 0) {
+		// The autosave slot in ScummVM takes up slot 0, but in SCI the first
+		// non-autosave save game number needs to be 0, so reduce the save
+		// number here to match what would come from the normal SCI save/restore
+		// dialog. There is additional special code for handling the autosave
+		// game inside of kRestoreGame32.
+		--saveNo;
+	}
+
+	return make_reg(0, saveNo);
+}
 #endif
 
 #pragma mark -
diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h
index a6875d4..ebb328c 100644
--- a/engines/sci/engine/guest_additions.h
+++ b/engines/sci/engine/guest_additions.h
@@ -29,6 +29,8 @@ namespace Sci {
 
 struct EngineState;
 class GameFeatures;
+class Kernel;
+class Script;
 class SegManager;
 
 /**
@@ -43,7 +45,9 @@ class SegManager;
  */
 class GuestAdditions {
 public:
-	GuestAdditions(EngineState *state, GameFeatures *features);
+	GuestAdditions(EngineState *state, GameFeatures *features, Kernel *kernel);
+
+#pragma mark -
 
 	/**
 	 * Synchronises audio volume settings from ScummVM to the game. Called
@@ -65,6 +69,7 @@ public:
 private:
 	EngineState *_state;
 	GameFeatures *_features;
+	Kernel *_kernel;
 	SegManager *_segMan;
 
 	/**
@@ -117,6 +122,32 @@ public:
 	 * Guest additions hook for kDoSoundSetVolume.
 	 */
 	void kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const;
+
+	/**
+	 * Guest additions hook for SegManager::instantiateScript.
+	 */
+	void instantiateScriptHook(Script &script) const;
+#endif
+
+#pragma mark -
+#pragma mark Save & restore
+
+public:
+	/**
+	 * Patches game scripts to hook into the ScummVM launcher UI when a user
+	 * tries to save or restore a game from inside the game.
+	 */
+	void patchGameSaveRestore() const;
+
+private:
+	void patchGameSaveRestoreSCI16() const;
+
+#ifdef ENABLE_SCI32
+public:
+	reg_t kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) const;
+
+private:
+	void patchGameSaveRestoreSCI32(Script &script) const;
 #endif
 
 #pragma mark -
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 3d33701..e5b84dd 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -491,6 +491,7 @@ reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv);
 reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv);
 reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
 reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
+reg_t kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv);
 
 reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv);
 reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 6f696c4..5dbd5a5 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -777,12 +777,13 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(RespondsTo),        SIG_EVERYWHERE,           ".i",                    NULL,            NULL },
 	{ MAP_CALL(RestartGame),       SIG_EVERYWHERE,           "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "RestoreGame", kRestoreGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0]",    NULL,            NULL },
+	{ "RestoreGame", kRestoreGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri[r0]",       NULL,            NULL },
 #endif
 	{ MAP_CALL(RestoreGame),       SIG_EVERYWHERE,           "[r0]i[r0]",             NULL,            NULL },
 	{ MAP_CALL(Said),              SIG_EVERYWHERE,           "[r0]",                  NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "SaveGame", kSaveGame32,     SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0][r0]",  NULL,            NULL },
+	{ "SaveGame", kSaveGame32,     SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri[r0][r0]",     NULL,            NULL },
+	{ MAP_CALL(ScummVMSaveLoad),   SIG_SCI32, SIGFOR_ALL,    "i([ro])",               NULL,            NULL },
 #endif
 	{ MAP_CALL(SaveGame),          SIG_SCI16, SIGFOR_ALL,    "[r0]i[r0](r0)",         NULL,            NULL },
 	{ MAP_CALL(ScriptID),          SIG_EVERYWHERE,           "[io](i)",               NULL,            NULL },
@@ -1263,7 +1264,7 @@ static const char *const sci2_default_knames[] = {
 	/*0x54*/ "Dummy",
 	/*0x55*/ "DeleteKey",
 	/*0x56*/ "Dummy",
-	/*0x57*/ "Dummy",
+	/*0x57*/ "ScummVMSaveLoad", // Dummy in SSCI
 	/*0x58*/ "ListAt",
 	/*0x59*/ "ListIndexOf",
 	/*0x5a*/ "ListEachElementDo",
@@ -1427,7 +1428,7 @@ static const char *const sci21_default_knames[] = {
 	/*0x54*/ "HaveMouse",
 	/*0x55*/ "SetCursor",
 	/*0x56*/ "VibrateMouse",	// Dummy in SCI3
-	/*0x57*/ "Dummy",
+	/*0x57*/ "ScummVMSaveLoad", // Dummy in SSCI
 	/*0x58*/ "Dummy",
 	/*0x59*/ "Dummy",
 	/*0x5a*/ "List",
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 6f9aa0d..bdf9bb6 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -41,6 +41,7 @@
 #include "sci/sound/audio.h"
 #include "sci/console.h"
 #ifdef ENABLE_SCI32
+#include "sci/engine/guest_additions.h"
 #include "sci/resource.h"
 #endif
 
@@ -1179,34 +1180,10 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
 #ifdef ENABLE_SCI32
 
 reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
-	const bool isScummVMSave = argv[0].isNull();
-	Common::String gameName = "";
-	int16 saveNo;
-	Common::String saveDescription;
-	Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]);
-
-	if (isScummVMSave) {
-		// ScummVM call, from a patched Game::save
-		g_sci->_soundCmd->pauseAll(true);
-		GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
-		saveNo = dialog.runModalWithCurrentTarget();
-		g_sci->_soundCmd->pauseAll(false);
-
-		if (saveNo < 0) {
-			// User cancelled save
-			return NULL_REG;
-		}
-
-		saveDescription = dialog.getResultString();
-		if (saveDescription.empty()) {
-			saveDescription = dialog.createDefaultSaveDescription(saveNo);
-		}
-	} else {
-		// Native script call
-		gameName = s->_segMan->getString(argv[0]);
-		saveNo = argv[1].toSint16();
-		saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
-	}
+	const Common::String gameName = s->_segMan->getString(argv[0]);
+	int16 saveNo = argv[1].toSint16();
+	const Common::String saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+	const Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]);
 
 	debugC(kDebugLevelFile, "Game name %s save %d desc %s ver %s", gameName.c_str(), saveNo, saveDescription.c_str(), gameVersion.c_str());
 
@@ -1218,9 +1195,7 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 			// Autosave slot 1 is a "new game" save
 			saveNo = kNewGameId;
 		}
-	} else if (!isScummVMSave) {
-		// ScummVM save screen will give a pre-corrected save number, but native
-		// save-load will not
+	} else {
 		saveNo += kSaveIdShift;
 	}
 
@@ -1252,26 +1227,10 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
-	const bool isScummVMRestore = argv[0].isNull();
-	Common::String gameName = "";
+	const Common::String gameName = s->_segMan->getString(argv[0]);
 	int16 saveNo = argv[1].toSint16();
 	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
 
-	if (isScummVMRestore && saveNo == -1) {
-		// ScummVM call, either from lancher or a patched Game::restore
-		g_sci->_soundCmd->pauseAll(true);
-		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
-		saveNo = dialog.runModalWithCurrentTarget();
-		g_sci->_soundCmd->pauseAll(false);
-
-		if (saveNo < 0) {
-			// User cancelled restore
-			return s->r_acc;
-		}
-	} else {
-		gameName = s->_segMan->getString(argv[0]);
-	}
-
 	if (gameName == "Autosave" || gameName == "Autosv") {
 		if (saveNo == 0) {
 			// Autosave slot 0 is the autosave
@@ -1279,9 +1238,7 @@ reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
 			// Autosave slot 1 is a "new game" save
 			saveNo = kNewGameId;
 		}
-	} else if (!isScummVMRestore) {
-		// ScummVM save screen will give a pre-corrected save number, but native
-		// save-load will not
+	} else {
 		saveNo += kSaveIdShift;
 	}
 
@@ -1377,6 +1334,10 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
 	return argv[0];
 }
 
+reg_t kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) {
+	return g_sci->_guestAdditions->kScummVMSaveLoad(s, argc, argv);
+}
+
 #endif
 
 } // End of namespace Sci
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index f05fdc5..5a60a60 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -1229,19 +1229,6 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
 		if (g_sci->_gfxScreen)
 			g_sci->_gfxScreen->clearForRestoreGame();
 	}
-#ifdef ENABLE_SCI32
-	// Delete current planes/elements of actively loaded VM, only when our ScummVM dialogs are patched in
-	// We MUST NOT delete all planes/screen items. At least Space Quest 6 has a few in memory like for example
-	// the options plane, which are not re-added and are in memory all the time right from the start of the
-	// game. Sierra SCI32 did not clear planes, only scripts cleared the ones inside planes::elements.
-	if (getSciVersion() >= SCI_VERSION_2) {
-		if (!s->_delayedRestoreFromLauncher) {
-			// Only do it, when we are restoring regulary and not from launcher
-			// As it could result in option planes etc. on the screen (happens in gk1)
-			g_sci->_gfxFrameout->syncWithScripts(false);
-		}
-	}
-#endif
 
 	s->reset(true);
 	s->saveLoadWithSerializer(ser);	// FIXME: Error handling?
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index 1262569..d13d94a 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -24,6 +24,9 @@
 #include "sci/engine/seg_manager.h"
 #include "sci/engine/state.h"
 #include "sci/engine/script.h"
+#ifdef ENABLE_SCI32
+#include "sci/engine/guest_additions.h"
+#endif
 
 namespace Sci {
 
@@ -1041,6 +1044,9 @@ int SegManager::instantiateScript(int scriptNum) {
 	scr->initializeLocals(this);
 	scr->initializeClasses(this);
 	scr->initializeObjects(this, segmentId);
+#ifdef ENABLE_SCI32
+	g_sci->_guestAdditions->instantiateScriptHook(*scr);
+#endif
 
 	return segmentId;
 }
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 5f96a8e..4799276 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -127,95 +127,12 @@ void GfxFrameout::run() {
 	_planes.add(initPlane);
 }
 
-// SCI32 actually did not clear anything at all it seems on restore. The scripts actually cleared up
-// planes + screen items right before restoring. And after restoring they sync'd its internal planes list
-// as well.
 void GfxFrameout::clear() {
 	_planes.clear();
 	_visiblePlanes.clear();
 	_showList.clear();
 }
 
-// This is what Game::restore does, only needed when our ScummVM dialogs are patched in
-// It actually does one pass before actual restore deleting screen items + planes
-// And after restore it does another pass adding screen items + planes.
-// Attention: at least Space Quest 6's option plane seems to stay in memory right from the start and is not re-created.
-void GfxFrameout::syncWithScripts(bool addElements) {
-	EngineState *engineState = g_sci->getEngineState();
-	SegManager *segMan = engineState->_segMan;
-
-	// In case original save/restore dialogs are active, don't do anything
-	if (ConfMan.getBool("originalsaveload"))
-		return;
-
-	// Get planes list object
-	reg_t planesListObject = engineState->variables[VAR_GLOBAL][kGlobalVarPlanes];
-	reg_t planesListElements = readSelector(segMan, planesListObject, SELECTOR(elements));
-
-	List *planesList = segMan->lookupList(planesListElements);
-	reg_t planesNodeObject = planesList->first;
-
-	// Go through all elements of planes::elements
-	while (!planesNodeObject.isNull()) {
-		Node *planesNode = segMan->lookupNode(planesNodeObject);
-		reg_t planeObject = planesNode->value;
-
-		if (addElements) {
-			// Add this plane object
-			kernelAddPlane(planeObject);
-		}
-
-		reg_t planeCastsObject = readSelector(segMan, planeObject, SELECTOR(casts));
-		reg_t setListElements = readSelector(segMan, planeCastsObject, SELECTOR(elements));
-
-		// Now go through all elements of plane::casts::elements
-		List *planeCastsList = segMan->lookupList(setListElements);
-		reg_t planeCastsNodeObject = planeCastsList->first;
-
-		while (!planeCastsNodeObject.isNull()) {
-			Node *castsNode = segMan->lookupNode(planeCastsNodeObject);
-			reg_t castsObject = castsNode->value;
-
-			reg_t castsListElements = readSelector(segMan, castsObject, SELECTOR(elements));
-
-			List *castsList = segMan->lookupList(castsListElements);
-			reg_t castNodeObject = castsList->first;
-
-			while (!castNodeObject.isNull()) {
-				Node *castNode = segMan->lookupNode(castNodeObject);
-				reg_t castObject = castNode->value;
-
-				// read selector "-info-" of this object
-				// TODO: Seems to have been changed for SCI3
-				// Do NOT use getInfoSelector in here. SCI3 games did not use infoToa, but an actual selector.
-				// Maybe that selector is just a straight copy, but it needs to get verified/checked.
-				uint16 castInfoSelector = readSelectorValue(segMan, castObject, SELECTOR(_info_));
-
-				if (castInfoSelector & kInfoFlagViewInserted) {
-					if (addElements) {
-						// Flag set, so add this screen item
-						kernelAddScreenItem(castObject);
-					} else {
-						// Flag set, so delete this screen item
-						kernelDeleteScreenItem(castObject);
-					}
-				}
-
-				castNodeObject = castNode->succ;
-			}
-
-			planeCastsNodeObject = castsNode->succ;
-		}
-
-		if (!addElements) {
-			// Delete this plane object
-			kernelDeletePlane(planeObject);
-		}
-
-		planesNodeObject = planesNode->succ;
-	}
-}
-
 bool GfxFrameout::gameIsHiRes() const {
 	// QFG4 is always low resolution
 	if (g_sci->getGameId() == GID_QFG4) {
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index 9738203..208c5b4 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -58,7 +58,6 @@ public:
 	bool _isHiRes;
 
 	void clear();
-	void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in
 	void run();
 
 #pragma mark -
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index c8e500b..0797fd8 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -283,7 +283,7 @@ Common::Error SciEngine::run() {
 		_vocabulary = new Vocabulary(_resMan, false);
 
 	_gamestate = new EngineState(segMan);
-	_guestAdditions = new GuestAdditions(_gamestate, _features);
+	_guestAdditions = new GuestAdditions(_gamestate, _features, _kernel);
 	_eventMan = new EventManager(_resMan->detectFontExtended());
 #ifdef ENABLE_SCI32
 	if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
@@ -336,9 +336,7 @@ Common::Error SciEngine::run() {
 
 	syncSoundSettings();
 	_guestAdditions->syncAudioOptionsFromScummVM();
-
-	// Patch in our save/restore code, so that dialogs are replaced
-	patchGameSaveRestore();
+	_guestAdditions->patchGameSaveRestore();
 	setLauncherLanguage();
 
 	// Check whether loading a savestate was requested
@@ -495,194 +493,6 @@ bool SciEngine::gameHasFanMadePatch() {
 	return false;
 }
 
-static byte patchGameRestoreSave[] = {
-	0x39, 0x03,        // pushi 03
-	0x76,              // push0
-	0x38, 0xff, 0xff,  // pushi -1
-	0x76,              // push0
-	0x43, 0xff, 0x06,  // callk kRestoreGame/kSaveGame (will get changed afterwards)
-	0x48,              // ret
-};
-
-#ifdef ENABLE_SCI32
-// SCI2 version: Same as above, but the second parameter to callk is a word
-// and third parameter is a string reference
-static byte patchGameRestoreSci2[] = {
-	0x39, 0x03,             // pushi 03
-	0x76,                   // push0          (game name)
-	0x38, 0xff, 0xff,       // pushi -1       (save number)
-	0x89, 0x1b,             // lsg global[27] (game version)
-	0x43, 0xff, 0x06, 0x00, // callk kRestoreGame (0xFF will be overwritten by patcher)
-	0x48,                   // ret
-};
-
-static byte patchGameSaveSci2[] = {
-	0x39, 0x04,             // pushi 04
-	0x76,                   // push0          (game name)
-	0x38, 0xff, 0xff,       // pushi -1       (save number)
-	0x76,                   // push0          (save description)
-	0x89, 0x1b,             // lsg global[27] (game version)
-	0x43, 0xff, 0x08, 0x00, // callk kSaveGame (0xFF will be overwritten by patcher)
-	0x48,                   // ret
-};
-
-// SCI2.1mid version: Same as above, but with an extra subop parameter
-static byte patchGameRestoreSci21[] = {
-	0x39, 0x04,             // pushi 04
-	0x78,                   // push1          (subop)
-	0x76,                   // push0          (game name)
-	0x38, 0xff, 0xff,       // pushi -1       (save number)
-	0x89, 0x1b,             // lsg global[27] (game version)
-	0x43, 0xff, 0x08, 0x00, // callk kSave (0xFF will be overwritten by patcher)
-	0x48,                   // ret
-};
-
-static byte patchGameSaveSci21[] = {
-	0x39, 0x05,             // pushi 05
-	0x76,                   // push0          (subop)
-	0x76,                   // push0          (game name)
-	0x38, 0xff, 0xff,       // pushi -1       (save number)
-	0x76,                   // push0          (save description)
-	0x89, 0x1b,             // lsg global[27] (game version)
-	0x43, 0xff, 0x0a, 0x00, // callk kSave (0xFF will be overwritten by patcher)
-	0x48,                   // ret
-};
-#endif
-
-static void patchGameSaveRestoreCode(SegManager *segMan, reg_t methodAddress, byte id) {
-	Script *script = segMan->getScript(methodAddress.getSegment());
-	byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
-
-	memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave));
-	patchPtr[8] = id;
-}
-
-#ifdef ENABLE_SCI32
-static void patchGameSaveRestoreCodeSci2(SegManager *segMan, reg_t methodAddress, byte id, bool doRestore) {
-	Script *script = segMan->getScript(methodAddress.getSegment());
-	byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
-	int kcallOffset;
-
-	if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
-		if (doRestore) {
-			memcpy(patchPtr, patchGameRestoreSci2, sizeof(patchGameRestoreSci2));
-			kcallOffset = 9;
-		} else {
-			memcpy(patchPtr, patchGameSaveSci2, sizeof(patchGameSaveSci2));
-			kcallOffset = 10;
-		}
-	} else {
-		if (doRestore) {
-			memcpy(patchPtr, patchGameRestoreSci21, sizeof(patchGameRestoreSci21));
-			kcallOffset = 10;
-		} else {
-			memcpy(patchPtr, patchGameSaveSci21, sizeof(patchGameSaveSci21));
-			kcallOffset = 11;
-		}
-	}
-
-	patchPtr[kcallOffset] = id;
-	if (g_sci->isBE()) {
-		SWAP(patchPtr[kcallOffset + 1], patchPtr[kcallOffset + 2]);
-	}
-}
-#endif
-
-void SciEngine::patchGameSaveRestore() {
-	SegManager *segMan = _gamestate->_segMan;
-	const Object *gameObject = segMan->getObject(_gameObjectAddress);
-	const Object *gameSuperObject = segMan->getObject(gameObject->getSuperClassSelector());
-	if (!gameSuperObject)
-		gameSuperObject = gameObject;	// happens in KQ5CD, when loading saved games before r54510
-	byte kernelIdRestore = 0;
-	byte kernelIdSave = 0;
-
-	switch (_gameId) {
-	case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all
-	case GID_HOYLE2: // gets confused, see hoyle1
-	case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required
-	case GID_KQ7: // has custom save/load code
-	case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
-	case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
-	case GID_MOTHERGOOSEHIRES: // has custom save/load code
-	case GID_PHANTASMAGORIA: // has custom save/load code
-	case GID_PQSWAT: // has custom save/load code
-	case GID_SHIVERS: // has custom save/load code
-		return;
-	default:
-		break;
-	}
-
-	if (ConfMan.getBool("originalsaveload"))
-		return;
-
-	uint16 kernelNamesSize = _kernel->getKernelNamesSize();
-	for (uint16 kernelNr = 0; kernelNr < kernelNamesSize; kernelNr++) {
-		Common::String kernelName = _kernel->getKernelName(kernelNr);
-		if (kernelName == "RestoreGame")
-			kernelIdRestore = kernelNr;
-		if (kernelName == "SaveGame")
-			kernelIdSave = kernelNr;
-		if (kernelName == "Save")
-			kernelIdSave = kernelIdRestore = kernelNr;
-	}
-
-	// Search for gameobject superclass ::restore
-	uint16 gameSuperObjectMethodCount = gameSuperObject->getMethodCount();
-	for (uint16 methodNr = 0; methodNr < gameSuperObjectMethodCount; methodNr++) {
-		uint16 selectorId = gameSuperObject->getFuncSelector(methodNr);
-		Common::String methodName = _kernel->getSelectorName(selectorId);
-		if (methodName == "restore") {
-#ifdef ENABLE_SCI32
-			if (getSciVersion() >= SCI_VERSION_2) {
-				patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore, true);
-			} else
-#endif
-				patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore);
-		}
-		else if (methodName == "save") {
-			if (_gameId != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
-#ifdef ENABLE_SCI32
-				if (getSciVersion() >= SCI_VERSION_2) {
-					patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
-				} else
-#endif
-					patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave);
-			}
-		}
-	}
-
-	const Object *patchObjectSave = nullptr;
-
-	if (getSciVersion() < SCI_VERSION_2) {
-		// Patch gameobject ::save for now for SCI0 - SCI1.1
-		// TODO: It seems this was never adjusted to superclass, but adjusting it now may cause
-		// issues with some game. Needs to get checked and then possibly changed.
-		patchObjectSave = gameObject;
-	} else {
-		// Patch superclass ::save for SCI32
-		patchObjectSave = gameSuperObject;
-	}
-
-	// Search for gameobject ::save, if there is one patch that one too
-	uint16 patchObjectMethodCount = patchObjectSave->getMethodCount();
-	for (uint16 methodNr = 0; methodNr < patchObjectMethodCount; methodNr++) {
-		uint16 selectorId = patchObjectSave->getFuncSelector(methodNr);
-		Common::String methodName = _kernel->getSelectorName(selectorId);
-		if (methodName == "save") {
-			if (_gameId != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
-#ifdef ENABLE_SCI32
-				if (getSciVersion() >= SCI_VERSION_2) {
-					patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
-				} else
-#endif
-					patchGameSaveRestoreCode(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave);
-			}
-			break;
-		}
-	}
-}
-
 bool SciEngine::initGame() {
 	// Script 0 needs to be allocated here before anything else!
 	int script0Segment = _gamestate->_segMan->getScriptSegment(0, SCRIPT_GET_LOCK);
@@ -851,7 +661,7 @@ void SciEngine::runGame() {
 			_gamestate->_segMan->resetSegMan();
 			initGame();
 			initStackBaseWithSelector(SELECTOR(play));
-			patchGameSaveRestore();
+			_guestAdditions->patchGameSaveRestore();
 			setLauncherLanguage();
 			_gamestate->gameIsRestarting = GAMEISRESTARTING_RESTART;
 			_gamestate->_throttleLastTime = 0;
@@ -863,7 +673,7 @@ void SciEngine::runGame() {
 			_gamestate->abortScriptProcessing = kAbortNone;
 			_gamestate->_executionStack.clear();
 			initStackBaseWithSelector(SELECTOR(replay));
-			patchGameSaveRestore();
+			_guestAdditions->patchGameSaveRestore();
 			setLauncherLanguage();
 			_gamestate->shrinkStackToBase();
 			_gamestate->abortScriptProcessing = kAbortNone;
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 744a1b4..ef2fb3b 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -314,8 +314,6 @@ public:
 	bool checkSelectorBreakpoint(BreakpointType breakpointType, reg_t send_obj, int selector);
 	bool checkAddressBreakpoint(const reg32_t &address);
 
-	void patchGameSaveRestore();
-
 public:
 
 	/**


Commit: c8486395fab2d34b688c47a941a8a0c79cf164d1
    https://github.com/scummvm/scummvm/commit/c8486395fab2d34b688c47a941a8a0c79cf164d1
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:35-05:00

Commit Message:
SCI: Clean up unnecessary delayed restore flags

_delayedRestoreGame is always set and cleared at the same time as
_delayedRestoreGameId, and _delayedRestoreFromLauncher is written
but never read.

Changed paths:
    engines/sci/detection.cpp
    engines/sci/engine/kevent.cpp
    engines/sci/engine/kgraphics.cpp
    engines/sci/engine/kvideo.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/state.cpp
    engines/sci/engine/state.h
    engines/sci/graphics/portrait.cpp
    engines/sci/sci.cpp


diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index 13b3326..272e332 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -853,7 +853,6 @@ void SciMetaEngine::removeSaveState(const char *target, int slot) const {
 
 Common::Error SciEngine::loadGameState(int slot) {
 	_gamestate->_delayedRestoreGameId = slot;
-	_gamestate->_delayedRestoreGame = true;
 	return Common::kNoError;
 }
 
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index a006306..e290878 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -76,7 +76,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
 
 	curEvent = g_sci->getEventManager()->getSciEvent(mask);
 
-	if (s->_delayedRestoreGame) {
+	if (s->_delayedRestoreGameId != -1) {
 		// delayed restore game from ScummVM menu got triggered
 		gamestate_delayedrestore(s);
 		return NULL_REG;
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index 07a1c47..a025a88 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -397,7 +397,7 @@ reg_t kWait(EngineState *s, int argc, reg_t *argv) {
 
 	s->wait(sleep_time);
 
-	if (s->_delayedRestoreGame) {
+	if (s->_delayedRestoreGameId != -1) {
 		// delayed restore game from ScummVM menu got triggered
 		gamestate_delayedrestore(s);
 		return NULL_REG;
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index e4f2ff7..6f93b04 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -110,7 +110,7 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
 			if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
 				skipVideo = true;
 		}
-		if (g_sci->getEngineState()->_delayedRestoreGame)
+		if (g_sci->getEngineState()->_delayedRestoreGameId != -1)
 			skipVideo = true;
 
 		g_system->delayMillis(10);
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 5a60a60..d7fe009 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -1276,8 +1276,6 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
 
 	// signal restored game to game scripts
 	s->gameIsRestarting = GAMEISRESTARTING_RESTORE;
-
-	s->_delayedRestoreFromLauncher = false;
 }
 
 bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata *meta) {
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index 6e22297..44aecdf 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -86,10 +86,7 @@ void EngineState::reset(bool isRestoring) {
 		g_sci->_guestAdditions->reset();
 	}
 
-	// reset delayed restore game functionality
-	_delayedRestoreGame = false;
-	_delayedRestoreGameId = 0;
-	_delayedRestoreFromLauncher = false;
+	_delayedRestoreGameId = -1;
 
 	executionStackBase = 0;
 	_executionStackPosChanged = false;
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index dd6b728..c41f86c 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -143,9 +143,7 @@ public:
 	int16 _lastSaveNewId;    // last newly created filename-id by kSaveGame
 
 	// see detection.cpp / SciEngine::loadGameState()
-	bool _delayedRestoreGame;  // boolean, that triggers delayed restore (triggered by ScummVM menu)
 	int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu)
-	bool _delayedRestoreFromLauncher; // is set, when the the delayed restore game was triggered from launcher
 
 	uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms
 
diff --git a/engines/sci/graphics/portrait.cpp b/engines/sci/graphics/portrait.cpp
index 675883e..6b66ac5 100644
--- a/engines/sci/graphics/portrait.cpp
+++ b/engines/sci/graphics/portrait.cpp
@@ -306,7 +306,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
 				if (curEvent.type == SCI_EVENT_MOUSE_PRESS ||
 					(curEvent.type == SCI_EVENT_KEYBOARD && curEvent.character == SCI_KEY_ESC) ||
 					g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame ||
-					g_sci->getEngineState()->_delayedRestoreGame)
+					g_sci->getEngineState()->_delayedRestoreGameId != -1)
 					userAbort = true;
 				curPosition = _audio->getAudioPosition();
 			} while ((curPosition != -1) && (curPosition < timerPosition) && (!userAbort));
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 0797fd8..a2ace9a 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -342,9 +342,7 @@ Common::Error SciEngine::run() {
 	// Check whether loading a savestate was requested
 	int directSaveSlotLoading = ConfMan.getInt("save_slot");
 	if (directSaveSlotLoading >= 0) {
-		_gamestate->_delayedRestoreGame = true;
 		_gamestate->_delayedRestoreGameId = directSaveSlotLoading;
-		_gamestate->_delayedRestoreFromLauncher = true;
 
 		// Jones only initializes its menus when restarting/restoring, thus set
 		// the gameIsRestarting flag here before initializing. Fixes bug #6536.


Commit: 4c0f2a37384df0ba14f55f223f47a26f208d0199
    https://github.com/scummvm/scummvm/commit/4c0f2a37384df0ba14f55f223f47a26f208d0199
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:35-05:00

Commit Message:
SCI: Move ScummVM kernel calls to 0xe0

Changed paths:
    engines/sci/engine/guest_additions.cpp
    engines/sci/engine/kernel.cpp
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/script_patches.cpp


diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
index 6477e32..8ac780a 100644
--- a/engines/sci/engine/guest_additions.cpp
+++ b/engines/sci/engine/guest_additions.cpp
@@ -315,10 +315,10 @@ void GuestAdditions::patchGameSaveRestoreSCI16() const {
 
 #ifdef ENABLE_SCI32
 static const byte SRDialogPatch[] = {
-	0x76,                   // push0
-	0x59, 0x00,             // &rest 0
-	0x43, 0x57, 0x00, 0x00, // callk kScummVMSaveLoad, 0
-	0x48                    // ret
+	0x76,                                 // push0
+	0x59, 0x01,                           // &rest 1
+	0x43, kScummVMSaveLoadId, 0x00, 0x00, // callk kScummVMSaveLoad, 0
+	0x48                                  // ret
 };
 
 void GuestAdditions::patchGameSaveRestoreSCI32(Script &script) const {
diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp
index 13a836a..68a09e9 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -905,11 +905,6 @@ void Kernel::loadKernelNames(GameFeatures *features) {
 		} else {
 			// Normal SCI2.1 kernel table
 			_kernelNames = Common::StringArray(sci21_default_knames, kKernelEntriesSci21);
-
-			// Used by script patcher to remove CPU spinning on kGetTime
-			if (g_sci->getGameId() == GID_HOYLE5) {
-				_kernelNames[0x4f] = "Wait";
-			}
 		}
 		break;
 
@@ -936,6 +931,32 @@ void Kernel::loadKernelNames(GameFeatures *features) {
 		break;
 	}
 
+#ifdef ENABLE_SCI32
+	// Reserve a high range of kernel call IDs (0xe0 to 0xef) that can be used
+	// by ScummVM to improve integration and fix bugs in games that require
+	// more help than can be provided by a simple script patch (e.g. spinloops
+	// in Hoyle5).
+	// Using a new high range instead of just replacing dummied kernel calls in
+	// the normal kernel range is intended to avoid any conflicts with fangames
+	// that might try to add their own kernel calls in the same manner. It also
+	// helps to separate ScummVM interpreter's kernel calls from SSCI's standard
+	// kernel calls.
+	if (getSciVersion() >= SCI_VERSION_2) {
+		const uint kernelListSize = _kernelNames.size();
+		_kernelNames.resize(0xe2);
+		for (uint id = kernelListSize; id < 0xe0; ++id) {
+			_kernelNames[id] = "Dummy";
+		}
+
+		// Used by Hoyle5 script patches to remove CPU spinning on kGetTime
+		// (this repurposes the existing SCI16 kWait call that was removed in SCI32)
+		_kernelNames[kScummVMWaitId] = "Wait";
+
+		// Used by GuestAdditions to support integrated save/load dialogue
+		_kernelNames[kScummVMSaveLoadId] = "ScummVMSaveLoad";
+	}
+#endif
+
 	mapFunctions();
 }
 
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index e5b84dd..2b61a9e 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -98,6 +98,11 @@ struct SciWorkaroundEntry;	// from workarounds.h
 
 // ---- Kernel signatures -----------------------------------------------------
 
+enum {
+	kScummVMWaitId     = 0xe0,
+	kScummVMSaveLoadId = 0xe1
+};
+
 // internal kernel signature data
 enum {
 	SIG_TYPE_NULL          =  0x01, // may be 0:0       [0]
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 5dbd5a5..f075da3 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -1264,7 +1264,7 @@ static const char *const sci2_default_knames[] = {
 	/*0x54*/ "Dummy",
 	/*0x55*/ "DeleteKey",
 	/*0x56*/ "Dummy",
-	/*0x57*/ "ScummVMSaveLoad", // Dummy in SSCI
+	/*0x57*/ "Dummy",
 	/*0x58*/ "ListAt",
 	/*0x59*/ "ListIndexOf",
 	/*0x5a*/ "ListEachElementDo",
@@ -1428,7 +1428,7 @@ static const char *const sci21_default_knames[] = {
 	/*0x54*/ "HaveMouse",
 	/*0x55*/ "SetCursor",
 	/*0x56*/ "VibrateMouse",	// Dummy in SCI3
-	/*0x57*/ "ScummVMSaveLoad", // Dummy in SSCI
+	/*0x57*/ "Dummy",
 	/*0x58*/ "Dummy",
 	/*0x59*/ "Dummy",
 	/*0x5a*/ "List",
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 97e53a0..82a10d5 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -704,8 +704,8 @@ static const SciScriptPatcherEntry freddypharkasSignatures[] = {
 // Several scripts in Hoyle5 contain a subroutine which spins on kGetTime until
 // a certain number of ticks elapse. Since this wastes CPU and makes ScummVM
 // unresponsive, the kWait kernel function (which was removed in SCI2) is
-// reintroduced at 0x4f in kernel.cpp only for Hoyle5, and the spin subroutines
-// are patched here to call that function instead.
+// reintroduced for Hoyle5, and the spin subroutines are patched here to call
+// that function instead.
 // Applies to at least: English Demo
 static const uint16 hoyle5SignatureSpinLoop[] = {
 	SIG_MAGICDWORD,
@@ -719,10 +719,10 @@ static const uint16 hoyle5SignatureSpinLoop[] = {
 };
 
 static const uint16 hoyle5PatchSpinLoop[] = {
-	0x78,                           // push1
-	0x8f, 0x01,                     // lsp param[1]
-	0x43, 0x4f, PATCH_UINT16(0x02), // callk Wait, $2
-	0x48,                           // ret
+	0x78,                                     // push1
+	0x8f, 0x01,                               // lsp param[1]
+	0x43, kScummVMWaitId, PATCH_UINT16(0x02), // callk Wait, $2
+	0x48,                                     // ret
 	PATCH_END
 };
 


Commit: 6f75bed90cdf36878f26cc1e091483efcc8091c8
    https://github.com/scummvm/scummvm/commit/6f75bed90cdf36878f26cc1e091483efcc8091c8
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:35-05:00

Commit Message:
SCI32: Remove original save/load option from games without ScummVM save integration

Changed paths:
    engines/sci/detection_tables.h
    engines/sci/engine/guest_additions.cpp
    engines/sci/engine/guest_additions.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/savegame.cpp


diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index c37dd4f..82d3bdd 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -1814,10 +1814,9 @@ static const struct ADGameDescription SciGameDescriptions[] = {
                             GUIO_LINKMUSICTOSFX, \
                             GUIO_LINKSPEECHTOSFX, \
                             GUIO_NOASPECT)
-#define GUIO_KQ7      GUIO4(GUIO_NOASPECT, \
+#define GUIO_KQ7      GUIO3(GUIO_NOASPECT, \
                             GUIO_LINKMUSICTOSFX, \
-                            GUIO_LINKSPEECHTOSFX, \
-                            GAMEOPTION_ORIGINAL_SAVELOAD)
+                            GUIO_LINKSPEECHTOSFX)
 
 	// King's Quest 7 - English Windows (from the King's Quest Collection)
 	// Executable scanning reports "2.100.002", VERSION file reports "1.4"
@@ -2887,7 +2886,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 #define GUIO_MOTHERGOOSEHIRES GUIO4(GUIO_NOSUBTITLES, \
                                     GUIO_NOASPECT, \
                                     GUIO_LINKSPEECHTOSFX, \
-                                    GAMEOPTION_ORIGINAL_SAVELOAD)
+                                    GUIO_NOLAUNCHLOAD)
 
 	// Mixed-Up Mother Goose Deluxe - English Windows/DOS CD (supplied by markcoolio in bug report #2723810)
 	// Executable scanning reports "2.100.002"
@@ -3414,12 +3413,11 @@ static const struct ADGameDescription SciGameDescriptions[] = {
                                GUIO_LINKSPEECHTOSFX, \
                                GUIO_NOASPECT, \
                                GUIO_NOLAUNCHLOAD)
-#define GUIO_PQSWAT      GUIO7(GUIO_NOSUBTITLES, \
+#define GUIO_PQSWAT      GUIO6(GUIO_NOSUBTITLES, \
                                GUIO_NOMIDI, \
                                GUIO_LINKMUSICTOSFX, \
                                GUIO_LINKSPEECHTOSFX, \
                                GUIO_NOASPECT, \
-                               GAMEOPTION_ORIGINAL_SAVELOAD, \
                                GAMEOPTION_ENABLE_BLACK_LINED_VIDEO)
 
 	// Police Quest: SWAT - English DOS/Windows Demo (from jvprat)
@@ -3946,11 +3944,10 @@ static const struct ADGameDescription SciGameDescriptions[] = {
                                 GUIO_LINKSPEECHTOSFX, \
                                 GUIO_LINKMUSICTOSFX, \
                                 GUIO_NOASPECT)
-#define GUIO_SHIVERS      GUIO6(GUIO_NOMIDI, \
+#define GUIO_SHIVERS      GUIO5(GUIO_NOMIDI, \
                                 GUIO_LINKSPEECHTOSFX, \
                                 GUIO_LINKMUSICTOSFX, \
                                 GUIO_NOASPECT, \
-                                GAMEOPTION_ORIGINAL_SAVELOAD, \
                                 GAMEOPTION_ENABLE_BLACK_LINED_VIDEO)
 
 	// Shivers - English Windows (from jvprat)
diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
index 8ac780a..57de4e3 100644
--- a/engines/sci/engine/guest_additions.cpp
+++ b/engines/sci/engine/guest_additions.cpp
@@ -210,14 +210,20 @@ void GuestAdditions::kDoSoundSetVolumeHook(const reg_t soundObj, const int16 vol
 }
 
 void GuestAdditions::instantiateScriptHook(Script &script) const {
-	if (getSciVersion() < SCI_VERSION_2) {
+	if (getSciVersion() < SCI_VERSION_2 || ConfMan.getBool("originalsaveload")) {
 		return;
 	}
 
-	// 64990 is the system script containing SRDialog. This script is normally
-	// used by the main Game object, but it is not loaded immediately, so we
-	// wait for it to be loaded before patching it
-	if (!ConfMan.getBool("originalsaveload") && script.getScriptNumber() == 64990) {
+	if (g_sci->getGameId() == GID_TORIN && script.getScriptNumber() == 64866) {
+		patchGameSaveRestoreTorin(script);
+	} else if (script.getScriptNumber() == 64990) {
+		// 64990 is the system script containing SRDialog. This script is used
+		// by the main Game object, but it is not loaded immediately, so we wait
+		// for it to be loaded before patching it. Attempting to preload this
+		// script early for patching will cause the order of entries in the
+		// segment table to change (versus save games that are not patched),
+		// breaking persistent objects (like the control panel in SQ6) which
+		// require reg_ts created during game startup to always be the same
 		patchGameSaveRestoreSCI32(script);
 	}
 }
@@ -347,8 +353,38 @@ void GuestAdditions::patchGameSaveRestoreSCI32(Script &script) const {
 	}
 }
 
+static const byte SRTorinPatch[] = {
+	0x38, 0x8d, 0x00,                     // pushi $8d (new)
+	0x76,                                 // push0
+	0x51, 0x0f,                           // class $f (Str)
+	0x4a, 0x04, 0x00,                     // send 4
+	0xa3, 0x01,                           // sal 1
+	0x76,                                 // push0
+	0x59, 0x01,                           // &rest 1
+	0x43, kScummVMSaveLoadId, 0x00, 0x00, // callk kScummVMSaveLoad, 0
+	0x48                                  // ret
+};
+
+void GuestAdditions::patchGameSaveRestoreTorin(Script &script) const {
+	const uint16 address = script.validateExportFunc(2, true);
+	byte *patchPtr = const_cast<byte *>(script.getBuf(address));
+	memcpy(patchPtr, SRTorinPatch, sizeof(SRTorinPatch));
+	if (g_sci->isBE()) {
+		SWAP(patchPtr[1], patchPtr[2]);
+		SWAP(patchPtr[8], patchPtr[9]);
+	}
+}
+
 reg_t GuestAdditions::kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) const {
-	const bool isSave = (bool)argv[0].toSint16();
+	if (g_sci->getGameId() == GID_TORIN) {
+		return promptSaveRestoreTorin(s, argc, argv);
+	}
+
+	return promptSaveRestoreDefault(s, argc, argv);
+}
+
+reg_t GuestAdditions::promptSaveRestoreDefault(EngineState *s, int argc, reg_t *argv) const {
+	const bool isSave = (argc > 0);
 	int saveNo;
 
 	if (isSave) {
@@ -356,10 +392,10 @@ reg_t GuestAdditions::kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) co
 		saveNo = dialog.runModalWithCurrentTarget();
 		if (saveNo != -1) {
 			reg_t descriptionId;
-			if (_segMan->isObject(argv[1])) {
-				descriptionId = readSelector(_segMan, argv[1], SELECTOR(data));
+			if (_segMan->isObject(argv[0])) {
+				descriptionId = readSelector(_segMan, argv[0], SELECTOR(data));
 			} else {
-				descriptionId = argv[1];
+				descriptionId = argv[0];
 			}
 			SciArray &description = *_segMan->lookupArray(descriptionId);
 			description.fromString(dialog.getResultString());
@@ -380,6 +416,44 @@ reg_t GuestAdditions::kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) co
 
 	return make_reg(0, saveNo);
 }
+
+reg_t GuestAdditions::promptSaveRestoreTorin(EngineState *s, int argc, reg_t *argv) const {
+	const bool isSave = (argc > 0 && (bool)argv[0].toSint16());
+	int saveNo;
+
+	if (isSave) {
+		GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
+		saveNo = dialog.runModalWithCurrentTarget();
+		if (saveNo != -1) {
+			reg_t descriptionId = s->variables[VAR_LOCAL][1];
+			reg_t dataId;
+			SciArray &description = *_segMan->allocateArray(kArrayTypeString, 0, &dataId);
+			description.fromString(dialog.getResultString());
+			writeSelector(_segMan, descriptionId, SELECTOR(data), dataId);
+		}
+	} else {
+		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
+		saveNo = dialog.runModalWithCurrentTarget();
+	}
+
+	if (saveNo > 0) {
+		// The autosave slot in ScummVM takes up slot 0, but in SCI the first
+		// non-autosave save game number needs to be 0, so reduce the save
+		// number here to match what would come from the normal SCI save/restore
+		// dialog. There is additional special code for handling the autosave
+		// game inside of kRestoreGame32.
+		--saveNo;
+	}
+
+	if (saveNo != -1) {
+		assert(s->variablesMax[VAR_LOCAL] > 2);
+		s->variables[VAR_LOCAL][2] = make_reg(0, saveNo);
+		s->variables[VAR_LOCAL][3] = make_reg(0, isSave ? 1 : 0);
+	}
+
+	return make_reg(0, saveNo != -1);
+}
+
 #endif
 
 #pragma mark -
diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h
index ebb328c..f9c1691 100644
--- a/engines/sci/engine/guest_additions.h
+++ b/engines/sci/engine/guest_additions.h
@@ -148,6 +148,10 @@ public:
 
 private:
 	void patchGameSaveRestoreSCI32(Script &script) const;
+	void patchGameSaveRestoreTorin(Script &script) const;
+
+	reg_t promptSaveRestoreDefault(EngineState *s, int argc, reg_t *argv) const;
+	reg_t promptSaveRestoreTorin(EngineState *s, int argc, reg_t *argv) const;
 #endif
 
 #pragma mark -
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index f075da3..7e3f4a8 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -783,7 +783,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(Said),              SIG_EVERYWHERE,           "[r0]",                  NULL,            NULL },
 #ifdef ENABLE_SCI32
 	{ "SaveGame", kSaveGame32,     SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri[r0][r0]",     NULL,            NULL },
-	{ MAP_CALL(ScummVMSaveLoad),   SIG_SCI32, SIGFOR_ALL,    "i([ro])",               NULL,            NULL },
+	{ MAP_CALL(ScummVMSaveLoad),   SIG_SCI32, SIGFOR_ALL,    "([iro])([ro0])",        NULL,            NULL },
 #endif
 	{ MAP_CALL(SaveGame),          SIG_SCI16, SIGFOR_ALL,    "[r0]i[r0](r0)",         NULL,            NULL },
 	{ MAP_CALL(ScriptID),          SIG_EVERYWHERE,           "[io](i)",               NULL,            NULL },
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index d7fe009..17bdebc 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -48,6 +48,7 @@
 #include "sci/sound/music.h"
 
 #ifdef ENABLE_SCI32
+#include "sci/engine/guest_additions.h"
 #include "sci/graphics/cursor32.h"
 #include "sci/graphics/frameout.h"
 #include "sci/graphics/palette32.h"
@@ -256,7 +257,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
 				ObjMap objects = scr->getObjectMap();
 				for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it)
 					it->_value.syncBaseObject(SciSpan<const byte>(scr->getBuf(it->_value.getPos().getOffset()), scr->getBufSize() - it->_value.getPos().getOffset()));
-
 			}
 
 			// Sync the script's string heap
@@ -297,6 +297,10 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
 					}
 				}
 			}
+
+			if (s.isLoading() && pass == passes) {
+				g_sci->_guestAdditions->instantiateScriptHook(*scr);
+			}
 		}
 	}
 }


Commit: 1ed185bbbb59891a906e316956f8b09419094ef3
    https://github.com/scummvm/scummvm/commit/1ed185bbbb59891a906e316956f8b09419094ef3
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:35-05:00

Commit Message:
SCI: Implement delayed restore for SCI32 and move implementations to GuestAdditions

Changed paths:
    engines/sci/engine/guest_additions.cpp
    engines/sci/engine/guest_additions.h
    engines/sci/engine/kevent.cpp
    engines/sci/engine/kgraphics.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/savegame.h
    engines/sci/engine/selector.cpp
    engines/sci/engine/selector.h


diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
index 57de4e3..0ef6d99 100644
--- a/engines/sci/engine/guest_additions.cpp
+++ b/engines/sci/engine/guest_additions.cpp
@@ -23,9 +23,11 @@
 #include "audio/mixer.h"
 #include "common/config-manager.h"
 #include "common/gui_options.h"
+#include "common/savefile.h"
 #include "sci/engine/features.h"
 #include "sci/engine/guest_additions.h"
 #include "sci/engine/kernel.h"
+#include "sci/engine/savegame.h"
 #include "sci/engine/state.h"
 #include "sci/engine/vm.h"
 #ifdef ENABLE_SCI32
@@ -209,8 +211,16 @@ void GuestAdditions::kDoSoundSetVolumeHook(const reg_t soundObj, const int16 vol
 	}
 }
 
-void GuestAdditions::instantiateScriptHook(Script &script) const {
-	if (getSciVersion() < SCI_VERSION_2 || ConfMan.getBool("originalsaveload")) {
+void GuestAdditions::instantiateScriptHook(Script &script, const bool ignoreDelayedRestore) const {
+	if (getSciVersion() < SCI_VERSION_2) {
+		return;
+	}
+
+	// If there is a delayed restore, we still want to patch the script so
+	// that the automatic return of the game ID works, but we do not want to
+	// patch the scripts that get restored
+	if (ConfMan.getBool("originalsaveload") &&
+		(ignoreDelayedRestore || _state->_delayedRestoreGameId == -1)) {
 		return;
 	}
 
@@ -228,10 +238,30 @@ void GuestAdditions::instantiateScriptHook(Script &script) const {
 	}
 }
 
+void GuestAdditions::segManSaveLoadScriptHook(Script &script) const {
+	instantiateScriptHook(script, true);
+}
+
+bool GuestAdditions::kGetEventHook() const {
+	if (_state->_delayedRestoreGameId != -1) {
+		g_sci->_guestAdditions->restoreFromLauncher();
+		return true;
+	}
+	return false;
+}
+
+bool GuestAdditions::kWaitHook() const {
+	if (_state->_delayedRestoreGameId != -1) {
+		g_sci->_guestAdditions->restoreFromLauncher();
+		return true;
+	}
+	return false;
+}
+
 #endif
 
 #pragma mark -
-#pragma mark Save & restore
+#pragma mark Integrated save & restore
 
 void GuestAdditions::patchGameSaveRestore() const {
 	if (ConfMan.getBool("originalsaveload") || getSciVersion() >= SCI_VERSION_2)
@@ -401,8 +431,12 @@ reg_t GuestAdditions::promptSaveRestoreDefault(EngineState *s, int argc, reg_t *
 			description.fromString(dialog.getResultString());
 		}
 	} else {
-		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
-		saveNo = dialog.runModalWithCurrentTarget();
+		if (s->_delayedRestoreGameId != -1) {
+			saveNo = s->_delayedRestoreGameId;
+		} else {
+			GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
+			saveNo = dialog.runModalWithCurrentTarget();
+		}
 	}
 
 	if (saveNo > 0) {
@@ -432,8 +466,12 @@ reg_t GuestAdditions::promptSaveRestoreTorin(EngineState *s, int argc, reg_t *ar
 			writeSelector(_segMan, descriptionId, SELECTOR(data), dataId);
 		}
 	} else {
-		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
-		saveNo = dialog.runModalWithCurrentTarget();
+		if (s->_delayedRestoreGameId != -1) {
+			saveNo = s->_delayedRestoreGameId;
+		} else {
+			GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
+			saveNo = dialog.runModalWithCurrentTarget();
+		}
 	}
 
 	if (saveNo > 0) {
@@ -457,6 +495,51 @@ reg_t GuestAdditions::promptSaveRestoreTorin(EngineState *s, int argc, reg_t *ar
 #endif
 
 #pragma mark -
+#pragma mark Restore from launcher
+
+void GuestAdditions::restoreFromLauncher() const {
+	assert(_state->_delayedRestoreGameId != -1);
+
+#ifdef ENABLE_SCI32
+	if (getSciVersion() >= SCI_VERSION_2) {
+		// In SQ6, delayed restore should not happen until room 100 (the Sierra
+		// logo & main menu room) is loaded, otherwise the game scripts will try
+		// to make calls to the subtitles window, which does not exist until
+		// after the main menu. The game scripts check if the current room is
+		// 100 and avoids making calls to the subtitles window if it is.
+		if (g_sci->getGameId() == GID_SQ6 &&
+			_state->variables[VAR_GLOBAL][kGlobalVarCurrentRoomNo] != make_reg(0, 100)) {
+
+			return;
+		}
+
+		// When `Game::restore` is invoked, it will call to `Restore::doit`
+		// which will automatically return the `_delayedRestoreGameId` instead
+		// of prompting the user for a save game
+		invokeSelector(g_sci->getGameObject(), SELECTOR(restore));
+	} else {
+#else
+	{
+#endif
+		int savegameId = _state->_delayedRestoreGameId; // delayedRestoreGameId gets destroyed within gamestate_restore()!
+		Common::String fileName = g_sci->getSavegameName(savegameId);
+		Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName);
+
+		if (in) {
+			// found a savegame file
+			gamestate_restore(_state, in);
+			delete in;
+			if (_state->r_acc != make_reg(0, 1)) {
+				gamestate_afterRestoreFixUp(_state, savegameId);
+				return;
+			}
+		}
+
+		error("Restoring gamestate '%s' failed", fileName.c_str());
+	}
+}
+
+#pragma mark -
 #pragma mark Message type sync
 
 void GuestAdditions::syncMessageTypeFromScummVM() const {
diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h
index f9c1691..bf4866b 100644
--- a/engines/sci/engine/guest_additions.h
+++ b/engines/sci/engine/guest_additions.h
@@ -126,11 +126,26 @@ public:
 	/**
 	 * Guest additions hook for SegManager::instantiateScript.
 	 */
-	void instantiateScriptHook(Script &script) const;
+	void instantiateScriptHook(Script &script, const bool ignoreDelayedRestore = false) const;
+
+	/**
+	 * Guest additions hook for SegManager::saveLoadWithSerializer.
+	 */
+	void segManSaveLoadScriptHook(Script &script) const;
 #endif
 
+	/**
+	 * Guest additions hook for kGetEvent.
+	 */
+	bool kGetEventHook() const;
+
+	/**
+	 * Guest additions hook for kWait.
+	 */
+	bool kWaitHook() const;
+
 #pragma mark -
-#pragma mark Save & restore
+#pragma mark Integrated save & restore
 
 public:
 	/**
@@ -155,6 +170,16 @@ private:
 #endif
 
 #pragma mark -
+#pragma mark Restore from launcher
+
+private:
+	/**
+	 * Invokes the game's restore mechanism to load a save game that was
+	 * selected from the ScummVM launcher.
+	 */
+	void restoreFromLauncher() const;
+
+#pragma mark -
 #pragma mark Message type sync
 
 private:
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index e290878..18205a4 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -24,6 +24,7 @@
 
 #include "sci/sci.h"
 #include "sci/engine/features.h"
+#include "sci/engine/guest_additions.h"
 #include "sci/engine/kernel.h"
 #include "sci/engine/savegame.h"
 #include "sci/engine/selector.h"
@@ -76,9 +77,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
 
 	curEvent = g_sci->getEventManager()->getSciEvent(mask);
 
-	if (s->_delayedRestoreGameId != -1) {
-		// delayed restore game from ScummVM menu got triggered
-		gamestate_delayedrestore(s);
+	if (g_sci->_guestAdditions->kGetEventHook()) {
 		return NULL_REG;
 	}
 
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index a025a88..ffb9042 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -32,6 +32,7 @@
 #include "sci/event.h"
 #include "sci/resource.h"
 #include "sci/engine/features.h"
+#include "sci/engine/guest_additions.h"
 #include "sci/engine/savegame.h"
 #include "sci/engine/state.h"
 #include "sci/engine/selector.h"
@@ -397,9 +398,7 @@ reg_t kWait(EngineState *s, int argc, reg_t *argv) {
 
 	s->wait(sleep_time);
 
-	if (s->_delayedRestoreGameId != -1) {
-		// delayed restore game from ScummVM menu got triggered
-		gamestate_delayedrestore(s);
+	if (g_sci->_guestAdditions->kWaitHook()) {
 		return NULL_REG;
 	}
 
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 17bdebc..057d590 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -299,7 +299,7 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
 			}
 
 			if (s.isLoading() && pass == passes) {
-				g_sci->_guestAdditions->instantiateScriptHook(*scr);
+				g_sci->_guestAdditions->segManSaveLoadScriptHook(*scr);
 			}
 		}
 	}
@@ -1094,24 +1094,6 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin
 
 extern void showScummVMDialog(const Common::String &message);
 
-void gamestate_delayedrestore(EngineState *s) {
-	int savegameId = s->_delayedRestoreGameId; // delayedRestoreGameId gets destroyed within gamestate_restore()!
-	Common::String fileName = g_sci->getSavegameName(savegameId);
-	Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName);
-
-	if (in) {
-		// found a savegame file
-		gamestate_restore(s, in);
-		delete in;
-		if (s->r_acc != make_reg(0, 1)) {
-			gamestate_afterRestoreFixUp(s, savegameId);
-			return;
-		}
-	}
-
-	error("Restoring gamestate '%s' failed", fileName.c_str());
-}
-
 void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) {
 	switch (g_sci->getGameId()) {
 	case GID_MOTHERGOOSE:
@@ -1256,13 +1238,6 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
 	if (g_sci->_gfxPorts)
 		g_sci->_gfxPorts->saveLoadWithSerializer(ser);
 
-	// SCI32:
-	// Current planes/screen elements of freshly loaded VM are re-added by scripts in [gameID]::replay
-	// We don't have to do that in here.
-	// But we may have to do it ourselves in case we ever implement some soft-error handling in case
-	// a saved game can't be restored. That way we can restore the game screen.
-	// see _gfxFrameout->syncWithScripts()
-
 	Vocabulary *voc = g_sci->getVocabulary();
 	if (ser.getVersion() >= 30 && voc)
 		voc->saveLoadWithSerializer(ser);
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index a75db25..274df25 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -97,9 +97,6 @@ struct SavegameMetadata {
  */
 bool gamestate_save(EngineState *s, Common::WriteStream *save, const Common::String &savename, const Common::String &version);
 
-// does a delayed saved game restore, used by ScummVM game menu - see detection.cpp / SciEngine::loadGameState()
-void gamestate_delayedrestore(EngineState *s);
-
 // does a few fixups right after restoring a saved game
 void gamestate_afterRestoreFixUp(EngineState *s, int savegameId);
 
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 8a1a9e7..5bfc73b 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -90,6 +90,7 @@ void Kernel::mapSelectors() {
 	FIND_SELECTOR(message);
 	// edit
 	FIND_SELECTOR(play);
+	FIND_SELECTOR(restore);
 	FIND_SELECTOR(number);
 	FIND_SELECTOR(handle);	// nodePtr
 	FIND_SELECTOR(client);
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index d8c0bf7..f2e4ec0 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -63,6 +63,7 @@ struct SelectorCache {
 	Selector message; ///< Used by GetEvent
 	// edit
 	Selector play; ///< Play function (first function to be called)
+	Selector restore;
 	Selector number;
 	Selector handle;	///< Replaced by nodePtr in SCI1+
 	Selector nodePtr;	///< Replaces handle in SCI1+


Commit: a2c48eff4ca281158aa48e34ede29a53e0871807
    https://github.com/scummvm/scummvm/commit/a2c48eff4ca281158aa48e34ede29a53e0871807
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:36-05:00

Commit Message:
SCI32: Only activate the Load button in the launcher when it is usable

Changed paths:
    engines/sci/detection.cpp


diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index 272e332..fa89aad 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -22,6 +22,7 @@
 
 #include "engines/advancedDetector.h"
 #include "base/plugins.h"
+#include "common/config-manager.h"
 #include "common/file.h"
 #include "common/ptr.h"
 #include "common/savefile.h"
@@ -883,13 +884,13 @@ Common::Error SciEngine::saveGameState(int slot, const Common::String &desc) {
 
 bool SciEngine::canLoadGameStateCurrently() {
 #ifdef ENABLE_SCI32
+	const Common::String &guiOptions = ConfMan.get("guioptions");
 	if (getSciVersion() >= SCI_VERSION_2) {
-		switch (getGameId()) {
-		case GID_PHANTASMAGORIA:
-		case GID_HOYLE5:
+		if (ConfMan.getBool("originalsaveload") ||
+			!Common::checkGameGUIOption(GAMEOPTION_ORIGINAL_SAVELOAD, guiOptions) ||
+			Common::checkGameGUIOption(GUIO_NOLAUNCHLOAD, guiOptions)) {
+
 			return false;
-		default:
-			break;
 		}
 	}
 #endif


Commit: 2862e510684cc696e7160dc7dc940ffea8cca0f5
    https://github.com/scummvm/scummvm/commit/2862e510684cc696e7160dc7dc940ffea8cca0f5
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:36-05:00

Commit Message:
SCI32: Fix launcher load for Shivers

Changed paths:
    engines/sci/detection.cpp
    engines/sci/engine/guest_additions.cpp


diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index fa89aad..6799c35 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -887,7 +887,6 @@ bool SciEngine::canLoadGameStateCurrently() {
 	const Common::String &guiOptions = ConfMan.get("guioptions");
 	if (getSciVersion() >= SCI_VERSION_2) {
 		if (ConfMan.getBool("originalsaveload") ||
-			!Common::checkGameGUIOption(GAMEOPTION_ORIGINAL_SAVELOAD, guiOptions) ||
 			Common::checkGameGUIOption(GUIO_NOLAUNCHLOAD, guiOptions)) {
 
 			return false;
diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
index 0ef6d99..be6955f 100644
--- a/engines/sci/engine/guest_additions.cpp
+++ b/engines/sci/engine/guest_additions.cpp
@@ -513,10 +513,17 @@ void GuestAdditions::restoreFromLauncher() const {
 			return;
 		}
 
-		// When `Game::restore` is invoked, it will call to `Restore::doit`
-		// which will automatically return the `_delayedRestoreGameId` instead
-		// of prompting the user for a save game
-		invokeSelector(g_sci->getGameObject(), SELECTOR(restore));
+		if (g_sci->getGameId() == GID_SHIVERS) {
+			// Shivers accepts the save game number as a parameter to
+			// `SHIVERS::restore`
+			reg_t args[] = { make_reg(0, _state->_delayedRestoreGameId - kSaveIdShift) };
+			invokeSelector(g_sci->getGameObject(), SELECTOR(restore), 1, args);
+		} else {
+			// When `Game::restore` is invoked, it will call to `Restore::doit`
+			// which will automatically return the `_delayedRestoreGameId` instead
+			// of prompting the user for a save game
+			invokeSelector(g_sci->getGameObject(), SELECTOR(restore));
+		}
 	} else {
 #else
 	{


Commit: f8703ae48d28fe3a1c8577e2feb53110e28765da
    https://github.com/scummvm/scummvm/commit/f8703ae48d28fe3a1c8577e2feb53110e28765da
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:36-05:00

Commit Message:
SCI32: Fix infinite recursion when loading invalid save from launcher

Changed paths:
    engines/sci/engine/guest_additions.cpp
    engines/sci/engine/guest_additions.h


diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
index be6955f..e24ef46 100644
--- a/engines/sci/engine/guest_additions.cpp
+++ b/engines/sci/engine/guest_additions.cpp
@@ -61,6 +61,7 @@ GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features, Kerne
 	_features(features),
 	_kernel(kernel),
 	_segMan(state->_segMan),
+	_restoring(false),
 	_messageTypeSynced(false) {}
 
 #pragma mark -
@@ -244,16 +245,14 @@ void GuestAdditions::segManSaveLoadScriptHook(Script &script) const {
 
 bool GuestAdditions::kGetEventHook() const {
 	if (_state->_delayedRestoreGameId != -1) {
-		g_sci->_guestAdditions->restoreFromLauncher();
-		return true;
+		return g_sci->_guestAdditions->restoreFromLauncher();
 	}
 	return false;
 }
 
 bool GuestAdditions::kWaitHook() const {
 	if (_state->_delayedRestoreGameId != -1) {
-		g_sci->_guestAdditions->restoreFromLauncher();
-		return true;
+		return g_sci->_guestAdditions->restoreFromLauncher();
 	}
 	return false;
 }
@@ -497,22 +496,33 @@ reg_t GuestAdditions::promptSaveRestoreTorin(EngineState *s, int argc, reg_t *ar
 #pragma mark -
 #pragma mark Restore from launcher
 
-void GuestAdditions::restoreFromLauncher() const {
+bool GuestAdditions::restoreFromLauncher() const {
 	assert(_state->_delayedRestoreGameId != -1);
 
 #ifdef ENABLE_SCI32
 	if (getSciVersion() >= SCI_VERSION_2) {
-		// In SQ6, delayed restore should not happen until room 100 (the Sierra
-		// logo & main menu room) is loaded, otherwise the game scripts will try
-		// to make calls to the subtitles window, which does not exist until
-		// after the main menu. The game scripts check if the current room is
-		// 100 and avoids making calls to the subtitles window if it is.
-		if (g_sci->getGameId() == GID_SQ6 &&
-			_state->variables[VAR_GLOBAL][kGlobalVarCurrentRoomNo] != make_reg(0, 100)) {
+		if (_restoring) {
+			// Recursion will occur if a restore fails, as
+			// _delayedRestoreGameId will not be reset so the kernel will try
+			// to keep restoring forever
+			_state->_delayedRestoreGameId = -1;
+			_restoring = false;
+			return false;
+		}
 
-			return;
+		// Delayed restore should not happen until after the benchmarking room.
+		// In particular, in SQ6, delayed restore must not happen until room 100
+		// (the Sierra logo & main menu room), otherwise the game scripts will
+		// try to make calls to the subtitles ScrollWindow, which does not
+		// exist. In other games, restoring early either breaks benchmarking,
+		// or, when trying to load an invalid save game, makes the dialog
+		// telling the user that the game is invalid impossible to read
+		if (Common::String(_segMan->getObjectName(_state->variables[VAR_GLOBAL][kGlobalVarCurrentRoom])) == "speedRoom") {
+			return false;
 		}
 
+		_restoring = true;
+
 		if (g_sci->getGameId() == GID_SHIVERS) {
 			// Shivers accepts the save game number as a parameter to
 			// `SHIVERS::restore`
@@ -524,6 +534,10 @@ void GuestAdditions::restoreFromLauncher() const {
 			// of prompting the user for a save game
 			invokeSelector(g_sci->getGameObject(), SELECTOR(restore));
 		}
+
+		_restoring = false;
+
+		return true;
 	} else {
 #else
 	{
@@ -538,7 +552,7 @@ void GuestAdditions::restoreFromLauncher() const {
 			delete in;
 			if (_state->r_acc != make_reg(0, 1)) {
 				gamestate_afterRestoreFixUp(_state, savegameId);
-				return;
+				return true;
 			}
 		}
 
diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h
index bf4866b..45d418b 100644
--- a/engines/sci/engine/guest_additions.h
+++ b/engines/sci/engine/guest_additions.h
@@ -177,7 +177,11 @@ private:
 	 * Invokes the game's restore mechanism to load a save game that was
 	 * selected from the ScummVM launcher.
 	 */
-	void restoreFromLauncher() const;
+	bool restoreFromLauncher() const;
+
+#ifdef ENABLE_SCI32
+	mutable bool _restoring;
+#endif
 
 #pragma mark -
 #pragma mark Message type sync


Commit: 2df939d33be64a6dabf5825a05d56c8b1c875f4a
    https://github.com/scummvm/scummvm/commit/2df939d33be64a6dabf5825a05d56c8b1c875f4a
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:36-05:00

Commit Message:
SCI32: Add workaround for SQ6 invalid read after a failed save game restore

Changed paths:
    engines/sci/engine/workarounds.cpp


diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 025e94a..89cce10 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -409,6 +409,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
 	{ GID_SQ6,            -1, 64950, -1,            "Feature", "handleEvent",                     NULL,     0, { WORKAROUND_FAKE,   0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places
 	{ GID_SQ6,            -1, 64964,  0,              "DPath", "init",                            NULL,     1, { WORKAROUND_FAKE,   0 } }, // during the game
 	{ GID_SQ6,           210,   210,  0,       "buttonSecret", "doVerb",                          NULL,     0, { WORKAROUND_FAKE,   0 } }, // after winning the first round of stooge fighter 3
+	{ GID_SQ6,            -1, 64994, -1,               "Game", "restore",                         NULL,     1, { WORKAROUND_FAKE,   0 } }, // When trying to load an invalid save game from the launcher
 	{ GID_TORIN,          -1, 64017,  0,             "oFlags", "clear",                           NULL,     0, { WORKAROUND_FAKE,   0 } }, // entering Torin's home in the French version
 	{ GID_TORIN,       10000, 64029,  0,          "oMessager", "nextMsg",                         NULL,     3, { WORKAROUND_FAKE,   0 } }, // start of chapter one
 	{ GID_TORIN,       20100, 64964,  0,              "DPath", "init",                            NULL,     1, { WORKAROUND_FAKE,   0 } }, // going down the cliff at the first screen of chapter 2 (washing area)


Commit: a22e461208db6000adce464eb501159d015e3492
    https://github.com/scummvm/scummvm/commit/a22e461208db6000adce464eb501159d015e3492
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:37-05:00

Commit Message:
SCI: Improve comments in GuestAdditions

Changed paths:
    engines/sci/engine/guest_additions.h


diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h
index 45d418b..9e5100d 100644
--- a/engines/sci/engine/guest_additions.h
+++ b/engines/sci/engine/guest_additions.h
@@ -37,11 +37,13 @@ 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.
+ * other audio-related settings, and integration of the ScummVM GUI when saving
+ * and loading game states.
  *
- * 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.
+ * NOTE: Some parts of the code used to manage audio sync are applied as script
+ * patches using the normal ScriptPatcher mechanism. These patches prevent the
+ * game from resetting audio volumes to defaults when starting up, and prevent
+ * the game from restoring audio volumes stored inside of a save game.
  */
 class GuestAdditions {
 public:
@@ -51,7 +53,7 @@ public:
 
 	/**
 	 * Synchronises audio volume settings from ScummVM to the game. Called
-	 * whenever the ScummVM launcher is dismissed.
+	 * whenever the ScummVM global menu is dismissed.
 	 */
 	void syncSoundSettings() const;
 
@@ -149,23 +151,52 @@ public:
 
 public:
 	/**
-	 * Patches game scripts to hook into the ScummVM launcher UI when a user
-	 * tries to save or restore a game from inside the game.
+	 * Patches game scripts to use the ScummVM save/load dialogue instead of the
+	 * game's native save/load dialogue when a user tries to save or restore a
+	 * game from inside the game.
 	 */
 	void patchGameSaveRestore() const;
 
 private:
+	/**
+	 * Patches the ScummVM save/load dialogue into the game for SCI16 games that
+	 * use Game::save and Game::restore.
+	 */
 	void patchGameSaveRestoreSCI16() const;
 
 #ifdef ENABLE_SCI32
 public:
+	/**
+	 * Finds the correct save file number and description to save or load and
+	 * returns it to the VM. For user-interactive save file lookup, this method
+	 * displays the ScummVM save/load dialogue. For delayed restores, it returns
+	 * the save game number sent by the ScummVM launcher without prompting the
+	 * user.
+	 */
 	reg_t kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) const;
 
 private:
+	/**
+	 * Patches the ScummVM save/load dialogue into SCI32 games that use
+	 * SRDialog.
+	 */
 	void patchGameSaveRestoreSCI32(Script &script) const;
+
+	/**
+	 * Patches the ScummVM save/load dialogue into Torin.
+	 */
 	void patchGameSaveRestoreTorin(Script &script) const;
 
+	/**
+	 * Prompts for a save game and returns it to game scripts using default
+	 * SRDialog game class semantics.
+	 */
 	reg_t promptSaveRestoreDefault(EngineState *s, int argc, reg_t *argv) const;
+
+	/**
+	 * Prompts for a save game and returns it to game scripts using Torin's
+	 * custom NewGame class semantics.
+	 */
 	reg_t promptSaveRestoreTorin(EngineState *s, int argc, reg_t *argv) const;
 #endif
 
@@ -174,12 +205,16 @@ private:
 
 private:
 	/**
-	 * Invokes the game's restore mechanism to load a save game that was
-	 * selected from the ScummVM launcher.
+	 * Invokes the game's save restore mechanism to load a save game that was
+	 * selected in the ScummVM launcher when the game was started.
 	 */
 	bool restoreFromLauncher() const;
 
 #ifdef ENABLE_SCI32
+	/**
+	 * If true, GuestAdditions is in the process of handling a delayed game
+	 * restore from the ScummVM launcher or global menu.
+	 */
 	mutable bool _restoring;
 #endif
 
@@ -188,20 +223,34 @@ private:
 
 private:
 	/**
-	 * true if the message type (text/speech/text+speech) has been synchronised
+	 * 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
+	 * Synchronises the message type (speech/text/speech+text) from ScummVM to
 	 * a game.
 	 */
 	void syncMessageTypeFromScummVM() const;
 
+	/**
+	 * Synchronises the message type from ScummVM using the default strategy
+	 * (global90).
+	 */
 	void syncMessageTypeFromScummVMUsingDefaultStrategy() const;
+
 #ifdef ENABLE_SCI32
+	/**
+	 * Synchronises the message type from ScummVM using the strategy used by
+	 * Shivers (global211).
+	 */
 	void syncMessageTypeFromScummVMUsingShiversStrategy() const;
+
+	/**
+	 * Synchronises the message type from ScummVM using the strategy used by
+	 * LSL6hires (gameFlags).
+	 */
 	void syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const;
 #endif
 
@@ -211,9 +260,23 @@ private:
 	 */
 	void syncMessageTypeToScummVM(const int index, const reg_t value);
 
+	/**
+	 * Synchronises the message type to ScummVM using the default strategy
+	 * (global90).
+	 */
 	void syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value);
+
 #ifdef ENABLE_SCI32
+	/**
+	 * Synchronises the message type to ScummVM using the strategy used by
+	 * Shivers (global211).
+	 */
 	void syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value);
+
+	/**
+	 * Synchronises the message type to ScummVM using the strategy used by
+	 * LSL6hires (gameFlags).
+	 */
 	void syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp);
 #endif
 
@@ -250,7 +313,12 @@ private:
 	 */
 	void syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const;
 
+	/**
+	 * Synchronises audio volume settings from ScummVM to GK1 when the game is
+	 * running.
+	 */
 	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;


Commit: 93b337b8335a455d0108c168ebcfae655327753c
    https://github.com/scummvm/scummvm/commit/93b337b8335a455d0108c168ebcfae655327753c
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:37-05:00

Commit Message:
SCI: Do not sync objects when saving games

Commit 5de2668939a6735da2b3438b7c586fc185791ef8 silently changed
behaviour from running this code only when restoring a game, to
running all the time, in an apparent copy-paste error.

Changed paths:
    engines/sci/engine/savegame.cpp


diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 057d590..3b8429b 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -271,35 +271,37 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
 
 	syncArray<Class>(s, _classTable);
 
-	// Now that all scripts are loaded, init their objects.
-	// Just like in Script::initializeObjectsSci0, we do two passes
-	// in case an object is loaded before its base.
-	int passes = getSciVersion() < SCI_VERSION_1_1 ? 2 : 1;
-	for (int pass = 1; pass <= passes; ++pass) {
-		for (uint i = 0; i < _heap.size(); i++) {
-			if (!_heap[i] ||  _heap[i]->getType() != SEG_TYPE_SCRIPT)
-				continue;
-
-			Script *scr = (Script *)_heap[i];
-			scr->syncLocalsBlock(this);
+	if (s.isLoading()) {
+		// Now that all scripts are loaded, init their objects.
+		// Just like in Script::initializeObjectsSci0, we do two passes
+		// in case an object is loaded before its base.
+		int passes = getSciVersion() < SCI_VERSION_1_1 ? 2 : 1;
+		for (int pass = 1; pass <= passes; ++pass) {
+			for (uint i = 0; i < _heap.size(); i++) {
+				if (!_heap[i] ||  _heap[i]->getType() != SEG_TYPE_SCRIPT)
+					continue;
 
-			ObjMap objects = scr->getObjectMap();
-			for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it) {
-				reg_t addr = it->_value.getPos();
-				Object *obj = scr->scriptObjInit(addr, false);
+				Script *scr = (Script *)_heap[i];
+				scr->syncLocalsBlock(this);
 
-				if (pass == 2) {
-					if (!obj->initBaseObject(this, addr, false)) {
-						// TODO/FIXME: This should not be happening at all. It might indicate a possible issue
-						// with the garbage collector. It happens for example in LSL5 (German, perhaps English too).
-						warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr));
-						objects.erase(addr.toUint16());
+				ObjMap objects = scr->getObjectMap();
+				for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it) {
+					reg_t addr = it->_value.getPos();
+					Object *obj = scr->scriptObjInit(addr, false);
+
+					if (pass == 2) {
+						if (!obj->initBaseObject(this, addr, false)) {
+							// TODO/FIXME: This should not be happening at all. It might indicate a possible issue
+							// with the garbage collector. It happens for example in LSL5 (German, perhaps English too).
+							warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr));
+							objects.erase(addr.toUint16());
+						}
 					}
 				}
-			}
 
-			if (s.isLoading() && pass == passes) {
-				g_sci->_guestAdditions->segManSaveLoadScriptHook(*scr);
+				if (pass == passes) {
+					g_sci->_guestAdditions->segManSaveLoadScriptHook(*scr);
+				}
 			}
 		}
 	}


Commit: 399551af09420e0b97ce4ee82d0bf368964e1333
    https://github.com/scummvm/scummvm/commit/399551af09420e0b97ce4ee82d0bf368964e1333
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:37-05:00

Commit Message:
SCI: Fix compilation when SCI32 is disabled

Changed paths:
    engines/sci/engine/guest_additions.cpp
    engines/sci/engine/savegame.cpp


diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp
index e24ef46..4a3afcd 100644
--- a/engines/sci/engine/guest_additions.cpp
+++ b/engines/sci/engine/guest_additions.cpp
@@ -61,7 +61,9 @@ GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features, Kerne
 	_features(features),
 	_kernel(kernel),
 	_segMan(state->_segMan),
+#ifdef ENABLE_SCI32
 	_restoring(false),
+#endif
 	_messageTypeSynced(false) {}
 
 #pragma mark -
@@ -243,6 +245,8 @@ void GuestAdditions::segManSaveLoadScriptHook(Script &script) const {
 	instantiateScriptHook(script, true);
 }
 
+#endif
+
 bool GuestAdditions::kGetEventHook() const {
 	if (_state->_delayedRestoreGameId != -1) {
 		return g_sci->_guestAdditions->restoreFromLauncher();
@@ -257,8 +261,6 @@ bool GuestAdditions::kWaitHook() const {
 	return false;
 }
 
-#endif
-
 #pragma mark -
 #pragma mark Integrated save & restore
 
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 3b8429b..e6a6af7 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -299,9 +299,11 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
 					}
 				}
 
+#ifdef ENABLE_SCI32
 				if (pass == passes) {
 					g_sci->_guestAdditions->segManSaveLoadScriptHook(*scr);
 				}
+#endif
 			}
 		}
 	}


Commit: e504efe4da62b3d2e32ffb895b935080be02aed4
    https://github.com/scummvm/scummvm/commit/e504efe4da62b3d2e32ffb895b935080be02aed4
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:37-05:00

Commit Message:
SCI32: Add palette code for late SCI2.1mid+ games

Sometime during SCI2.1mid, the palette manager was changed to
save and restore the source palette, and to add in-game gamma
correction. Previously, only the vary start and target palettes
were saved, and gamma correction was only configurable in SSCI by
editing RESOURCE.CFG.

Changed paths:
    engines/sci/engine/features.h
    engines/sci/engine/kgraphics32.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/savegame.h
    engines/sci/graphics/palette32.cpp
    engines/sci/graphics/palette32.h
    engines/sci/graphics/remap32.cpp


diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h
index 044111a..6d9460d 100644
--- a/engines/sci/engine/features.h
+++ b/engines/sci/engine/features.h
@@ -124,10 +124,20 @@ public:
 			gid != GID_MOTHERGOOSEHIRES;
 	}
 
-	inline bool hasNewPaletteCode() const {
+	inline bool hasMidPaletteCode() const {
 		return getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7;
 	}
 
+	inline bool hasLatePaletteCode() const {
+		return getSciVersion() > SCI_VERSION_2_1_MIDDLE ||
+			g_sci->getGameId() == GID_GK2 ||
+			g_sci->getGameId() == GID_PQSWAT ||
+			// Guessing that Shivers has the late palette code because it has a
+			// brightness slider
+			g_sci->getGameId() == GID_SHIVERS ||
+			g_sci->getGameId() == GID_TORIN;
+	}
+
 	inline bool VMDOpenStopsAudio() const {
 		// Of the games that use VMDs:
 		// Yes: Phant1, Shivers, Torin
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 07a63c7..ae84e6b 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -934,16 +934,8 @@ reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv) {
 	return make_reg(0, g_sci->_gfxPalette32->matchColor(r, g, b));
 }
 
-/*
- * Used starting in Shivers 1. SCI3 contains 6 gamma look-up tables, with the
- * first table (gamma = 0) being the default one.
- */
 reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv) {
-	const uint8 gamma = argv[0].toUint16();
-	assert(gamma <= 6);
-
-	warning("TODO: kPaletteSetGamma(%d)", gamma);
-
+	g_sci->_gfxPalette32->setGamma(argv[0].toSint16());
 	return s->r_acc;
 }
 
@@ -962,7 +954,7 @@ reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) {
 	int16 fromColor;
 	int16 toColor;
 
-	if (g_sci->_features->hasNewPaletteCode() && argc > 4) {
+	if (g_sci->_features->hasMidPaletteCode() && argc > 4) {
 		fromColor = argv[3].toSint16();
 		toColor = argv[4].toSint16();
 	} else {
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index e6a6af7..04715d6 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -865,10 +865,18 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {
 		s.syncAsByte(_cycleMap[i]);
 	}
 
+	if (g_sci->_features->hasLatePaletteCode() && s.getVersion() >= 41) {
+		s.syncAsSint16LE(_gammaLevel);
+		saveLoadPalette32(s, &_sourcePalette);
+		++_version;
+		_needsUpdate = true;
+		_gammaChanged = true;
+	}
+
 	saveLoadOptionalPalette32(s, &_varyTargetPalette);
 	saveLoadOptionalPalette32(s, &_varyStartPalette);
-	// NOTE: _sourcePalette and _nextPalette are not saved
-	// by SCI engine
+
+	// _nextPalette is not saved by SSCI
 
 	for (int i = 0; i < ARRAYSIZE(_cyclers); ++i) {
 		PalCycler *cycler = nullptr;
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 274df25..7298246 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,6 +37,7 @@ struct EngineState;
  *
  * Version - new/changed feature
  * =============================
+ *      41 - palette support for newer SCI2.1 games
  *      40 - always store palvary variables
  *      39 - Accurate SCI32 arrays/strings, score metadata, avatar metadata
  *      38 - SCI32 cursor
@@ -65,7 +66,7 @@ struct EngineState;
  */
 
 enum {
-	CURRENT_SAVEGAME_VERSION = 40,
+	CURRENT_SAVEGAME_VERSION = 41,
 	MINIMUM_SAVEGAME_VERSION = 14
 };
 
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index febb682..d13ae2c 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -122,9 +122,212 @@ const Palette HunkPalette::toPalette() const {
 }
 
 #pragma mark -
+#pragma mark Gamma correction tables
+
+static const uint8 gammaTables[GfxPalette32::numGammaTables][256] = {
+	{ 0, 2, 3, 5, 6, 7, 9, 10,
+	11, 13, 14, 15, 16, 18, 19, 20,
+	21, 22, 23, 25, 26, 27, 28, 29,
+	30, 32, 33, 34, 35, 36, 37, 38,
+	39, 41, 42, 43, 44, 45, 46, 47,
+	48, 49, 50, 51, 52, 54, 55, 56,
+	57, 58, 59, 60, 61, 62, 63, 64,
+	65, 66, 67, 68, 69, 70, 71, 72,
+	74, 75, 76, 77, 78, 79, 80, 81,
+	82, 83, 84, 85, 86, 87, 88, 89,
+	90, 91, 92, 93, 94, 95, 96, 97,
+	98, 99, 100, 101, 102, 103, 104, 105,
+	106, 107, 108, 109, 110, 111, 112, 113,
+	114, 115, 116, 117, 118, 119, 120, 121,
+	122, 123, 124, 125, 126, 127, 128, 128,
+	129, 130, 131, 132, 133, 134, 135, 136,
+	137, 138, 139, 140, 141, 142, 143, 144,
+	145, 146, 147, 148, 149, 150, 151, 152,
+	153, 153, 154, 155, 156, 157, 158, 159,
+	160, 161, 162, 163, 164, 165, 166, 167,
+	168, 169, 170, 171, 171, 172, 173, 174,
+	175, 176, 177, 178, 179, 180, 181, 182,
+	183, 184, 185, 186, 186, 187, 188, 189,
+	190, 191, 192, 193, 194, 195, 196, 197,
+	198, 199, 199, 200, 201, 202, 203, 204,
+	205, 206, 207, 208, 209, 210, 211, 211,
+	212, 213, 214, 215, 216, 217, 218, 219,
+	220, 221, 222, 222, 223, 224, 225, 226,
+	227, 228, 229, 230, 231, 232, 232, 233,
+	234, 235, 236, 237, 238, 239, 240, 241,
+	242, 242, 243, 244, 245, 246, 247, 248,
+	249, 250, 251, 251, 252, 253, 254, 255 },
+
+	{ 0, 3, 5, 6, 8, 10, 11, 13,
+	14, 16, 17, 19, 20, 22, 23, 24,
+	26, 27, 28, 30, 31, 32, 33, 35,
+	36, 37, 38, 40, 41, 42, 43, 44,
+	46, 47, 48, 49, 50, 51, 53, 54,
+	55, 56, 57, 58, 59, 60, 62, 63,
+	64, 65, 66, 67, 68, 69, 70, 71,
+	73, 74, 75, 76, 77, 78, 79, 80,
+	81, 82, 83, 84, 85, 86, 87, 88,
+	89, 90, 91, 92, 93, 94, 95, 96,
+	97, 99, 100, 101, 102, 103, 104, 105,
+	106, 107, 108, 108, 109, 110, 111, 112,
+	113, 114, 115, 116, 117, 118, 119, 120,
+	121, 122, 123, 124, 125, 126, 127, 128,
+	129, 130, 131, 132, 133, 134, 135, 136,
+	136, 137, 138, 139, 140, 141, 142, 143,
+	144, 145, 146, 147, 148, 149, 150, 151,
+	151, 152, 153, 154, 155, 156, 157, 158,
+	159, 160, 161, 162, 162, 163, 164, 165,
+	166, 167, 168, 169, 170, 171, 172, 172,
+	173, 174, 175, 176, 177, 178, 179, 180,
+	180, 181, 182, 183, 184, 185, 186, 187,
+	188, 188, 189, 190, 191, 192, 193, 194,
+	195, 196, 196, 197, 198, 199, 200, 201,
+	202, 202, 203, 204, 205, 206, 207, 208,
+	209, 209, 210, 211, 212, 213, 214, 215,
+	215, 216, 217, 218, 219, 220, 221, 221,
+	222, 223, 224, 225, 226, 227, 227, 228,
+	229, 230, 231, 232, 233, 233, 234, 235,
+	236, 237, 238, 238, 239, 240, 241, 242,
+	243, 243, 244, 245, 246, 247, 248, 249,
+	249, 250, 251, 252, 253, 254, 254, 255 },
+
+	{ 0, 4, 6, 8, 10, 12, 14, 16,
+	18, 19, 21, 23, 24, 26, 27, 29,
+	30, 32, 33, 35, 36, 37, 39, 40,
+	41, 43, 44, 45, 47, 48, 49, 50,
+	52, 53, 54, 55, 57, 58, 59, 60,
+	61, 62, 64, 65, 66, 67, 68, 69,
+	71, 72, 73, 74, 75, 76, 77, 78,
+	79, 81, 82, 83, 84, 85, 86, 87,
+	88, 89, 90, 91, 92, 93, 94, 95,
+	96, 97, 98, 99, 100, 102, 103, 104,
+	105, 106, 107, 108, 109, 110, 111, 112,
+	112, 113, 114, 115, 116, 117, 118, 119,
+	120, 121, 122, 123, 124, 125, 126, 127,
+	128, 129, 130, 131, 132, 133, 134, 135,
+	135, 136, 137, 138, 139, 140, 141, 142,
+	143, 144, 145, 146, 146, 147, 148, 149,
+	150, 151, 152, 153, 154, 155, 156, 156,
+	157, 158, 159, 160, 161, 162, 163, 163,
+	164, 165, 166, 167, 168, 169, 170, 170,
+	171, 172, 173, 174, 175, 176, 177, 177,
+	178, 179, 180, 181, 182, 183, 183, 184,
+	185, 186, 187, 188, 188, 189, 190, 191,
+	192, 193, 194, 194, 195, 196, 197, 198,
+	199, 199, 200, 201, 202, 203, 203, 204,
+	205, 206, 207, 208, 208, 209, 210, 211,
+	212, 212, 213, 214, 215, 216, 217, 217,
+	218, 219, 220, 221, 221, 222, 223, 224,
+	225, 225, 226, 227, 228, 229, 229, 230,
+	231, 232, 233, 233, 234, 235, 236, 237,
+	237, 238, 239, 240, 240, 241, 242, 243,
+	244, 244, 245, 246, 247, 247, 248, 249,
+	250, 251, 251, 252, 253, 254, 254, 255 },
+
+	{ 0, 5, 9, 11, 14, 16, 19, 21,
+	23, 25, 26, 28, 30, 32, 33, 35,
+	37, 38, 40, 41, 43, 44, 46, 47,
+	49, 50, 52, 53, 54, 56, 57, 58,
+	60, 61, 62, 64, 65, 66, 67, 69,
+	70, 71, 72, 73, 75, 76, 77, 78,
+	79, 80, 82, 83, 84, 85, 86, 87,
+	88, 89, 91, 92, 93, 94, 95, 96,
+	97, 98, 99, 100, 101, 102, 103, 104,
+	105, 106, 107, 108, 109, 110, 111, 112,
+	113, 114, 115, 116, 117, 118, 119, 120,
+	121, 122, 123, 124, 125, 126, 127, 128,
+	129, 130, 131, 132, 133, 134, 134, 135,
+	136, 137, 138, 139, 140, 141, 142, 143,
+	144, 144, 145, 146, 147, 148, 149, 150,
+	151, 152, 152, 153, 154, 155, 156, 157,
+	158, 158, 159, 160, 161, 162, 163, 164,
+	164, 165, 166, 167, 168, 169, 169, 170,
+	171, 172, 173, 174, 174, 175, 176, 177,
+	178, 179, 179, 180, 181, 182, 183, 183,
+	184, 185, 186, 187, 187, 188, 189, 190,
+	191, 191, 192, 193, 194, 195, 195, 196,
+	197, 198, 199, 199, 200, 201, 202, 202,
+	203, 204, 205, 205, 206, 207, 208, 209,
+	209, 210, 211, 212, 212, 213, 214, 215,
+	215, 216, 217, 218, 218, 219, 220, 221,
+	221, 222, 223, 224, 224, 225, 226, 227,
+	227, 228, 229, 230, 230, 231, 232, 232,
+	233, 234, 235, 235, 236, 237, 238, 238,
+	239, 240, 240, 241, 242, 243, 243, 244,
+	245, 245, 246, 247, 248, 248, 249, 250,
+	250, 251, 252, 252, 253, 254, 255, 255 },
+
+	{ 0, 9, 14, 18, 21, 24, 27, 29,
+	32, 34, 37, 39, 41, 43, 45, 47,
+	48, 50, 52, 54, 55, 57, 59, 60,
+	62, 63, 65, 66, 68, 69, 71, 72,
+	73, 75, 76, 77, 79, 80, 81, 83,
+	84, 85, 86, 88, 89, 90, 91, 92,
+	94, 95, 96, 97, 98, 99, 100, 102,
+	103, 104, 105, 106, 107, 108, 109, 110,
+	111, 112, 113, 114, 115, 116, 117, 118,
+	119, 120, 121, 122, 123, 124, 125, 126,
+	127, 128, 129, 130, 131, 132, 133, 134,
+	135, 136, 137, 137, 138, 139, 140, 141,
+	142, 143, 144, 145, 145, 146, 147, 148,
+	149, 150, 151, 151, 152, 153, 154, 155,
+	156, 156, 157, 158, 159, 160, 161, 161,
+	162, 163, 164, 165, 165, 166, 167, 168,
+	169, 169, 170, 171, 172, 173, 173, 174,
+	175, 176, 176, 177, 178, 179, 179, 180,
+	181, 182, 182, 183, 184, 185, 185, 186,
+	187, 188, 188, 189, 190, 191, 191, 192,
+	193, 194, 194, 195, 196, 196, 197, 198,
+	199, 199, 200, 201, 201, 202, 203, 203,
+	204, 205, 206, 206, 207, 208, 208, 209,
+	210, 210, 211, 212, 212, 213, 214, 214,
+	215, 216, 216, 217, 218, 218, 219, 220,
+	220, 221, 222, 222, 223, 224, 224, 225,
+	226, 226, 227, 228, 228, 229, 230, 230,
+	231, 231, 232, 233, 233, 234, 235, 235,
+	236, 237, 237, 238, 238, 239, 240, 240,
+	241, 242, 242, 243, 243, 244, 245, 245,
+	246, 247, 247, 248, 248, 249, 250, 250,
+	251, 251, 252, 253, 253, 254, 254, 255 },
+
+	{ 0, 16, 23, 28, 32, 36, 39, 42,
+	45, 48, 50, 53, 55, 58, 60, 62,
+	64, 66, 68, 70, 71, 73, 75, 77,
+	78, 80, 81, 83, 84, 86, 87, 89,
+	90, 92, 93, 94, 96, 97, 98, 100,
+	101, 102, 103, 105, 106, 107, 108, 109,
+	111, 112, 113, 114, 115, 116, 117, 118,
+	119, 121, 122, 123, 124, 125, 126, 127,
+	128, 129, 130, 131, 132, 133, 134, 135,
+	135, 136, 137, 138, 139, 140, 141, 142,
+	143, 144, 145, 145, 146, 147, 148, 149,
+	150, 151, 151, 152, 153, 154, 155, 156,
+	156, 157, 158, 159, 160, 160, 161, 162,
+	163, 164, 164, 165, 166, 167, 167, 168,
+	169, 170, 170, 171, 172, 173, 173, 174,
+	175, 176, 176, 177, 178, 179, 179, 180,
+	181, 181, 182, 183, 183, 184, 185, 186,
+	186, 187, 188, 188, 189, 190, 190, 191,
+	192, 192, 193, 194, 194, 195, 196, 196,
+	197, 198, 198, 199, 199, 200, 201, 201,
+	202, 203, 203, 204, 204, 205, 206, 206,
+	207, 208, 208, 209, 209, 210, 211, 211,
+	212, 212, 213, 214, 214, 215, 215, 216,
+	217, 217, 218, 218, 219, 220, 220, 221,
+	221, 222, 222, 223, 224, 224, 225, 225,
+	226, 226, 227, 228, 228, 229, 229, 230,
+	230, 231, 231, 232, 233, 233, 234, 234,
+	235, 235, 236, 236, 237, 237, 238, 238,
+	239, 240, 240, 241, 241, 242, 242, 243,
+	243, 244, 244, 245, 245, 246, 246, 247,
+	247, 248, 248, 249, 249, 250, 250, 251,
+	251, 252, 252, 253, 253, 254, 254, 255 }
+};
+
+#pragma mark -
 #pragma mark GfxPalette32
 
-GfxPalette32::GfxPalette32(ResourceManager *resMan)
+	GfxPalette32::GfxPalette32(ResourceManager *resMan)
 	: _resMan(resMan),
 
 	// Palette versioning
@@ -147,7 +350,12 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan)
 
 	// Palette cycling
 	_cyclers(),
-	_cycleMap() {
+	_cycleMap(),
+
+	// Gamma correction
+	_gammaLevel(-1),
+	_gammaChanged(false) {
+
 	_varyPercent = _varyTargetPercent;
 	for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) {
 		_fadeTable[i] = 100;
@@ -248,7 +456,7 @@ void GfxPalette32::updateFFrame() {
 }
 
 void GfxPalette32::updateHardware(const bool updateScreen) {
-	if (_currentPalette == _nextPalette) {
+	if (_currentPalette == _nextPalette && !_gammaChanged) {
 		return;
 	}
 
@@ -257,21 +465,21 @@ void GfxPalette32::updateHardware(const bool updateScreen) {
 	for (int i = 0; i < ARRAYSIZE(_currentPalette.colors) - 1; ++i) {
 		_currentPalette.colors[i] = _nextPalette.colors[i];
 
-		// NOTE: If the brightness option in the user configuration file is set,
-		// SCI engine adjusts palette brightnesses here by mapping RGB values to
-		// values in some hard-coded brightness tables. There is no reason on
-		// modern hardware to implement this, unless it is discovered that some
-		// game uses a non-standard brightness setting by default
-
 		// All color entries MUST be copied, not just "used" entries, otherwise
 		// uninitialised memory from bpal makes its way into the system palette.
 		// This would not normally be a problem, except that games sometimes use
 		// unused palette entries. e.g. Phant1 title screen references palette
 		// entries outside its own palette, so will render garbage colors where
 		// the game expects them to be black
-		bpal[i * 3    ] = _currentPalette.colors[i].r;
-		bpal[i * 3 + 1] = _currentPalette.colors[i].g;
-		bpal[i * 3 + 2] = _currentPalette.colors[i].b;
+		if (_gammaLevel == -1) {
+			bpal[i * 3    ] = _currentPalette.colors[i].r;
+			bpal[i * 3 + 1] = _currentPalette.colors[i].g;
+			bpal[i * 3 + 2] = _currentPalette.colors[i].b;
+		} else {
+			bpal[i * 3    ] = gammaTables[_gammaLevel][_currentPalette.colors[i].r];
+			bpal[i * 3 + 1] = gammaTables[_gammaLevel][_currentPalette.colors[i].g];
+			bpal[i * 3 + 2] = gammaTables[_gammaLevel][_currentPalette.colors[i].b];
+		}
 	}
 
 	if (g_sci->getPlatform() != Common::kPlatformMacintosh) {
@@ -289,6 +497,8 @@ void GfxPalette32::updateHardware(const bool updateScreen) {
 	if (updateScreen) {
 		g_system->updateScreen();
 	}
+
+	_gammaChanged = false;
 }
 
 Palette GfxPalette32::getPaletteFromResource(const GuiResourceId resourceId) const {
@@ -560,7 +770,7 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in
 	}
 
 	uint16 numColorsToCycle = toColor - fromColor;
-	if (g_sci->_features->hasNewPaletteCode()) {
+	if (g_sci->_features->hasMidPaletteCode()) {
 		numColorsToCycle += 1;
 	}
 	cycler->fromColor = fromColor;
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index d6d7d0d..267ec39 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -572,6 +572,37 @@ private:
 	 * The intensity levels of each palette entry, in percent. Defaults to 100.
 	 */
 	uint16 _fadeTable[256];
+
+#pragma mark -
+#pragma mark Gamma correction
+public:
+	enum {
+		/**
+		 * The number of available gamma corrections.
+		 */
+		numGammaTables = 6
+	};
+
+	/**
+	 * Sets the gamma correction level, from 0 (off) to `numGammaTables`,
+	 * inclusive.
+	 */
+	void setGamma(const int16 level) {
+		_gammaLevel = CLIP<int16>(level, 0, numGammaTables) - 1;
+		_gammaChanged = true;
+	}
+
+private:
+	/**
+	 * The current gamma correction level. -1 means no correction.
+	 */
+	int8 _gammaLevel;
+
+	/**
+	 * Whether the gamma correction has changed since the last call to update
+	 * the hardware palette.
+	 */
+	bool _gammaChanged;
 };
 
 } // End of namespace Sci
diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp
index 768594f..9b5ffcd 100644
--- a/engines/sci/graphics/remap32.cpp
+++ b/engines/sci/graphics/remap32.cpp
@@ -301,7 +301,7 @@ GfxRemap32::GfxRemap32() :
 	// match the highest possible value of `_remapStartColor`
 	assert(_remapStartColor == 236);
 
-	if (g_sci->_features->hasNewPaletteCode()) {
+	if (g_sci->_features->hasMidPaletteCode()) {
 		_remaps.resize(9);
 	} else {
 		_remaps.resize(19);


Commit: a2fb53790b85050e2bac1af5e7a3ab626a6dc3db
    https://github.com/scummvm/scummvm/commit/a2fb53790b85050e2bac1af5e7a3ab626a6dc3db
Author: Colin Snover (github.com at zetafleet.com)
Date: 2017-04-22T13:01:38-05:00

Commit Message:
SCI32: Make version 41 the first supported save game version

Save games created by earlier versions of ScummVM are prone to
having subtle graphics problems or other corruption caused by
incomplete save/load code.

Changed paths:
    engines/sci/engine/kfile.cpp
    engines/sci/engine/savegame.h


diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index bdf9bb6..da4f65b 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -1279,7 +1279,7 @@ reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv) {
 		return NULL_REG;
 	}
 
-	if (save.version < MINIMUM_SAVEGAME_VERSION ||
+	if (save.version < MINIMUM_SCI32_SAVEGAME_VERSION ||
 		save.version > CURRENT_SAVEGAME_VERSION ||
 		save.gameVersion != gameVersion) {
 
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 7298246..b6a673b 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,7 +37,7 @@ struct EngineState;
  *
  * Version - new/changed feature
  * =============================
- *      41 - palette support for newer SCI2.1 games
+ *      41 - palette support for newer SCI2.1 games; stable SCI2/2.1 save games
  *      40 - always store palvary variables
  *      39 - Accurate SCI32 arrays/strings, score metadata, avatar metadata
  *      38 - SCI32 cursor
@@ -68,6 +68,10 @@ struct EngineState;
 enum {
 	CURRENT_SAVEGAME_VERSION = 41,
 	MINIMUM_SAVEGAME_VERSION = 14
+#ifdef ENABLE_SCI32
+	,
+	MINIMUM_SCI32_SAVEGAME_VERSION = 41
+#endif
 };
 
 // Savegame metadata





More information about the Scummvm-git-logs mailing list