1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "audio/mixer.h"
24 #include "common/config-manager.h"
25 #include "common/gui_options.h"
26 #include "common/savefile.h"
27 #include "sci/engine/features.h"
28 #include "sci/engine/guest_additions.h"
29 #include "sci/engine/kernel.h"
30 #include "sci/engine/savegame.h"
31 #include "sci/engine/state.h"
32 #include "sci/engine/vm.h"
33 #ifdef ENABLE_SCI32
34 #include "common/translation.h"
35 #include "gui/saveload.h"
36 #include "sci/graphics/frameout.h"
37 #endif
38 #include "sci/sound/music.h"
39 #include "sci/sci.h"
40 
41 namespace Sci {
42 
43 enum {
44 	kSoundsMusicType = 0,
45 	kSoundsSoundType = 1
46 };
47 
48 enum {
49 	kMessageTypeSubtitles = 1,
50 	kMessageTypeSpeech    = 2
51 };
52 
GuestAdditions(EngineState * state,GameFeatures * features,Kernel * kernel)53 GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features, Kernel *kernel) :
54 	_state(state),
55 	_features(features),
56 	_kernel(kernel),
57 	_segMan(state->_segMan),
58 #ifdef ENABLE_SCI32
59 	_restoring(false),
60 #endif
61 	_messageTypeSynced(false) {}
62 
63 #pragma mark -
64 
syncSoundSettingsFromScummVM() const65 void GuestAdditions::syncSoundSettingsFromScummVM() const {
66 #ifdef ENABLE_SCI32
67 	if (_features->audioVolumeSyncUsesGlobals())
68 		syncAudioVolumeGlobalsFromScummVM();
69 	else
70 #endif
71 		syncMasterVolumeFromScummVM();
72 }
73 
syncAudioOptionsFromScummVM() const74 void GuestAdditions::syncAudioOptionsFromScummVM() const {
75 #ifdef ENABLE_SCI32
76 	if (_features->supportsTextSpeed()) {
77 		syncTextSpeedFromScummVM();
78 	}
79 #endif
80 	syncMessageTypeFromScummVM();
81 }
82 
reset()83 void GuestAdditions::reset() {
84 	_messageTypeSynced = false;
85 }
86 
invokeSelector(const reg_t objId,const Selector selector,const int argc,const StackPtr argv) const87 void GuestAdditions::invokeSelector(const reg_t objId, const Selector selector, const int argc, const StackPtr argv) const {
88 	::Sci::invokeSelector(_state, objId, selector, 0, _state->_executionStack.back().sp, argc, argv);
89 }
90 
shouldSyncAudioToScummVM() const91 bool GuestAdditions::shouldSyncAudioToScummVM() const {
92 	const SciGameId gameId = g_sci->getGameId();
93 	Common::List<ExecStack>::const_iterator it;
94 	for (it = _state->_executionStack.begin(); it != _state->_executionStack.end(); ++it) {
95 		const ExecStack &call = *it;
96 		const Common::String objName = _segMan->getObjectName(call.sendp);
97 
98 		if (getSciVersion() < SCI_VERSION_2 && (objName == "TheMenuBar" ||
99 												objName == "MenuBar")) {
100 			// SCI16 with menu bar
101 			return true;
102 		} else if (objName == "volumeSlider") {
103 			// SCI16 with icon bar, QFG4, Hoyle5, RAMA
104 			return true;
105 		} else if (gameId == GID_MOTHERGOOSE256 && objName == "soundBut") {
106 			return true;
107 		} else if (gameId == GID_SLATER && objName == "volButton") {
108 			return true;
109 		} else if (gameId == GID_LSL6 && objName == "menuBar") {
110 			return true;
111 #ifdef ENABLE_SCI32
112 		} else if ((gameId == GID_GK1 || gameId == GID_SQ6) && (objName == "musicBar" ||
113 																objName == "soundBar")) {
114 			return true;
115 		} else if (gameId == GID_GK2 && objName == "soundSlider") {
116 			return true;
117 		} else if (gameId == GID_HOYLE5 && objName == "volumeSliderF") {
118 			// Hoyle5 has a second control panel with a different slider name
119 			return true;
120 		} else if (gameId == GID_KQ7 && (objName == "volumeUp" ||
121 										 objName == "volumeDown")) {
122 			return true;
123 		} else if (gameId == GID_LSL6HIRES && (objName == "hiResMenu" ||
124 											   objName == "volumeDial")) {
125 			return true;
126 		} else if ((gameId == GID_LSL7 || gameId == GID_TORIN) && (objName == "oMusicScroll" ||
127 																   objName == "oSFXScroll" ||
128 																   objName == "oAudioScroll")) {
129 			return true;
130 		} else if (gameId == GID_MOTHERGOOSEHIRES && objName == "MgButtonBar") {
131 			return true;
132 		} else if (gameId == GID_PHANTASMAGORIA && (objName == "midiVolDown" ||
133 													objName == "midiVolUp" ||
134 													objName == "dacVolDown" ||
135 													objName == "dacVolUp")) {
136 			return true;
137 		} else if (gameId == GID_PHANTASMAGORIA2 && objName == "foo2") {
138 			return true;
139 		} else if (gameId == GID_PQ4 && (objName == "increaseVolume" ||
140 										 objName == "decreaseVolume")) {
141 			return true;
142 		} else if (gameId == GID_PQSWAT && (objName == "volumeDownButn" ||
143 											objName == "volumeUpButn")) {
144 			return true;
145 		} else if (gameId == GID_SHIVERS && objName == "spVolume") {
146 			return true;
147 #endif
148 		}
149 	}
150 
151 	return false;
152 }
153 
154 #pragma mark -
155 #pragma mark Hooks
156 
sciEngineRunGameHook()157 void GuestAdditions::sciEngineRunGameHook() {
158 	_messageTypeSynced = true;
159 }
160 
writeVarHook(const int type,const int index,const reg_t value)161 void GuestAdditions::writeVarHook(const int type, const int index, const reg_t value) {
162 	if (type == VAR_GLOBAL) {
163 #ifdef ENABLE_SCI32
164 		if (getSciVersion() >= SCI_VERSION_2) {
165 			if (_features->audioVolumeSyncUsesGlobals() && shouldSyncAudioToScummVM()) {
166 				syncAudioVolumeGlobalsToScummVM(index, value);
167 			} else if (g_sci->getGameId() == GID_GK1) {
168 				syncGK1StartupVolumeFromScummVM(index, value);
169 			} else if (g_sci->getGameId() == GID_HOYLE5 && index == kGlobalVarHoyle5MusicVolume) {
170 				syncHoyle5VolumeFromScummVM((ConfMan.getInt("music_volume") + 1) * kHoyle5VolumeMax / Audio::Mixer::kMaxMixerVolume);
171 			} else if (g_sci->getGameId() == GID_HOYLE5 && index == kGlobalVarHoyle5ResponseTime && value.getOffset() == 0) {
172 				// WORKAROUND: Global 899 contains the response time value,
173 				// which may have values between 1 and 15. There is a script
174 				// bug when loading values from game.opt, where this variable
175 				// may be incorrectly set to 0. This makes the opponent freeze
176 				// while playing Backgammon and Bridge. Fix this case here, by
177 				// setting the correct minimum value, 1.
178 				// TODO: Either make this a script patch, or find out if it's
179 				// a bug with ScummVM when reading values from text files.
180 				_state->variables[VAR_GLOBAL][index].setOffset(1);
181 			} else if (g_sci->getGameId() == GID_RAMA && !g_sci->isDemo() && index == kGlobalVarRamaMusicVolume) {
182 				syncRamaVolumeFromScummVM((ConfMan.getInt("music_volume") + 1) * kRamaVolumeMax / Audio::Mixer::kMaxMixerVolume);
183 			}
184 
185 			if (_features->supportsTextSpeed()) {
186 				syncTextSpeedToScummVM(index, value);
187 			}
188 		}
189 #endif
190 		syncMessageTypeToScummVM(index, value);
191 	}
192 }
193 
kDoSoundMasterVolumeHook(const int volume) const194 bool GuestAdditions::kDoSoundMasterVolumeHook(const int volume) const {
195 	if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudioToScummVM()) {
196 		syncMasterVolumeToScummVM(volume);
197 		return true;
198 	}
199 	return false;
200 }
201 
202 #ifdef ENABLE_SCI32
sciEngineInitGameHook()203 void GuestAdditions::sciEngineInitGameHook() {
204 	if (g_sci->getGameId() == GID_PHANTASMAGORIA2 && Common::checkGameGUIOption(GAMEOPTION_ENABLE_CENSORING, ConfMan.get("guioptions"))) {
205 		_state->variables[VAR_GLOBAL][kGlobalVarPhant2CensorshipFlag] = make_reg(0, ConfMan.getBool("enable_censoring"));
206 	}
207 
208 	if (g_sci->getGameId() == GID_KQ7 && Common::checkGameGUIOption(GAMEOPTION_UPSCALE_VIDEOS, ConfMan.get("guioptions"))) {
209 		uint16 value = ConfMan.getBool("enable_video_upscale") ? 32 : 0;
210 		_state->variables[VAR_GLOBAL][kGlobalVarKQ7UpscaleVideos] = make_reg(0, value);
211 	}
212 }
213 
sendSelectorHook(const reg_t sendObj,Selector & selector,reg_t * argp)214 void GuestAdditions::sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp) {
215 	if (_features->getMessageTypeSyncStrategy() == kMessageTypeSyncStrategyLSL6Hires) {
216 		syncMessageTypeToScummVMUsingLSL6HiresStrategy(sendObj, selector, argp);
217 	}
218 }
219 
audio32SetVolumeHook(const int16 channelIndex,int16 volume) const220 bool GuestAdditions::audio32SetVolumeHook(const int16 channelIndex, int16 volume) const {
221 	if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudioToScummVM()) {
222 		volume = volume * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume;
223 		if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) {
224 			ConfMan.setInt("music_volume", volume);
225 		}
226 		ConfMan.setInt("sfx_volume", volume);
227 		ConfMan.setInt("speech_volume", volume);
228 		g_sci->updateSoundMixerVolumes();
229 		return true;
230 	}
231 
232 	return false;
233 }
234 
kDoSoundSetVolumeHook(const reg_t soundObj,const int16 volume) const235 void GuestAdditions::kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const {
236 	if (g_sci->getGameId() == GID_GK1 && shouldSyncAudioToScummVM()) {
237 		syncGK1AudioVolumeToScummVM(soundObj, volume);
238 	}
239 }
240 
instantiateScriptHook(Script & script,const bool ignoreDelayedRestore) const241 void GuestAdditions::instantiateScriptHook(Script &script, const bool ignoreDelayedRestore) const {
242 	if (getSciVersion() < SCI_VERSION_2) {
243 		return;
244 	}
245 
246 	// If there is a delayed restore, we still want to patch the script so
247 	// that the automatic return of the game ID works, but we do not want to
248 	// patch the scripts that get restored
249 	if (ConfMan.getBool("originalsaveload") &&
250 		(ignoreDelayedRestore || _state->_delayedRestoreGameId == -1)) {
251 		return;
252 	}
253 
254 	if ((g_sci->getGameId() == GID_LSL7 || g_sci->getGameId() == GID_TORIN) &&
255 		script.getScriptNumber() == 64866) {
256 
257 		patchGameSaveRestoreTorin(script);
258 	} else if (g_sci->getGameId() == GID_PHANTASMAGORIA2 && script.getScriptNumber() == 64978) {
259 		patchGameSaveRestorePhant2(script);
260 	} else if (script.getScriptNumber() == 64990) {
261 		// 64990 is the system script containing SRDialog. This script is used
262 		// by the main Game object, but it is not loaded immediately, so we wait
263 		// for it to be loaded before patching it. Attempting to preload this
264 		// script early for patching will cause the order of entries in the
265 		// segment table to change (versus save games that are not patched),
266 		// breaking persistent objects (like the control panel in SQ6) which
267 		// require reg_ts created during game startup to always be the same
268 
269 		if (g_sci->getGameId() == GID_RAMA) {
270 			patchGameSaveRestoreRama(script);
271 		} else {
272 			patchGameSaveRestoreSCI32(script);
273 		}
274 	}
275 }
276 
segManSaveLoadScriptHook(Script & script) const277 void GuestAdditions::segManSaveLoadScriptHook(Script &script) const {
278 	instantiateScriptHook(script, true);
279 }
280 
281 #endif
282 
kGetEventHook() const283 bool GuestAdditions::kGetEventHook() const {
284 	if (_state->_delayedRestoreGameId == -1) {
285 		return false;
286 	}
287 
288 #ifdef ENABLE_SCI32
289 	// Loading a save game while Lighthouse is still initializing itself will
290 	// cause loading to fail if the save game contains a saved Robot state,
291 	// because the Robot will try to restore itself into a game plane which does
292 	// not exist yet
293 	if (g_sci->getGameId() == GID_LIGHTHOUSE && _state->callInStack(g_sci->getGameObject(), SELECTOR(init))) {
294 		return false;
295 	}
296 #endif
297 
298 	return g_sci->_guestAdditions->restoreFromLauncher();
299 }
300 
kWaitHook() const301 bool GuestAdditions::kWaitHook() const {
302 	if (_state->_delayedRestoreGameId == -1) {
303 		return false;
304 	}
305 
306 	// kWait cannot be used in Phant2 for delayed restore because it is
307 	// called during the fade-in of music in the intro room, before graphics
308 	// are fully initialized, which causes "Click to continue" text to be
309 	// brokenly drawn over the game and then crashes the engine on the next
310 	// room transition
311 	if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
312 		return false;
313 	}
314 
315 	return g_sci->_guestAdditions->restoreFromLauncher();
316 }
317 
318 #ifdef ENABLE_SCI32
kPlayDuckPlayVMDHook() const319 bool GuestAdditions::kPlayDuckPlayVMDHook() const {
320 	return _state->_delayedRestoreGameId != -1;
321 }
322 #endif
323 
324 #pragma mark -
325 #pragma mark Integrated save & restore
326 
patchGameSaveRestore() const327 void GuestAdditions::patchGameSaveRestore() const {
328 	if (ConfMan.getBool("originalsaveload") || getSciVersion() >= SCI_VERSION_2)
329 		return;
330 
331 	patchGameSaveRestoreSCI16();
332 }
333 
334 static const byte kSaveRestorePatch[] = {
335 	0x39, 0x03,        // pushi 03
336 	0x76,              // push0
337 	0x38, 0xff, 0xff,  // pushi -1
338 	0x76,              // push0
339 	0x43, 0xff, 0x06,  // callk kRestoreGame/kSaveGame (will get changed afterwards)
340 	0x48               // ret
341 };
342 
patchKSaveRestore(SegManager * segMan,reg_t methodAddress,byte id)343 static void patchKSaveRestore(SegManager *segMan, reg_t methodAddress, byte id) {
344 	Script *script = segMan->getScript(methodAddress.getSegment());
345 	byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
346 	memcpy(patchPtr, kSaveRestorePatch, sizeof(kSaveRestorePatch));
347 	patchPtr[8] = id;
348 }
349 
patchGameSaveRestoreSCI16() const350 void GuestAdditions::patchGameSaveRestoreSCI16() const {
351 	const Object *gameObject = _segMan->getObject(g_sci->getGameObject());
352 	const Object *gameSuperObject = _segMan->getObject(gameObject->getSuperClassSelector());
353 	if (!gameSuperObject)
354 		gameSuperObject = gameObject;	// happens in KQ5CD, when loading saved games before r54510
355 	byte kernelIdRestore = 0;
356 	byte kernelIdSave = 0;
357 
358 	switch (g_sci->getGameId()) {
359 	case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all
360 	case GID_HOYLE2: // gets confused, see hoyle1
361 	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
362 	case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
363 	case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
364 		return;
365 	default:
366 		break;
367 	}
368 
369 	uint16 kernelNamesSize = _kernel->getKernelNamesSize();
370 	for (uint16 kernelNr = 0; kernelNr < kernelNamesSize; kernelNr++) {
371 		Common::String kernelName = _kernel->getKernelName(kernelNr);
372 		if (kernelName == "RestoreGame")
373 			kernelIdRestore = kernelNr;
374 		if (kernelName == "SaveGame")
375 			kernelIdSave = kernelNr;
376 		if (kernelName == "Save")
377 			kernelIdSave = kernelIdRestore = kernelNr;
378 	}
379 
380 	// Search for gameobject superclass ::restore
381 	uint16 gameSuperObjectMethodCount = gameSuperObject->getMethodCount();
382 	for (uint16 methodNr = 0; methodNr < gameSuperObjectMethodCount; methodNr++) {
383 		uint16 selectorId = gameSuperObject->getFuncSelector(methodNr);
384 		Common::String methodName = _kernel->getSelectorName(selectorId);
385 		if (methodName == "restore") {
386 				patchKSaveRestore(_segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore);
387 		} else if (methodName == "save") {
388 			if (g_sci->getGameId() != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
389 					patchKSaveRestore(_segMan, gameSuperObject->getFunction(methodNr), kernelIdSave);
390 			}
391 		}
392 	}
393 
394 	// Patch gameobject ::save for now for SCI0 - SCI1.1
395 	// TODO: It seems this was never adjusted to superclass, but adjusting it now may cause
396 	// issues with some game. Needs to get checked and then possibly changed.
397 	const Object *patchObjectSave = gameObject;
398 
399 	// Search for gameobject ::save, if there is one patch that one too
400 	uint16 patchObjectMethodCount = patchObjectSave->getMethodCount();
401 	for (uint16 methodNr = 0; methodNr < patchObjectMethodCount; methodNr++) {
402 		uint16 selectorId = patchObjectSave->getFuncSelector(methodNr);
403 		Common::String methodName = _kernel->getSelectorName(selectorId);
404 		if (methodName == "save") {
405 			if (g_sci->getGameId() != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
406 					patchKSaveRestore(_segMan, patchObjectSave->getFunction(methodNr), kernelIdSave);
407 			}
408 			break;
409 		}
410 	}
411 }
412 
413 #ifdef ENABLE_SCI32
414 static const byte SRDialogPatch[] = {
415 	0x76,                                 // push0
416 	0x59, 0x01,                           // &rest 1
417 	0x43, kScummVMSaveLoadId, 0x00, 0x00, // callk kScummVMSaveLoad, 0
418 	0x48                                  // ret
419 };
420 
patchGameSaveRestoreSCI32(Script & script) const421 void GuestAdditions::patchGameSaveRestoreSCI32(Script &script) const {
422 	patchSRDialogDoit(script, "SRDialog", SRDialogPatch, sizeof(SRDialogPatch));
423 }
424 
425 static const byte SRTorinPatch[] = {
426 	0x38, 0xFF, 0xFF,                     // pushi new
427 	0x76,                                 // push0
428 	0x51, 0x0f,                           // class Str
429 	0x4a, 0x04, 0x00,                     // send 4
430 	0xa3, 0x01,                           // sal 1
431 	0x76,                                 // push0
432 	0x59, 0x01,                           // &rest 1
433 	0x43, kScummVMSaveLoadId, 0x00, 0x00, // callk kScummVMSaveLoad, 0
434 	0x48                                  // ret
435 };
436 
patchGameSaveRestoreTorin(Script & script) const437 void GuestAdditions::patchGameSaveRestoreTorin(Script &script) const {
438 	const uint32 address = script.validateExportFunc(2, true);
439 	byte *patchPtr = const_cast<byte *>(script.getBuf(address));
440 	memcpy(patchPtr, SRTorinPatch, sizeof(SRTorinPatch));
441 
442 	const Selector newSelector = SELECTOR(new_);
443 	assert(newSelector != -1);
444 	patchPtr[1] = newSelector & 0xFF;
445 	patchPtr[2] = (newSelector >> 8) & 0xFF;
446 
447 	if (g_sci->isBE()) {
448 		SWAP(patchPtr[1], patchPtr[2]);
449 		SWAP(patchPtr[8], patchPtr[9]);
450 	}
451 }
452 
patchGameSaveRestorePhant2(Script & script) const453 void GuestAdditions::patchGameSaveRestorePhant2(Script &script) const {
454 	const ObjMap &objects = script.getObjectMap();
455 	for (ObjMap::const_iterator it = objects.begin(); it != objects.end(); ++it) {
456 		const Object &obj = it->_value;
457 
458 		if (strcmp(_segMan->derefString(obj.getNameSelector()), "srGetGame") != 0) {
459 			continue;
460 		}
461 
462 		int methodIndex = obj.funcSelectorPosition(SELECTOR(init));
463 		if (methodIndex == -1) {
464 			continue;
465 		}
466 
467 		byte *scriptData = const_cast<byte *>(script.getBuf(obj.getFunction(methodIndex).getOffset()));
468 		memcpy(scriptData, SRDialogPatch, sizeof(SRDialogPatch));
469 		break;
470 	}
471 }
472 
473 static const byte RamaSRDialogPatch[] = {
474 	0x78,                                 // push1
475 	0x7c,                                 // pushSelf
476 	0x43, kScummVMSaveLoadId, 0x02, 0x00, // callk kScummVMSaveLoad, 0
477 	0x48                                  // ret
478 };
479 
480 static const int RamaSRDialogUint16Offsets[] = { 4 };
481 
patchGameSaveRestoreRama(Script & script) const482 void GuestAdditions::patchGameSaveRestoreRama(Script &script) const {
483 	patchSRDialogDoit(script, "Save", RamaSRDialogPatch, sizeof(RamaSRDialogPatch), RamaSRDialogUint16Offsets, ARRAYSIZE(RamaSRDialogUint16Offsets));
484 	patchSRDialogDoit(script, "Restore", RamaSRDialogPatch, sizeof(RamaSRDialogPatch), RamaSRDialogUint16Offsets, ARRAYSIZE(RamaSRDialogUint16Offsets));
485 }
486 
patchSRDialogDoit(Script & script,const char * const objectName,const byte * patchData,const int patchSize,const int * uint16Offsets,const uint numOffsets) const487 void GuestAdditions::patchSRDialogDoit(Script &script, const char *const objectName, const byte *patchData, const int patchSize, const int *uint16Offsets, const uint numOffsets) const {
488 	const ObjMap &objMap = script.getObjectMap();
489 	for (ObjMap::const_iterator it = objMap.begin(); it != objMap.end(); ++it) {
490 		const Object &obj = it->_value;
491 		if (strcmp(_segMan->getObjectName(obj.getPos()), objectName) != 0) {
492 			continue;
493 		}
494 
495 		const uint16 methodCount = obj.getMethodCount();
496 		for (uint16 methodNr = 0; methodNr < methodCount; ++methodNr) {
497 			const uint16 selectorId = obj.getFuncSelector(methodNr);
498 			const Common::String methodName = _kernel->getSelectorName(selectorId);
499 			if (methodName == "doit") {
500 				const reg_t methodAddress = obj.getFunction(methodNr);
501 				byte *patchPtr = const_cast<byte *>(script.getBuf(methodAddress.getOffset()));
502 				memcpy(patchPtr, patchData, patchSize);
503 
504 				if (g_sci->isBE()) {
505 					for (uint i = 0; i < numOffsets; ++i) {
506 						const int offset = uint16Offsets[i];
507 						SWAP(patchPtr[offset], patchPtr[offset + 1]);
508 					}
509 				}
510 
511 				return;
512 			}
513 		}
514 	}
515 }
516 
kScummVMSaveLoad(EngineState * s,int argc,reg_t * argv) const517 reg_t GuestAdditions::kScummVMSaveLoad(EngineState *s, int argc, reg_t *argv) const {
518 	if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
519 		return promptSaveRestorePhant2(s, argc, argv);
520 	}
521 
522 	if (g_sci->getGameId() == GID_LSL7 || g_sci->getGameId() == GID_TORIN) {
523 		return promptSaveRestoreTorin(s, argc, argv);
524 	}
525 
526 	if (g_sci->getGameId() == GID_RAMA) {
527 		return promptSaveRestoreRama(s, argc, argv);
528 	}
529 
530 	return promptSaveRestoreDefault(s, argc, argv);
531 }
532 
promptSaveRestoreDefault(EngineState * s,int argc,reg_t * argv) const533 reg_t GuestAdditions::promptSaveRestoreDefault(EngineState *s, int argc, reg_t *argv) const {
534 	return make_reg(0, runSaveRestore(argc > 0, argc > 0 ? argv[0] : NULL_REG, s->_delayedRestoreGameId));
535 }
536 
promptSaveRestoreTorin(EngineState * s,int argc,reg_t * argv) const537 reg_t GuestAdditions::promptSaveRestoreTorin(EngineState *s, int argc, reg_t *argv) const {
538 	const bool isSave = (argc > 0 && (bool)argv[0].toSint16());
539 
540 	reg_t descriptionId = NULL_REG;
541 	if (isSave) {
542 		_segMan->allocateArray(kArrayTypeString, 0, &descriptionId);
543 	}
544 
545 	const int saveNo = runSaveRestore(isSave, descriptionId, s->_delayedRestoreGameId);
546 
547 	if (saveNo != -1) {
548 		assert(s->variablesMax[VAR_LOCAL] > 2);
549 		writeSelector(_segMan, s->variables[VAR_LOCAL][1], SELECTOR(data), descriptionId);
550 		s->variables[VAR_LOCAL][2] = make_reg(0, saveNo);
551 		s->variables[VAR_LOCAL][3] = make_reg(0, isSave ? 1 : 0);
552 	} else if (isSave) {
553 		_segMan->freeArray(descriptionId);
554 	}
555 
556 	return make_reg(0, saveNo != -1);
557 }
558 
promptSaveRestorePhant2(EngineState * s,int argc,reg_t * argv) const559 reg_t GuestAdditions::promptSaveRestorePhant2(EngineState *s, int argc, reg_t *argv) const {
560 	assert(argc == 2);
561 	const bool isSave = argv[1].toSint16() == 0;
562 	const int saveNo = runSaveRestore(isSave, argv[0], s->_delayedRestoreGameId);
563 
564 	// Clear the highlighted state of the button so if the same control panel is
565 	// opened again it does not appear to be opened to the save/load panels
566 	reg_t button;
567 	if (isSave) {
568 		button = _segMan->findObjectByName("saveButton");
569 	} else {
570 		button = _segMan->findObjectByName("loadButton");
571 	}
572 	writeSelectorValue(_segMan, button, SELECTOR(cel), 0);
573 
574 	// This causes the control panel to quit its internal event loop and hide
575 	// itself
576 	const reg_t controlPanel = s->variables[VAR_GLOBAL][kGlobalVarPhant2ControlPanel];
577 	writeSelector(_segMan, controlPanel, SELECTOR(scratch), TRUE_REG);
578 
579 	return make_reg(0, saveNo);
580 }
581 
promptSaveRestoreRama(EngineState * s,int argc,reg_t * argv) const582 reg_t GuestAdditions::promptSaveRestoreRama(EngineState *s, int argc, reg_t *argv) const {
583 	assert(argc == 1);
584 	const bool isSave = (strcmp(_segMan->getObjectName(argv[0]), "Save") == 0);
585 
586 	const reg_t editor = _segMan->findObjectByName("editI");
587 	reg_t outDescription = readSelector(_segMan, editor, SELECTOR(text));
588 	if (!_segMan->isValidAddr(outDescription, SEG_TYPE_ARRAY)) {
589 		_segMan->allocateArray(kArrayTypeString, 0, &outDescription);
590 		writeSelector(_segMan, editor, SELECTOR(text), outDescription);
591 	}
592 
593 	int saveNo = runSaveRestore(isSave, outDescription, s->_delayedRestoreGameId);
594 	int saveIndex = -1;
595 	if (saveNo != -1) {
596 		// The save number returned by runSaveRestore is a SCI save number
597 		// because normally SRDialogs return the save ID, but RAMA returns the
598 		// save game's index in the save game list instead, so we need to
599 		// convert back to the ScummVM save number here to find the correct
600 		// index
601 		saveNo += kSaveIdShift;
602 
603 		Common::Array<SavegameDesc> saves;
604 		listSavegames(saves);
605 		saveIndex = findSavegame(saves, saveNo);
606 
607 		if (isSave) {
608 			bool resetCatalogFile = false;
609 			const Common::String saveGameName = _segMan->getString(outDescription);
610 
611 			// The original game save/restore code returns index 0 when a game
612 			// is created that does not already exist and then the scripts find
613 			// the next hole and insert there, but the ScummVM GUI works
614 			// differently and allows users to insert a game wherever they want,
615 			// so we need to force the save game to exist in advance so RAMA's
616 			// save code will successfully put it where we want it
617 			if (saveIndex == -1) {
618 				// We need to touch the save file just so it exists here, since
619 				// otherwise the game will not let us save to the new save slot
620 				// (it will try to come up with a brand new slot instead)
621 				Common::OutSaveFile *out = g_sci->getSaveFileManager()->openForSaving(g_sci->getSavegameName(saveNo));
622 				set_savegame_metadata(out, saveGameName, "");
623 
624 				// Make sure the save file is fully written before we try to
625 				// re-retrieve the list of saves, since otherwise it may not
626 				// show up in the list
627 				delete out;
628 
629 				// We have to re-retrieve saves and find the index instead of
630 				// assuming the newest save will be in index 0 because save game
631 				// times are not guaranteed to be steady
632 				saves.clear();
633 				listSavegames(saves);
634 				saveIndex = findSavegame(saves, saveNo);
635 				if (saveIndex == -1) {
636 					warning("Stub save not found when trying to save a new game to slot %d", saveNo);
637 				} else {
638 					// Kick the CatalogFile into believing that this new save
639 					// game exists already, otherwise it the game will not
640 					// actually save into the new save
641 					resetCatalogFile = true;
642 				}
643 			} else if (strncmp(saveGameName.c_str(), saves[saveIndex].name, kMaxSaveNameLength) != 0) {
644 				// The game doesn't let the save game name change for the same
645 				// slot, but ScummVM's GUI does, so force the new name into the
646 				// save file metadata if it has changed so it actually makes it
647 				// into the save game
648 				Common::ScopedPtr<Common::OutSaveFile> out(g_sci->getSaveFileManager()->openForSaving(g_sci->getSavegameName(saveNo)));
649 				set_savegame_metadata(out.get(), saveGameName, "");
650 				resetCatalogFile = true;
651 			}
652 
653 			if (resetCatalogFile) {
654 				const reg_t catalogFileId = _state->variables[VAR_GLOBAL][kGlobalVarRamaCatalogFile];
655 				if (catalogFileId.isNull()) {
656 					warning("Could not find CatalogFile when saving from launcher");
657 				}
658 				reg_t args[] = { NULL_REG };
659 				invokeSelector(catalogFileId, SELECTOR(dispose));
660 				invokeSelector(catalogFileId, SELECTOR(init), ARRAYSIZE(args), args);
661 			}
662 		}
663 	}
664 
665 	return make_reg(0, saveIndex);
666 }
667 
runSaveRestore(const bool isSave,reg_t outDescription,const int forcedSaveNo) const668 int GuestAdditions::runSaveRestore(const bool isSave, reg_t outDescription, const int forcedSaveNo) const {
669 	int saveNo;
670 	Common::String descriptionString;
671 
672 	if (!isSave && forcedSaveNo != -1) {
673 		saveNo = forcedSaveNo;
674 	} else {
675 		const char *title;
676 		const char *action;
677 		if (isSave) {
678 			title = _("Save game:");
679 			action = _("Save");
680 		} else {
681 			title = _("Restore game:");
682 			action = _("Restore");
683 		}
684 
685 		GUI::SaveLoadChooser dialog(title, action, isSave);
686 		saveNo = dialog.runModalWithCurrentTarget();
687 		if (saveNo != -1) {
688 			descriptionString = dialog.getResultString();
689 			if (descriptionString.empty()) {
690 				descriptionString = dialog.createDefaultSaveDescription(saveNo - 1);
691 			}
692 		}
693 	}
694 
695 	assert(!isSave || !outDescription.isNull());
696 	if (!outDescription.isNull()) {
697 		if (_segMan->isObject(outDescription)) {
698 			outDescription = readSelector(_segMan, outDescription, SELECTOR(data));
699 		}
700 		SciArray &description = *_segMan->lookupArray(outDescription);
701 		description.fromString(descriptionString);
702 	}
703 
704 	// The autosave slot in ScummVM takes up slot 0, but in SCI the first
705 	// non-autosave save game number needs to be 0, so reduce the save
706 	// number here to match what would come from the normal SCI save/restore
707 	// dialog. Wrap slot 0 around to kMaxShiftedSaveId so that it remains
708 	// a legal SCI value.
709 	if (saveNo > 0) {
710 		saveNo -= kSaveIdShift;
711 	} else if (saveNo == 0) {
712 		saveNo = kMaxShiftedSaveId;
713 	}
714 
715 	return saveNo;
716 }
717 
718 #endif
719 
720 #pragma mark -
721 #pragma mark Restore from launcher
722 
restoreFromLauncher() const723 bool GuestAdditions::restoreFromLauncher() const {
724 	assert(_state->_delayedRestoreGameId != -1);
725 
726 #ifdef ENABLE_SCI32
727 	if (getSciVersion() >= SCI_VERSION_2) {
728 		if (_restoring) {
729 			// Recursion will occur if a restore fails, as
730 			// _delayedRestoreGameId will not be reset so the kernel will try
731 			// to keep restoring forever
732 			_state->_delayedRestoreGameId = -1;
733 			_restoring = false;
734 			return false;
735 		}
736 
737 		// Delayed restore should not happen until after the benchmarking room.
738 		// In particular, in SQ6, delayed restore must not happen until room 100
739 		// (the Sierra logo & main menu room), otherwise the game scripts will
740 		// try to make calls to the subtitles ScrollWindow, which does not
741 		// exist. In other games, restoring early either breaks benchmarking,
742 		// or, when trying to load an invalid save game, makes the dialog
743 		// telling the user that the game is invalid impossible to read
744 		if (strcmp(_segMan->getObjectName(_state->variables[VAR_GLOBAL][kGlobalVarCurrentRoom]), "speedRoom") == 0) {
745 			return false;
746 		}
747 
748 		// Delayed restore should not happen in LSL6 hires until the room number is set.
749 		//  LSL6:restore tests room numbers to determine if restoring is allowed, but the
750 		//  Mac version adds a call to kGetEvent in LSL6:init before the initial call to
751 		//  LSL6:newRoom. If the room number isn't set yet then restoring isn't allowed.
752 		if (g_sci->getGameId() == GID_LSL6HIRES && _state->variables[VAR_GLOBAL][kGlobalVarCurrentRoomNo] == NULL_REG) {
753 			return false;
754 		}
755 
756 		_restoring = true;
757 
758 		// Any events queued up before the game restore can cause accidental
759 		// input into the game if they are not flushed (this is particularly
760 		// noticeable in Phant2, where the game will display "Click to continue"
761 		// for one frame if the user clicked during startup)
762 		g_sci->getEventManager()->flushEvents();
763 
764 		if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
765 			// Phantasmagoria 2 moves the function that actually restores
766 			// a game, and uses a property of the main game object when picking
767 			// the save game to restore
768 			writeSelectorValue(_segMan, g_sci->getGameObject(), SELECTOR(num), _state->_delayedRestoreGameId - kSaveIdShift);
769 			invokeSelector(g_sci->getGameObject(), SELECTOR(reallyRestore));
770 		} else if (g_sci->getGameId() == GID_SHIVERS) {
771 			// Shivers accepts the save game number as a parameter to
772 			// `SHIVERS::restore`
773 			reg_t args[] = { make_reg(0, _state->_delayedRestoreGameId - kSaveIdShift) };
774 			invokeSelector(g_sci->getGameObject(), SELECTOR(restore), 1, args);
775 		} else {
776 			// When `Game::restore` is invoked, it will call to `Restore::doit`
777 			// which will automatically return the `_delayedRestoreGameId` instead
778 			// of prompting the user for a save game
779 			invokeSelector(g_sci->getGameObject(), SELECTOR(restore));
780 
781 			// The normal save game system resets _delayedRestoreGameId with a
782 			// call to `EngineState::reset`, but RAMA uses a custom save game
783 			// system which does not reset the engine, so we need to clear the
784 			// ID here or the engine will just try to restore the game forever
785 			if (g_sci->getGameId() == GID_RAMA) {
786 				_state->_delayedRestoreGameId = -1;
787 			}
788 		}
789 
790 		_restoring = false;
791 
792 		return true;
793 	} else {
794 #else
795 	{
796 #endif
797 		int savegameId = _state->_delayedRestoreGameId; // delayedRestoreGameId gets destroyed within gamestate_restore()!
798 		Common::String fileName = g_sci->getSavegameName(savegameId);
799 		Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName);
800 
801 		if (in) {
802 			// found a savegame file
803 			gamestate_restore(_state, in);
804 			delete in;
805 			if (_state->r_acc != make_reg(0, 1)) {
806 				gamestate_afterRestoreFixUp(_state, savegameId);
807 				return true;
808 			}
809 		}
810 
811 		error("Restoring gamestate '%s' failed", fileName.c_str());
812 	}
813 }
814 
815 #pragma mark -
816 #pragma mark Message type sync
817 
818 void GuestAdditions::syncMessageTypeFromScummVM() const {
819 	switch (_features->getMessageTypeSyncStrategy()) {
820 	case kMessageTypeSyncStrategyDefault:
821 		syncMessageTypeFromScummVMUsingDefaultStrategy();
822 		break;
823 
824 #ifdef ENABLE_SCI32
825 	case kMessageTypeSyncStrategyShivers:
826 		syncMessageTypeFromScummVMUsingShiversStrategy();
827 		break;
828 
829 	case kMessageTypeSyncStrategyLSL6Hires:
830 		syncMessageTypeFromScummVMUsingLSL6HiresStrategy();
831 		break;
832 #endif
833 	case kMessageTypeSyncStrategyNone:
834 		break;
835 	}
836 }
837 
838 void GuestAdditions::syncMessageTypeFromScummVMUsingDefaultStrategy() const {
839 	uint8 value = 0;
840 	if (ConfMan.getBool("subtitles")) {
841 		value |= kMessageTypeSubtitles;
842 	}
843 	if (!ConfMan.getBool(("speech_mute"))) {
844 		value |= kMessageTypeSpeech;
845 	}
846 
847 	if (value == kMessageTypeSubtitles + kMessageTypeSpeech && !_features->supportsSpeechWithSubtitles()) {
848 		value &= ~kMessageTypeSubtitles;
849 	}
850 
851 	if (value) {
852 		_state->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, value);
853 	}
854 
855 #ifdef ENABLE_SCI32
856 	if (g_sci->getGameId() == GID_GK1 && value == kMessageTypeSubtitles) {
857 		// The narrator speech needs to be forced off if speech has been
858 		// disabled in ScummVM, but otherwise the narrator toggle should just
859 		// be allowed to persist to whatever the user chose previously, since
860 		// it is controlled independently of other speech in the game and there
861 		// is no equivalent option in the ScummVM GUI
862 		_state->variables[VAR_GLOBAL][kGlobalVarGK1NarratorMode] = NULL_REG;
863 	}
864 
865 	if (g_sci->getGameId() == GID_QFG4) {
866 		// QFG4 uses a game flag to control the Audio button's state in the control panel.
867 		//  This flag must be kept in sync with the standard global 90 speech bit.
868 		uint flagNumber = 400;
869 		uint globalNumber = kGlobalVarQFG4Flags + (flagNumber / 16);
870 		if (value & kMessageTypeSpeech) {
871 			_state->variables[VAR_GLOBAL][globalNumber] |= (int16)0x8000;
872 		} else {
873 			_state->variables[VAR_GLOBAL][globalNumber] &= (int16)~0x8000;
874 		}
875 	}
876 #endif
877 }
878 
879 #ifdef ENABLE_SCI32
880 void GuestAdditions::syncMessageTypeFromScummVMUsingShiversStrategy() const {
881 	if (ConfMan.getBool("subtitles")) {
882 		_state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] |= 256;
883 	} else {
884 		_state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] &= ~256;
885 	}
886 }
887 
888 void GuestAdditions::syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const {
889 	// LSL6hires synchronisation happens in send_selector, except when
890 	// restoring a game, where it happens here
891 	if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags].isNull()) {
892 		return;
893 	}
894 
895 	reg_t params[] = { make_reg(0, kLSL6HiresSubtitleFlag) };
896 	Selector selector;
897 	reg_t restore;
898 
899 	if (ConfMan.getBool("subtitles")) {
900 		restore = TRUE_REG;
901 		selector = SELECTOR(clear);
902 	} else {
903 		restore = NULL_REG;
904 		selector = SELECTOR(set);
905 	}
906 
907 	// Attempting to show or hide the ScrollWindow used for subtitles
908 	// directly (by invoking `show` or `hide`) causes the game to crash with
909 	// an error about passing an invalid ScrollWindow ID. Fortunately, the
910 	// game scripts store a flag that restores the window when a game is
911 	// restored
912 	_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresRestoreTextWindow] = restore;
913 	invokeSelector(_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags], selector, 1, params);
914 }
915 #endif
916 
917 void GuestAdditions::syncMessageTypeToScummVM(const int index, const reg_t value) {
918 	switch (_features->getMessageTypeSyncStrategy()) {
919 	case kMessageTypeSyncStrategyDefault:
920 		syncMessageTypeToScummVMUsingDefaultStrategy(index, value);
921 		break;
922 
923 #ifdef ENABLE_SCI32
924 	case kMessageTypeSyncStrategyShivers:
925 		syncMessageTypeToScummVMUsingShiversStrategy(index, value);
926 		break;
927 
928 	case kMessageTypeSyncStrategyLSL6Hires:
929 		// LSL6hires synchronisation happens via send_selector
930 #endif
931 	case kMessageTypeSyncStrategyNone:
932 		break;
933 	}
934 }
935 
936 void GuestAdditions::syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value) {
937 	if (index == kGlobalVarMessageType) {
938 		// ScummVM audio options haven't been applied yet. Use this set call
939 		// as a trigger to apply defaults from ScummVM, ignoring the default
940 		// value that was just received from the game scripts
941 		if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
942 			_messageTypeSynced = true;
943 			syncAudioOptionsFromScummVM();
944 			return;
945 		}
946 
947 		ConfMan.setBool("subtitles", value.toSint16() & kMessageTypeSubtitles);
948 		ConfMan.setBool("speech_mute", !(value.toSint16() & kMessageTypeSpeech));
949 
950 		// need to update sound mixer volumes so that speech_mute will take effect
951 		g_sci->updateSoundMixerVolumes();
952 	}
953 }
954 
955 #ifdef ENABLE_SCI32
956 void GuestAdditions::syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value) {
957 	if (index == kGlobalVarShiversFlags) {
958 		// ScummVM audio options haven't been applied yet, so apply them
959 		// and ignore the default value that was just received from the
960 		// game scripts
961 		if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
962 			_messageTypeSynced = true;
963 			syncAudioOptionsFromScummVM();
964 			return;
965 		}
966 
967 		ConfMan.setBool("subtitles", value.toUint16() & 256);
968 	}
969 }
970 
971 void GuestAdditions::syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp) {
972 	if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags] == sendObj &&
973 		(selector == SELECTOR(clear) || selector == SELECTOR(set))) {
974 
975 		if (argp[1].toUint16() == kLSL6HiresSubtitleFlag) {
976 			if (_messageTypeSynced) {
977 				ConfMan.setBool("subtitles", selector == SELECTOR(clear));
978 			} else if (ConfMan.getBool("subtitles")) {
979 				selector = SELECTOR(clear);
980 				argp[-1].setOffset(selector);
981 				_messageTypeSynced = true;
982 			} else {
983 				selector = SELECTOR(set);
984 				argp[-1].setOffset(selector);
985 				_messageTypeSynced = true;
986 			}
987 		}
988 	}
989 }
990 #endif
991 
992 #pragma mark -
993 #pragma mark Master volume sync
994 
995 void GuestAdditions::syncMasterVolumeFromScummVM() const {
996 #ifdef ENABLE_SCI32
997 	const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
998 	const int16 sfxVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
999 
1000 	// Volume was changed from ScummVM during the game, so resync the
1001 	// in-game UI
1002 	syncInGameUI(musicVolume, sfxVolume);
1003 #endif
1004 }
1005 
1006 void GuestAdditions::syncMasterVolumeToScummVM(const int16 masterVolume) const {
1007 	const int scummVMVolume = masterVolume * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
1008 	ConfMan.setInt("music_volume", scummVMVolume);
1009 
1010 	if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) {
1011 		ConfMan.setInt("sfx_volume", scummVMVolume);
1012 		if (Common::checkGameGUIOption(GUIO_LINKSPEECHTOSFX, ConfMan.get("guioptions"))) {
1013 			ConfMan.setInt("speech_volume", scummVMVolume);
1014 		}
1015 	}
1016 
1017 	// In SCI32, digital audio volume is controlled separately by
1018 	// kDoAudioVolume
1019 	// TODO: In SCI16, the volume slider only changed the music volume.
1020 	// Is this non-standard behavior better, or just wrong?
1021 	if (getSciVersion() < SCI_VERSION_2) {
1022 		ConfMan.setInt("sfx_volume", scummVMVolume);
1023 		ConfMan.setInt("speech_volume", scummVMVolume);
1024 	}
1025 	g_sci->updateSoundMixerVolumes();
1026 }
1027 
1028 #ifdef ENABLE_SCI32
1029 #pragma mark -
1030 #pragma mark Globals volume sync
1031 
1032 void GuestAdditions::syncAudioVolumeGlobalsFromScummVM() const {
1033 	// On muting: Setting the music volume to zero when mute is enabled is done
1034 	// only for the games that use MIDI for music playback, since MIDI playback
1035 	// does not always run through the ScummVM mixer. Games that use digital
1036 	// audio for music do not need any extra code since that always runs
1037 	// straight through the audio mixer, which gets muted directly
1038 	switch (g_sci->getGameId()) {
1039 	case GID_GK1: {
1040 		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
1041 		const int16 dacVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
1042 		syncGK1VolumeFromScummVM(musicVolume, dacVolume);
1043 		syncGK1UI();
1044 		break;
1045 	}
1046 
1047 	case GID_GK2: {
1048 		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
1049 		syncGK2VolumeFromScummVM(musicVolume);
1050 		syncGK2UI();
1051 		break;
1052 	}
1053 
1054 	case GID_HOYLE5: {
1055 		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * kHoyle5VolumeMax / Audio::Mixer::kMaxMixerVolume;
1056 		syncHoyle5VolumeFromScummVM(musicVolume);
1057 		syncHoyle5UI(musicVolume);
1058 		break;
1059 	}
1060 
1061 	case GID_LSL6HIRES: {
1062 		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * kLSL6HiresUIVolumeMax / Audio::Mixer::kMaxMixerVolume;
1063 		syncLSL6HiresVolumeFromScummVM(musicVolume);
1064 		syncLSL6HiresUI(musicVolume);
1065 		break;
1066 	}
1067 
1068 	case GID_PHANTASMAGORIA: {
1069 		reg_t &musicGlobal = _state->variables[VAR_GLOBAL][kGlobalVarPhant1MusicVolume];
1070 		reg_t &dacGlobal   = _state->variables[VAR_GLOBAL][kGlobalVarPhant1DACVolume];
1071 
1072 		const int16 oldMusicVolume = musicGlobal.toSint16();
1073 		const int16 oldDacVolume   = dacGlobal.toSint16();
1074 
1075 		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
1076 		const int16 dacVolume   = (ConfMan.getInt("sfx_volume") + 1)   * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume;
1077 
1078 		g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : musicVolume);
1079 
1080 		// Phant1 has a fragile volume UI. Global volumes need to be set during
1081 		// UI updates to move the volume bars to the correct position
1082 		syncPhant1UI(oldMusicVolume, musicVolume, musicGlobal, oldDacVolume, dacVolume, dacGlobal);
1083 		break;
1084 	}
1085 
1086 	case GID_PHANTASMAGORIA2: {
1087 		const int16 masterVolume = (ConfMan.getInt("sfx_volume") + 1) * kPhant2VolumeMax / Audio::Mixer::kMaxMixerVolume;
1088 		syncPhant2VolumeFromScummVM(masterVolume);
1089 		syncPhant2UI(masterVolume);
1090 		break;
1091 	}
1092 
1093 	case GID_RAMA: {
1094 		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * kRamaVolumeMax / Audio::Mixer::kMaxMixerVolume;
1095 		syncRamaVolumeFromScummVM(musicVolume);
1096 		syncRamaUI(musicVolume);
1097 		break;
1098 	}
1099 
1100 	case GID_LSL7:
1101 	case GID_TORIN: {
1102 		const int16 musicVolume  = (ConfMan.getInt("music_volume") + 1)  * 100 / Audio::Mixer::kMaxMixerVolume;
1103 		const int16 sfxVolume    = (ConfMan.getInt("sfx_volume") + 1)    * 100 / Audio::Mixer::kMaxMixerVolume;
1104 		const int16 speechVolume = (ConfMan.getInt("speech_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume;
1105 		syncTorinVolumeFromScummVM(musicVolume, sfxVolume, speechVolume);
1106 		syncTorinUI(musicVolume, sfxVolume, speechVolume);
1107 		break;
1108 	}
1109 
1110 	default:
1111 		error("Trying to sync audio volume globals in a game with no implementation");
1112 	}
1113 }
1114 
1115 void GuestAdditions::syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const {
1116 	if (index == kGlobalVarGK1Music1 || index == kGlobalVarGK1Music2 ||
1117 		index == kGlobalVarGK1DAC1 || index == kGlobalVarGK1DAC2 ||
1118 		index == kGlobalVarGK1DAC3) {
1119 
1120 		int16 volume;
1121 		Selector selector;
1122 
1123 		switch (readSelectorValue(_segMan, value, SELECTOR(type))) {
1124 		case kSoundsMusicType: {
1125 			volume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
1126 			selector = SELECTOR(musicVolume);
1127 			break;
1128 		}
1129 
1130 		case kSoundsSoundType: {
1131 			volume = (ConfMan.getInt("sfx_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume;
1132 			selector = SELECTOR(soundVolume);
1133 			break;
1134 		}
1135 
1136 		default:
1137 			error("Unknown sound type");
1138 		}
1139 
1140 		writeSelectorValue(_segMan, value, selector, volume);
1141 	}
1142 }
1143 
1144 void GuestAdditions::syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const {
1145 	const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds];
1146 	if (!soundsId.isNull()) {
1147 		List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements)));
1148 		reg_t soundId = sounds->first;
1149 		while (!soundId.isNull()) {
1150 			Node *sound = _segMan->lookupNode(soundId);
1151 			const int16 type = readSelectorValue(_segMan, sound->value, SELECTOR(type));
1152 			int16 volume;
1153 
1154 			if (type == kSoundsMusicType) {
1155 				volume = ConfMan.getBool("mute") ? 0 : musicVolume;
1156 				writeSelectorValue(_segMan, sound->value, SELECTOR(musicVolume), musicVolume);
1157 			} else if (type == kSoundsSoundType) {
1158 				volume = dacVolume;
1159 				writeSelectorValue(_segMan, sound->value, SELECTOR(soundVolume), dacVolume);
1160 			} else {
1161 				error("Unknown sound type %d", type);
1162 			}
1163 
1164 			// `setVolume` will set the `vol` property on the sound object;
1165 			// if it did not do this, an invocation of the `setVol` selector
1166 			// would need to be here (though doing so would result in
1167 			// recursion, so don't)
1168 			g_sci->_soundCmd->setVolume(sound->value, volume);
1169 			soundId = sound->succ;
1170 		}
1171 	}
1172 }
1173 
1174 void GuestAdditions::syncGK2VolumeFromScummVM(const int16 musicVolume) const {
1175 	_state->variables[VAR_GLOBAL][kGlobalVarGK2MusicVolume] = make_reg(0, musicVolume);
1176 
1177 	// Calling `setVol` on all sounds is necessary to propagate the volume
1178 	// change to existing sounds, and matches how game scripts propagate
1179 	// volume changes when the in-game music slider is moved
1180 	const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds];
1181 	if (!soundsId.isNull()) {
1182 		List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements)));
1183 		reg_t soundId = sounds->first;
1184 		while (!soundId.isNull()) {
1185 			Node *sound = _segMan->lookupNode(soundId);
1186 			reg_t params[] = { make_reg(0, musicVolume) };
1187 			invokeSelector(sound->value, SELECTOR(setVol), 1, params);
1188 			soundId = sound->succ;
1189 		}
1190 	}
1191 }
1192 
1193 void GuestAdditions::syncHoyle5VolumeFromScummVM(const int16 musicVolume) const {
1194 	_state->variables[VAR_GLOBAL][kGlobalVarHoyle5MusicVolume] = make_reg(0, musicVolume);
1195 	g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : (musicVolume * MUSIC_MASTERVOLUME_MAX / kHoyle5VolumeMax));
1196 }
1197 
1198 void GuestAdditions::syncLSL6HiresVolumeFromScummVM(const int16 musicVolume) const {
1199 	_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresMusicVolume] = make_reg(0, musicVolume);
1200 	g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : (musicVolume * MUSIC_MASTERVOLUME_MAX / kLSL6HiresUIVolumeMax));
1201 }
1202 
1203 void GuestAdditions::syncPhant2VolumeFromScummVM(const int16 masterVolume) const {
1204 	_state->variables[VAR_GLOBAL][kGlobalVarPhant2MasterVolume] = make_reg(0, masterVolume);
1205 	_state->variables[VAR_GLOBAL][kGlobalVarPhant2SecondaryVolume] = make_reg(0, masterVolume);
1206 
1207 	const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds];
1208 	if (!soundsId.isNull()) {
1209 		reg_t params[] = { make_reg(0, SELECTOR(setVol)), make_reg(0, masterVolume) };
1210 		invokeSelector(soundsId, SELECTOR(eachElementDo), 2, params);
1211 	}
1212 }
1213 
1214 void GuestAdditions::syncRamaVolumeFromScummVM(const int16 musicVolume) const {
1215 	_state->variables[VAR_GLOBAL][kGlobalVarRamaMusicVolume] = make_reg(0, musicVolume);
1216 	const reg_t gameId = _state->variables[VAR_GLOBAL][kGlobalVarGame];
1217 	if (!gameId.isNull()) {
1218 		reg_t args[] = { make_reg(0, musicVolume) };
1219 		invokeSelector(gameId, SELECTOR(masterVolume), 1, args);
1220 	}
1221 }
1222 
1223 void GuestAdditions::syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const {
1224 	_state->variables[VAR_GLOBAL][kGlobalVarTorinMusicVolume]  = make_reg(0, musicVolume);
1225 	_state->variables[VAR_GLOBAL][kGlobalVarTorinSFXVolume]    = make_reg(0, sfxVolume);
1226 	_state->variables[VAR_GLOBAL][kGlobalVarTorinSpeechVolume] = make_reg(0, speechVolume);
1227 
1228 	// Calling `reSyncVol` on all sounds is necessary to propagate the
1229 	// volume change to existing sounds, and matches how game scripts
1230 	// propagate volume changes when the in-game volume sliders are moved
1231 	const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds];
1232 	if (!soundsId.isNull()) {
1233 		const Selector selector = SELECTOR(reSyncVol);
1234 		List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements)));
1235 		reg_t soundId = sounds->first;
1236 		while (!soundId.isNull()) {
1237 			Node *sound = _segMan->lookupNode(soundId);
1238 			const reg_t &soundObj = sound->value;
1239 
1240 			if (_segMan->isHeapObject(soundObj) && lookupSelector(_segMan, soundObj, selector, nullptr, nullptr) != kSelectorNone) {
1241 				invokeSelector(sound->value, selector);
1242 			}
1243 			soundId = sound->succ;
1244 		}
1245 	}
1246 }
1247 
1248 void GuestAdditions::syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const {
1249 	switch (g_sci->getGameId()) {
1250 	case GID_GK2:
1251 		if (index == kGlobalVarGK2MusicVolume) {
1252 			const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume;
1253 			ConfMan.setInt("music_volume", musicVolume);
1254 		}
1255 		break;
1256 
1257 	case GID_HOYLE5:
1258 		if (index == kGlobalVarHoyle5MusicVolume) {
1259 			const int16 masterVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / kHoyle5VolumeMax;
1260 			ConfMan.setInt("music_volume", masterVolume);
1261 			ConfMan.setInt("sfx_volume", masterVolume);
1262 			ConfMan.setInt("speech_volume", masterVolume);
1263 		}
1264 		break;
1265 
1266 	case GID_LSL6HIRES:
1267 		if (index == kGlobalVarLSL6HiresMusicVolume) {
1268 			const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / kLSL6HiresUIVolumeMax;
1269 			ConfMan.setInt("music_volume", musicVolume);
1270 		}
1271 		break;
1272 
1273 	case GID_PHANTASMAGORIA:
1274 		if (index == kGlobalVarPhant1MusicVolume) {
1275 			const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
1276 			ConfMan.setInt("music_volume", musicVolume);
1277 		} else if (index == kGlobalVarPhant1DACVolume) {
1278 			const int16 dacVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume;
1279 			ConfMan.setInt("sfx_volume", dacVolume);
1280 			ConfMan.setInt("speech_volume", dacVolume);
1281 		}
1282 		break;
1283 
1284 	case GID_PHANTASMAGORIA2:
1285 		if (index == kGlobalVarPhant2MasterVolume) {
1286 			const int16 masterVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / kPhant2VolumeMax;
1287 			ConfMan.setInt("music_volume", masterVolume);
1288 			ConfMan.setInt("sfx_volume", masterVolume);
1289 			ConfMan.setInt("speech_volume", masterVolume);
1290 		}
1291 		break;
1292 
1293 	case GID_RAMA:
1294 		if (index == kGlobalVarRamaMusicVolume) {
1295 			const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / kRamaVolumeMax;
1296 			ConfMan.setInt("music_volume", musicVolume);
1297 		}
1298 		break;
1299 
1300 	case GID_LSL7:
1301 	case GID_TORIN:
1302 		if (index == kGlobalVarTorinMusicVolume ||
1303 			index == kGlobalVarTorinSFXVolume ||
1304 			index == kGlobalVarTorinSpeechVolume) {
1305 
1306 			const int16 volume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / 100;
1307 
1308 			switch (index) {
1309 			case kGlobalVarTorinMusicVolume:
1310 				ConfMan.setInt("music_volume", volume);
1311 				break;
1312 			case kGlobalVarTorinSFXVolume:
1313 				ConfMan.setInt("sfx_volume", volume);
1314 				break;
1315 			case kGlobalVarTorinSpeechVolume:
1316 				ConfMan.setInt("speech_volume", volume);
1317 				break;
1318 			}
1319 		}
1320 		break;
1321 
1322 	default:
1323 		break;
1324 	}
1325 }
1326 
1327 void GuestAdditions::syncGK1AudioVolumeToScummVM(const reg_t soundObj, int16 volume) const {
1328 	const Common::String objName = _segMan->getObjectName(soundObj);
1329 	volume = volume * Audio::Mixer::kMaxMixerVolume / MUSIC_VOLUME_MAX;
1330 
1331 	// Using highest-numbered sound objects to sync only after all slots
1332 	// have been set by the volume slider
1333 	if (objName == "gkMusic2") {
1334 		ConfMan.setInt("music_volume", volume);
1335 		g_sci->updateSoundMixerVolumes();
1336 	} else if (objName == "gkSound3") {
1337 		ConfMan.setInt("sfx_volume", volume);
1338 		ConfMan.setInt("speech_volume", volume);
1339 		g_sci->updateSoundMixerVolumes();
1340 	}
1341 }
1342 
1343 #pragma mark -
1344 #pragma mark Audio UI sync
1345 
1346 void GuestAdditions::syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const {
1347 	if (_state->abortScriptProcessing != kAbortNone) {
1348 		// Attempting to update a UI that is in the process of being destroyed
1349 		// will result in a crash
1350 		return;
1351 	}
1352 
1353 	switch (g_sci->getGameId()) {
1354 	case GID_MOTHERGOOSEHIRES:
1355 		syncMGDXUI(musicVolume);
1356 		break;
1357 
1358 	case GID_PQ4:
1359 		syncPQ4UI(musicVolume);
1360 		break;
1361 
1362 	case GID_PQSWAT:
1363 		syncPQSWATUI();
1364 		break;
1365 
1366 	case GID_QFG4:
1367 		syncQFG4UI(musicVolume);
1368 		break;
1369 
1370 	case GID_HOYLE5:
1371 		syncHoyle5UI(musicVolume);
1372 		break;
1373 
1374 	case GID_SHIVERS:
1375 		syncShivers1UI(sfxVolume);
1376 		break;
1377 
1378 	case GID_SQ6:
1379 		syncSQ6UI();
1380 		break;
1381 
1382 	default:
1383 		break;
1384 	}
1385 }
1386 
1387 void GuestAdditions::syncGK1UI() const {
1388 	const reg_t bars[] = { _segMan->findObjectByName("musicBar"),
1389 						   _segMan->findObjectByName("soundBar") };
1390 
1391 	for (int i = 0; i < ARRAYSIZE(bars); ++i) {
1392 		const reg_t barId = bars[i];
1393 		if (!barId.isNull()) {
1394 			// Resetting the position to 0 causes the bar to refresh its
1395 			// position when it next draws
1396 			writeSelectorValue(_segMan, barId, SELECTOR(position), 0);
1397 
1398 			// The `signal` property indicates bar visibility (for some
1399 			// reason, the normal `-info-` flag is not used)
1400 			if (readSelectorValue(_segMan, barId, SELECTOR(signal)) & 0x20) {
1401 				// `show` pulls a new value from the underlying sound object
1402 				// and refreshes the bar rendering
1403 				invokeSelector(barId, SELECTOR(show));
1404 			}
1405 		}
1406 	}
1407 }
1408 
1409 void GuestAdditions::syncGK2UI() const {
1410 	const reg_t sliderId = _segMan->findObjectByName("soundSlider");
1411 	if (!sliderId.isNull() && _segMan->getObject(sliderId)->isInserted()) {
1412 		const reg_t oldAcc = _state->r_acc;
1413 		invokeSelector(sliderId, SELECTOR(initialOff));
1414 		writeSelector(_segMan, sliderId, SELECTOR(x), _state->r_acc);
1415 		_state->r_acc = oldAcc;
1416 	}
1417 }
1418 
1419 void GuestAdditions::syncHoyle5UI(const int16 musicVolume) const {
1420 	// Hoyle5 has two control panels with different slider names
1421 	const reg_t sliders[] = { _segMan->findObjectByName("volumeSlider"),
1422 							  _segMan->findObjectByName("volumeSliderF") };
1423 	for (int i = 0; i < ARRAYSIZE(sliders); ++i) {
1424 		const reg_t sliderId = sliders[i];
1425 		if (!sliderId.isNull()) {
1426 			const int16 yPosition = 167 - musicVolume * 145 / 10;
1427 			writeSelectorValue(_segMan, sliderId, SELECTOR(y), yPosition);
1428 
1429 			// There does not seem to be any good way to learn whether the
1430 			// volume slider is visible (and thus eligible for
1431 			// kUpdateScreenItem)
1432 			const reg_t planeId = readSelector(_segMan, sliderId, SELECTOR(plane));
1433 			if (g_sci->_gfxFrameout->getPlanes().findByObject(planeId) != nullptr) {
1434 				g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId);
1435 			}
1436 		}
1437 	}
1438 }
1439 
1440 void GuestAdditions::syncLSL6HiresUI(const int16 musicVolume) const {
1441 	const reg_t musicDialId = _segMan->findObjectByName("volumeDial");
1442 	if (!musicDialId.isNull()) {
1443 		writeSelectorValue(_segMan, musicDialId, SELECTOR(curPos), musicVolume);
1444 		writeSelectorValue(_segMan, musicDialId, SELECTOR(cel), musicVolume);
1445 		reg_t params[] = { make_reg(0, musicVolume) };
1446 		invokeSelector(musicDialId, SELECTOR(update), 1, params);
1447 		if (_segMan->getObject(musicDialId)->isInserted()) {
1448 			g_sci->_gfxFrameout->kernelUpdateScreenItem(musicDialId);
1449 		}
1450 	}
1451 }
1452 
1453 void GuestAdditions::syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const {
1454 	const reg_t buttonId = _segMan->findObjectByName("dacVolUp");
1455 	if (buttonId.isNull() || !_segMan->getObject(buttonId)->isInserted()) {
1456 		// No inserted dacVolUp button means the control panel with the
1457 		// volume controls is not visible and we can just update the values
1458 		// and leave
1459 		musicGlobal.setOffset(musicVolume);
1460 		dacGlobal.setOffset(dacVolume);
1461 		return;
1462 	}
1463 
1464 	reg_t thermo = _segMan->findObjectByName("midiVolThermo");
1465 	if (!thermo.isNull()) {
1466 		int count = ABS(musicVolume - oldMusicVolume);
1467 		const int stepSize = (musicVolume > oldMusicVolume ? 1 : -1);
1468 		while (count--) {
1469 			musicGlobal.incOffset(stepSize);
1470 			invokeSelector(thermo, SELECTOR(doit));
1471 		}
1472 	}
1473 
1474 	thermo = _segMan->findObjectByName("dacVolThermo");
1475 	if (!thermo.isNull()) {
1476 		int count = ABS(dacVolume - oldDacVolume) / 8;
1477 		const int stepSize = (dacVolume > oldDacVolume ? 8 : -8);
1478 		while (count--) {
1479 			dacGlobal.incOffset(stepSize);
1480 			invokeSelector(thermo, SELECTOR(doit));
1481 		}
1482 	}
1483 }
1484 
1485 void GuestAdditions::syncPhant2UI(const int16 masterVolume) const {
1486 	const reg_t masterVolumeScript = _segMan->findObjectByName("foo2");
1487 	Common::Array<reg_t> scrollBars = _segMan->findObjectsByName("P2ScrollBar");
1488 	for (uint i = 0; i < scrollBars.size(); ++i) {
1489 		if (readSelector(_segMan, scrollBars[i], SELECTOR(client)) == masterVolumeScript) {
1490 			// P2ScrollBar objects may exist without actually being on-screen;
1491 			// the easiest way to tell seems to be to look to see if it has
1492 			// non-null pointers to subviews. (The game will correctly set the
1493 			// position of the scrollbar when it first becomes visible, so this
1494 			// is fine.)
1495 			if (!readSelector(_segMan, scrollBars[i], SELECTOR(physicalBar)).isNull()) {
1496 				reg_t params[] = { make_reg(0, masterVolume), make_reg(0, 1) };
1497 				invokeSelector(scrollBars[i], SELECTOR(move), 2, params);
1498 				break;
1499 			}
1500 		}
1501 	}
1502 }
1503 
1504 void GuestAdditions::syncMGDXUI(const int16 musicVolume) const {
1505 	const reg_t sliderId = _segMan->findObjectByName("icon1");
1506 	if (!sliderId.isNull()) {
1507 		const int16 celNo = 7 - (musicVolume * 8 / (MUSIC_MASTERVOLUME_MAX + 1));
1508 		writeSelectorValue(_segMan, sliderId, SELECTOR(mainCel), celNo);
1509 		writeSelectorValue(_segMan, sliderId, SELECTOR(cel), celNo);
1510 
1511 		// There does not seem to be any good way to learn whether the
1512 		// volume slider is visible (and thus eligible for
1513 		// kUpdateScreenItem)
1514 		const reg_t planeId = readSelector(_segMan, sliderId, SELECTOR(plane));
1515 		if (g_sci->_gfxFrameout->getPlanes().findByObject(planeId) != nullptr) {
1516 			g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId);
1517 		}
1518 	}
1519 }
1520 
1521 void GuestAdditions::syncPQ4UI(const int16 musicVolume) const {
1522 	const SegmentId segment = _segMan->getScriptSegment(9, SCRIPT_GET_DONT_LOAD);
1523 	if (segment != 0 && _segMan->getScript(segment)->getLocalsCount() > 2) {
1524 		const reg_t barId = _segMan->getScript(segment)->getLocalsBegin()[2];
1525 		if (!barId.isNull()) {
1526 			reg_t params[] = { make_reg(0, musicVolume) };
1527 			invokeSelector(barId, SELECTOR(setSize), 1, params);
1528 		}
1529 	}
1530 }
1531 
1532 void GuestAdditions::syncPQSWATUI() const {
1533 	const reg_t barId = _segMan->findObjectByName("volumeLed");
1534 	if (!barId.isNull() && _segMan->getObject(barId)->isInserted()) {
1535 		invokeSelector(barId, SELECTOR(displayValue));
1536 	}
1537 }
1538 
1539 void GuestAdditions::syncQFG4UI(const int16 musicVolume) const {
1540 	const reg_t sliderId = _segMan->findObjectByName("volumeSlider");
1541 	if (!sliderId.isNull()) {
1542 		const int16 yPosition = 84 - musicVolume * 34 / 10;
1543 		writeSelectorValue(_segMan, sliderId, SELECTOR(y), yPosition);
1544 
1545 		// There does not seem to be any good way to learn whether the
1546 		// volume slider is visible (and thus eligible for
1547 		// kUpdateScreenItem)
1548 		const reg_t planeId = readSelector(_segMan, sliderId, SELECTOR(plane));
1549 		if (g_sci->_gfxFrameout->getPlanes().findByObject(planeId) != nullptr) {
1550 			g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId);
1551 		}
1552 	}
1553 }
1554 
1555 void GuestAdditions::syncRamaUI(const int16 musicVolume) const {
1556 	const reg_t sliderId = _segMan->findObjectByName("volumeSlider");
1557 	if (!sliderId.isNull() && !readSelector(_segMan, sliderId, SELECTOR(plane)).isNull()) {
1558 		reg_t args[] = { make_reg(0, musicVolume) };
1559 		invokeSelector(sliderId, SELECTOR(setCel), 1, args);
1560 	}
1561 }
1562 
1563 void GuestAdditions::syncShivers1UI(const int16 dacVolume) const {
1564 	const reg_t sliderId = _segMan->findObjectByName("spVolume");
1565 	if (!sliderId.isNull()) {
1566 		const int16 xPosition = dacVolume * 78 / Audio32::kMaxVolume + 32;
1567 		writeSelectorValue(_segMan, sliderId, SELECTOR(x), xPosition);
1568 		if (_segMan->getObject(sliderId)->isInserted()) {
1569 			g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId);
1570 		}
1571 	}
1572 }
1573 
1574 void GuestAdditions::syncSQ6UI() const {
1575 	const reg_t bars[] = { _segMan->findObjectByName("musicBar"),
1576 						   _segMan->findObjectByName("soundBar") };
1577 	for (int i = 0; i < ARRAYSIZE(bars); ++i) {
1578 		const reg_t barId = bars[i];
1579 		if (!barId.isNull()) {
1580 			invokeSelector(barId, SELECTOR(show));
1581 		}
1582 	}
1583 }
1584 
1585 void GuestAdditions::syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const {
1586 	const reg_t sliders[] = { _segMan->findObjectByName("oMusicScroll"),
1587 							  _segMan->findObjectByName("oSFXScroll"),
1588 							  _segMan->findObjectByName("oAudioScroll") };
1589 	const int16 values[] = { musicVolume, sfxVolume, speechVolume };
1590 	for (int i = 0; i < ARRAYSIZE(sliders); ++i) {
1591 		const reg_t sliderId = sliders[i];
1592 		if (!sliderId.isNull()) {
1593 			reg_t params[] = { make_reg(0, values[i]) };
1594 			invokeSelector(sliderId, SELECTOR(setPos), 1, params);
1595 		}
1596 	}
1597 }
1598 
1599 #pragma mark -
1600 #pragma mark Talk speed sync
1601 
1602 void GuestAdditions::syncTextSpeedFromScummVM() const {
1603 	const int16 textSpeed = 8 - (ConfMan.getInt("talkspeed") + 1) * 8 / 255;
1604 
1605 	_state->variables[VAR_GLOBAL][kGlobalVarTextSpeed] = make_reg(0, textSpeed);
1606 
1607 	if (g_sci->getGameId() == GID_GK1) {
1608 		const reg_t textBarId = _segMan->findObjectByName("textBar");
1609 		if (!textBarId.isNull()) {
1610 			// Resetting the bar position to 0 causes the game to retrieve the
1611 			// new text speed value and re-render
1612 			writeSelectorValue(_segMan, textBarId, SELECTOR(position), 0);
1613 		}
1614 	}
1615 }
1616 
1617 void GuestAdditions::syncTextSpeedToScummVM(const int index, const reg_t value) const {
1618 	if (index == kGlobalVarTextSpeed) {
1619 		ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8);
1620 	}
1621 }
1622 
1623 #endif
1624 
1625 } // End of namespace Sci
1626