[Scummvm-git-logs] scummvm master -> 580a7e6568839a6505b0d8a10244a814dd16c03b

sev- sev at scummvm.org
Fri Jul 24 22:33:48 UTC 2020


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

Summary:
2ca52fc007 CINE: Add support for Operation Stealth.
9c68b62eb9 CINE: Fix clang compilation and DeepCode warning.
580a7e6568 CINE: Fix formatting, string copy and signedness.


Commit: 2ca52fc00790a3803110d866771bd2aef61c5914
    https://github.com/scummvm/scummvm/commit/2ca52fc00790a3803110d866771bd2aef61c5914
Author: Kari Salminen (kari.salminen at gmail.com)
Date: 2020-07-25T00:33:42+02:00

Commit Message:
CINE: Add support for Operation Stealth.

Add support for Operation Stealth PC 16 and 256 color versions with
AdLib and Roland MT-32 sound. Add support for 20 extended savegames
(Thumbnails, playtime etc) for both Future Wars and Operation Stealth
(20 because it fits on screen using the original save/load interface).

Details:
 - Add versioning to Future Wars and Operation Stealth savegames.
 - Add fade in effect to both Future Wars and Operation Stealth.
 - Add mouse wheel support and keyboard support to moving in menus.
 - Map middle mouse button to pressing both left and right buttons.
 - Make interface more responsive (See manageEvents() and drawFrame()).
 - Amiga versions should be completable but sound may or may not work.
 - Atari ST versions completely untested.

Game options currently supported:
 - Using original save/load interface
 - Using transparent dialog boxes in 16 color scenes (Also for PC)

Console commands currently supported:
 - labyrinthCheat (For cheating in Operation Stealth's labyrinths)
 - disableLabyrinthCheat (Disabling labyrinth cheat)
 - disableHacks (Disabling hacks, useful for testing)
 - enableHacks (Enabling hacks, useful for testing. On by default)

Changed paths:
    engines/cine/anim.cpp
    engines/cine/anim.h
    engines/cine/bg.cpp
    engines/cine/bg.h
    engines/cine/bg_list.cpp
    engines/cine/bg_list.h
    engines/cine/cine.cpp
    engines/cine/cine.h
    engines/cine/console.cpp
    engines/cine/console.h
    engines/cine/detection.cpp
    engines/cine/detection_tables.h
    engines/cine/gfx.cpp
    engines/cine/gfx.h
    engines/cine/main_loop.cpp
    engines/cine/main_loop.h
    engines/cine/msg.cpp
    engines/cine/msg.h
    engines/cine/object.cpp
    engines/cine/object.h
    engines/cine/pal.cpp
    engines/cine/pal.h
    engines/cine/part.cpp
    engines/cine/prc.cpp
    engines/cine/saveload.cpp
    engines/cine/saveload.h
    engines/cine/script.h
    engines/cine/script_fw.cpp
    engines/cine/script_os.cpp
    engines/cine/sound.cpp
    engines/cine/sound.h
    engines/cine/texte.cpp
    engines/cine/texte.h
    engines/cine/various.cpp
    engines/cine/various.h


diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index 6ecf07fe15..bcef2ddc0d 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -27,6 +27,7 @@
 #include "common/endian.h"
 #include "common/memstream.h"
 #include "common/textconsole.h"
+#include "audio/mididrv.h"
 
 #include "cine/cine.h"
 #include "cine/anim.h"
@@ -39,14 +40,23 @@ namespace Cine {
 
 struct AnimHeader2Struct {
 	uint32 field_0;
-	uint16 width;
-	uint16 height;
-	uint16 type;
+	int16 width;
+	int16 height;
+	int16 type;
 	uint16 field_A;
 	uint16 field_C;
 	uint16 field_E;
 };
 
+static const AnimDataMapping resNameMapping[] = {
+	{"PLONGEON", "PLONG110"},
+	{"PNEUMATI", "PNEUMA05"},
+	{"RELAITRE", "RIDEAU__"},
+	{"TIRROIR_", "PORTE___"},
+	{"VERREDO_", "EAU_____"},
+	{"ZODIAC__", "TAXIGO__"}
+};
+
 static const AnimDataEntry transparencyData[] = {
 	{"ALPHA", 0xF},
 	{"TITRE2", 0xF},
@@ -184,6 +194,7 @@ static const AnimDataEntry transparencyData[] = {
 void convertMask(byte *dest, const byte *source, int16 width, int16 height);
 void convert8BBP(byte *dest, const byte *source, int16 width, int16 height);
 void convert8BBP2(byte *dest, byte *source, int16 width, int16 height);
+int loadSet(const char *resourceName, int16 idx, int16 frameIndex = -1);
 
 AnimData::AnimData() : _width(0), _height(0), _bpp(0), _var1(0), _data(NULL),
 	_mask(NULL), _fileIdx(-1), _frameIdx(-1), _realWidth(0), _size(0) {
@@ -483,19 +494,17 @@ void convert4BBP(byte *dest, const byte *source, int16 width, int16 height) {
  * @param readS Input stream open for reading
  */
 void loadAnimHeader(AnimHeaderStruct &animHeader, Common::SeekableReadStream &readS) {
-	animHeader.field_0 = readS.readByte();
-	animHeader.field_1 = readS.readByte();
-	animHeader.field_2 = readS.readByte();
-	animHeader.field_3 = readS.readByte();
-	animHeader.frameWidth = readS.readUint16BE();
-	animHeader.frameHeight = readS.readUint16BE();
+	readS.read(animHeader.idString, sizeof(animHeader.idString));
+	animHeader.idString[sizeof(animHeader.idString) - 1] = 0;
+	animHeader.frameWidth = readS.readSint16BE();
+	animHeader.frameHeight = readS.readSint16BE();
 	animHeader.field_8 = readS.readByte();
 	animHeader.field_9 = readS.readByte();
 	animHeader.field_A = readS.readByte();
 	animHeader.field_B = readS.readByte();
 	animHeader.field_C = readS.readByte();
 	animHeader.field_D = readS.readByte();
-	animHeader.numFrames = readS.readUint16BE();
+	animHeader.numFrames = readS.readSint16BE();
 	animHeader.field_10 = readS.readByte();
 	animHeader.field_11 = readS.readByte();
 	animHeader.field_12 = readS.readByte();
@@ -559,9 +568,9 @@ int loadMsk(const char *resourceName, int16 idx, int16 frameIndex) {
 	byte *ptr;
 	AnimHeaderStruct animHeader;
 
-	Common::MemoryReadStream readS(dataPtr, 0x16);
+	Common::MemoryReadStream readS(dataPtr, ANIM_HEADER_SIZE);
 	loadAnimHeader(animHeader, readS);
-	ptr = dataPtr + 0x16;
+	ptr = dataPtr + ANIM_HEADER_SIZE;
 
 	int16 startFrame = 0;
 	int16 endFrame = animHeader.numFrames;
@@ -602,9 +611,18 @@ int loadAni(const char *resourceName, int16 idx, int16 frameIndex) {
 	byte transparentColor;
 	AnimHeaderStruct animHeader;
 
-	Common::MemoryReadStream readS(dataPtr, 0x16);
+	Common::MemoryReadStream readS(dataPtr, ANIM_HEADER_SIZE);
 	loadAnimHeader(animHeader, readS);
-	ptr = dataPtr + 0x16;
+	ptr = dataPtr + ANIM_HEADER_SIZE;
+
+	// HACK: If the underlying resource is really a ".SET" then use that loading routine.
+	// Try to detect door animations in SP11_01.ANI and SP11_02.ANI that are .SET files.
+	// These are on Dr. Why's island the opening and closing doors.
+	if (hacksEnabled && scumm_stricmp(animHeader.idString, "SET") == 0 &&
+		idx >= 161 && idx <= 164 && animHeader.frameHeight == 0) {
+		free(dataPtr);
+		return loadSet(resourceName, idx, frameIndex);
+	}
 
 	int16 startFrame = 0;
 	int16 endFrame = animHeader.numFrames;
@@ -621,7 +639,7 @@ int loadAni(const char *resourceName, int16 idx, int16 frameIndex) {
 	// HACK: Versions of TITRE.ANI with height 37 use color 0xF for transparency.
 	//       Versions of TITRE.ANI with height 57 use color 0x0 for transparency.
 	//       Fixes bug #2057619: FW: Glitches in title display of demo (regression).
-	if (scumm_stricmp(resourceName, "TITRE.ANI") == 0 && animHeader.frameHeight == 37) {
+	if (hacksEnabled && scumm_stricmp(resourceName, "TITRE.ANI") == 0 && animHeader.frameHeight == 37) {
 		transparentColor = 0xF;
 	}
 
@@ -704,7 +722,7 @@ void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) {
  * @param frameIndex frame of animation to load (-1 for all frames)
  * @return The number of the animDataTable entry after the loaded image set (-1 if error)
  */
-int loadSet(const char *resourceName, int16 idx, int16 frameIndex = -1) {
+int loadSet(const char *resourceName, int16 idx, int16 frameIndex) {
 	AnimHeader2Struct header2;
 	uint16 numSpriteInAnim;
 	int16 foundFileIdx = findFileInBundle(resourceName);
@@ -784,7 +802,7 @@ int loadSeq(const char *resourceName, int16 idx) {
 	byte *dataPtr = readBundleFile(foundFileIdx);
 	int entry = idx < 0 ? emptyAnimSpace() : idx;
 
-	g_cine->_animDataTable[entry].load(dataPtr + 0x16, ANIM_RAW, g_cine->_partBuffer[foundFileIdx].unpackedSize - 0x16, 1, foundFileIdx, 0, currentPartName);
+	g_cine->_animDataTable[entry].load(dataPtr + ANIM_HEADER_SIZE, ANIM_RAW, g_cine->_partBuffer[foundFileIdx].unpackedSize - 0x16, 1, foundFileIdx, 0, currentPartName);
 	free(dataPtr);
 	return entry + 1;
 }
@@ -798,8 +816,34 @@ int loadSeq(const char *resourceName, int16 idx) {
  */
 int loadResource(const char *resourceName, int16 idx, int16 frameIndex) {
 	int result = -1; // Return an error by default
+
+	if (g_cine->getGameType() == Cine::GType_OS &&
+		g_cine->getPlatform() == Common::kPlatformDOS &&
+		g_sound->musicType() != MT_MT32 &&
+		(strstr(resourceName, ".SPL") || strstr(resourceName, ".H32")))
+	{
+		char base[20];
+		removeExtention(base, resourceName);
+
+		for (int i = 0; i < ARRAYSIZE(resNameMapping); i++) {
+			if (scumm_stricmp(base, resNameMapping[i].from) == 0) {
+				strncpy(base, resNameMapping[i].to, sizeof(base));
+				break;
+			}
+		}
+
+		const char* ext = (g_sound->musicType() == MT_ADLIB) ? ".ADL" : ".HP";
+		strcat(base, ext);
+		return loadResource(base, idx, frameIndex);
+	}
+
+	bool preferSeq = (g_cine->getGameType() == Cine::GType_OS && g_sound->musicType() == MT_MT32);
+
 	if (strstr(resourceName, ".SPL")) {
-		result = loadSpl(resourceName, idx);
+		if (preferSeq)
+			result = loadSeq(resourceName, idx);
+		else
+			result = loadSpl(resourceName, idx);
 	} else if (strstr(resourceName, ".MSK")) {
 		result = loadMsk(resourceName, idx, frameIndex);
 	} else if (strstr(resourceName, ".ANI")) {
@@ -811,9 +855,16 @@ int loadResource(const char *resourceName, int16 idx, int16 frameIndex) {
 	} else if (strstr(resourceName, ".SEQ")) {
 		result = loadSeq(resourceName, idx);
 	} else if (strstr(resourceName, ".H32")) {
-		warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
+		if (preferSeq)
+			result = loadSeq(resourceName, idx);
+		else
+			result = loadSpl(resourceName, idx);
+	} else if (strstr(resourceName, ".HP")) {
+		result = loadSpl(resourceName, idx);
+	} else if (strstr(resourceName, ".ADL")) {
+		result = loadSpl(resourceName, idx);
 	} else if (strstr(resourceName, ".AMI")) {
-		warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
+		result = loadAni(resourceName, idx, frameIndex);
 	} else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure
 		g_cine->quitGame();
 	} else {
diff --git a/engines/cine/anim.h b/engines/cine/anim.h
index 7b4daafa88..d4d07133e6 100644
--- a/engines/cine/anim.h
+++ b/engines/cine/anim.h
@@ -29,20 +29,20 @@
 
 namespace Cine {
 
+// Size of AnimHeaderStruct payload in bytes
+#define ANIM_HEADER_SIZE 0x16
+
 struct AnimHeaderStruct {
-	byte field_0;
-	byte field_1;
-	byte field_2;
-	byte field_3;
-	uint16 frameWidth;
-	uint16 frameHeight;
+	char idString[4];
+	int16 frameWidth;
+	int16 frameHeight;
 	byte field_8;
 	byte field_9;
 	byte field_A;
 	byte field_B;
 	byte field_C;
 	byte field_D;
-	uint16 numFrames;
+	int16 numFrames;
 	byte field_10;
 	byte field_11;
 	byte field_12;
@@ -55,6 +55,11 @@ struct AnimDataEntry {
 	byte color;
 };
 
+struct AnimDataMapping {
+	char from[9];
+	char to[9];
+};
+
 #define ANIM_RAW 0 // memcpy
 #define ANIM_MASK 1 // convertMask
 #define ANIM_SPRITE 2 // gfxConvertSpriteToRaw
@@ -84,6 +89,8 @@ public:
 
 	AnimData &operator=(const AnimData &src);
 
+	int size() { return _size; }
+	int16 frameIndex() { return _frameIdx; }
 	const byte *data() const { return _data; } ///< Image data
 	const byte *mask() const { return _mask; } ///< Image mask (may be NULL)
 	byte getColor(int x, int y);
diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp
index ce808e0f6a..26e076ec04 100644
--- a/engines/cine/bg.cpp
+++ b/engines/cine/bg.cpp
@@ -33,18 +33,15 @@ namespace Cine {
 
 uint16 bgVar0;
 byte *additionalBgTable[9];
-int16 currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0;
 
-byte loadCtFW(const char *ctName) {
+int16 loadCtFW(const char *ctName) {
 	debugC(1, kCineDebugCollision, "loadCtFW(\"%s\")", ctName);
 	byte *ptr, *dataPtr;
 
 	int16 foundFileIdx = findFileInBundle(ctName);
-	if (foundFileIdx == -1) {
+	if (foundFileIdx < 0) {
 		warning("loadCtFW: Unable to find collision data file '%s'", ctName);
-		// FIXME: Rework this function's return value policy and return an appropriate value here.
-		// The return value isn't yet used for anything so currently it doesn't really matter.
-		return 0;
+		return -1;
 	}
 
 	if (currentCtName != ctName)
@@ -62,16 +59,14 @@ byte loadCtFW(const char *ctName) {
 	return 0;
 }
 
-byte loadCtOS(const char *ctName) {
+int16 loadCtOS(const char *ctName) {
 	debugC(1, kCineDebugCollision, "loadCtOS(\"%s\")", ctName);
 	byte *ptr, *dataPtr;
 
 	int16 foundFileIdx = findFileInBundle(ctName);
-	if (foundFileIdx == -1) {
+	if (foundFileIdx < 0) {
 		warning("loadCtOS: Unable to find collision data file '%s'", ctName);
-		// FIXME: Rework this function's return value policy and return an appropriate value here.
-		// The return value isn't yet used for anything so currently it doesn't really matter.
-		return 0;
+		return -1;
 	}
 
 	if (currentCtName != ctName)
@@ -85,7 +80,6 @@ byte loadCtOS(const char *ctName) {
 	if (bpp == 8) {
 		renderer->loadCt256(ptr, ctName);
 	} else {
-		gfxConvertSpriteToRaw(collisionPage, ptr + 32, 160, 200);
 		renderer->loadCt16(ptr, ctName);
 	}
 
@@ -93,10 +87,15 @@ byte loadCtOS(const char *ctName) {
 	return 0;
 }
 
-byte loadBg(const char *bgName) {
+int16 loadBg(const char *bgName) {
 	byte *ptr, *dataPtr;
 
-	byte fileIdx = findFileInBundle(bgName);
+	int16 fileIdx = findFileInBundle(bgName);
+	if (fileIdx < 0) {
+		warning("loadBg(\"%s\"): Could not find background in file bundle.", bgName);
+		return -1;
+	}
+	checkDataDisk(-1);
 	ptr = dataPtr = readBundleFile(fileIdx);
 
 	uint16 bpp = READ_BE_UINT16(ptr);
@@ -112,23 +111,8 @@ byte loadBg(const char *bgName) {
 		renderer->loadBg16(ptr, bgName);
 	}
 	free(dataPtr);
-	return 0;
-}
-
-void addBackground(const char *bgName, uint16 bgIdx) {
-	byte *ptr, *dataPtr;
-
-	byte fileIdx = findFileInBundle(bgName);
-	ptr = dataPtr = readBundleFile(fileIdx);
 
-	uint16 bpp = READ_BE_UINT16(ptr); ptr += 2;
-
-	if (bpp == 8) {
-		renderer->loadBg256(ptr, bgName, bgIdx);
-	} else {
-		renderer->loadBg16(ptr, bgName, bgIdx);
-	}
-	free(dataPtr);
+	return 0;
 }
 
 } // End of namespace Cine
diff --git a/engines/cine/bg.h b/engines/cine/bg.h
index ac884463cb..c35654c8fb 100644
--- a/engines/cine/bg.h
+++ b/engines/cine/bg.h
@@ -24,17 +24,12 @@
 #define CINE_BG_H
 
 namespace Cine {
-byte loadBg(const char *bgName);
-byte loadCtFW(const char *bgName);
-byte loadCtOS(const char *bgName);
-
-void addBackground(const char *bgName, uint16 bgIdx);
+int16 loadBg(const char *bgName);
+int16 loadCtFW(const char *bgName);
+int16 loadCtOS(const char *bgName);
 
 extern uint16 bgVar0;
 
-extern int16 currentAdditionalBgIdx;
-extern int16 currentAdditionalBgIdx2;
-
 } // End of namespace Cine
 
 #endif
diff --git a/engines/cine/bg_list.cpp b/engines/cine/bg_list.cpp
index d2f05a3fbd..304781012d 100644
--- a/engines/cine/bg_list.cpp
+++ b/engines/cine/bg_list.cpp
@@ -54,6 +54,17 @@ void addSpriteFilledToBGList(int16 objIdx) {
 	renderer->incrustMask(g_cine->_bgIncrustList.back());
 }
 
+void removeBgIncrustsWithBgIdx(int16 bgIdx) {
+	Common::List<BGIncrust>::iterator it;
+	for (it = g_cine->_bgIncrustList.begin(); it != g_cine->_bgIncrustList.end();) {
+		if (it->bgIdx == bgIdx) {
+			it = g_cine->_bgIncrustList.erase(it);
+		} else {
+			++it;
+		}
+	}
+}
+
 /**
  * Add new element to incrust list
  * @param objIdx Element description
@@ -68,7 +79,8 @@ void createBgIncrustListElement(int16 objIdx, int16 param) {
 	tmp.x = g_cine->_objectTable[objIdx].x;
 	tmp.y = g_cine->_objectTable[objIdx].y;
 	tmp.frame = g_cine->_objectTable[objIdx].frame;
-	tmp.part = g_cine->_objectTable[objIdx].part;
+	tmp.part = g_cine->_objectTable[objIdx].part & 0x0f;
+	tmp.bgIdx = renderer->currentBg();
 
 	g_cine->_bgIncrustList.push_back(tmp);
 }
@@ -84,7 +96,7 @@ void resetBgIncrustList() {
  * Restore incrust list from savefile
  * @param fHandle Savefile open for reading
  */
-void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) {
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle, bool hasBgIdx) {
 	BGIncrust tmp;
 	int size = fHandle.readSint16BE();
 
@@ -99,6 +111,7 @@ void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) {
 		tmp.y = fHandle.readUint16BE();
 		tmp.frame = fHandle.readUint16BE();
 		tmp.part = fHandle.readUint16BE();
+		tmp.bgIdx = (hasBgIdx ? fHandle.readUint16BE() : 0);
 
 		g_cine->_bgIncrustList.push_back(tmp);
 
diff --git a/engines/cine/bg_list.h b/engines/cine/bg_list.h
index 3cfc33e274..0a0366d5df 100644
--- a/engines/cine/bg_list.h
+++ b/engines/cine/bg_list.h
@@ -37,16 +37,18 @@ struct BGIncrust {
 	int16 y;
 	int16 frame;
 	int16 part;
+	int16 bgIdx;
 };
 
 extern uint32 var8;
 
 void addToBGList(int16 objIdx);
 void addSpriteFilledToBGList(int16 idx);
+void removeBgIncrustsWithBgIdx(int16 bgIdx);
 
 void createBgIncrustListElement(int16 objIdx, int16 param);
 void resetBgIncrustList();
-void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle);
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle, bool hasBgIdx = false);
 
 } // End of namespace Cine
 
diff --git a/engines/cine/cine.cpp b/engines/cine/cine.cpp
index a84a6575b8..82e8038376 100644
--- a/engines/cine/cine.cpp
+++ b/engines/cine/cine.cpp
@@ -67,7 +67,7 @@ CineEngine::CineEngine(OSystem *syst, const CINEGameDescription *gameDesc)
 	}
 	_restartRequested = false;
 	_preLoad = false;
-	_timerDelayMultiplier = 12;
+	setDefaultGameSpeed();
 }
 
 CineEngine::~CineEngine() {
@@ -124,7 +124,7 @@ Common::Error CineEngine::run() {
 		_restartRequested = false;
 
 		CursorMan.showMouse(true);
-		mainLoop(1);
+		mainLoop(BOOT_SCRIPT_INDEX);
 
 		delete renderer;
 		delete[] collisionPage;
@@ -135,7 +135,7 @@ Common::Error CineEngine::run() {
 	return Common::kNoError;
 }
 
-int CineEngine::getTimerDelay() const {
+uint32 CineEngine::getTimerDelay() const {
 	return (10923000 * _timerDelayMultiplier) / 1193180;
 }
 
@@ -150,7 +150,12 @@ int CineEngine::modifyGameSpeed(int speedChange) {
 	return _timerDelayMultiplier;
 }
 
+void CineEngine::setDefaultGameSpeed() {
+	_timerDelayMultiplier = 12;
+}
+
 void CineEngine::initialize() {
+	setTotalPlayTime(0); // Reset total play time
 	_globalVars.reinit(NUM_MAX_VAR + 1);
 
 	// Initialize all savegames' descriptions to empty strings
@@ -172,7 +177,7 @@ void CineEngine::initialize() {
 	g_cine->_zoneQuery.resize(NUM_MAX_ZONE);
 	Common::fill(g_cine->_zoneQuery.begin(), g_cine->_zoneQuery.end(), 0);
 
-	_timerDelayMultiplier = 12; // Set default speed
+	setDefaultGameSpeed();
 	setupOpcodes();
 
 	initLanguage(getLanguage());
@@ -184,6 +189,11 @@ void CineEngine::initialize() {
 	}
 
 	renderer->initialize();
+	forbidBgPalReload = 0;
+	reloadBgPalOnNextFlip = 0;
+	gfxFadeOutCompleted = 0;
+	gfxFadeInRequested = 0;
+	currentDisk = 1;
 
 	collisionPage = new byte[320 * 200];
 	memset(collisionPage, 0, 320 * 200);
@@ -213,6 +223,7 @@ void CineEngine::initialize() {
 	g_cine->_overlayList.clear();
 	g_cine->_messageTable.clear();
 	resetObjectTable();
+	g_cine->_seqList.clear();
 
 	if (getGameType() == Cine::GType_OS) {
 		disableSystemMenu = 1;
@@ -228,21 +239,32 @@ void CineEngine::initialize() {
 	}
 
 	var8 = 0;
-
-	var2 = var3 = var4 = var5 = 0;
-
+	bgVar0 = 0;
+	var2 = var3 = var4 = lastType20OverlayBgIdx = 0;
 	musicIsPlaying = 0;
 	currentDatName[0] = 0;
+	_keyInputList.clear();
+
+	// Used for making sound effects work using Roland MT-32 and AdLib in
+	// Operation Stealth after loading a savegame. The sound effects are loaded
+	// in AUTO00.PRC using a combination of o2_loadAbs and o2_playSample(1, ...)
+	// before checking if _globalVars[255] == 0. In the original game AUTO00.PRC
+	// was run when starting or restarting the game and one could not load a savegame
+	// before passing the copy protection. Thus, we try to emulate that behaviour by
+	// running at least part of AUTO00.PRC before loading a savegame.
+	if (getGameType() == Cine::GType_OS && !(getFeatures() & GF_DEMO)) {
+		loadPrc(BOOT_PRC_NAME);
+		strcpy(currentPrcName, BOOT_PRC_NAME);
+		addScriptToGlobalScripts(BOOT_SCRIPT_INDEX);
+		runOnlyUntilCopyProtectionCheck = true;
+		executeGlobalScripts();
+	}
 
 	_preLoad = false;
 	if (ConfMan.hasKey("save_slot") && !_restartRequested) {
-		char saveNameBuffer[256];
-
-		sprintf(saveNameBuffer, "%s.%1d", _targetName.c_str(), ConfMan.getInt("save_slot"));
-
-		bool res = makeLoad(saveNameBuffer);
+		Common::Error loadError = loadGameState(ConfMan.getInt("save_slot"));
 
-		if (res)
+		if (loadError.getCode() == Common::kNoError)
 			_preLoad = true;
 	}
 
diff --git a/engines/cine/cine.h b/engines/cine/cine.h
index fdd6d39fd6..6bc04b3c5d 100644
--- a/engines/cine/cine.h
+++ b/engines/cine/cine.h
@@ -47,6 +47,7 @@
 #include "cine/bg_list.h"
 #include "cine/various.h"
 #include "cine/console.h"
+#include "cine/sound.h"
 
 //#define DUMP_SCRIPTS
 
@@ -95,7 +96,14 @@ enum CineGameFeatures {
 struct CINEGameDescription;
 struct SeqListElement;
 
-typedef Common::HashMap<Common::String, const char *> StringPtrHashMap;
+struct VolumeResource {
+	char name[10];
+	uint32 pNamesList;
+	int16 diskNum;
+	int32 sizeOfNamesList;
+};
+
+typedef Common::HashMap<Common::String, Common::Array<VolumeResource> > StringToVolumeResourceArrayHashMap;
 
 class CineConsole;
 
@@ -116,6 +124,7 @@ public:
 
 	void syncSoundSettings() override;
 
+	bool mayHave256Colors() const;
 	int getGameType() const;
 	uint32 getFeatures() const;
 	Common::Language getLanguage() const;
@@ -125,7 +134,8 @@ public:
 	void makeSystemMenu();
 	int scummVMSaveLoadDialog(bool isSave);
 	int modifyGameSpeed(int speedChange);
-	int getTimerDelay() const;
+	void setDefaultGameSpeed();
+	uint32 getTimerDelay() const;
 	Common::Error loadGameState(int slot) override;
 	Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
 	virtual Common::String getSaveStateName(int slot) const override;
@@ -137,8 +147,8 @@ public:
 
 	Common::RandomSource _rnd;
 
-	Common::StringArray _volumeResourceFiles;
-	StringPtrHashMap _volumeEntriesMap;
+	StringToVolumeResourceArrayHashMap _volumeEntriesMap;
+
 	TextHandler _textHandler;
 
 	bool _restartRequested;
@@ -147,14 +157,19 @@ private:
 	void initialize();
 	void showSplashScreen();
 	void resetEngine();
-	bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat);
-	bool loadTempSaveOS(Common::SeekableReadStream &in);
+	bool checkSaveHeaderData(const ChunkHeader& hdr);
+	bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat, uint32 version);	
+	bool loadVersionedSaveFW(Common::SeekableReadStream &in);
+	bool loadVersionedSaveOS(Common::SeekableReadStream &in);
 	bool makeLoad(const Common::String &saveName);
+	void writeSaveHeader(Common::OutSaveFile &out, uint32 headerId);
 	void makeSaveFW(Common::OutSaveFile &out);
 	void makeSaveOS(Common::OutSaveFile &out);
-	void makeSave(const Common::String &saveFileName);
+	void makeSave(const Common::String &saveFileName, uint32 playtime,
+		Common::String desc, bool isAutosave);
 	void mainLoop(int bootScriptIdx);
 	void readVolCnf();
+	Common::String getTargetSaveStateName(Common::String target, int slot) const;
 
 	bool _preLoad;
 	int _timerDelayMultiplier;
@@ -182,17 +197,20 @@ public:
 	ScriptVars _globalVars;
 	RawScriptArray _scriptTable; ///< Table of script bytecode
 
-	Common::Array<uint16> _zoneData;
+	Common::Array<int16> _zoneData;
 	Common::Array<uint16> _zoneQuery; ///< Only exists in Operation Stealth
 
 	Common::List<SeqListElement> _seqList;
 
 	Common::String _commandBuffer;
+	Common::Array<Common::KeyState> _keyInputList;
 };
 
 extern CineEngine *g_cine;
+extern Sound *g_sound;
 
 #define BOOT_PRC_NAME "AUTO00.PRC"
+#define BOOT_SCRIPT_INDEX 1
 #define COPY_PROT_FAIL_PRC_NAME "L201.ANI"
 
 enum {
diff --git a/engines/cine/console.cpp b/engines/cine/console.cpp
index 4646bdf280..39264c31fb 100644
--- a/engines/cine/console.cpp
+++ b/engines/cine/console.cpp
@@ -26,12 +26,17 @@
 namespace Cine {
 
 bool labyrinthCheat;
+bool hacksEnabled;
 
 CineConsole::CineConsole(CineEngine *vm) : GUI::Debugger(), _vm(vm) {
 	assert(_vm);
 	registerCmd("labyrinthCheat", WRAP_METHOD(CineConsole, Cmd_LabyrinthCheat));
+	registerCmd("disableLabyrinthCheat", WRAP_METHOD(CineConsole, Cmd_DisableLabyrinthCheat));
+	registerCmd("disableHacks", WRAP_METHOD(CineConsole, Cmd_DisableHacks));
+	registerCmd("enableHacks", WRAP_METHOD(CineConsole, Cmd_EnableHacks));
 
 	labyrinthCheat = false;
+	hacksEnabled = true;
 }
 
 CineConsole::~CineConsole() {
@@ -44,4 +49,19 @@ bool CineConsole::Cmd_LabyrinthCheat(int argc, const char **argv) {
 	return true;
 }
 
+bool CineConsole::Cmd_DisableLabyrinthCheat(int argc, const char **argv) {
+	labyrinthCheat = false;
+	return true;
+}
+
+bool CineConsole::Cmd_DisableHacks(int argc, const char **argv) {
+	hacksEnabled = false;
+	return true;
+}
+
+bool CineConsole::Cmd_EnableHacks(int argc, const char **argv) {
+	hacksEnabled = true;
+	return true;
+}
+
 } // End of namespace Cine
diff --git a/engines/cine/console.h b/engines/cine/console.h
index 48afec597e..478e3d5031 100644
--- a/engines/cine/console.h
+++ b/engines/cine/console.h
@@ -28,6 +28,7 @@
 namespace Cine {
 
 extern bool labyrinthCheat;
+extern bool hacksEnabled;
 
 class CineEngine;
 
@@ -40,6 +41,9 @@ private:
 	CineEngine *_vm;
 
 	bool Cmd_LabyrinthCheat(int argc, const char **argv);
+	bool Cmd_DisableLabyrinthCheat(int argc, const char **argv);
+	bool Cmd_DisableHacks(int argc, const char **argv);
+	bool Cmd_EnableHacks(int argc, const char **argv);
 };
 
 } // End of namespace Cine
diff --git a/engines/cine/detection.cpp b/engines/cine/detection.cpp
index fdc6bf6cfc..79f797527f 100644
--- a/engines/cine/detection.cpp
+++ b/engines/cine/detection.cpp
@@ -27,12 +27,17 @@
 #include "common/system.h"
 #include "common/textconsole.h"
 #include "common/translation.h"
+#include "common/util.h"
 
 #include "cine/cine.h"
 #include "cine/various.h"
 
 namespace Cine {
 
+#define MAX_SAVEGAMES (ARRAYSIZE(Cine::currentSaveName))
+#define SAVEGAME_NAME_LEN (sizeof(Cine::currentSaveName[0]))
+#define SAVELIST_SIZE (MAX_SAVEGAMES * SAVEGAME_NAME_LEN)
+
 struct CINEGameDescription {
 	ADGameDescription desc;
 
@@ -40,6 +45,7 @@ struct CINEGameDescription {
 	uint32 features;
 };
 
+bool CineEngine::mayHave256Colors() const { return getGameType() == Cine::GType_OS && getPlatform() == Common::kPlatformDOS; }
 int CineEngine::getGameType() const { return _gameDescription->gameType; }
 uint32 CineEngine::getFeatures() const { return _gameDescription->features; }
 Common::Language CineEngine::getLanguage() const { return _gameDescription->desc.language; }
@@ -65,6 +71,15 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 			false
 		}
 	},
+	{
+		GAMEOPTION_TRANSPARENT_DIALOG_BOXES,
+		{
+			_s("Use transparent dialog boxes in 16 color scenes"),
+			_s("Use transparent dialog boxes in 16 color scenes even if the original game version did not support them"),
+			"transparentdialogboxes",
+			false
+		}
+	},
 
 	AD_EXTRA_GUI_OPTIONS_TERMINATOR
 };
@@ -72,7 +87,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 class CineMetaEngine : public AdvancedMetaEngine {
 public:
 	CineMetaEngine() : AdvancedMetaEngine(Cine::gameDescriptions, sizeof(Cine::CINEGameDescription), cineGames, optionsList) {
-		_guiOptions = GUIO2(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVELOAD);
+		_guiOptions = GUIO3(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_TRANSPARENT_DIALOG_BOXES);
 	}
 
 	const char *getEngineId() const override {
@@ -96,13 +111,20 @@ public:
 	SaveStateList listSaves(const char *target) const override;
 	int getMaximumSaveSlot() const override;
 	void removeSaveState(const char *target, int slot) const override;
+	const char *getSavegameFile(int saveGameIdx, const char *target = nullptr) const override;
+	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
 };
 
 bool CineMetaEngine::hasFeature(MetaEngineFeature f) const {
 	return
 		(f == kSupportsListSaves) ||
 		(f == kSupportsLoadingDuringStartup) ||
-		(f == kSupportsDeleteSave);
+		(f == kSupportsDeleteSave) ||
+		(f == kSavesSupportMetaInfo) ||
+		(f == kSavesSupportThumbnail) ||
+		(f == kSavesSupportCreationDate) ||
+		(f == kSavesSupportPlayTime) ||
+		(f == kSavesUseExtendedFormat);
 }
 
 bool Cine::CineEngine::hasFeature(EngineFeature f) const {
@@ -124,51 +146,159 @@ SaveStateList CineMetaEngine::listSaves(const char *target) const {
 	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
 	SaveStateList saveList;
 
-	Common::String pattern = target;
-	pattern += ".#";
-	Common::StringArray filenames = saveFileMan->listSavefiles(pattern);
+	Common::String pattern;
+
 	Common::StringArray::const_iterator file;
 
 	Common::String filename = target;
 	filename += ".dir";
 	Common::InSaveFile *in = saveFileMan->openForLoading(filename);
+	bool foundAutosave = false;
 	if (in) {
-		typedef char CommandeType[20];
-		CommandeType saveNames[10];
+		typedef char CommandeType[SAVEGAME_NAME_LEN];
+		CommandeType saveNames[MAX_SAVEGAMES];
 
 		// Initialize all savegames' descriptions to empty strings
 		// so that if the savegames' descriptions can only be partially read from file
 		// then the missing ones are correctly set to empty strings.
 		memset(saveNames, 0, sizeof(saveNames));
 
-		in->read(saveNames, 10 * 20);
+		in->read(saveNames, SAVELIST_SIZE);
 		CommandeType saveDesc;
 
-		for (file = filenames.begin(); file != filenames.end(); ++file) {
-			// Obtain the last digit of the filename, since they correspond to the save slot
-			int slotNum = atoi(file->c_str() + file->size() - 1);
-
-			// Copy the savegame description making sure it ends with a trailing zero
-			strncpy(saveDesc, saveNames[slotNum], 20);
-			saveDesc[sizeof(CommandeType) - 1] = 0;
+		pattern = target;
+		pattern += ".#*";
+		Common::StringArray filenames = saveFileMan->listSavefiles(pattern);
 
-			saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
+		for (file = filenames.begin(); file != filenames.end(); ++file) {
+			// Obtain the extension part of the filename, since it corresponds to the save slot number
+			Common::String ext = Common::lastPathComponent(*file, '.');
+			int slotNum = (int)ext.asUint64();
+
+			if (ext.equals(Common::String::format("%d", slotNum)) &&
+				slotNum >= 0 && slotNum < MAX_SAVEGAMES) {
+				// Copy the savegame description making sure it ends with a trailing zero
+				strncpy(saveDesc, saveNames[slotNum], SAVEGAME_NAME_LEN);
+				saveDesc[sizeof(CommandeType) - 1] = 0;
+
+				SaveStateDescriptor saveStateDesc(slotNum, saveDesc);
+				saveStateDesc.setAutosave(slotNum == getAutosaveSlot());
+				saveStateDesc.setWriteProtectedFlag(saveStateDesc.isAutosave());
+
+				if (saveStateDesc.getDescription().empty()) {
+					if (saveStateDesc.isAutosave()) {
+						saveStateDesc.setDescription(_("Unnamed autosave"));
+					} else {
+						saveStateDesc.setDescription(_("Unnamed savegame"));
+					}
+				}
+
+				if (saveStateDesc.isAutosave()) {
+					foundAutosave = true;
+				}
+
+				saveList.push_back(saveStateDesc);
+			}
 		}
 	}
 
 	delete in;
 
+	// No saving on empty autosave slot
+	if (!foundAutosave) {
+		SaveStateDescriptor desc;
+		desc.setDescription(_("Empty autosave"));
+		desc.setSaveSlot(getAutosaveSlot());
+		desc.setWriteProtectedFlag(true);
+		saveList.push_back(desc);
+	}
+
 	// Sort saves based on slot number.
 	Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
 	return saveList;
 }
 
-int CineMetaEngine::getMaximumSaveSlot() const { return 9; }
+int CineMetaEngine::getMaximumSaveSlot() const { return MAX_SAVEGAMES - 1; }
+
+const char *CineMetaEngine::getSavegameFile(int saveGameIdx, const char *target) const {
+	static char buffer[200];
+
+	snprintf(buffer, sizeof(buffer), "%s.%d", target == nullptr ? getEngineId() : target, saveGameIdx);
+
+	return buffer;
+}
+
+SaveStateDescriptor CineMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+	if (slot < 0 || slot > getMaximumSaveSlot()) {
+		// HACK: Try to make SaveLoadChooserGrid::open() not use save slot
+		// numbers over the maximum save slot number for "New save".
+		SaveStateDescriptor desc;
+		desc.setWriteProtectedFlag(true);
+		return desc;
+	}
+
+	Common::ScopedPtr<Common::InSaveFile> f(g_system->getSavefileManager()->openForLoading(
+		getSavegameFile(slot, target)));
+
+	if (f) {
+		// Create the return descriptor
+		SaveStateDescriptor desc;
+
+		ExtendedSavegameHeader header;
+		if (readSavegameHeader(f.get(), &header, false)) {
+			parseSavegameHeader(&header, &desc);
+			desc.setThumbnail(header.thumbnail);
+		} else {
+			// Load savegame descriptions from index file
+			typedef char CommandeType[SAVEGAME_NAME_LEN];
+			CommandeType saveNames[MAX_SAVEGAMES];
+			memset(saveNames, 0, sizeof(saveNames));
+
+			Common::InSaveFile *in;
+			in = g_system->getSavefileManager()->openForLoading(Common::String::format("%s.dir", target));
+
+			if (in) {
+				in->read(saveNames, SAVELIST_SIZE);
+				delete in;
+			}
+
+			saveNames[slot][SAVEGAME_NAME_LEN - 1] = 0;
+			Common::String saveNameStr((const char *)saveNames[slot]);
+			desc.setDescription(saveNameStr);
+		}
+
+		if (desc.getDescription().empty()) {
+			desc.setDescription(_("Unnamed savegame"));
+		}
+
+		desc.setSaveSlot(slot);
+		desc.setAutosave(slot == getAutosaveSlot());
+		desc.setWriteProtectedFlag(desc.isAutosave());
+
+		return desc;
+	}
+
+	// No saving on empty autosave slot
+	if (slot == getAutosaveSlot()) {
+		SaveStateDescriptor desc;
+		desc.setDescription(_("Empty autosave"));
+		desc.setSaveSlot(slot);
+		desc.setAutosave(true);		
+		desc.setWriteProtectedFlag(true);
+		return desc;
+	}
+
+	return SaveStateDescriptor();
+}
 
 void CineMetaEngine::removeSaveState(const char *target, int slot) const {
+	if (slot < 0 || slot >= MAX_SAVEGAMES) {
+		return;
+	}
+
 	// Load savegame descriptions from index file
-	typedef char CommandeType[20];
-	CommandeType saveNames[10];
+	typedef char CommandeType[SAVEGAME_NAME_LEN];
+	CommandeType saveNames[MAX_SAVEGAMES];
 
 	// Initialize all savegames' descriptions to empty strings
 	// so that if the savegames' descriptions can only be partially read from file
@@ -181,13 +311,13 @@ void CineMetaEngine::removeSaveState(const char *target, int slot) const {
 	if (!in)
 		return;
 
-	in->read(saveNames, 10 * 20);
+	in->read(saveNames, SAVELIST_SIZE);
 	delete in;
 
 	// Set description for selected slot
-	char slotName[20];
+	char slotName[SAVEGAME_NAME_LEN];
 	slotName[0] = 0;
-	Common::strlcpy(saveNames[slot], slotName, 20);
+	Common::strlcpy(saveNames[slot], slotName, SAVEGAME_NAME_LEN);
 
 	// Update savegame descriptions
 	Common::String indexFile = Common::String::format("%s.dir", target);
@@ -197,12 +327,11 @@ void CineMetaEngine::removeSaveState(const char *target, int slot) const {
 		return;
 	}
 
-	out->write(saveNames, 10 * 20);
+	out->write(saveNames, SAVELIST_SIZE);
 	delete out;
 
 	// Delete save file
-	char saveFileName[256];
-	sprintf(saveFileName, "%s.%1d", target, slot);
+	const char * saveFileName = getSavegameFile(slot, target);
 
 	g_system->getSavefileManager()->removeSavefile(saveFileName);
 }
@@ -222,11 +351,15 @@ Common::Error CineEngine::loadGameState(int slot) {
 }
 
 Common::Error CineEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
+	if (slot < 0 || slot >= MAX_SAVEGAMES) {
+		return Common::kCreatingFileFailed;
+	}
+
 	// Load savegame descriptions from index file
 	loadSaveDirectory();
 
 	// Set description for selected slot making sure it ends with a trailing zero
-	strncpy(currentSaveName[slot], desc.c_str(), 20);
+	strncpy(currentSaveName[slot], desc.c_str(), sizeof(CommandeType));
 	currentSaveName[slot][sizeof(CommandeType) - 1] = 0;
 
 	// Update savegame descriptions
@@ -238,11 +371,11 @@ Common::Error CineEngine::saveGameState(int slot, const Common::String &desc, bo
 		return Common::kUnknownError;
 	}
 
-	fHandle->write(currentSaveName, 10 * 20);
+	fHandle->write(currentSaveName, SAVELIST_SIZE);
 	delete fHandle;
 
 	// Save game
-	makeSave(getSaveStateName(slot));
+	makeSave(getSaveStateName(slot), getTotalPlayTime() / 1000, desc, isAutosave);
 
 	checkDataDisk(-1);
 
@@ -250,7 +383,7 @@ Common::Error CineEngine::saveGameState(int slot, const Common::String &desc, bo
 }
 
 Common::String CineEngine::getSaveStateName(int slot) const {
-	return Common::String::format("%s.%1d", _targetName.c_str(), slot);
+	return getMetaEngine().getSavegameFile(slot, _targetName.c_str());
 }
 
 bool CineEngine::canLoadGameStateCurrently() {
diff --git a/engines/cine/detection_tables.h b/engines/cine/detection_tables.h
index c85aff86ce..7a6a52e27d 100644
--- a/engines/cine/detection_tables.h
+++ b/engines/cine/detection_tables.h
@@ -23,6 +23,7 @@
 namespace Cine {
 
 #define GAMEOPTION_ORIGINAL_SAVELOAD   GUIO_GAMEOPTIONS1
+#define GAMEOPTION_TRANSPARENT_DIALOG_BOXES   GUIO_GAMEOPTIONS2
 
 static const CINEGameDescription gameDescriptions[] = {
 	{
diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp
index 981d73e539..ca5bf5dac6 100644
--- a/engines/cine/gfx.cpp
+++ b/engines/cine/gfx.cpp
@@ -26,18 +26,24 @@
 #include "cine/various.h"
 #include "cine/pal.h"
 
+#include "common/config-manager.h"
 #include "common/endian.h"
 #include "common/events.h"
+#include "common/str.h"
 #include "common/system.h"
 #include "common/textconsole.h"
 
 #include "graphics/cursorman.h"
+#include "graphics/primitives.h"
 
 namespace Cine {
 
 byte *collisionPage;
 FWRenderer *renderer = NULL;
 
+#define DEFAULT_MESSAGE_BG 1
+#define DEFAULT_CMD_Y 185
+
 // Constants related to kLowPalFormat
 #define kLowPalBytesPerColor 2
 #define kLowPalNumColors 16
@@ -103,12 +109,19 @@ static const byte cursorPalette[] = {
 	0xff, 0xff, 0xff, 0xff
 };
 
+void plotPoint(int x, int y, int color, void *data) {
+	byte *output = (byte *)data;
+	if (x >= 0 && x < 320 && y >= 0 && y < 200) {
+		output[y * 320 + x] = (byte)color;
+	}
+}
+
 /**
  * Initialize renderer
  */
-FWRenderer::FWRenderer() : _background(NULL), _backupPal(), _cmd(""),
-	_cmdY(0), _messageBg(0), _backBuffer(new byte[_screenSize]),
-	_activePal(), _changePal(0), _showCollisionPage(false) {
+FWRenderer::FWRenderer() : _savedBackBuffers(), _background(NULL), _backupPal(), _cmd(""),
+	_messageBg(DEFAULT_MESSAGE_BG), _cmdY(DEFAULT_CMD_Y), _backBuffer(new byte[_screenSize]),
+	_activePal(), _changePal(0), _showCollisionPage(false), _fadeToBlackLastCalledMs(0) {
 
 	assert(_backBuffer);
 
@@ -116,6 +129,14 @@ FWRenderer::FWRenderer() : _background(NULL), _backupPal(), _cmd(""),
 	memset(_bgName, 0, sizeof(_bgName));
 }
 
+void FWRenderer::removeSavedBackBuffers() {
+	for (int i = 0; i < ARRAYSIZE(_savedBackBuffers); i++) {
+		if (_savedBackBuffers[i]) {
+			delete[] _savedBackBuffers[i];
+			_savedBackBuffers[i] = nullptr;
+		}
+	}
+}
 
 /**
  * Destroy renderer
@@ -124,11 +145,13 @@ FWRenderer::~FWRenderer() {
 	delete[] _background;
 	delete[] _backBuffer;
 
+	removeSavedBackBuffers();
+
 	clearMenuStack();
 }
 
 bool FWRenderer::initialize() {
-	_activePal = Palette(kLowPalFormat, kLowPalNumColors);
+	_backupPal = _activePal = Palette(kLowPalFormat, kLowPalNumColors);
 	return true;
 }
 
@@ -144,14 +167,19 @@ void FWRenderer::clear() {
 	_activePal.clear();
 
 	memset(_backBuffer, 0, _screenSize);
+	removeSavedBackBuffers();
 
 	_cmd = "";
-	_cmdY = 0;
-	_messageBg = 0;
+	_cmdY = DEFAULT_CMD_Y;
+	_messageBg = DEFAULT_MESSAGE_BG;
 	_changePal = 0;
 	_showCollisionPage = false;
 }
 
+const Cine::Palette& FWRenderer::getFadeInSourcePalette() {
+	return _backupPal;
+}
+
 /**
  * Draw 1bpp sprite using selected color
  * @param obj Object info
@@ -263,11 +291,34 @@ void FWRenderer::drawCommand() {
 
 void FWRenderer::drawString(const char *string, byte param) {
 	int width;
+	byte minBrightnessColorIndex = 4;
+
+	bool useEnsureContrast = true;
+	if (useEnsureContrast && g_cine->getGameType() == Cine::GType_OS) {
+		bool paletteChanged = _activePal.ensureContrast(minBrightnessColorIndex);
+		if (paletteChanged) {
+			clearBackBuffer();
+			setPalette();
+		}
+	}
+
+	// Both Future Wars and Operation Stealth 16 color PC versions do this
+	int y = 80;
+	if (param == 1) {
+		y = 20;
+	} else if (param == 2) {
+		y = 140;
+	}
 
-	width = getStringWidth(string) + 10;
-	width = width > 300 ? 300 : width;
+	width = getStringWidth(string);
+
+	if (width == 0) {
+		return;
+	}
 
-	drawMessage(string, (320 - width) / 2, 80, width, 4);
+	width = MIN<int>(width + 20, 300);
+
+	drawMessage(string, (320 - width) / 2, y, width, minBrightnessColorIndex);
 
 	blit();
 }
@@ -287,10 +338,8 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
 	int line = 0, words = 0, cw = 0;
 	int space = 0, extraSpace = 0;
 
-	const bool isAmiga = (g_cine->getPlatform() == Common::kPlatformAmiga);
-
 	if (color >= 0) {
-		if (isAmiga)
+		if (useTransparentDialogBoxes())
 			drawTransparentBox(x, y, width, 4);
 		else
 			drawPlainBox(x, y, width, 4, color);
@@ -316,7 +365,7 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
 
 			ty += 9;
 			if (color >= 0) {
-				if (isAmiga)
+				if (useTransparentDialogBoxes())
 					drawTransparentBox(x, ty, width, 9);
 				else
 					drawPlainBox(x, ty, width, 9, color);
@@ -338,11 +387,11 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
 
 	ty += 9;
 	if (color >= 0) {
-		if (isAmiga)
+		if (useTransparentDialogBoxes())
 			drawTransparentBox(x, ty, width, 4);
 		else
 			drawPlainBox(x, ty, width, 4, color);
-		drawDoubleBorder(x, y, width, ty - y + 4, isAmiga ? 18 : 2);
+		drawDoubleBorder(x, y, width, ty - y + 4, (useTransparentDialogBoxes() ? transparentDialogBoxStartColor() : 0) + 2);
 	}
 }
 
@@ -357,19 +406,6 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
  * @note An on-screen rectangle's drawn height is always at least one.
  */
 void FWRenderer::drawPlainBox(int x, int y, int width, int height, byte color) {
-	// Make width's and height's absolute values at least one
-	// which forces this function to always draw something if the
-	// drawing position is inside screen bounds. This fixes at least
-	// the showing of the oxygen gauge meter in Operation Stealth's
-	// first arcade sequence where this function is called with a
-	// height of zero.
-	if (width == 0) {
-		width = 1;
-	}
-	if (height == 0) {
-		height = 1;
-	}
-
 	// Handle horizontally flipped boxes
 	if (width < 0) {
 		width = ABS(width);
@@ -393,7 +429,19 @@ void FWRenderer::drawPlainBox(int x, int y, int width, int height, byte color) {
 	}
 }
 
+bool FWRenderer::useTransparentDialogBoxes() {
+	return _activePal.colorCount() == 16 &&
+		((g_cine->getPlatform() == Common::kPlatformAmiga) ||
+		ConfMan.getBool("transparentdialogboxes"));
+}
+
+byte FWRenderer::transparentDialogBoxStartColor() {
+	return 16;
+}
+
 void FWRenderer::drawTransparentBox(int x, int y, int width, int height) {
+	byte startColor = transparentDialogBoxStartColor();
+
 	// Handle horizontally flipped boxes
 	if (width < 0) {
 		width = ABS(width);
@@ -415,8 +463,8 @@ void FWRenderer::drawTransparentBox(int x, int y, int width, int height) {
 	const int lineAdd = 320 - boxRect.width();
 	for (int i = 0; i < boxRect.height(); ++i) {
 		for (int j = 0; j < boxRect.width(); ++j, ++dest) {
-			if (*dest < 16)
-				*dest += 16;
+			if (*dest < startColor)
+				*dest += startColor;
 		}
 		dest += lineAdd;
 	}
@@ -506,17 +554,25 @@ int FWRenderer::undrawChar(char character, int x, int y) {
 }
 
 int FWRenderer::getStringWidth(const char *str) {
+	int padding = (g_cine->getGameType() == Cine::GType_OS) ? 2 : 1;
 	const char *p = str;
 	int width = 0;
+	int maxWidth = 0;
 
 	while (*p) {
-		if (*p == ' ')
+		unsigned char currChar = (unsigned char)*p;
+		if (currChar == '|') {
+			maxWidth = MAX<int>(width, maxWidth);
+			width = 0;
+		} else if (currChar == ' ')
 			width += 5;
 		else
-			width += g_cine->_textHandler.fontParamTable[(unsigned char)*p].characterWidth;
+			width += g_cine->_textHandler.fontParamTable[currChar].characterWidth + padding;
 		p++;
 	}
 
+	maxWidth = MAX<int>(width, maxWidth);
+
 	return width;
 }
 
@@ -572,6 +628,12 @@ void FWRenderer::drawBackground() {
 	memcpy(_backBuffer, _background, _screenSize);
 }
 
+void FWRenderer::clearBackBuffer() {
+	if (_backBuffer) {
+		memset(_backBuffer, 0, _screenSize);
+	}
+}
+
 /**
  * Draw one overlay
  * @param it Overlay info
@@ -645,6 +707,20 @@ void FWRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
  * Draw overlays
  */
 void FWRenderer::drawOverlays() {
+	// WORKAROUND: Show player behind stairs by moving him behind everything
+	// in the scene right after leaving Dr. Why's control room.
+	if (g_cine->getGameType() == Cine::GType_OS &&
+		g_cine->_overlayList.size() >= 2 &&
+		g_cine->_overlayList.back().objIdx == 1 &&
+		g_cine->_objectTable.size() >= 2 &&
+		g_cine->_objectTable[1].x == 231 &&
+		g_cine->_objectTable[1].y >= 142 &&
+		scumm_stricmp(renderer->getBgName(), "56VIDE.PI1") == 0) {
+		Cine::overlay playerOverlay = g_cine->_overlayList.back();
+		g_cine->_overlayList.pop_back();
+		g_cine->_overlayList.push_front(playerOverlay);
+	}
+
 	Common::List<overlay>::iterator it;
 
 	for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
@@ -655,7 +731,7 @@ void FWRenderer::drawOverlays() {
 /**
  * Draw another frame
  */
-void FWRenderer::drawFrame() {
+void FWRenderer::drawFrame(bool wait) {
 	drawBackground();
 	drawOverlays();
 
@@ -663,15 +739,36 @@ void FWRenderer::drawFrame() {
 		drawCommand();
 	}
 
-	if (_changePal) {
-		refreshPalette();
+	// DIFFERENCE FROM DISASSEMBLY:
+	// Waiting for g_cine->getTimerDelay() since last call to this function
+	// from mainLoop() was in Future Wars and Operation Stealth disassembly here.
+	// The wait did nothing else but simply wait for the waiting period to end.
+	// It has been moved to manageEvents() function call in executePlayerInput()
+	// to make better use of the waiting period. Now it is used to read mouse button
+	// status and possibly update the command line while moving the mouse
+	// (e.g. "EXAMINE DOOR" -> "EXAMINE BUTTON").
+
+	if (reloadBgPalOnNextFlip) {
+		_activePal = getFadeInSourcePalette();
+		reloadBgPalOnNextFlip = 0;
+		_changePal = 1; // From disassembly
+	}
+
+	if (_changePal) { // From disassembly
+		setPalette();
+		_changePal = 0; // From disassembly
 	}
 
 	const int menus = _menuStack.size();
 	for (int i = 0; i < menus; ++i)
 		_menuStack[i]->drawMenu(*this, (i == menus - 1));
 
-	blit();
+	blit();	
+
+	if (gfxFadeInRequested) {
+		fadeFromBlack();
+		gfxFadeInRequested = 0;
+	}
 }
 
 /**
@@ -683,14 +780,53 @@ void FWRenderer::showCollisionPage(bool state) {
 	_showCollisionPage = state;
 }
 
+void FWRenderer::blitBackBuffer() {
+	blit(false);
+}
+
+void FWRenderer::blit(bool useCollisionPage) {
+	// Show the back buffer or the collision page. Normally the back
+	// buffer but showing the collision page is useful for debugging.
+	byte *source = (useCollisionPage ? collisionPage : _backBuffer);
+	g_system->copyRectToScreen(source, 320, 0, 0, 320, 200);
+	g_system->updateScreen();
+}
+
 /**
  * Update screen
  */
 void FWRenderer::blit() {
-	// Show the back buffer or the collision page. Normally the back
-	// buffer but showing the collision page is useful for debugging.
-	byte *source = (_showCollisionPage ? collisionPage : _backBuffer);
-	g_system->copyRectToScreen(source, 320, 0, 0, 320, 200);
+	blit(_showCollisionPage);
+}
+
+bool FWRenderer::hasSavedBackBuffer(BackBufferSource source) {
+	return source >= 0 && source < MAX_BACK_BUFFER_SOURCES && _savedBackBuffers[source];
+}
+
+void FWRenderer::saveBackBuffer(BackBufferSource source) {
+	if (_backBuffer && source >= 0 && source < MAX_BACK_BUFFER_SOURCES) {
+		if (!_savedBackBuffers[source]) {
+			_savedBackBuffers[source] = new byte[_screenSize];
+		}
+		memcpy(_savedBackBuffers[source], _backBuffer, _screenSize);
+	}
+}
+
+void FWRenderer::popSavedBackBuffer(BackBufferSource source) {
+	restoreSavedBackBuffer(source);
+	removeSavedBackBuffer(source);
+}
+
+void FWRenderer::restoreSavedBackBuffer(BackBufferSource source) {
+	if (_backBuffer && hasSavedBackBuffer(source)) {
+		memcpy(_backBuffer, _savedBackBuffers[source], _screenSize);
+		blitBackBuffer();
+	}
+}
+
+void FWRenderer::removeSavedBackBuffer(BackBufferSource source) {
+	delete[] _savedBackBuffers[source];
+	_savedBackBuffers[source] = nullptr;
 }
 
 /**
@@ -701,22 +837,24 @@ void FWRenderer::setCommand(Common::String cmd) {
 	_cmd = cmd;
 }
 
-/**
- * Refresh current palette
- */
-void FWRenderer::refreshPalette() {
+Common::String FWRenderer::getCommand() {
+	return _cmd;
+}
+
+void FWRenderer::setBlackPalette(bool updateChangePal) {
+	_activePal.fillWithBlack();
+	if (updateChangePal) {
+		_changePal = 1; // From disassembly when called from main loop's initialization section
+	}
+}
+
+void FWRenderer::setPalette() {
 	assert(_activePal.isValid() && !_activePal.empty());
 	_activePal.setGlobalOSystemPalette();
-	_changePal = 0;
 }
 
-/**
- * Load palette of current background
- */
-void FWRenderer::reloadPalette() {
-	assert(_backupPal.isValid() && !_backupPal.empty());
-	_activePal = _backupPal;
-	_changePal = 1;
+int16 FWRenderer::addBackground(const char *bgName, uint16 bgIdx) {
+	error("Future Wars renderer doesn't support multiple backgrounds");
 }
 
 /**
@@ -824,7 +962,7 @@ void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version
 	fHandle.read(buf, kLowPalNumBytes);
 	_backupPal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
 
-	_changePal = 1;
+	_changePal = 1; // From disassembly
 }
 
 /**
@@ -867,8 +1005,8 @@ void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
 	_activePal.save(buf, sizeof(buf), CINE_LITTLE_ENDIAN);
 	fHandle.write(buf, kHighPalNumBytes);
 
-	// Write the active 256 color palette a second time.
-	// FIXME: The backup 256 color palette should be saved here instead of the active one.
+	// Write the backup 256 color palette.
+	_backupPal.save(buf, sizeof(buf), CINE_LITTLE_ENDIAN);
 	fHandle.write(buf, kHighPalNumBytes);
 }
 
@@ -878,22 +1016,29 @@ void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
  */
 void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version) {
 	byte buf[kHighPalNumBytes];
-	uint colorCount = (version > 0) ? fHandle.readUint16LE() : kHighPalNumBytes;
+	uint colorCount = (version > 0) ? fHandle.readUint16LE() : kHighPalNumColors;
 
+	// Load the active color palette
 	fHandle.read(buf, kHighPalNumBytes);
 
 	if (colorCount == kHighPalNumColors) {
 		// Load the active 256 color palette from file
 		_activePal.load(buf, sizeof(buf), kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
 	} else {
+		// Load the active 16 color palette from file
 		_activePal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_LITTLE_ENDIAN);
 	}
 
-	// Jump over the backup 256 color palette.
-	// FIXME: Load the backup 256 color palette and use it properly.
-	fHandle.seek(kHighPalNumBytes, SEEK_CUR);
+	// Load the backup color palette
+	fHandle.read(buf, kHighPalNumBytes);
 
-	_changePal = 1;
+	if (colorCount == kHighPalNumColors) {
+		_backupPal.load(buf, sizeof(buf), kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+	} else {
+		_backupPal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_LITTLE_ENDIAN);
+	}
+
+	_changePal = 1; // From disassembly
 }
 
 /**
@@ -902,9 +1047,28 @@ void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version
  * @param b Last color to rotate
  * @param c Possibly rotation step, must be 0 or 1 at the moment
  */
-void FWRenderer::rotatePalette(int a, int b, int c) {
-	_activePal.rotateRight(a, b, c);
-	refreshPalette();
+void FWRenderer::rotatePalette(int firstIndex, int lastIndex, int mode) {
+	if (mode == 1) {
+		_activePal.rotateRight(firstIndex, lastIndex);
+	} else if (mode == 2) {
+		_activePal.rotateLeft(firstIndex, lastIndex);
+	} else {
+		_activePal = _backupPal;
+	}
+	setPalette();
+}
+
+void OSRenderer::rotatePalette(int firstIndex, int lastIndex, int mode) {
+	if (mode == 1) {
+		_activePal.rotateRight(firstIndex, lastIndex);
+	} else if (mode == 2) {
+		_activePal.rotateLeft(firstIndex, lastIndex);
+	} else if (_currentBg > 0 && _currentBg < 8) {
+		_activePal = _bgTable[_currentBg].pal;
+	} else { // background indices 0 and 8 use backup palette
+		_activePal = _backupPal;
+	}
+	setPalette();
 }
 
 /**
@@ -921,7 +1085,24 @@ void FWRenderer::transformPalette(int first, int last, int r, int g, int b) {
 	}
 
 	_backupPal.saturatedAddColor(_activePal, first, last, r, g, b);
-	refreshPalette();
+	_changePal = 1; // From disassembly
+	gfxFadeOutCompleted = 0;
+}
+
+uint FWRenderer::fadeDelayMs() {
+	// For PC wait for vertical retrace and wait for three timer interrupt ticks.
+	// On PC vertical retrace is 70Hz (1000ms / 70 ~= 14.29ms) and
+	// timer interrupt tick is set to (10923000ms / 1193180) ~= 9.15ms.
+	// So 14.29ms + 3 * 9.15ms ~= 41.74ms ~= 42ms. That's the maximum to wait for PC.
+	// Because the vertical retrace might come earlier the minimum to wait is
+	// 0ms + 3 * 9.15ms (The wait for three timer ticks is absolute) = 27.45ms ~= 27ms.
+	// So the wait on PC is something between 27ms and 42ms.
+	// Probably something else on Amiga (Didn't they have 50Hz or 60Hz monitors?).
+	return 42;
+}
+
+uint FWRenderer::fadeToBlackMinMs() {
+	return 1000;
 }
 
 /**
@@ -932,15 +1113,59 @@ void FWRenderer::transformPalette(int first, int last, int r, int g, int b) {
 void FWRenderer::fadeToBlack() {
 	assert(_activePal.isValid() && !_activePal.empty());
 
-	for (int i = 0; i < 8; i++) {
+	bool skipFade = false;
+	uint32 now = g_system->getMillis();
+
+	// HACK: Try to cirmumvent double fade outs by throttling function call.
+	if (hacksEnabled && _fadeToBlackLastCalledMs != 0 && (now - _fadeToBlackLastCalledMs) < fadeToBlackMinMs()) {
+		skipFade = true;
+		warning("Skipping fade to black (Time since last called = %d ms < throttling value of %d ms)",
+			now - _fadeToBlackLastCalledMs, fadeToBlackMinMs());
+	} else {
+		_fadeToBlackLastCalledMs = now;
+	}
+
+	for (int i = (skipFade ? 7 : 0); i < 8; i++) {
 		// Fade out the whole palette by 1/7th
 		// (Operation Stealth used 36 / 252, which is 1 / 7. Future Wars used 1 / 7 directly).
 		_activePal.saturatedAddNormalizedGray(_activePal, 0, _activePal.colorCount() - 1, -1, 7);
 
-		refreshPalette();
+		setPalette();
+		g_system->updateScreen();
+		g_system->delayMillis(fadeDelayMs());
+	}
+
+	clearBackBuffer();
+	forbidBgPalReload = gfxFadeOutCompleted = 1;
+
+	// HACK: This is not present in disassembly
+	// but this is an attempt to prevent flashing a
+	// normally illuminated screen and then fading it in by
+	// resetting possible pending background palette reload.
+	if (hacksEnabled) {
+		reloadBgPalOnNextFlip = 0;
+	}
+}
+
+void FWRenderer::fadeFromBlack() {
+	assert(_activePal.isValid() && !_activePal.empty());
+
+	const Palette& sourcePalette = getFadeInSourcePalette();
+
+	// Initialize active palette to source palette's format and size if they differ
+	if (_activePal.colorFormat() != sourcePalette.colorFormat() || _activePal.colorCount() != sourcePalette.colorCount()) {
+		_activePal = Cine::Palette(sourcePalette.colorFormat(), sourcePalette.colorCount());
+	}
+
+	for (int i = 7; i >= 0; i--) {
+		sourcePalette.saturatedAddNormalizedGray(_activePal, 0, _activePal.colorCount() - 1, -i, 7);
+
+		setPalette();
 		g_system->updateScreen();
-		g_system->delayMillis(50);
+		g_system->delayMillis(fadeDelayMs());
 	}
+
+	forbidBgPalReload = gfxFadeOutCompleted = 0;
 }
 
 // Menu implementation
@@ -988,14 +1213,14 @@ void SelectionMenu::drawMenu(FWRenderer &r, bool top) {
 	if (y + height > 199)
 		y = 199 - height;
 
-	const bool isAmiga = (g_cine->getPlatform() == Common::kPlatformAmiga);
+	byte doubleBorderColor = (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0) + 2;
 
-	if (isAmiga) {
+	if (r.useTransparentDialogBoxes()) {
 		r.drawTransparentBox(x, y, _width, height);
-		r.drawDoubleBorder(x, y, _width, height, 18);
+		r.drawDoubleBorder(x, y, _width, height, doubleBorderColor);
 	} else {
 		r.drawPlainBox(x, y, _width, height, r._messageBg);
-		r.drawDoubleBorder(x, y, _width, height, 2);
+		r.drawDoubleBorder(x, y, _width, height, doubleBorderColor);
 	}
 
 	int lineY = y + 4;
@@ -1005,16 +1230,10 @@ void SelectionMenu::drawMenu(FWRenderer &r, bool top) {
 		int charX = x + 4;
 
 		if (i == _selection) {
-			int color;
+			int color = (r.useTransparentDialogBoxes() ? 2 : 0);
 
-			if (isAmiga) {
-				if (top) {
-					color = 2;
-				} else {
-					color = 18;
-				}
-			} else {
-				color = 0;
+			if (!top) {
+				color += (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0);
 			}
 
 			r.drawPlainBox(x + 2, lineY - 1, _width - 3, 9, color);
@@ -1022,7 +1241,7 @@ void SelectionMenu::drawMenu(FWRenderer &r, bool top) {
 
 		const int size = _elements[i].size();
 		for (int j = 0; j < size; ++j) {
-			if (isAmiga && i == _selection) {
+			if (r.useTransparentDialogBoxes() && i == _selection) {
 				charX = r.undrawChar(_elements[i][j], charX, lineY);
 			} else {
 				charX = r.drawChar(_elements[i][j], charX, lineY);
@@ -1048,9 +1267,7 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
 	int line = 0, words = 0, cw = 0;
 	int space = 0, extraSpace = 0;
 
-	const bool isAmiga = (g_cine->getPlatform() == Common::kPlatformAmiga);
-
-	if (isAmiga)
+	if (r.useTransparentDialogBoxes())
 		r.drawTransparentBox(x, y, _width, 4);
 	else
 		r.drawPlainBox(x, y, _width, 4, r._messageBg);
@@ -1075,7 +1292,7 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
 			}
 
 			ty += 9;
-			if (isAmiga)
+			if (r.useTransparentDialogBoxes())
 				r.drawTransparentBox(x, ty, _width, 9);
 			else
 				r.drawPlainBox(x, ty, _width, 9, r._messageBg);
@@ -1096,7 +1313,7 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
 
 	// input area background
 	ty += 9;
-	if (isAmiga)
+	if (r.useTransparentDialogBoxes())
 		r.drawTransparentBox(x, ty, _width, 9);
 	else
 		r.drawPlainBox(x, ty, _width, 9, r._messageBg);
@@ -1118,11 +1335,11 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
 	}
 
 	ty += 9;
-	if (isAmiga)
+	if (r.useTransparentDialogBoxes())
 		r.drawTransparentBox(x, ty, _width, 4);
 	else
 		r.drawPlainBox(x, ty, _width, 4, r._messageBg);
-	r.drawDoubleBorder(x, y, _width, ty - y + 4, isAmiga ? 18 : 2);
+	r.drawDoubleBorder(x, y, _width, ty - y + 4, (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0) + 2);
 }
 
 // -------------------
@@ -1146,7 +1363,7 @@ OSRenderer::~OSRenderer() {
 }
 
 bool OSRenderer::initialize() {
-	_activePal = Palette(kHighPalFormat, kHighPalNumColors);
+	_backupPal = _activePal = Palette(kHighPalFormat, kHighPalNumColors);
 	return true;
 }
 
@@ -1175,13 +1392,23 @@ void OSRenderer::incrustMask(const BGIncrust &incrust, uint8 color) {
 	const byte *data = g_cine->_animDataTable[obj.frame].data();
 	int x, y, width, height;
 
-	x = obj.x;
-	y = obj.y;
+	x = incrust.x;
+	y = incrust.y;
 	width = g_cine->_animDataTable[obj.frame]._realWidth;
 	height = g_cine->_animDataTable[obj.frame]._height;
 
-	if (_bgTable[_currentBg].bg) {
-		gfxFillSprite(data, width, height, _bgTable[_currentBg].bg, x, y, color);
+	if (_bgTable[incrust.bgIdx].bg) {
+		gfxFillSprite(data, width, height, _bgTable[incrust.bgIdx].bg, x, y, color);
+	}
+}
+
+const Cine::Palette& OSRenderer::getFadeInSourcePalette() {
+	assert(_currentBg >= 0 && _currentBg <= 8);
+
+	if (_currentBg == 0) {
+		return _backupPal;
+	} else {
+		return _bgTable[_currentBg].pal;
 	}
 }
 
@@ -1217,8 +1444,16 @@ void OSRenderer::incrustSprite(const BGIncrust &incrust) {
 	width = g_cine->_animDataTable[incrust.frame]._realWidth;
 	height = g_cine->_animDataTable[incrust.frame]._height;
 
-	if (_bgTable[_currentBg].bg) {
-		drawSpriteRaw2(data, transColor, width, height, _bgTable[_currentBg].bg, x, y);
+	if (_bgTable[incrust.bgIdx].bg) {
+		// HACK: Fix transparency colors of shadings near walls
+		// in labyrinth scene in Operation Stealth after loading a savegame
+		// saved in the labyrinth.
+		if (hacksEnabled && incrust.objIdx == 1 && incrust.frame < 16 && transColor == 5 &&
+			scumm_stricmp(currentPrcName, "LABY.PRC") == 0) {
+			transColor = 0;
+		}
+
+		drawSpriteRaw2(data, transColor, width, height, _bgTable[incrust.bgIdx].bg, x, y);
 	}
 }
 
@@ -1254,17 +1489,22 @@ void OSRenderer::drawBackground() {
 	if (!_bgShift) {
 		memcpy(_backBuffer, main, _screenSize);
 	} else {
+		unsigned int rowShift = _bgShift % 200;
 		byte *scroll = _bgTable[_scrollBg].bg;
-		int mainShift = _bgShift * _screenWidth;
-		int mainSize = _screenSize - mainShift;
-
 		assert(scroll);
 
-		if (mainSize > 0) { // Just a precaution
-			memcpy(_backBuffer, main + mainShift, mainSize);
-		}
-		if (mainShift > 0) { // Just a precaution
-			memcpy(_backBuffer + mainSize, scroll, mainShift);
+		if (!rowShift) {
+			memcpy(_backBuffer, scroll, _screenSize);
+		} else {
+			int mainShift = rowShift * _screenWidth;
+			int mainSize = _screenSize - mainShift;
+
+			if (mainSize > 0) { // Just a precaution
+				memcpy(_backBuffer, main + mainShift, mainSize);
+			}
+			if (mainShift > 0) { // Just a precaution
+				memcpy(_backBuffer + mainSize, scroll, mainShift);
+			}
 		}
 	}
 }
@@ -1278,7 +1518,8 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
 	int len, idx, width, height;
 	ObjectStruct *obj;
 	AnimData *sprite;
-	byte color;
+	byte color, transparentColor;
+	bool useTopLeftForTransCol = false;
 
 	switch (it->type) {
 	// color sprite
@@ -1286,8 +1527,44 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
 		if (g_cine->_objectTable[it->objIdx].frame < 0) {
 			break;
 		}
+
 		sprite = &g_cine->_animDataTable[g_cine->_objectTable[it->objIdx].frame];
-		drawSprite(&(*it), sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, g_cine->_objectTable[it->objIdx].x, g_cine->_objectTable[it->objIdx].y, g_cine->_objectTable[it->objIdx].part, sprite->_bpp);
+		obj = &g_cine->_objectTable[it->objIdx];
+		transparentColor = obj->part & 0x0F;
+
+		// HACK: Correct transparency color from 6 to 0 for the first frame of sea animation
+		// in 16 color DOS version of Operation Stealth in the flower shop scene
+		// (The scene in which the player arrives immediately after leaving the airport).
+		if (hacksEnabled && it->objIdx == 141 && obj->frame == 100 && obj->part == 6 && sprite->_bpp == 4 &&
+			scumm_stricmp(currentPrcName, "AIRPORT.PRC") == 0 &&
+			scumm_stricmp(renderer->getBgName(), "21.PI1") == 0) {
+			useTopLeftForTransCol = true;
+		}
+
+		// HACK: Correct transparency color from 8 to 51 for the player's walking animation
+		// in 256 color DOS version of Operation Stealth in the scene right after
+		// leaving Dr. Why's control room.
+		if (hacksEnabled && it->objIdx == 1 && obj->part == 8 && sprite->_bpp == 5 &&
+			scumm_stricmp(currentPrcName, "ILE.PRC") == 0 &&
+			scumm_stricmp(renderer->getBgName(), "56VIDE.PI1") == 0) {
+			useTopLeftForTransCol = true;
+		}
+
+		// HACK: Correct transparency color from 1 to 3 for the player emerging from a manhole
+		// in 256 color DOS version of Operation Stealth when entering the Dr. Why's island.
+		if (hacksEnabled && it->objIdx == 43 && obj->frame >= 100 && obj->frame <= 102 &&
+			obj->part == 1 && sprite->_bpp == 5 &&
+			scumm_stricmp(currentPrcName, "SOUSMAR2.PRC") == 0 &&
+			scumm_stricmp(renderer->getBgName(), "56.PI1") == 0) {
+			useTopLeftForTransCol = true;
+		}
+
+		if (useTopLeftForTransCol) {
+			// Use top left corner value for transparency
+			transparentColor = sprite->getColor(0, 0);
+		}
+
+		drawSprite(&(*it), sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, g_cine->_objectTable[it->objIdx].x, g_cine->_objectTable[it->objIdx].y, transparentColor, sprite->_bpp);
 		break;
 
 	// game message
@@ -1326,7 +1603,7 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
 	// masked background
 	case 20:
 		assert(it->objIdx < NUM_MAX_OBJECT);
-		var5 = it->x; // A global variable updated here!
+		lastType20OverlayBgIdx = it->x; // A global variable updated here!
 		obj = &g_cine->_objectTable[it->objIdx];
 		sprite = &g_cine->_animDataTable[obj->frame];
 
@@ -1334,19 +1611,18 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
 			break;
 		}
 
-		maskBgOverlay(_bgTable[it->x].bg, sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, obj->x, obj->y);
+		maskBgOverlay(it->x, _bgTable[it->x].bg, sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, obj->x, obj->y);
 		break;
 
+	// line drawing
 	case 22:
-		// TODO: Check it this implementation really works correctly (Some things might be wrong, needs testing).
 		assert(it->objIdx < NUM_MAX_OBJECT);
 		obj = &g_cine->_objectTable[it->objIdx];
 		color = obj->part & 0x0F;
 		width = obj->frame;
 		height = obj->costume;
-		drawPlainBox(obj->x, obj->y, width, height, color);
-		debug(5, "renderOverlay: type=%d, x=%d, y=%d, width=%d, height=%d, color=%d",
-			  it->type, obj->x, obj->y, width, height, color);
+		// Using Bresenham's algorithm, looks good enough for visual purposes in Operation Stealth
+		Graphics::drawLine(obj->x, obj->y, width, height, color, plotPoint, _backBuffer);
 		break;
 
 	// something else
@@ -1356,20 +1632,6 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
 	}
 }
 
-/**
- * Load palette of current background
- */
-void OSRenderer::reloadPalette() {
-	// selected background in plane takeoff scene has swapped colors 12
-	// and 14, shift background has it right
-	palBg *bg = _bgShift ? &_bgTable[_scrollBg] : &_bgTable[_currentBg];
-
-	assert(bg->pal.isValid() && !(bg->pal.empty()));
-
-	_activePal = bg->pal;
-	_changePal = 1;
-}
-
 /**
  * Copy part of backup palette to active palette and transform
  * @param first First color to transform
@@ -1379,15 +1641,55 @@ void OSRenderer::reloadPalette() {
  * @param b Blue channel transformation
  */
 void OSRenderer::transformPalette(int first, int last, int r, int g, int b) {
-	palBg *bg = _bgShift ? &_bgTable[_scrollBg] : &_bgTable[_currentBg];
+	// Background indices 0 and 8 use backup palette
+	const Cine::Palette& srcPal =
+		(_currentBg > 0 && _currentBg < 8) ? _bgTable[_currentBg].pal : _backupPal;
 
 	// Initialize active palette to current background's palette format and size if they differ
-	if (_activePal.colorFormat() != bg->pal.colorFormat() || _activePal.colorCount() != bg->pal.colorCount()) {
-		_activePal = Cine::Palette(bg->pal.colorFormat(), bg->pal.colorCount());
+	if (_activePal.colorFormat() != srcPal.colorFormat() || _activePal.colorCount() != srcPal.colorCount()) {
+		_activePal = Cine::Palette(srcPal.colorFormat(), srcPal.colorCount());
+	}
+
+	// If asked to change whole 16 color palette then
+	// assume it means the whole palette regardless of size.
+	// In Operation Stealth DOS 16 color and 256 color disassembly mapping was from 0-15 to 0-255.
+	if (first == 0 && last == 15) {
+		last = srcPal.colorCount() - 1;
+	}
+
+	srcPal.saturatedAddColor(_activePal, first, last, r, g, b, kLowPalFormat);
+	_changePal = 1; // From disassembly
+	gfxFadeOutCompleted = 0;
+}
+
+int16 OSRenderer::addBackground(const char *bgName, uint16 bgIdx) {
+	byte *ptr, *dataPtr;
+
+	int16 fileIdx = findFileInBundle(bgName);
+	if (fileIdx < 0) {
+		warning("OSRenderer::addBackground(\"%s\", %d): Could not find background in file bundle.", bgName, bgIdx);
+		return -1;
+	}
+	checkDataDisk(-1);
+	ptr = dataPtr = readBundleFile(fileIdx);
+
+	uint16 bpp = READ_BE_UINT16(ptr); ptr += 2;
+
+	if (!_bgTable[bgIdx].bg) {
+		_bgTable[bgIdx].bg = new byte[_screenSize];
 	}
 
-	bg->pal.saturatedAddColor(_activePal, first, last, r, g, b, kLowPalFormat);
-	refreshPalette();
+	Common::strlcpy(_bgTable[bgIdx].name, bgName, sizeof(_bgTable[bgIdx].name));
+
+	if (bpp == 8) {
+		_bgTable[bgIdx].pal.load(ptr, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+		memcpy(_bgTable[bgIdx].bg, ptr + kHighPalNumBytes, _screenSize);
+	} else {
+		_bgTable[bgIdx].pal.load(ptr, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
+		gfxConvertSpriteToRaw(_bgTable[bgIdx].bg, ptr + kLowPalNumBytes, 160, 200);
+	}
+	free(dataPtr);
+	return 0;
 }
 
 /**
@@ -1409,7 +1711,7 @@ void OSRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
 	Common::strlcpy(_bgTable[idx].name, name, sizeof(_bgTable[idx].name));
 
 	// Load the 16 color palette
-	_bgTable[idx].pal.load(bg, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
+	_backupPal.load(bg, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
 
 	// Jump over the palette data to the background data
 	bg += kLowPalNumBytes;
@@ -1423,10 +1725,21 @@ void OSRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
  * @param name Background filename
  */
 void OSRenderer::loadCt16(const byte *ct, const char *name) {
+	assert(collisionPage);
+
 	// Make the 9th background point directly to the collision page
 	// and load the picture into it.
+	setBackground8ToCollisionPage();
+	_bgTable[kCollisionPageBgIdxAlias].pal.load(ct, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
+	gfxConvertSpriteToRaw(_bgTable[kCollisionPageBgIdxAlias].bg, ct + kLowPalNumBytes, 160, 200);
+}
+
+void OSRenderer::setBackground8ToCollisionPage() {
+	byte* oldBg = _bgTable[kCollisionPageBgIdxAlias].bg;
+	if (oldBg && oldBg != collisionPage) {
+		delete[] _bgTable[kCollisionPageBgIdxAlias].bg;
+	}
 	_bgTable[kCollisionPageBgIdxAlias].bg = collisionPage;
-	loadBg16(ct, name, kCollisionPageBgIdxAlias);
 }
 
 /**
@@ -1445,7 +1758,8 @@ void OSRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
 	assert(_bgTable[idx].bg);
 
 	Common::strlcpy(_bgTable[idx].name, name, sizeof(_bgTable[idx].name));
-	_bgTable[idx].pal.load(bg, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+	_backupPal.load(bg, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+
 	memcpy(_bgTable[idx].bg, bg + kHighPalNumBytes, _screenSize);
 }
 
@@ -1455,10 +1769,13 @@ void OSRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
  * @param name Background filename
  */
 void OSRenderer::loadCt256(const byte *ct, const char *name) {
+	assert(collisionPage);
+
 	// Make the 9th background point directly to the collision page
 	// and load the picture into it.
-	_bgTable[kCollisionPageBgIdxAlias].bg = collisionPage;
-	loadBg256(ct, name, kCollisionPageBgIdxAlias);
+	setBackground8ToCollisionPage();
+	_bgTable[kCollisionPageBgIdxAlias].pal.load(ct, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+	memcpy(_bgTable[kCollisionPageBgIdxAlias].bg, ct + kHighPalNumBytes, _screenSize);
 }
 
 /**
@@ -1468,12 +1785,13 @@ void OSRenderer::loadCt256(const byte *ct, const char *name) {
 void OSRenderer::selectBg(unsigned int idx) {
 	assert(idx < 9);
 
-	if (_bgTable[idx].bg) {
-		assert(_bgTable[idx].pal.isValid() && !(_bgTable[idx].pal.empty()));
+	if (idx <= 8 && _bgTable[idx].bg) {
 		_currentBg = idx;
+		if (!forbidBgPalReload) {
+			reloadBgPalOnNextFlip = 1;
+		}
 	} else
 		warning("OSRenderer::selectBg(%d) - attempt to select null background", idx);
-	reloadPalette();
 }
 
 /**
@@ -1483,10 +1801,9 @@ void OSRenderer::selectBg(unsigned int idx) {
 void OSRenderer::selectScrollBg(unsigned int idx) {
 	assert(idx < 9);
 
-	if (_bgTable[idx].bg) {
+	if (idx <= 8 && _bgTable[idx].bg) {
 		_scrollBg = idx;
 	}
-	reloadPalette();
 }
 
 /**
@@ -1494,9 +1811,7 @@ void OSRenderer::selectScrollBg(unsigned int idx) {
  * @param shift Background scroll in pixels
  */
 void OSRenderer::setScroll(unsigned int shift) {
-	assert(shift <= 200);
-
-	_bgShift = shift;
+	_bgShift = shift % 400;
 }
 
 /**
@@ -1884,7 +2199,7 @@ void drawSpriteRaw2(const byte *spritePtr, byte transColor, int16 width, int16 h
 	}
 }
 
-void maskBgOverlay(const byte *bgPtr, const byte *maskPtr, int16 width, int16 height,
+void maskBgOverlay(int targetBgIdx, const byte *bgPtr, const byte *maskPtr, int16 width, int16 height,
 				   byte *page, int16 x, int16 y) {
 	int16 i, j, tmpWidth, tmpHeight;
 	Common::List<BGIncrust>::iterator it;
@@ -1915,6 +2230,12 @@ void maskBgOverlay(const byte *bgPtr, const byte *maskPtr, int16 width, int16 he
 
 	// incrust pass
 	for (it = g_cine->_bgIncrustList.begin(); it != g_cine->_bgIncrustList.end(); ++it) {
+		// HACK: Remove drawing of red corners around doors in rat maze in Operation Stealth
+		// by skipping drawing of possible collision table data to non-collision table page.
+		if (hacksEnabled && it->bgIdx == kCollisionPageBgIdxAlias && targetBgIdx != kCollisionPageBgIdxAlias) {
+			continue;
+		}
+
 		tmpWidth = g_cine->_animDataTable[it->frame]._realWidth;
 		tmpHeight = g_cine->_animDataTable[it->frame]._height;
 		byte *mask = (byte *)malloc(tmpWidth * tmpHeight);
diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h
index ad6e4b20ef..5349f300d0 100644
--- a/engines/cine/gfx.h
+++ b/engines/cine/gfx.h
@@ -34,6 +34,12 @@ namespace Cine {
 extern byte *collisionPage;
 static const int kCollisionPageBgIdxAlias = 8;
 
+enum BackBufferSource {
+	BEFORE_OPENING_MENU = 0,
+	BEFORE_TAKING_THUMBNAIL,
+	MAX_BACK_BUFFER_SOURCES
+};
+
 /**
  * Background with palette
  */
@@ -124,6 +130,7 @@ class FWRenderer : public Common::NonCopyable {
 	friend class SelectionMenu;
 	friend class TextInputMenu;
 private:
+	byte * _savedBackBuffers[MAX_BACK_BUFFER_SOURCES];
 	byte *_background; ///< Current background
 	char _bgName[13]; ///< Background filename
 
@@ -140,14 +147,16 @@ protected:
 	Common::Stack<Menu *> _menuStack; ///< All displayed menus
 	int _changePal; ///< Load active palette to video backend on next frame
 	bool _showCollisionPage; ///< Should we show the collision page instead of the back buffer? Used for debugging.
+	uint32 _fadeToBlackLastCalledMs;
 
+	virtual const Cine::Palette& getFadeInSourcePalette();
 	void fillSprite(const ObjectStruct &obj, uint8 color = 0);
 	void drawMaskedSprite(const ObjectStruct &obj, const byte *mask);
 	virtual void drawSprite(const ObjectStruct &obj);
 
-	void drawCommand();
 	void drawMessage(const char *str, int x, int y, int width, int color);
 	void drawPlainBox(int x, int y, int width, int height, byte color);
+	byte transparentDialogBoxStartColor();
 	void drawTransparentBox(int x, int y, int width, int height);
 	void drawBorder(int x, int y, int width, int height, byte color);
 	void drawDoubleBorder(int x, int y, int width, int height, byte color);
@@ -156,11 +165,12 @@ protected:
 	void drawLine(int x, int y, int width, int height, byte color);
 	void remaskSprite(byte *mask, Common::List<overlay>::iterator it);
 	virtual void drawBackground();
+	virtual void clearBackBuffer();
+	virtual void removeSavedBackBuffers();
 
 	virtual void renderOverlay(const Common::List<overlay>::iterator &it);
 	void drawOverlays();
-
-	void blit();
+	virtual void blit(bool useCollisionPage);
 
 public:
 	uint16 _messageBg; ///< Message box background color
@@ -173,15 +183,35 @@ public:
 
 	/** Test if renderer is ready to draw */
 	virtual bool ready() { return _background != NULL; }
+	virtual unsigned int currentBg() { return 0; };
+	virtual unsigned int scrollBg() { return 0; }
+	virtual bool useTransparentDialogBoxes();
 
 	virtual void clear();
 
-	void drawFrame();
+	void drawFrame(bool wait = false);
+	void drawCommand();
 	void setCommand(Common::String cmd);
+	Common::String getCommand();
+	virtual void blit();
+	virtual void blitBackBuffer();
 
 	virtual void incrustMask(const BGIncrust &incrust, uint8 color = 0);
 	virtual void incrustSprite(const BGIncrust &incrust);
 
+	virtual bool hasSavedBackBuffer(BackBufferSource source);
+
+	/** Saves back buffer content without palette. */
+	virtual void saveBackBuffer(BackBufferSource source);
+
+	virtual void popSavedBackBuffer(BackBufferSource source);
+
+	/** Restores back buffer content without palette. */ 
+	virtual void restoreSavedBackBuffer(BackBufferSource source);
+
+	virtual void removeSavedBackBuffer(BackBufferSource source);
+
+	virtual int16 addBackground(const char *bgName, uint16 bgIdx);
 	virtual void loadBg16(const byte *bg, const char *name, unsigned int idx = 0);
 	virtual void loadCt16(const byte *ct, const char *name);
 	virtual void loadBg256(const byte *bg, const char *name, unsigned int idx = 0);
@@ -192,20 +222,23 @@ public:
 	virtual uint getScroll() const;
 	virtual void removeBg(unsigned int idx);
 	virtual void saveBgNames(Common::OutSaveFile &fHandle);
-	virtual const char *getBgName(uint idx = 0) const;
+	virtual const char *getBgName(uint idx = 0) const;	
 
-	virtual void refreshPalette();
-	virtual void reloadPalette();
+	virtual void setBlackPalette(bool updateChangePal);
+	virtual void setPalette();
 	virtual void restorePalette(Common::SeekableReadStream &fHandle, int version);
 	virtual void savePalette(Common::OutSaveFile &fHandle);
-	virtual void rotatePalette(int a, int b, int c);
+	virtual void rotatePalette(int firstIndex, int lastIndex, int mode);
 	virtual void transformPalette(int first, int last, int r, int g, int b);
 
 	void pushMenu(Menu *menu);
 	Menu *popMenu();
 	void clearMenuStack();
 
+	virtual uint fadeDelayMs();
+	virtual uint fadeToBlackMinMs();
 	virtual void fadeToBlack();
+	virtual void fadeFromBlack();
 	void showCollisionPage(bool state);
 
 	void drawString(const char *string, byte param);
@@ -223,7 +256,8 @@ private:
 	unsigned int _bgShift; ///< Background shift
 
 protected:
-
+	void setBackground8ToCollisionPage();
+	const Cine::Palette& getFadeInSourcePalette() override;
 	void drawSprite(const ObjectStruct &obj) override;
 	void drawSprite(overlay *overlayPtr, const byte *spritePtr, int16 width, int16 height, byte *page, int16 x, int16 y, byte transparentColor, byte bpp);
 	int drawChar(char character, int x, int y) override;
@@ -238,12 +272,15 @@ public:
 
 	/** Test if renderer is ready to draw */
 	bool ready() override { return _bgTable[_currentBg].bg != NULL; }
+	unsigned int currentBg() override { return _currentBg; };
+	unsigned int scrollBg() override { return _scrollBg; }
 
 	void clear() override;
 
 	void incrustMask(const BGIncrust &incrust, uint8 color = 0) override;
 	void incrustSprite(const BGIncrust &incrust) override;
 
+	int16 addBackground(const char *bgName, uint16 bgIdx) override;
 	void loadBg16(const byte *bg, const char *name, unsigned int idx = 0) override;
 	void loadCt16(const byte *ct, const char *name) override;
 	void loadBg256(const byte *bg, const char *name, unsigned int idx = 0) override;
@@ -256,11 +293,10 @@ public:
 	void saveBgNames(Common::OutSaveFile &fHandle) override;
 	const char *getBgName(uint idx = 0) const override;
 
-	void reloadPalette() override;
 	void restorePalette(Common::SeekableReadStream &fHandle, int version) override;
 	void savePalette(Common::OutSaveFile &fHandle) override;
+	void rotatePalette(int firstIndex, int lastIndex, int mode) override;
 	void transformPalette(int first, int last, int r, int g, int b) override;
-
 };
 
 void gfxDrawSprite(byte *src4, uint16 sw, uint16 sh, byte *dst4, int16 sx, int16 sy);
@@ -293,7 +329,7 @@ void gfxFlipRawPage(byte *frontBuffer);
 void drawSpriteRaw(const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y);
 void gfxDrawPlainBoxRaw(int16 x1, int16 y1, int16 x2, int16 y2, byte color, byte *page);
 void drawSpriteRaw2(const byte *spritePtr, byte transColor, int16 width, int16 height, byte *page, int16 x, int16 y);
-void maskBgOverlay(const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y);
+void maskBgOverlay(int targetBgIdx, const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y);
 
 void fadeFromBlack();
 void fadeToBlack();
diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp
index 6cf7c5c43b..27878c8475 100644
--- a/engines/cine/main_loop.cpp
+++ b/engines/cine/main_loop.cpp
@@ -45,8 +45,6 @@ MouseStatusStruct mouseData;
 uint16 mouseRight = 0;
 uint16 mouseLeft = 0;
 
-int lastKeyStroke = 0;
-
 uint16 mouseUpdateStatus;
 uint16 dummyU16;
 
@@ -58,14 +56,26 @@ static void processEvent(Common::Event &event) {
 	case Common::EVENT_RBUTTONDOWN:
 		mouseRight = 1;
 		break;
+	case Common::EVENT_MBUTTONDOWN:
+		mouseLeft = mouseRight = 1;
+		break;
 	case Common::EVENT_LBUTTONUP:
 		mouseLeft = 0;
 		break;
 	case Common::EVENT_RBUTTONUP:
 		mouseRight = 0;
 		break;
+	case Common::EVENT_MBUTTONUP:
+		mouseLeft = mouseRight = 0;
+		break;
 	case Common::EVENT_MOUSEMOVE:
 		break;
+	case Common::EVENT_WHEELUP:
+		g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_UP));
+		break;
+	case Common::EVENT_WHEELDOWN:
+		g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_DOWN));
+		break;
 	case Common::EVENT_KEYDOWN:
 		switch (event.kbd.keycode) {
 		case Common::KEYCODE_RETURN:
@@ -130,6 +140,9 @@ static void processEvent(Common::Event &event) {
 		case Common::KEYCODE_F11:
 			renderer->showCollisionPage(true);
 			break;
+		case Common::KEYCODE_KP0:
+			g_cine->setDefaultGameSpeed();
+			break;
 		case Common::KEYCODE_MINUS:
 		case Common::KEYCODE_KP_MINUS:
 			g_cine->modifyGameSpeed(-1); // Slower
@@ -138,19 +151,15 @@ static void processEvent(Common::Event &event) {
 		case Common::KEYCODE_KP_PLUS:
 			g_cine->modifyGameSpeed(+1); // Faster
 			break;
-		case Common::KEYCODE_LEFT:
 		case Common::KEYCODE_KP4:
 			moveUsingKeyboard(-1, 0); // Left
 			break;
-		case Common::KEYCODE_RIGHT:
 		case Common::KEYCODE_KP6:
 			moveUsingKeyboard(+1, 0); // Right
 			break;
-		case Common::KEYCODE_UP:
 		case Common::KEYCODE_KP8:
 			moveUsingKeyboard(0, +1); // Up
 			break;
-		case Common::KEYCODE_DOWN:
 		case Common::KEYCODE_KP2:
 			moveUsingKeyboard(0, -1); // Down
 			break;
@@ -166,24 +175,39 @@ static void processEvent(Common::Event &event) {
 		case Common::KEYCODE_KP3:
 			moveUsingKeyboard(+1, -1); // Down & Right
 			break;
+		case Common::KEYCODE_LEFT:
+			// fall through
+		case Common::KEYCODE_RIGHT:
+			// fall through
+		case Common::KEYCODE_UP:
+			// fall through
+		case Common::KEYCODE_DOWN:
+			// fall through
 		default:
-			lastKeyStroke = event.kbd.keycode;
+			g_cine->_keyInputList.push_back(event.kbd);
 			break;
 		}
 		break;
 	case Common::EVENT_KEYUP:
 		switch (event.kbd.keycode) {
+		case Common::KEYCODE_RETURN:
+		case Common::KEYCODE_KP_ENTER:
+		case Common::KEYCODE_KP5:
+			if (allowPlayerInput) {
+				mouseLeft = 0;
+			}
+			break;
+		case Common::KEYCODE_ESCAPE:
+			if (allowPlayerInput) {
+				mouseRight = 0;
+			}
+			break;
 		case Common::KEYCODE_F11:
 			renderer->showCollisionPage(false);
 			break;
-		case Common::KEYCODE_KP5:   // Emulated left mouse button click
-		case Common::KEYCODE_LEFT:  // Left
 		case Common::KEYCODE_KP4:   // Left
-		case Common::KEYCODE_RIGHT: // Right
 		case Common::KEYCODE_KP6:   // Right
-		case Common::KEYCODE_UP:    // Up
 		case Common::KEYCODE_KP8:   // Up
-		case Common::KEYCODE_DOWN:  // Down
 		case Common::KEYCODE_KP2:   // Down
 		case Common::KEYCODE_KP9:   // Up & Right
 		case Common::KEYCODE_KP7:   // Up & Left
@@ -200,23 +224,166 @@ static void processEvent(Common::Event &event) {
 	}
 }
 
-void manageEvents() {
+void manageEvents(CallSource callSource, EventTarget eventTarget, bool useMaxMouseButtonState, Common::Array<Common::Rect> rects) {
 	Common::EventManager *eventMan = g_system->getEventManager();
+	Common::Point mousePos;
+	uint keysPressed = g_cine->_keyInputList.size();
+	bool foundTarget = false;
+	int eventsChecked = 0;
+	uint16 maxMouseLeft = mouseLeft;
+	uint16 maxMouseRight = mouseRight;
+	uint32 waitStart = g_system->getMillis();
+	uint32 waitEnd = waitStart + g_cine->getTimerDelay();
+	uint32 frameEnd = waitStart + 20;
+	bool frameEnded = false;
+	bool waitEnded = false;
+	bool checkWaitEnd = (eventTarget == UNTIL_WAIT_ENDED);
+	bool updateScreen = false;
+	bool updateAudio = false;
 
-	uint32 nextFrame = g_system->getMillis() + g_cine->getTimerDelay();
 	do {
 		Common::Event event;
-		while (eventMan->pollEvent(event)) {
+		int eventsCheckedBeforePolling = eventsChecked;
+		while (!foundTarget && !frameEnded && eventMan->pollEvent(event)) {
 			processEvent(event);
+			eventsChecked++;
+			maxMouseLeft = MAX<uint16>(mouseLeft, maxMouseLeft);
+			maxMouseRight = MAX<uint16>(mouseRight, maxMouseRight);
+
+			bool mouseButtonDown = (mouseLeft != 0 || mouseRight != 0);
+			bool mouseButtonUp = !mouseButtonDown;
+
+			switch (eventTarget) {
+			case UNTIL_MOUSE_BUTTON_UP_DOWN_UP:
+				// fall through
+			case UNTIL_MOUSE_BUTTON_UP_DOWN:
+				// fall through
+			case UNTIL_MOUSE_BUTTON_UP:
+				// fall through
+			case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED:
+				foundTarget = mouseButtonUp;
+				break;
+			case UNTIL_MOUSE_BUTTON_DOWN_UP:
+				// fall through
+			case UNTIL_MOUSE_BUTTON_DOWN:
+				foundTarget = mouseButtonDown;
+				break;
+			case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS:
+				foundTarget = mouseButtonDown;
+				if (!g_cine->_keyInputList.empty()) {
+					Common::KeyState key = g_cine->_keyInputList.back();
+					if (key.keycode == Common::KEYCODE_UP || key.keycode == Common::KEYCODE_DOWN) {
+						foundTarget = true;
+					}
+				}
+				mousePos = g_system->getEventManager()->getMousePos();
+				for (Common::Array<Common::Rect>::iterator it = rects.begin(); it != rects.end(); ++it) {
+					if (it->contains(mousePos)) {
+						foundTarget = true;
+						break;
+					}
+				}
+				break;
+			case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT:
+				foundTarget = mouseButtonDown || keysPressed < g_cine->_keyInputList.size();
+				break;
+			}
+
+			uint32 now = g_system->getMillis();
+			frameEnded = (now >= frameEnd);
+			waitEnded = (now >= waitEnd);
+			
+			if (foundTarget) {
+				switch (eventTarget) {
+				case UNTIL_MOUSE_BUTTON_UP_DOWN_UP:
+					eventTarget = UNTIL_MOUSE_BUTTON_DOWN_UP;
+					foundTarget = false;
+					break;
+				case UNTIL_MOUSE_BUTTON_UP_DOWN:
+					eventTarget = UNTIL_MOUSE_BUTTON_DOWN;
+					foundTarget = false;
+					break;
+				case UNTIL_MOUSE_BUTTON_DOWN_UP:
+					eventTarget = UNTIL_MOUSE_BUTTON_UP;
+					foundTarget = false;
+					break;
+				case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED:
+					eventTarget = UNTIL_WAIT_ENDED;
+					checkWaitEnd = true;
+					foundTarget = false;
+					break;
+				}
+			}
+
+			foundTarget |= (checkWaitEnd && waitEnded);
 		}
-		g_system->updateScreen();
-		g_system->delayMillis(20);
-	} while (g_system->getMillis() < nextFrame);
+		int eventsCheckedAfterPolling = eventsChecked;
+
+		bool eventQueueEmpty = (eventsCheckedBeforePolling == eventsCheckedAfterPolling);
 
-	mouseData.left = mouseLeft;
-	mouseData.right = mouseRight;
+		if (eventQueueEmpty) {
+			uint32 now = g_system->getMillis();
+			frameEnded = (now >= frameEnd);
+			waitEnded = (now >= waitEnd);
+		}
 
-	g_system->getAudioCDManager()->update();
+		if (eventTarget == UNTIL_WAIT_ENDED) {
+			foundTarget = waitEnded;
+		}
+
+		if (eventTarget == EMPTY_EVENT_QUEUE) {
+			foundTarget = eventQueueEmpty;
+		}
+
+		foundTarget |= (checkWaitEnd && waitEnded);
+
+		if (!foundTarget && eventsChecked == 0) {
+			// If there are no events to check then
+			// add an empty event to check the current state.
+			eventMan->pushEvent(Common::Event());
+			continue;
+		}
+		
+		updateScreen = updateAudio = (foundTarget || frameEnded);
+
+		if (updateScreen) {
+			if (callSource != EXECUTE_PLAYER_INPUT) {
+				g_system->updateScreen();
+			} else {
+				// Make the command line (e.g. "EXAMINE DOOR" -> "EXAMINE BUTTON")
+				// responsive by updating it here.
+				if (allowPlayerInput && playerCommand != -1 && !mouseLeft && !mouseRight) {
+					// A player command is given, left and right mouse buttons are up
+					Common::String oldCommand = renderer->getCommand();
+					mousePos = eventMan->getMousePos();
+					playerCommandMouseLeftRightUp(mousePos.x, mousePos.y);
+					if (!oldCommand.equals(renderer->getCommand())) {
+						renderer->drawCommand();
+					}
+				}
+
+				renderer->blit();
+			}
+		}
+
+		if (updateAudio) {
+			g_system->getAudioCDManager()->update(); // For Future Wars CD version
+		}
+
+		if (frameEnded) {
+			frameEnd += 20;
+		}
+
+		g_system->delayMillis(10);
+	} while (!foundTarget && !g_cine->shouldQuit());
+
+	if (useMaxMouseButtonState) {
+		mouseData.left = maxMouseLeft;
+		mouseData.right = maxMouseRight;
+	} else {
+		mouseData.left = mouseLeft;
+		mouseData.right = mouseRight;
+	}
 }
 
 void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY) {
@@ -235,14 +402,6 @@ void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY) {
 	}
 }
 
-int getKeyData() {
-	int k = lastKeyStroke;
-
-	lastKeyStroke = -1;
-
-	return k;
-}
-
 /** Removes elements from seqList that have their member variable var4 set to value -1. */
 void purgeSeqList() {
 	Common::List<SeqListElement>::iterator it = g_cine->_seqList.begin();
@@ -260,7 +419,6 @@ void purgeSeqList() {
 void CineEngine::mainLoop(int bootScriptIdx) {
 	bool playerAction;
 	byte di;
-	uint16 mouseButton;
 
 	if (_preLoad == false) {
 		resetBgIncrustList();
@@ -281,7 +439,10 @@ void CineEngine::mainLoop(int bootScriptIdx) {
 		allowPlayerInput = 0;
 		checkForPendingDataLoadSwitch = 0;
 
-		fadeRequired = false;
+		reloadBgPalOnNextFlip = 0;
+		forbidBgPalReload = 0;
+		gfxFadeOutCompleted = 0;
+		gfxFadeInRequested = 0;
 		isDrawCommandEnabled = 0;
 		waitForPlayerClick = 0;
 		menuCommandLen = 0;
@@ -298,6 +459,8 @@ void CineEngine::mainLoop(int bootScriptIdx) {
 			g_cine->_globalVars[VAR_LOW_MEMORY] = 0; // set to 1 to disable some animations, sounds etc.
 		}
 
+		renderer->setBlackPalette(true); // Sets _changePal = true
+
 		strcpy(newPrcName, "");
 		strcpy(newRelName, "");
 		strcpy(newObjectName, "");
@@ -336,7 +499,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
 		// jump over by moving the character to (204, 109). The script handling the
 		// flower shop scene is AIRPORT.PRC's 13th script.
 		// FIXME: Remove the hack and solve what's really causing the problem in the first place.
-		if (g_cine->getGameType() == Cine::GType_OS) {
+		if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS) {
 			if (scumm_stricmp(renderer->getBgName(), "21.PI1") == 0 && g_cine->_objectTable[1].x == 204 && g_cine->_objectTable[1].y == 110) {
 				g_cine->_objectTable[1].y--; // Move the player character upward on-screen by one pixel
 			}
@@ -368,8 +531,12 @@ void CineEngine::mainLoop(int bootScriptIdx) {
 			setMouseCursor(MOUSE_CURSOR_CROSS);
 		}
 
+		if (gfxFadeInRequested) {
+			gfxFadeOutCompleted = 0;
+		}
+
 		if (renderer->ready()) {
-			renderer->drawFrame();
+			renderer->drawFrame(true);
 		}
 
 		// NOTE: In the original Future Wars and Operation Stealth messages
@@ -384,27 +551,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
 			if (_messageLen < 800)
 				_messageLen = 800;
 
-			do {
-				manageEvents();
-				getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
-			} while (mouseButton != 0 && !shouldQuit());
-
-			menuVar = 0;
-
-			do {
-				manageEvents();
-				getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
-				playerAction = (mouseButton != 0) || processKeyboard(menuVar);
-				mainLoopSub6();
-			} while (!playerAction && !shouldQuit());
-
-			menuVar = 0;
-
-			do {
-				manageEvents();
-				getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
-			} while (mouseButton != 0 && !shouldQuit());
-
+			manageEvents(MAIN_LOOP_WAIT_FOR_PLAYER_CLICK, UNTIL_MOUSE_BUTTON_UP_DOWN_UP);
 			waitForPlayerClick = 0;
 		}
 
@@ -424,9 +571,6 @@ void CineEngine::mainLoop(int bootScriptIdx) {
 				menuCommandLen = 0;
 			}
 		}
-
-		manageEvents();
-
 	} while (!shouldQuit() && !_restartRequested);
 
 	hideMouse();
diff --git a/engines/cine/main_loop.h b/engines/cine/main_loop.h
index 98e8f8878a..f357df2f1c 100644
--- a/engines/cine/main_loop.h
+++ b/engines/cine/main_loop.h
@@ -25,8 +25,31 @@
 
 namespace Cine {
 
+enum CallSource {
+	EXECUTE_PLAYER_INPUT, ///< Called from executePlayerInput function.
+	MAIN_LOOP_WAIT_FOR_PLAYER_CLICK, ///< Called from mainLoop function's waiting for player click section.
+	MAKE_MENU_CHOICE, ///< Called from makeMenuChoice function.
+	MAKE_SYSTEM_MENU, ///< Called from makeSystemMenu function.
+	MAKE_TEXT_ENTRY_MENU, ///< Called from makeTextEntryMenu function.
+	PROCESS_INVENTORY, ///< Called from processInventory function.
+	WAIT_PLAYER_INPUT ///< Called from waitPlayerInput function.
+};
+
+enum EventTarget {
+	UNTIL_MOUSE_BUTTON_UP_DOWN_UP, ///< Wait until first mouse buttons all up, then at least one down, finally all up.
+	UNTIL_MOUSE_BUTTON_DOWN_UP, ///< Wait until first at least one mouse button down, finally all up.
+	UNTIL_MOUSE_BUTTON_UP, ///< Wait until all mouse buttons up.
+	UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED, ///< Wait until all mouse buttons up and wait period (getTimerDelay()) ended.
+	UNTIL_MOUSE_BUTTON_UP_DOWN, ///< Wait until first all mouse buttons up, finally at least one down.
+	UNTIL_MOUSE_BUTTON_DOWN, ///< Wait until at least one mouse button down.
+	UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS, ///< Wait until at least one mouse button down, up key pressed, down key pressed or mouse position in at least one of the given rectangles.
+	UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT, ///< Wait until at least one mouse button down or a key pressed.
+	UNTIL_WAIT_ENDED, ///< Wait until wait period (getTimerDelay()) ended.
+	EMPTY_EVENT_QUEUE ///< Empty the event queue.
+};
+
 void mainLoop(int bootScriptIdx);
-void manageEvents();
+void manageEvents(CallSource callSource, EventTarget eventTarget, bool useMaxMouseButtonState = false, Common::Array<Common::Rect> rects = Common::Array<Common::Rect>());
 
 } // End of namespace Cine
 
diff --git a/engines/cine/msg.cpp b/engines/cine/msg.cpp
index 5366da4a05..78587f5771 100644
--- a/engines/cine/msg.cpp
+++ b/engines/cine/msg.cpp
@@ -29,12 +29,19 @@
 
 namespace Cine {
 
-void loadMsg(char *pMsgName) {
+int16 loadMsg(char *pMsgName) {
 	uint32 sourceSize;
 
 	checkDataDisk(-1);
 	g_cine->_messageTable.clear();
-	byte *dataPtr = readBundleFile(findFileInBundle(pMsgName), &sourceSize);
+
+	int16 foundFileIdx = findFileInBundle(pMsgName);
+	if (foundFileIdx < 0) {
+		warning("loadMsg(\"%s\"): Could not find file in bundle.", pMsgName);
+		return -1;
+	}
+
+	byte *dataPtr = readBundleFile(foundFileIdx, &sourceSize);
 
 	setMouseCursor(MOUSE_CURSOR_DISK);
 
@@ -67,6 +74,7 @@ void loadMsg(char *pMsgName) {
 	}
 
 	free(dataPtr);
+	return 0;
 }
 
 } // End of namespace Cine
diff --git a/engines/cine/msg.h b/engines/cine/msg.h
index dce70c4295..b86f1f7296 100644
--- a/engines/cine/msg.h
+++ b/engines/cine/msg.h
@@ -29,7 +29,7 @@ namespace Cine {
 
 #define NUM_MAX_MESSAGE 255
 
-void loadMsg(char *pMsgName);
+int16 loadMsg(char *pMsgName);
 
 } // End of namespace Cine
 
diff --git a/engines/cine/object.cpp b/engines/cine/object.cpp
index 5eabc79e8b..e538e202bb 100644
--- a/engines/cine/object.cpp
+++ b/engines/cine/object.cpp
@@ -39,7 +39,7 @@ void resetObjectTable() {
 	}
 }
 
-void loadObject(char *pObjectName) {
+int16 loadObject(char *pObjectName) {
 	debug(5, "loadObject(\"%s\")", pObjectName);
 	uint16 numEntry;
 	uint16 entrySize;
@@ -48,7 +48,12 @@ void loadObject(char *pObjectName) {
 
 	checkDataDisk(-1);
 
-	ptr = dataPtr = readBundleFile(findFileInBundle(pObjectName));
+	int16 foundFileIdx = findFileInBundle(pObjectName);
+	if (foundFileIdx < 0) {
+		return -1;
+	}
+
+	ptr = dataPtr = readBundleFile(foundFileIdx);
 
 	setMouseCursor(MOUSE_CURSOR_DISK);
 
@@ -59,7 +64,19 @@ void loadObject(char *pObjectName) {
 	assert(numEntry <= NUM_MAX_OBJECT);
 
 	for (i = 0; i < numEntry; i++) {
-		if (g_cine->_objectTable[i].costume != -2 && g_cine->_objectTable[i].costume != -3) { // flag is keep?
+		bool overwrite =
+			(g_cine->getGameType() == Cine::GType_FW && g_cine->_objectTable[i].costume != -2) ||
+			(g_cine->getGameType() == Cine::GType_OS && g_cine->_objectTable[i].costume != -3);
+
+		// HACK: Fix handling of electric razor and cable in Amiga version of Operation Stealth
+		// when entering the Dr. Why's control room.
+		if (hacksEnabled && g_cine->getPlatform() == Common::kPlatformAmiga &&
+			g_cine->getGameType() == Cine::GType_OS && (i == 231 || i == 232) &&
+			scumm_stricmp(pObjectName, "SALLE59.REL") == 0) {
+			overwrite = false;
+		}
+
+		if (overwrite) {
 			Common::MemoryReadStream readS(ptr, entrySize);
 
 			g_cine->_objectTable[i].x = readS.readSint16BE();
@@ -80,6 +97,7 @@ void loadObject(char *pObjectName) {
 	}
 
 	free(dataPtr);
+	return 0;
 }
 
 /**
diff --git a/engines/cine/object.h b/engines/cine/object.h
index 9c44162d28..ae959e67f9 100644
--- a/engines/cine/object.h
+++ b/engines/cine/object.h
@@ -61,7 +61,7 @@ struct overlay {
 #define NUM_MAX_VAR 255
 
 void resetObjectTable();
-void loadObject(char *pObjectName);
+int16 loadObject(char *pObjectName);
 void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4);
 void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue);
 
diff --git a/engines/cine/pal.cpp b/engines/cine/pal.cpp
index 18f260cab7..21cdc068e8 100644
--- a/engines/cine/pal.cpp
+++ b/engines/cine/pal.cpp
@@ -140,19 +140,29 @@ int power(int base, int power) {
 }
 } // end of anonymous namespace
 
-// a.k.a. palRotate
-Palette &Palette::rotateRight(byte firstIndex, byte lastIndex, signed rotationAmount) {
-	debug(1, "Palette::rotateRight(firstIndex: %d, lastIndex: %d, rotationAmount:%d)", firstIndex, lastIndex, rotationAmount);
-	assert(rotationAmount >= 0);
+Palette &Palette::rotateRight(byte firstIndex, byte lastIndex) {
+	debug(1, "Palette::rotateRight(firstIndex: %d, lastIndex: %d)", firstIndex, lastIndex);
 
-	for (int j = 0; j < rotationAmount; j++) {
-		const Color lastColor = _colors[lastIndex];
+	const Color lastColor = _colors[lastIndex];
 
-		for (int i = lastIndex; i > firstIndex; i--)
-			_colors[i] = _colors[i - 1];
+	for (int i = lastIndex; i > firstIndex; i--)
+		_colors[i] = _colors[i - 1];
+
+	_colors[firstIndex] = lastColor;
+
+	return *this;
+}
+
+Palette &Palette::rotateLeft(byte firstIndex, byte lastIndex) {
+	debug(1, "Palette::rotateLeft(firstIndex: %d, lastIndex: %d)", firstIndex, lastIndex);
+
+	const Color firstColor = _colors[firstIndex];
+
+	for (int i = firstIndex; i < lastIndex; i++)
+		_colors[i] = _colors[i + 1];
+
+	_colors[lastIndex] = firstColor;
 
-		_colors[firstIndex] = lastColor;
-	}
 	return *this;
 }
 
@@ -164,6 +174,47 @@ uint Palette::colorCount() const {
 	return _colors.size();
 }
 
+byte Palette::brightness(byte colorIndex) {
+	return (byte) ((_colors[colorIndex].r*19 +
+		_colors[colorIndex].g*38 +
+		_colors[colorIndex].b*7) / 64);
+}
+
+bool Palette::isEqual(byte index1, byte index2) {
+	return _colors[index1].r == _colors[index2].r &&
+		_colors[index1].g == _colors[index2].g &&
+		_colors[index1].b == _colors[index2].b;
+}
+
+int Palette::findMinBrightnessColorIndex(uint minColorIndex) {
+	int minFoundBrightness = 999;
+	int foundColorIndex = 0;
+	for (uint i = minColorIndex; i < colorCount(); i++) {
+		byte currColorBrightness = brightness(i);
+		if (currColorBrightness < minFoundBrightness) {
+			minFoundBrightness = currColorBrightness;
+			foundColorIndex = i;
+		}
+	}
+
+	return (_colors.size() >= 3 && isEqual(2, foundColorIndex)) ? 0 : foundColorIndex;
+}
+
+bool Palette::ensureContrast(byte &minBrightnessColorIndex) {
+	minBrightnessColorIndex = findMinBrightnessColorIndex();
+	if (_colors.size() >= 3 && isEqual(2, minBrightnessColorIndex)) {
+		Color black{ 0, 0, 0 };
+		Color white{_format.rMax(), _format.gMax(), _format.bMax()	};
+
+		_colors[2] = white;
+		if (isEqual(2, minBrightnessColorIndex)) {
+			_colors[minBrightnessColorIndex] = black;
+		}
+		return true;
+	}
+	return false;
+}
+
 Palette &Palette::fillWithBlack() {
 	for (uint i = 0; i < _colors.size(); i++) {
 		_colors[i].r = 0;
@@ -187,10 +238,15 @@ const Graphics::PixelFormat &Palette::colorFormat() const {
 
 void Palette::setGlobalOSystemPalette() const {
 	byte buf[256 * 3]; // Allocate space for the largest possible palette
+
+	if (g_cine->mayHave256Colors()) {
+		memset(buf, 0, sizeof(buf)); // Clear whole palette
+	}
+	
 	// The color format used by OSystem's setPalette-function:
 	save(buf, sizeof(buf), Graphics::PixelFormat(3, 8, 8, 8, 0, 0, 8, 16, 0), CINE_LITTLE_ENDIAN);
 
-	if (g_cine->getPlatform() == Common::kPlatformAmiga && colorCount() == 16) {
+	if (renderer->useTransparentDialogBoxes() && colorCount() == 16) {
 		// The Amiga version of Future Wars does use the upper 16 colors for a darkened
 		// game palette to allow transparent dialog boxes. To support that in our code
 		// we do calculate that palette over here and append it to the screen palette.
@@ -198,6 +254,13 @@ void Palette::setGlobalOSystemPalette() const {
 			buf[16 * 3 + i] = buf[i] >> 1;
 
 		g_system->getPaletteManager()->setPalette(buf, 0, colorCount() * 2);
+	} else if (g_cine->mayHave256Colors()) {
+		// If 256 colors are possible then always set 256 colors
+		// because resources may be a combination of 16 colors and 256 colors
+		// and going from a 16 color screen to a 256 color screen (e.g. when leaving
+		// the rat maze on Dr. Why's island in Operation Stealth) may leave
+		// the upper 240 colors not faded out.
+		g_system->getPaletteManager()->setPalette(buf, 0, 256);
 	} else {
 		g_system->getPaletteManager()->setPalette(buf, 0, colorCount());
 	}
@@ -265,6 +328,19 @@ Palette::Palette(const Graphics::PixelFormat format, const uint numColors) : _fo
 	fillWithBlack();
 }
 
+Palette::Palette(const Palette& other) :
+	_format(other._format),
+	_colors(other._colors) {
+}
+
+Palette& Palette::operator=(const Palette& other) {
+	if (this != &other) {
+		_format = other._format;
+		_colors = other._colors;
+	}
+	return *this;
+}
+
 Palette &Palette::clear() {
 	_format = Graphics::PixelFormat();
 	_colors.clear();
diff --git a/engines/cine/pal.h b/engines/cine/pal.h
index 6dc56d8c19..482715acee 100644
--- a/engines/cine/pal.h
+++ b/engines/cine/pal.h
@@ -67,6 +67,8 @@ public:
 	 * @note For the default constructed object (i.e. no parameters given) this will hold: empty() && !isValid()
 	 */
 	Palette(const Graphics::PixelFormat format = Graphics::PixelFormat(), const uint numColors = 0);
+	Palette(const Palette& other);
+	Palette& operator=(const Palette& other);
 
 	/**
 	 * Clear the palette (Set color count to zero, release memory, overwrite color format with default value).
@@ -113,10 +115,10 @@ public:
 	byte *save(byte *buf, const uint size, const Graphics::PixelFormat format, const uint numColors, const EndianType endian, const byte firstIndex = 0) const;
 
 	/**
-	 * Rotate the palette in color range [firstIndex, lastIndex] to the right by the specified rotation amount.
-	 * @param rotationAmount Amount to rotate the sub-palette to the right. Only values 0 and 1 are currently supported!
+	 * Rotate the palette in color range [firstIndex, lastIndex] to the right by one.
 	 */
-	Palette &rotateRight(byte firstIndex, byte lastIndex, signed rotationAmount = 1);
+	Palette &rotateRight(byte firstIndex, byte lastIndex);
+	Palette &rotateLeft(byte firstIndex, byte lastIndex);
 	Palette &saturatedAddColor(Palette &output, byte firstIndex, byte lastIndex, signed r, signed g, signed b) const;
 
 	/**
@@ -146,8 +148,7 @@ public:
 	Palette &saturatedAddNormalizedGray(Palette &output, byte firstIndex, byte lastIndex, signed grayDividend, signed grayDenominator) const;
 
 	bool empty() const;
-	uint colorCount() const;
-
+	uint colorCount() const;	
 	Palette &fillWithBlack();
 
 	/** Is the palette valid? (Mostly just checks the color format for correctness) */
@@ -171,7 +172,12 @@ public:
 	/** Get the blue color component of the color at the given palette index. */
 	uint8 getB(byte index) const;
 
+	bool ensureContrast(byte &minBrightnessColorIndex);
+	bool isEqual(byte index1, byte index2);
+
 private:
+	int findMinBrightnessColorIndex(uint minColorIndex = 1);
+	byte brightness(byte colorIndex);
 	void setColorFormat(const Graphics::PixelFormat format);
 
 	// WORKAROUND: Using a reference to a result here instead of returning an Color object.
diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp
index 30d9461a6a..e9161c309e 100644
--- a/engines/cine/part.cpp
+++ b/engines/cine/part.cpp
@@ -22,6 +22,7 @@
 
 #include "common/debug.h"
 #include "common/endian.h"
+#include "common/memstream.h"
 #include "common/textconsole.h"
 
 #include "cine/cine.h"
@@ -71,6 +72,7 @@ static Common::String fixVolCnfFileName(const uint8 *src, uint len) {
 	char tmp[14];
 	memcpy(tmp, src, len);
 	tmp[len] = 0;
+	Common::String result;
 
 	if (len == 11) {
 		// Filenames of length 11 have no separation of the extension and the basename
@@ -93,14 +95,15 @@ static Common::String fixVolCnfFileName(const uint8 *src, uint len) {
 		tmp[8] = 0; // Force separation of extension and basename
 		Common::String basename(tmp);
 		if (extension.empty()) {
-			return basename;
+			result = basename;
 		} else {
-			return basename + "." + extension;
+			result = basename + "." + extension;
 		}
 	} else {
 		// Filenames of length 13 are okay as they are, no need for conversion
-		return Common::String(tmp);
+		result = Common::String(tmp);
 	}
+	return result;
 }
 
 void CineEngine::readVolCnf() {
@@ -127,14 +130,19 @@ void CineEngine::readVolCnf() {
 		error("Error while unpacking 'vol.cnf' data");
 	}
 	delete[] packedBuf;
+	Common::Array<VolumeResource> volumeResourceFiles;
 	uint8 *p = buf;
-	int resourceFilesCount = READ_BE_UINT16(p); p += 2;
-	int entrySize = READ_BE_UINT16(p); p += 2;
+	int resourceFilesCount = READ_BE_INT16(p); p += 2;
+	int entrySize = READ_BE_INT16(p); p += 2;
 	for (int i = 0; i < resourceFilesCount; ++i) {
-		char volumeResourceFile[9];
-		memcpy(volumeResourceFile, p, 8);
-		volumeResourceFile[8] = 0;
-		_volumeResourceFiles.push_back(volumeResourceFile);
+		Common::MemoryReadStream readS(p, 0x14);
+		VolumeResource res;
+		readS.read(res.name, sizeof(res.name));
+		res.name[sizeof(res.name) - 1] = 0;
+		res.pNamesList = readS.readUint32BE();
+		res.diskNum = readS.readSint16BE();
+		res.sizeOfNamesList = readS.readUint32BE();
+		volumeResourceFiles.push_back(res);
 		p += entrySize;
 	}
 
@@ -170,11 +178,15 @@ void CineEngine::readVolCnf() {
 
 	p = buf + 4 + resourceFilesCount * entrySize;
 	for (int i = 0; i < resourceFilesCount; ++i) {
+		const VolumeResource& volRes = volumeResourceFiles[i];
 		int count = READ_BE_UINT32(p) / fileNameLength; p += 4;
 		while (count--) {
 			Common::String volumeEntryName = fixVolCnfFileName(p, fileNameLength);
-			_volumeEntriesMap.setVal(volumeEntryName, _volumeResourceFiles[i].c_str());
-			debugC(5, kCineDebugPart, "Added volume entry name '%s' resource file '%s'", volumeEntryName.c_str(), _volumeResourceFiles[i].c_str());
+			if (!_volumeEntriesMap.contains(volumeEntryName)) {
+				_volumeEntriesMap.setVal(volumeEntryName, Common::Array<VolumeResource>());
+			}
+			_volumeEntriesMap.find(volumeEntryName)->_value.push_back(volRes);
+			debugC(5, kCineDebugPart, "Added volume entry name '%s', disk number %d, resource file '%s'", volumeEntryName.c_str(), volRes.diskNum, volRes.name);
 			p += fileNameLength;
 		}
 	}
@@ -183,21 +195,45 @@ void CineEngine::readVolCnf() {
 }
 
 int16 findFileInBundle(const char *fileName) {
+	// HACK: Fix underwater background palette by reading it from correct file
+	if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS &&
+		scumm_stricmp(currentPrcName, "SOUSMAR2.PRC") == 0) {
+		Common::Array<VolumeResource> volRes = g_cine->_volumeEntriesMap.find(fileName)->_value;
+		if (volRes.size() == 2 && scumm_stricmp(volRes[0].name, "rsc12") == 0 &&
+			scumm_stricmp(volRes[1].name, "rsc08") == 0 &&
+			(scumm_stricmp(fileName, "39.PI1") == 0 ||
+			scumm_stricmp(fileName, "SP39_11.SET") == 0 ||
+			scumm_stricmp(fileName, "SP39_12.SET") == 0)) {
+			debugC(5, kCineDebugPart, "Reading underwater background and fish from file rsc12 for the original (broken) palette.");
+			loadPart("rsc08");
+		}
+	}
+
 	if (g_cine->getGameType() == Cine::GType_OS) {
-		// look first in currently loaded resource file
 		for (uint i = 0; i < g_cine->_partBuffer.size(); i++) {
 			if (!scumm_stricmp(fileName, g_cine->_partBuffer[i].partName)) {
 				return i;
 			}
 		}
-		// not found, open the required resource file
-		StringPtrHashMap::const_iterator it = g_cine->_volumeEntriesMap.find(fileName);
+
+		StringToVolumeResourceArrayHashMap::const_iterator it = g_cine->_volumeEntriesMap.find(fileName);
 		if (it == g_cine->_volumeEntriesMap.end()) {
 			warning("Unable to find part file for filename '%s'", fileName);
 			return -1;
 		}
-		const char *part = (*it)._value;
-		loadPart(part);
+
+		// Prefer current disk's resource file
+		Common::Array<VolumeResource> volRes = it->_value;
+		VolumeResource match = volRes[0];
+		for (int i = 0; i < volRes.size(); i++) {
+			if (volRes[i].diskNum == currentDisk) {
+				match = volRes[i];
+				break;
+			}
+		}
+
+		checkDataDisk(match.diskNum);
+		loadPart(match.name);
 	}
 	for (uint i = 0; i < g_cine->_partBuffer.size(); i++) {
 		if (!scumm_stricmp(fileName, g_cine->_partBuffer[i].partName)) {
@@ -238,7 +274,16 @@ byte *readBundleFile(int16 foundFileIdx, uint32 *size) {
 	return dataPtr;
 }
 
-byte *readBundleSoundFile(const char *entryName, uint32 *size) {
+byte *readBundleSoundFileOS(const char *entryName, uint32 *size) {
+	int16 index = findFileInBundle(entryName);
+	if (index == -1) {
+		return NULL;
+	}
+
+	return readBundleFile(index, size);
+}
+
+byte *readBundleSoundFileFW(const char *entryName, uint32 *size) {
 	int16 index;
 	byte *data = 0;
 	char previousPartName[15] = "";
@@ -260,6 +305,14 @@ byte *readBundleSoundFile(const char *entryName, uint32 *size) {
 	return data;
 }
 
+byte *readBundleSoundFile(const char *entryName, uint32 *size) {
+	if (g_cine->getGameType() == Cine::GType_FW) {
+		return readBundleSoundFileFW(entryName, size);
+	} else {
+		return readBundleSoundFileOS(entryName, size);
+	}
+}
+
 /** Rotate byte value to the left by n bits */
 byte rolByte(byte value, uint n) {
 	n %= 8;
@@ -292,7 +345,10 @@ byte *readFile(const char *filename, bool crypted) {
 	return dataPtr;
 }
 
-void checkDataDisk(int16 param) {
+void checkDataDisk(int16 diskNum) {
+	if (diskNum != -1) {
+		currentDisk = diskNum;
+	}
 }
 
 void dumpBundle(const char *fileName) {
diff --git a/engines/cine/prc.cpp b/engines/cine/prc.cpp
index 55d3504b7e..553f2f80ac 100644
--- a/engines/cine/prc.cpp
+++ b/engines/cine/prc.cpp
@@ -62,6 +62,7 @@ bool loadPrc(const char *pPrcName) {
 		(!scumm_stricmp(pPrcName, BOOT_PRC_NAME) || !scumm_stricmp(pPrcName, "demo.prc"))) {
 		scriptPtr = dataPtr = readFile(pPrcName, (g_cine->getFeatures() & GF_CRYPTED_BOOT_PRC) != 0);
 	} else {
+		checkDataDisk(-1);
 		scriptPtr = dataPtr = readBundleFile(findFileInBundle(pPrcName));
 	}
 
diff --git a/engines/cine/saveload.cpp b/engines/cine/saveload.cpp
index dfd3a1f4bc..8d8fcb43fc 100644
--- a/engines/cine/saveload.cpp
+++ b/engines/cine/saveload.cpp
@@ -23,6 +23,7 @@
 #include "common/debug.h"
 #include "common/savefile.h"
 #include "common/textconsole.h"
+#include "common/translation.h"
 
 #include "cine/cine.h"
 #include "cine/bg_list.h"
@@ -30,6 +31,10 @@
 #include "cine/sound.h"
 #include "cine/various.h"
 
+#include "engines/metaengine.h"
+
+#include "gui/message.h"
+
 namespace Cine {
 
 int16 currentDisk;
@@ -69,13 +74,20 @@ enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle
 	// First check for the temporary Operation Stealth savegame format.
 	fHandle.seek(0);
 	ChunkHeader hdr;
-	loadChunkHeader(fHandle, hdr);
+	bool loadedHeader = loadChunkHeader(fHandle, hdr);
 	fHandle.seek(prevStreamPos);
-	if (hdr.id == TEMP_OS_FORMAT_ID) {
+
+	if (!loadedHeader) {
+		return ANIMSIZE_UNKNOWN;
+	} else if (hdr.id == TEMP_OS_FORMAT_ID) {
 		return TEMP_OS_FORMAT;
+	} else if (hdr.id == VERSIONED_FW_FORMAT_ID) {
+		return VERSIONED_FW_FORMAT;
+	} else if (hdr.id == VERSIONED_OS_FORMAT_ID) {
+		return VERSIONED_OS_FORMAT;
 	}
 
-	// Ok, so the savegame isn't using the temporary Operation Stealth savegame format.
+	// Ok, so the savegame isn't using the newer savegame formats.
 	// Let's check for the plain Future Wars savegame format and its different versions then.
 	// The animDataTable begins at savefile position 0x2315.
 	// Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443)
@@ -246,7 +258,7 @@ bool loadObjectTable(Common::SeekableReadStream &in) {
 
 bool loadZoneData(Common::SeekableReadStream &in) {
 	for (int i = 0; i < 16; i++) {
-		g_cine->_zoneData[i] = in.readUint16BE();
+		g_cine->_zoneData[i] = in.readSint16BE();
 	}
 	return !(in.eos() || in.err());
 }
@@ -340,7 +352,7 @@ void saveObjectTable(Common::OutSaveFile &out) {
 
 void saveZoneData(Common::OutSaveFile &out) {
 	for (int i = 0; i < 16; i++) {
-		out.writeUint16BE(g_cine->_zoneData[i]);
+		out.writeSint16BE(g_cine->_zoneData[i]);
 	}
 }
 
@@ -427,6 +439,10 @@ void saveBgIncrustList(Common::OutSaveFile &out) {
 		out.writeUint16BE(it->y);
 		out.writeUint16BE(it->frame);
 		out.writeUint16BE(it->part);
+
+		if (g_cine->getGameType() == Cine::GType_OS) {
+			out.writeUint16BE(it->bgIdx);
+		}
 	}
 }
 
@@ -471,7 +487,7 @@ bool CineEngine::loadSaveDirectory() {
 	// then the missing ones are correctly set to empty strings.
 	memset(currentSaveName, 0, sizeof(currentSaveName));
 
-	fHandle->read(currentSaveName, 10 * 20);
+	fHandle->read(currentSaveName, sizeof(currentSaveName));
 	delete fHandle;
 
 	// Make sure all savegames' descriptions end with a trailing zero.
@@ -481,32 +497,53 @@ bool CineEngine::loadSaveDirectory() {
 	return true;
 }
 
-bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
-	char musicName[13];
-	char bgNames[8][13];
+bool CineEngine::checkSaveHeaderData(const ChunkHeader& hdr) {
+	if (hdr.version > CURRENT_SAVE_VER) {
+		warning("checkSaveHeader: Detected newer format version. Not loading savegame");
+		return false;
+	} else if (hdr.version < CURRENT_SAVE_VER) {
+		debug(3, "checkSaveHeader: Loading older format version (%d < %d).", hdr.version, CURRENT_SAVE_VER);
+	} else {
+		debug(3, "checkSaveHeader: Found correct header (Both the identifier and version number match).");
+	}
 
-	// First check the temporary Operation Stealth savegame format header.
+	// There shouldn't be any data in the header's chunk currently so it's an error if there is.
+	if (hdr.size > 0) {
+		warning("checkSaveHeader: Format header's chunk seems to contain data so format is incorrect. Not loading savegame");
+		return false;
+	}
+
+	return true;
+}
+
+bool CineEngine::loadVersionedSaveFW(Common::SeekableReadStream &in) {
 	ChunkHeader hdr;
 	loadChunkHeader(in, hdr);
-	if (hdr.id != TEMP_OS_FORMAT_ID) {
-		warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame");
+	if (hdr.id != VERSIONED_FW_FORMAT_ID) {
+		warning("loadVersionedSaveFW: File has incorrect identifier. Not loading savegame");
 		return false;
-	} else if (hdr.version > CURRENT_OS_SAVE_VER) {
-		warning("loadTempSaveOS: Detected newer format version. Not loading savegame");
+	} else if (!checkSaveHeaderData(hdr)) {
+		warning("loadVersionedSaveFW: Detected incompatible savegame. Not loading savegame");
 		return false;
-	} else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) {
-		warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break");
-	} else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER
-		debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match).");
 	}
 
-	// There shouldn't be any data in the header's chunk currently so it's an error if there is.
-	if (hdr.size > 0) {
-		warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame");
+	return loadPlainSaveFW(in, ANIMSIZE_30_PTRS_INTACT, hdr.version);
+}
+
+bool CineEngine::loadVersionedSaveOS(Common::SeekableReadStream &in) {
+	char bgNames[8][13];
+
+	ChunkHeader hdr;
+	loadChunkHeader(in, hdr);
+	if (hdr.id != VERSIONED_OS_FORMAT_ID && hdr.id != TEMP_OS_FORMAT_ID) {
+		warning("loadVersionedSaveOS: File has incorrect identifier. Not loading savegame");
+		return false;
+	} else if (!checkSaveHeaderData(hdr)) {
+		warning("loadVersionedSaveOS: Detected incompatible savegame. Not loading savegame");
 		return false;
 	}
 
-	// Ok, so we've got a correct header for a temporary Operation Stealth savegame.
+	// Ok, so we've got a correct header for an Operation Stealth savegame.
 	// Let's start loading the plain savegame data then.
 	currentDisk = in.readUint16BE();
 	in.read(currentPartName, 13);
@@ -532,9 +569,6 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
 	// semantic differences result in having to do things
 	// in a different order).
 	{
-		// Not sure if this is needed with Operation Stealth...
-		checkDataDisk(currentDisk);
-
 		if (strlen(currentPrcName)) {
 			loadPrc(currentPrcName);
 		}
@@ -543,15 +577,6 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
 			loadRel(currentRelName);
 		}
 
-		// Reset background music in CD version of Future Wars
-		if (getGameType() == GType_FW && (getFeatures() & GF_CD)) {
-			if (strlen(bgNames[0])) {
-				char buffer[20];
-				removeExtention(buffer, bgNames[0]);
-				g_sound->setBgMusic(atoi(buffer + 1));
-			}
-		}
-
 		// Load first background (Uses loadBg)
 		if (strlen(bgNames[0])) {
 			loadBg(bgNames[0]);
@@ -560,7 +585,7 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
 		// Add backgrounds 1-7 (Uses addBackground)
 		for (int i = 1; i < 8; i++) {
 			if (strlen(bgNames[i])) {
-				addBackground(bgNames[i], i);
+				renderer->addBackground(bgNames[i], i);
 			}
 		}
 
@@ -580,44 +605,39 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
 	renderer->setCommand(g_cine->_commandBuffer);
 	loadZoneQuery(in);
 
-	// TODO: Use the loaded string (Current music name (String, 13 bytes)).
-	in.read(musicName, 13);
+	// Current music name (String, 13 bytes).
+	in.read(currentDatName, 13);
 
 	// TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)).
 	in.readUint16BE();
 
-	// TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)).
-	in.readUint16BE();
+	// Is music playing? (Uint16BE, Boolean).
+	musicIsPlaying = in.readUint16BE();
 
 	renderer->_cmdY      = in.readUint16BE();
-	in.readUint16BE(); // Some unknown variable that seems to always be zero
+	bgVar0               = in.readUint16BE();
 	allowPlayerInput     = in.readUint16BE();
 	playerCommand        = in.readUint16BE();
 	commandVar1          = in.readUint16BE();
 	isDrawCommandEnabled = in.readUint16BE();
-	var5                 = in.readUint16BE();
+	lastType20OverlayBgIdx = in.readUint16BE();
 	var4                 = in.readUint16BE();
 	var3                 = in.readUint16BE();
 	var2                 = in.readUint16BE();
 	commandVar2          = in.readUint16BE();
 	renderer->_messageBg = in.readUint16BE();
 
-	// TODO: Use the loaded value (adBgVar1 (Uint16BE)).
-	in.readUint16BE();
-
-	currentAdditionalBgIdx = in.readSint16BE();
-	currentAdditionalBgIdx2 = in.readSint16BE();
+	reloadBgPalOnNextFlip = in.readUint16BE(); // From Operation Stealth's disassembly
 
-	// TODO: Check whether the scroll value really gets used correctly after this.
-	// Note that the backgrounds are loaded only later than this value is set.
+	renderer->selectBg(in.readSint16BE());
+	renderer->selectScrollBg(in.readSint16BE());
 	renderer->setScroll(in.readUint16BE());
 
-	// TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?).
-	in.readUint16BE();
+	forbidBgPalReload = in.readUint16BE();
 
 	disableSystemMenu = in.readUint16BE();
 
-	// TODO: adBgVar1 = 1 here
+	reloadBgPalOnNextFlip = 1; // From Operation Stealth's disassembly
 
 	// Load the animDataTable entries
 	in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth).
@@ -629,7 +649,7 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
 	loadObjectScripts(in);
 	loadSeqList(in);
 	loadOverlayList(in);
-	loadBgIncrustFromSave(in);
+	loadBgIncrustFromSave(in, (int)hdr.version >= 2);
 
 	// Left this here instead of moving it earlier in this function with
 	// the other current value loadings (e.g. loading of current procedure,
@@ -639,19 +659,17 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
 		loadMsg(currentMsgName);
 	}
 
-	// TODO: Add current music loading and playing here
-	// TODO: Palette handling?
-
-	if (in.pos() == in.size()) {
-		debug(3, "loadTempSaveOS: Loaded the whole savefile.");
-	} else {
-		warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over");
+	if (strlen(currentDatName)) {
+		g_sound->loadMusic(currentDatName);
+		if (musicIsPlaying) {
+			g_sound->playMusic();
+		}
 	}
 
 	return !(in.eos() || in.err());
 }
 
-bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) {
+bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat, uint32 version) {
 	char bgName[13];
 
 	// At savefile position 0x0000:
@@ -707,7 +725,7 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor
 	loadObjectTable(in);
 
 	// At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32):
-	renderer->restorePalette(in, 0);
+	renderer->restorePalette(in, version);
 
 	// At 0x2083 (i.e. 0x2043 + 16 * 2 * 2):
 	g_cine->_globalVars.load(in, NUM_MAX_VAR);
@@ -738,7 +756,7 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor
 	// At 0x2303:
 	isDrawCommandEnabled = in.readUint16BE();
 	// At 0x2305:
-	var5 = in.readUint16BE();
+	lastType20OverlayBgIdx = in.readUint16BE();
 	// At 0x2307:
 	var4 = in.readUint16BE();
 	// At 0x2309:
@@ -765,6 +783,8 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor
 	loadOverlayList(in);
 	loadBgIncrustFromSave(in);
 
+	disableSystemMenu = ((version >= 4) ? in.readUint16BE() : 0);
+
 	if (strlen(currentMsgName)) {
 		loadMsg(currentMsgName);
 	}
@@ -831,18 +851,30 @@ bool CineEngine::makeLoad(const Common::String &saveName) {
 		// then let's try the default format and hope for the best.
 		warning("Couldn't detect the used savegame format, trying default savegame format. Things may break");
 		saveGameFormat = ANIMSIZE_30_PTRS_INTACT;
+	} else if (saveGameFormat == TEMP_OS_FORMAT) {
+		GUI::MessageDialog alert(_("WARNING: The savegame you are loading is using "
+			"a temporary broken format. Things will be broken. Please consider starting "
+			"Operation Stealth from beginning using new savegames."),
+			_("Load anyway"), _("Cancel"));
+		load = (alert.runModal() == GUI::kMessageOK);
 	}
 
 	if (load) {
 		// Reset the engine's state
 		resetEngine();
 
-		if (saveGameFormat == TEMP_OS_FORMAT) {
-			// Load the temporary Operation Stealth savegame format
-			result = loadTempSaveOS(*in);
+		if (saveGameFormat == VERSIONED_FW_FORMAT) {
+			result = loadVersionedSaveFW(*in);
+		} else if (saveGameFormat == VERSIONED_OS_FORMAT || saveGameFormat == TEMP_OS_FORMAT) {
+			result = loadVersionedSaveOS(*in);
 		} else {
-			// Load the plain Future Wars savegame format
-			result = loadPlainSaveFW(*in, saveGameFormat);
+			// Load the plain Future Wars savegame format using version number 0
+			result = loadPlainSaveFW(*in, saveGameFormat, 0);
+		}
+
+		ExtendedSavegameHeader header;
+		if (MetaEngine::readSavegameHeader(saveFile.get(), &header)) {
+			setTotalPlayTime(header.playtime);
 		}
 	}
 
@@ -851,7 +883,19 @@ bool CineEngine::makeLoad(const Common::String &saveName) {
 	return result;
 }
 
+void CineEngine::writeSaveHeader(Common::OutSaveFile &out, uint32 headerId) {
+	ChunkHeader header;
+	header.id = headerId;
+	header.version = CURRENT_SAVE_VER;
+	header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it.
+	writeChunkHeader(out, header);
+}
+
 void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
+	// Make a Future Wars savegame format chunk header and save it.
+	writeSaveHeader(out, VERSIONED_FW_FORMAT_ID);
+
+	// Start outputting the plain savegame data right after the chunk header.
 	out.writeUint16BE(currentDisk);
 	out.write(currentPartName, 13);
 	out.write(currentDatName, 13);
@@ -875,7 +919,7 @@ void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
 	out.writeUint16BE(playerCommand);
 	out.writeUint16BE(commandVar1);
 	out.writeUint16BE(isDrawCommandEnabled);
-	out.writeUint16BE(var5);
+	out.writeUint16BE(lastType20OverlayBgIdx);
 	out.writeUint16BE(var4);
 	out.writeUint16BE(var3);
 	out.writeUint16BE(var2);
@@ -892,23 +936,11 @@ void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
 }
 
 /**
- * Save an Operation Stealth type savegame. WIP!
- *
- * NOTE: This is going to be very much a work in progress so the Operation Stealth's
- *       savegame formats that are going to be tried are extremely probably not going
- *       to be supported at all after Operation Stealth becomes officially supported.
- *       This means that the savegame format will hopefully change to something nicer
- *       when official support for Operation Stealth begins.
+ * Save an Operation Stealth type savegame.
  */
 void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
-	int i;
-
-	// Make a temporary Operation Stealth savegame format chunk header and save it.
-	ChunkHeader header;
-	header.id = TEMP_OS_FORMAT_ID;
-	header.version = CURRENT_OS_SAVE_VER;
-	header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it.
-	writeChunkHeader(out, header);
+	// Make an Operation Stealth savegame format chunk header and save it.
+	writeSaveHeader(out, VERSIONED_OS_FORMAT_ID);
 
 	// Start outputting the plain savegame data right after the chunk header.
 	out.writeUint16BE(currentDisk);
@@ -927,42 +959,36 @@ void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
 	saveCommandBuffer(out);
 	saveZoneQuery(out);
 
-	// FIXME: Save a proper name here, saving an empty string currently.
 	// 0x2925: Current music name (String, 13 bytes).
-	for (i = 0; i < 13; i++) {
-		out.writeByte(0);
-	}
+	out.write(currentDatName, 13);
+
 	// FIXME: Save proper value for this variable, currently writing zero
 	// 0x2932: Is music loaded? (Uint16BE, Boolean).
 	out.writeUint16BE(0);
-	// FIXME: Save proper value for this variable, currently writing zero
+
 	// 0x2934: Is music playing? (Uint16BE, Boolean).
-	out.writeUint16BE(0);
+	out.writeUint16BE(musicIsPlaying);
 
 	out.writeUint16BE(renderer->_cmdY);
-	out.writeUint16BE(0); // Some unknown variable that seems to always be zero
+	out.writeUint16BE(bgVar0);
 	out.writeUint16BE(allowPlayerInput);
 	out.writeUint16BE(playerCommand);
 	out.writeUint16BE(commandVar1);
 	out.writeUint16BE(isDrawCommandEnabled);
-	out.writeUint16BE(var5);
+	out.writeUint16BE(lastType20OverlayBgIdx);
 	out.writeUint16BE(var4);
 	out.writeUint16BE(var3);
 	out.writeUint16BE(var2);
 	out.writeUint16BE(commandVar2);
 	out.writeUint16BE(renderer->_messageBg);
 
-	// FIXME: Save proper value for this variable, currently writing zero.
-	// An unknown variable at 0x295E: adBgVar1 (Uint16BE).
-	out.writeUint16BE(0);
-	out.writeSint16BE(currentAdditionalBgIdx);
-	out.writeSint16BE(currentAdditionalBgIdx2);
-	// FIXME: Save proper value for this variable, currently writing zero.
+	out.writeUint16BE(reloadBgPalOnNextFlip);
+	out.writeSint16BE(renderer->currentBg());
+	out.writeSint16BE(renderer->scrollBg());
+
 	// 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift.
-	out.writeUint16BE(0);
-	// FIXME: Save proper value for this variable, currently writing zero.
-	// An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0?
-	out.writeUint16BE(0);
+	out.writeUint16BE(renderer->getScroll());
+	out.writeUint16BE(forbidBgPalReload);
 	out.writeUint16BE(disableSystemMenu);
 
 	saveAnimDataTable(out);
@@ -974,7 +1000,8 @@ void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
 	saveBgIncrustList(out);
 }
 
-void CineEngine::makeSave(const Common::String &saveFileName) {
+void CineEngine::makeSave(const Common::String &saveFileName, uint32 playtime,
+	Common::String desc, bool isAutosave) {
 	Common::SharedPtr<Common::OutSaveFile> fHandle(_saveFileMan->openForSaving(saveFileName));
 
 	setMouseCursor(MOUSE_CURSOR_DISK);
@@ -992,6 +1019,15 @@ void CineEngine::makeSave(const Common::String &saveFileName) {
 		}
 	}
 
+	renderer->saveBackBuffer(BEFORE_TAKING_THUMBNAIL);
+	if (renderer->hasSavedBackBuffer(BEFORE_OPENING_MENU)) {
+		renderer->popSavedBackBuffer(BEFORE_OPENING_MENU);
+	}
+
+	MetaEngine::appendExtendedSave(fHandle.get(), playtime, desc, isAutosave);
+
+	renderer->restoreSavedBackBuffer(BEFORE_TAKING_THUMBNAIL);
+
 	setMouseCursor(MOUSE_CURSOR_NORMAL);
 }
 
diff --git a/engines/cine/saveload.h b/engines/cine/saveload.h
index 91c9452d60..821a5105e4 100644
--- a/engines/cine/saveload.h
+++ b/engines/cine/saveload.h
@@ -27,6 +27,8 @@
 
 namespace Cine {
 
+extern int16 currentDisk;
+
 /**
  * Cine engine's save game formats.
  * Enumeration entries (Excluding the one used as an error)
@@ -64,14 +66,31 @@ enum CineSaveGameFormat {
 	ANIMSIZE_23,
 	ANIMSIZE_30_PTRS_BROKEN,
 	ANIMSIZE_30_PTRS_INTACT,
-	TEMP_OS_FORMAT
+	TEMP_OS_FORMAT,
+	VERSIONED_FW_FORMAT,
+	VERSIONED_OS_FORMAT
 };
 
 /** Identifier for the temporary Operation Stealth savegame format. */
 static const uint32 TEMP_OS_FORMAT_ID = MKTAG('T', 'E', 'M', 'P');
 
-/** The current version number of Operation Stealth's savegame format. */
-static const uint32 CURRENT_OS_SAVE_VER = 1;
+/** Identifiers for versioned Future Wars and Operation Stealth savegame formats. */
+static const uint32 VERSIONED_FW_FORMAT_ID = MKTAG('C', '1', 'F', 'W');
+static const uint32 VERSIONED_OS_FORMAT_ID = MKTAG('C', '2', 'O', 'S');
+
+/** The current version number of versioned Future Wars and Operation Stealth savegame formats.
+Version 4: First version used. Added disableSystemMenu to Future Wars savegame format.
+*/
+static const uint32 CURRENT_SAVE_VER = 4;
+
+/** The last version number of temporary Operation Stealth's savegame format.
+Version 0: Color count was not saved, was assumed to be 256. BGIncrust.bgIdx does not exist, _currentBg was used.
+Version 1: Saving of real color count was added but still 256 colors were always saved.
+Version 2: BGIncrust.bgIdx was added.
+Version 3: Saving real values for current music name, music playing status, current background index,
+           scroll background index and background scrolling was added.
+*/
+static const uint32 LAST_TEMP_OS_SAVE_VER = 3;
 
 /** Chunk header used by the temporary Operation Stealth savegame format. */
 struct ChunkHeader {
diff --git a/engines/cine/script.h b/engines/cine/script.h
index 1dc9ab2823..c088544a42 100644
--- a/engines/cine/script.h
+++ b/engines/cine/script.h
@@ -177,7 +177,7 @@ protected:
 	int o1_loadMask4();
 	int o1_unloadMask4();
 	int o1_addSpriteFilledToBgList();
-	int o1_op1B();
+	int o1_clearBgIncrustList();
 	int o1_label();
 	int o1_goto();
 	int o1_gotoIfSup();
@@ -239,7 +239,7 @@ protected:
 	int o2_removeSeq();
 	int o2_playSample();
 	int o2_playSampleAlt();
-	int o2_op81();
+	int o2_clearSeqList();
 	int o2_modifySeqListElement();
 	int o2_isSeqRunning();
 	int o2_gotoIfSupNearest();
diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp
index 4383c11d81..2ed3e96ebd 100644
--- a/engines/cine/script_fw.cpp
+++ b/engines/cine/script_fw.cpp
@@ -79,7 +79,7 @@ void FWScript::setupTable() {
 		{ &FWScript::o1_loadMask4, "b" },
 		{ &FWScript::o1_unloadMask4, "b" },
 		{ &FWScript::o1_addSpriteFilledToBgList, "b" },
-		{ &FWScript::o1_op1B, "" },
+		{ &FWScript::o1_clearBgIncrustList, "" },
 		/* 1C */
 		{ 0, 0 },
 		{ &FWScript::o1_label, "l" },
@@ -947,7 +947,7 @@ int FWScript::o1_loadVar() {
 			_localVars[varIdx] = _localVars[dataIdx];
 			break;
 		case 2:
-			debugC(5, kCineDebugScript, "Line: %d: var[%d] = globalVars[%d]", _line, varIdx, dataIdx);
+			debugC(5, kCineDebugScript, "Line: %d: var[%d] = globalVars[%d] (= %d)", _line, varIdx, dataIdx, _globalVars[dataIdx]);
 			_localVars[varIdx] = _globalVars[dataIdx];
 			break;
 		case 3:
@@ -1196,7 +1196,7 @@ int FWScript::o1_addSpriteFilledToBgList() {
 	return 0;
 }
 
-int FWScript::o1_op1B() {
+int FWScript::o1_clearBgIncrustList() {
 	debugC(5, kCineDebugScript, "Line: %d: freeBgIncrustList", _line);
 	g_cine->_bgIncrustList.clear();
 	return 0;
@@ -1349,6 +1349,7 @@ int FWScript::o1_startGlobalScript() {
 	}
 
 	addScriptToGlobalScripts(param);
+
 	return 0;
 }
 
@@ -1389,7 +1390,11 @@ int FWScript::o1_loadBg() {
 	}
 
 	loadBg(param);
-	g_cine->_bgIncrustList.clear();
+	if (g_cine->getGameType() == Cine::GType_OS) {
+		removeBgIncrustsWithBgIdx(0);
+	} else {
+		g_cine->_bgIncrustList.clear();
+	}
 	bgVar0 = 0;
 	return 0;
 }
@@ -1455,11 +1460,18 @@ int FWScript::o1_requestCheckPendingDataLoad() {
 
 int FWScript::o1_blitAndFade() {
 	debugC(5, kCineDebugScript, "Line: %d: request fadein", _line);
-	// TODO: use real code
 
-//	fadeFromBlack();
+	// HACK: This section is not present in disassembly
+	// but this is an attempt to prevent flashing a
+	// normally illuminated screen and then fading it in by
+	// setting the palette initially to black.
+	if (hacksEnabled) {
+		renderer->setBlackPalette(false); // Does not update _changePal
+		renderer->setPalette();
+		g_system->updateScreen();
+	}
 
-	renderer->reloadPalette();
+	gfxFadeInRequested = 1;
 	return 0;
 }
 
@@ -1543,6 +1555,11 @@ int FWScript::o1_break() {
 		return 0;
 	}
 
+	// Jump over breaks when running only until copy protection check
+	if (runOnlyUntilCopyProtectionCheck) {
+		return 0;
+	}
+
 	return 1;
 }
 
@@ -1623,6 +1640,16 @@ int FWScript::o1_compareGlobalVar() {
 			_compare = kCmpEQ;
 		} else {
 			_compare = compareVars(_globalVars[varIdx], value);
+
+			// Used for bailing out early from AUTO00.PRC before loading a savegame.
+			// Used for making sound effects work using Roland MT-32 and AdLib in
+			// Operation Stealth after loading a savegame. The sound effects are loaded
+			// in AUTO00.PRC using a combination of o2_loadAbs and o2_playSample(1, ...)
+			// before checking if _globalVars[255] == 0.
+			if (varIdx == 255 && value == 0 && runOnlyUntilCopyProtectionCheck) {
+				runOnlyUntilCopyProtectionCheck = false;
+				return o1_endScript();
+			}
 		}
 	}
 
@@ -1685,10 +1712,18 @@ int FWScript::o1_initializeZoneData() {
 
 int FWScript::o1_setZoneDataEntry() {
 	byte zoneIdx = getNextByte();
-	uint16 var = getNextWord();
+	int16 var = getNextWord();
 
+	// HACK: Fix storage room's door animation on Dr. Why's island.
+	if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS && zoneIdx == 2 && var == 8 &&
+		_script._size >= 10 && _script.getByte(9) == 0 &&
+		scumm_stricmp(_script.getString(0), "Z012_INIT") == 0 && _line == 34) {
+		return 0;
+	}
 	debugC(5, kCineDebugScript, "Line: %d: setZone[%d] = %d", _line, zoneIdx, var);
-	g_cine->_zoneData[zoneIdx] = var;
+	if (zoneIdx < NUM_MAX_ZONE) {
+		g_cine->_zoneData[zoneIdx] = var;
+	}
 	return 0;
 }
 
@@ -1697,6 +1732,7 @@ int FWScript::o1_getZoneDataEntry() {
 	byte var = getNextByte();
 
 	_localVars[var] = g_cine->_zoneData[zoneIdx];
+	debugC(5, kCineDebugScript, "Line: %d: SET localVars[%d] = zoneData[%d] (= %d)", _line, var, zoneIdx, g_cine->_zoneData[zoneIdx]);
 	return 0;
 }
 
@@ -1966,7 +2002,7 @@ int16 getZoneFromPosition(byte *page, int16 x, int16 y, int16 width) {
 	return zoneVar;
 }
 
-int16 getZoneFromPositionRaw(byte *page, int16 x, int16 y, int16 width) {
+byte getZoneFromPositionRaw(byte *page, int16 x, int16 y, int16 width) {
 	// WORKAROUND for bug #2848940 ("ScummVM crashes with Future wars"):
 	// Vertical positions outside the 320x200 screen (e.g. in range 200-232)
 	// are accessed after teleporting Lo'Ann to the future using the pendant
@@ -1989,46 +2025,62 @@ int16 getZoneFromPositionRaw(byte *page, int16 x, int16 y, int16 width) {
 	return zoneVar;
 }
 
-int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
-	debugC(1, kCineDebugCollision, "checkCollision(objIdx: %d x: %d y:%d numZones:%d zoneIdx: %d)", objIdx, x, y, numZones, zoneIdx);
+int16 checkCollisionFW(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
+	int16 lx = g_cine->_objectTable[objIdx].x + x;
+	int16 ly = g_cine->_objectTable[objIdx].y + y;
+
+	for (int16 i = 0; i < numZones; i++, lx++) {
+		int16 idx = getZoneFromPositionRaw(collisionPage, lx, ly, 320);
+
+		if (idx < NUM_MAX_ZONE && g_cine->_zoneData[idx] == zoneIdx) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+int16 checkCollisionOS(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
 	int16 lx = g_cine->_objectTable[objIdx].x + x;
 	int16 ly = g_cine->_objectTable[objIdx].y + y;
-	int16 idx;
 	int16 result = 0;
 
-	for (int16 i = 0; i < numZones; i++) {
-		// Don't try to read data in Operation Stealth if position isn't in 320x200 screen bounds.
-		if (g_cine->getGameType() == Cine::GType_OS) {
-			if ((lx + i) < 0 || (lx + i) > 319 || ly < 0 || ly > 199) {
-				continue;
-			}
+	if (ly < 0 || ly > 199) {
+		return result;
+	}
+
+	for (int16 i = 0; i < numZones; i++, lx++) {
+		if (lx < 0 || lx > 319) {
+			continue;
 		}
 
-		idx = getZoneFromPositionRaw(collisionPage, lx + i, ly, 320);
+		int16 idx = getZoneFromPositionRaw(collisionPage, lx, ly, 320);
 
-		assert(idx >= 0 && idx < NUM_MAX_ZONE);
+		if (idx < NUM_MAX_ZONE) {
+			idx = g_cine->_zoneData[idx];
+		}
 
-		// The zoneQuery table is updated here only in Operation Stealth
-		if (g_cine->getGameType() == Cine::GType_OS) {
-			if (g_cine->_zoneData[idx] < NUM_MAX_ZONE) {
-				g_cine->_zoneQuery[g_cine->_zoneData[idx]]++;
-			}
+		if (idx >= 0 && idx < NUM_MAX_ZONE) {
+			g_cine->_zoneQuery[idx]++;
 		}
 
-		if (g_cine->_zoneData[idx] == zoneIdx) {
+		if (idx == zoneIdx) {
 			result = 1;
-			// Future Wars breaks out early on the first match, but
-			// Operation Stealth doesn't because it needs to update
-			// the zoneQuery table for the whole loop's period.
-			if (g_cine->getGameType() == Cine::GType_FW) {
-				break;
-			}
 		}
 	}
 
 	return result;
 }
 
+int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
+	debugC(1, kCineDebugCollision, "checkCollision(objIdx: %d x: %d y:%d numZones:%d zoneIdx: %d)", objIdx, x, y, numZones, zoneIdx);
+	if (g_cine->getGameType() == Cine::GType_OS) {
+		return checkCollisionOS(objIdx, x, y, numZones, zoneIdx);
+	} else {
+		return checkCollisionFW(objIdx, x, y, numZones, zoneIdx);
+	}
+}
+
 uint16 compareVars(int16 a, int16 b) {
 	uint16 flag = 0;
 
@@ -2589,6 +2641,23 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
 
 			break;
 		}
+
+		case 0x4A: {
+			byte param1, param2, param3;
+
+			param1 = *(localScriptPtr + position);
+			position++;
+
+			param2 = *(localScriptPtr + position);
+			position++;
+
+			param3 = *(localScriptPtr + position);
+			position++;
+
+			sprintf(lineBuffer, "palRotate(%d,%d,%d)\n", param1, param2, param3);
+			break;
+		}
+
 		case 0x4F: {
 			sprintf(lineBuffer, "break()\n");
 			exitScript = 1;
@@ -3066,33 +3135,59 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
 			break;
 		}
 		case 0xA0: { // OS only
-			byte param1;
-			byte param2;
+			uint16 param1;
+			uint16 param2;
 
-			param1 = *(localScriptPtr + position);
-			position++;
+			param1 = READ_BE_UINT16(localScriptPtr + position);
+			position += 2;
 
-			param2 = *(localScriptPtr + position);
-			position++;
+			param2 = READ_BE_UINT16(localScriptPtr + position);
+			position += 2;
 
 			sprintf(lineBuffer, "OP_A0(%d,%d)\n", param1, param2);
 
 			break;
 		}
 		case 0xA1: { // OS only
-			byte param1;
-			byte param2;
+			uint16 param1;
+			uint16 param2;
 
-			param1 = *(localScriptPtr + position);
-			position++;
+			param1 = READ_BE_UINT16(localScriptPtr + position);
+			position += 2;
 
-			param2 = *(localScriptPtr + position);
-			position++;
+			param2 = READ_BE_UINT16(localScriptPtr + position);
+			position += 2;
 
 			sprintf(lineBuffer, "OP_A1(%d,%d)\n", param1, param2);
 
 			break;
 		}
+		case 0xA2: { // OS only
+			uint16 param1;
+			uint16 param2;
+
+			param1 = READ_BE_UINT16(localScriptPtr + position);
+			position += 2;
+
+			param2 = READ_BE_UINT16(localScriptPtr + position);
+			position += 2;
+
+			sprintf(lineBuffer, "OP_A2(%d,%d)\n", param1, param2);
+
+			break;
+		}
+
+		case 0x9A: { // OS only
+			byte param1;
+
+			param1 = *(localScriptPtr + position);
+			position++;
+
+			sprintf(lineBuffer, "o2_wasZoneChecked(%d)\n", param1);
+			
+			break;
+		}
+
 		default: {
 			sprintf(lineBuffer, "Unsupported opcode %X in decompileScript\n\n", opcode - 1);
 			position = scriptSize;
diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp
index 07ed295eab..09516d62c4 100644
--- a/engines/cine/script_os.cpp
+++ b/engines/cine/script_os.cpp
@@ -75,7 +75,7 @@ void OSScript::setupTable() {
 		{ &FWScript::o1_loadMask4, "b" },
 		{ &FWScript::o1_unloadMask4, "b" },
 		{ &FWScript::o1_addSpriteFilledToBgList, "b" },
-		{ &FWScript::o1_op1B, "" }, /* TODO: Name this opcode properly. */
+		{ &FWScript::o1_clearBgIncrustList, "" },
 		/* 1C */
 		{ 0, 0 },
 		{ &FWScript::o1_label, "l" },
@@ -203,7 +203,7 @@ void OSScript::setupTable() {
 		{ &FWScript::o2_addSeqListElement, "bbbbwww" },
 		/* 80 */
 		{ &FWScript::o2_removeSeq, "bb" },
-		{ &FWScript::o2_op81, "" }, /* TODO: Name this opcode properly. */
+		{ &FWScript::o2_clearSeqList, "" },
 		{ &FWScript::o2_modifySeqListElement, "bbwwb" },
 		{ &FWScript::o2_isSeqRunning, "bb" },
 		/* 84 */
@@ -385,6 +385,7 @@ int FWScript::o2_loadCt() {
 
 	debugC(5, kCineDebugScript, "Line: %d: loadCt(\"%s\")", _line, param);
 	loadCtOS(param);
+	removeBgIncrustsWithBgIdx(kCollisionPageBgIdxAlias);
 	return 0;
 }
 
@@ -406,15 +407,42 @@ int FWScript::o2_playSample() {
 		getNextWord();
 		return 0;
 	}
-	return o1_playSample();
+
+	debugC(5, kCineDebugScript, "Line: %d: o2_playSample()", _line);
+
+	byte mode = getNextByte();
+	byte channel = getNextByte();
+
+	int16 param3 = getNextWord();
+	int16 param4 = getNextByte();
+
+	int16 param5 = getNextWord();
+	uint16 size = getNextWord();
+
+	if (mode == 2) {
+		switch (param4) {
+		case 0:
+			param4 = param5;
+			break;
+		case 1:
+			param4 = _localVars[param5];
+			break;
+		case 2:
+			param4 = _globalVars[param5];
+			break;
+		}
+	}
+
+	g_sound->playSound(mode, channel, param3, param4, param5, size);
+	return 0;
 }
 
 int FWScript::o2_playSampleAlt() {
 	byte num = getNextByte();
 	byte channel = getNextByte();
 	uint16 frequency = getNextWord();
-	getNextByte();
-	getNextWord();
+	byte param4 = getNextByte();
+	uint16 param5 = getNextWord();
 	uint16 size = getNextWord();
 
 	if (size == 0xFFFF) {
@@ -451,18 +479,17 @@ int FWScript::o2_removeSeq() {
 	byte a = getNextByte();
 	byte b = getNextByte();
 
-	debugC(5, kCineDebugScript, "Line: %d: removeSeq(%d,%d) -> TODO", _line, a, b);
+	debugC(5, kCineDebugScript, "Line: %d: removeSeq(%d,%d)", _line, a, b);
 	removeSeq(a, 0, b);
 	return 0;
 }
 
 /**
- * @todo Implement this instruction
  * @note According to the scripts' opcode usage comparison this opcode isn't used at all.
  */
-int FWScript::o2_op81() {
-	warning("STUB: o2_op81()");
-	// freeUnkList();
+int FWScript::o2_clearSeqList() {
+	debugC(5, kCineDebugScript, "Line: %d: clearSeqList()", _line);
+	g_cine->_seqList.clear();
 	return 0;
 }
 
@@ -639,7 +666,8 @@ int FWScript::o2_addBackground() {
 	const char *param2 = getNextString();
 
 	debugC(5, kCineDebugScript, "Line: %d: addBackground(%s,%d)", _line, param2, param1);
-	addBackground(param2, param1);
+	renderer->addBackground(param2, param1);
+	removeBgIncrustsWithBgIdx(param1);
 	return 0;
 }
 
@@ -651,6 +679,7 @@ int FWScript::o2_removeBackground() {
 	debugC(5, kCineDebugScript, "Line: %d: removeBackground(%d)", _line, param);
 
 	renderer->removeBg(param);
+	removeBgIncrustsWithBgIdx(param);
 	return 0;
 }
 
@@ -687,11 +716,11 @@ int FWScript::o2_loadAbs() {
 int FWScript::o2_loadBg() {
 	byte param = getNextByte();
 
-	assert(param < 9);
-
 	debugC(5, kCineDebugScript, "Line: %d: useBg(%d)", _line, param);
 
-	renderer->selectBg(param);
+	if (param <= 8) {
+		renderer->selectBg(param);
+	}
 	return 0;
 }
 
@@ -705,6 +734,7 @@ int FWScript::o2_wasZoneChecked() {
 /**
  * @todo Implement this instruction
  * @note According to the scripts' opcode usage comparison this opcode isn't used at all.
+ * @note In Operation Stealth 16 color DOS version this calculates temporary values and discards them.
  */
 int FWScript::o2_op9B() {
 	uint16 a = getNextWord();
@@ -722,6 +752,7 @@ int FWScript::o2_op9B() {
 /**
  * @todo Implement this instruction
  * @note According to the scripts' opcode usage comparison this opcode isn't used at all.
+ * @note In Operation Stealth 16 color DOS version this calculates temporary values and discards them.
  */
 int FWScript::o2_op9C() {
 	uint16 a = getNextWord();
@@ -739,35 +770,64 @@ int FWScript::o2_useBgScroll() {
 
 	debugC(5, kCineDebugScript, "Line: %d: useBgScroll(%d)", _line, param);
 
-	renderer->selectScrollBg(param);
+	if (param <= 8)	{
+		renderer->selectScrollBg(param);
+	}
 	return 0;
 }
 
 int FWScript::o2_setAdditionalBgVScroll() {
+	uint16 mouseX, mouseY;
+	unsigned int scroll = renderer->getScroll();
 	byte param1 = getNextByte();
 
 	if (param1) {
 		byte param2 = getNextByte();
 
-		debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = var[%d]", _line, param2);
-		renderer->setScroll(_localVars[param2]);
+		switch (param1)	{
+		case 1:
+			debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = var[%d]", _line, param2);
+			scroll = _localVars[param2];
+			break;
+		case 2:
+			debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = globalVar[%d]", _line, param2);
+			scroll = _globalVars[param2];
+			break;
+		case 3:
+			debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = mouseX", _line);
+			getMouseData(mouseUpdateStatus, &dummyU16, &mouseX, &mouseY);
+			scroll = mouseX;
+			break;
+		case 4:
+			debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = mouseY", _line);
+			getMouseData(mouseUpdateStatus, &dummyU16, &mouseX, &mouseY);
+			scroll = mouseY;
+			break;
+		case 5:
+			debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = rand() %% %d", _line, param2);
+			scroll = ((param2 == 0) ? 0 : g_cine->_rnd.getRandomNumber(param2 - 1));
+			break;
+		}
 	} else {
 		uint16 param2 = getNextWord();
 
 		debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = %d", _line, param2);
-		renderer->setScroll(param2);
+		scroll = param2;
 	}
+
+	renderer->setScroll(scroll);
 	return 0;
 }
 
 /**
  * @todo Implement this instruction
  * @note According to the scripts' opcode usage comparison this opcode isn't used at all.
+ * @note In Operation Stealth 16 color DOS version this calculates temporary values and discards them.
  */
 int FWScript::o2_op9F() {
 	warning("o2_op9F()");
-	getNextWord();
-	getNextWord();
+	uint16 param1 = getNextWord();
+	uint16 param2 = getNextWord();	
 	return 0;
 }
 
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index cfd6ef8089..0bd590d3ac 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -48,9 +48,11 @@ public:
 
 	virtual ~PCSoundDriver() {}
 
+	virtual MusicType musicType() const = 0;
 	virtual void setupChannel(int channel, const byte *data, int instrument, int volume) = 0;
 	virtual void setChannelFrequency(int channel, int frequency) = 0;
 	virtual void stopChannel(int channel) = 0;
+	virtual void playSample(int mode, int channel, int param3, int param4, int param5, int size) = 0;
 	virtual void playSample(const byte *data, int size, int channel, int volume) = 0;
 	virtual void stopAll() = 0;
 	virtual const char *getInstrumentExtension() const { return ""; }
@@ -66,19 +68,16 @@ protected:
 	static const int _noteTableCount;
 };
 
+// 8 octaves, 12 notes per octave
 const int PCSoundDriver::_noteTable[] = {
-	0xEEE, 0xE17, 0xD4D, 0xC8C, 0xBD9, 0xB2F, 0xA8E, 0x9F7,
-	0x967, 0x8E0, 0x861, 0x7E8, 0x777, 0x70B, 0x6A6, 0x647,
-	0x5EC, 0x597, 0x547, 0x4FB, 0x4B3, 0x470, 0x430, 0x3F4,
-	0x3BB, 0x385, 0x353, 0x323, 0x2F6, 0x2CB, 0x2A3, 0x27D,
-	0x259, 0x238, 0x218, 0x1FA, 0x1DD, 0x1C2, 0x1A9, 0x191,
-	0x17B, 0x165, 0x151, 0x13E, 0x12C, 0x11C, 0x10C, 0x0FD,
-	0x0EE, 0x0E1, 0x0D4, 0x0C8, 0x0BD, 0x0B2, 0x0A8, 0x09F,
-	0x096, 0x08E, 0x086, 0x07E, 0x077, 0x070, 0x06A, 0x064,
-	0x05E, 0x059, 0x054, 0x04F, 0x04B, 0x047, 0x043, 0x03F,
-	0x03B, 0x038, 0x035, 0x032, 0x02F, 0x02C, 0x02A, 0x027,
-	0x025, 0x023, 0x021, 0x01F, 0x01D, 0x01C, 0x01A, 0x019,
-	0x017, 0x016, 0x015, 0x013, 0x012, 0x011, 0x010, 0x00F
+	0xEEE, 0xE17, 0xD4D, 0xC8C, 0xBD9, 0xB2F, 0xA8E, 0x9F7,	0x967, 0x8E0, 0x861, 0x7E8,
+	0x777, 0x70B, 0x6A6, 0x647, 0x5EC, 0x597, 0x547, 0x4FB, 0x4B3, 0x470, 0x430, 0x3F4,
+	0x3BB, 0x385, 0x353, 0x323, 0x2F6, 0x2CB, 0x2A3, 0x27D,	0x259, 0x238, 0x218, 0x1FA,
+	0x1DD, 0x1C2, 0x1A9, 0x191,	0x17B, 0x165, 0x151, 0x13E, 0x12C, 0x11C, 0x10C, 0x0FD,
+	0x0EE, 0x0E1, 0x0D4, 0x0C8, 0x0BD, 0x0B2, 0x0A8, 0x09F,	0x096, 0x08E, 0x086, 0x07E,
+	0x077, 0x070, 0x06A, 0x064,	0x05E, 0x059, 0x054, 0x04F, 0x04B, 0x047, 0x043, 0x03F,
+	0x03B, 0x038, 0x035, 0x032, 0x02F, 0x02C, 0x02A, 0x027,	0x025, 0x023, 0x021, 0x01F,
+	0x01D, 0x01C, 0x01A, 0x019,	0x017, 0x016, 0x015, 0x013, 0x012, 0x011, 0x010, 0x00F
 };
 
 const int PCSoundDriver::_noteTableCount = ARRAYSIZE(_noteTable);
@@ -108,18 +107,23 @@ public:
 	AdLibSoundDriver(Audio::Mixer *mixer);
 	~AdLibSoundDriver() override;
 
+	MusicType musicType() const override { return MT_ADLIB; }
 	// PCSoundDriver interface
 	void setUpdateCallback(UpdateCallback upCb, void *ref) override;
-	void setupChannel(int channel, const byte *data, int instrument, int volume) override;
-	void stopChannel(int channel) override;
+	void setupChannel(int channel, const byte *data, int instrument, int volume) override;	
 	void stopAll() override;
 
 	void initCard();
 	void onTimer();
-	void setupInstrument(const byte *data, int channel);
+	void setupPreloadedInstrument(int channel);
+	void setupInstrument(const byte *data, int channel, bool loadData = true);
 	void loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg);
 	virtual void loadInstrument(const byte *data, AdLibSoundInstrument *asi) = 0;
 
+	enum {
+		MAX_ADLIB_CHANNELS = 8
+	};
+
 protected:
 	UpdateCallback _upCb;
 	void *_upRef;
@@ -128,8 +132,8 @@ protected:
 	Audio::Mixer *_mixer;
 
 	byte _vibrato;
-	int _channelsVolumeTable[4];
-	AdLibSoundInstrument _instrumentsTable[4];
+	int _channelsVolumeTable[MAX_ADLIB_CHANNELS];
+	AdLibSoundInstrument _instrumentsTable[MAX_ADLIB_CHANNELS];
 
 	static const int _freqTable[];
 	static const int _freqTableCount;
@@ -164,18 +168,25 @@ public:
 	AdLibSoundDriverINS(Audio::Mixer *mixer) : AdLibSoundDriver(mixer) {}
 	const char *getInstrumentExtension() const override { return ".INS"; }
 	void loadInstrument(const byte *data, AdLibSoundInstrument *asi) override;
+	void stopChannel(int channel) override;
 	void setChannelFrequency(int channel, int frequency) override;
+	void playSample(int mode, int channel, int param3, int param4, int param5, int size) override;
 	void playSample(const byte *data, int size, int channel, int volume) override;
 };
 
 // Operation Stealth AdLib driver
 class AdLibSoundDriverADL : public AdLibSoundDriver {
 public:
-	AdLibSoundDriverADL(Audio::Mixer *mixer) : AdLibSoundDriver(mixer) {}
+	AdLibSoundDriverADL(Audio::Mixer *mixer);
 	const char *getInstrumentExtension() const override { return ".ADL"; }
 	void loadInstrument(const byte *data, AdLibSoundInstrument *asi) override;
+	void stopChannel(int channel) override;
 	void setChannelFrequency(int channel, int frequency) override;
+	void playSample(int mode, int channel, int param3, int param4, int param5, int size) override;
 	void playSample(const byte *data, int size, int channel, int volume) override;
+
+protected:
+	AdLibSoundInstrument _samples[49];
 };
 
 // (Future Wars) MIDI driver
@@ -184,15 +195,18 @@ public:
 	MidiSoundDriverH32(MidiDriver *output);
 	~MidiSoundDriverH32() override;
 
+	MusicType musicType() const override { return MT_MT32; }
 	void setUpdateCallback(UpdateCallback upCb, void *ref) override;
 	void setupChannel(int channel, const byte *data, int instrument, int volume) override;
 	void setChannelFrequency(int channel, int frequency) override;
 	void stopChannel(int channel) override;
+	void playSample(int mode, int channel, int param3, int param4, int param5, int size) override;
 	void playSample(const byte *data, int size, int channel, int volume) override;
 	void stopAll() override {}
 	const char *getInstrumentExtension() const override { return ".H32"; }
 	void notifyInstrumentLoad(const byte *data, int size, int channel) override;
 
+
 private:
 	MidiDriver *_output;
 	UpdateCallback _callback;
@@ -200,6 +214,10 @@ private:
 
 	void writeInstrument(int offset, const byte *data, int size);
 	void selectInstrument(int channel, int timbreGroup, int timbreNumber, int volume);
+	void selectInstrument2(int channel, int timbreGroup, int timbreNumber);
+	void selectInstrument3(int channel, int offsetMode, int timbreGroup);
+	void selectInstrument4(int offsetMode, int timbreGroup, int timbreNumber, int keyShift);
+	void selectInstrument5(int messageNum);
 };
 
 class PCSoundFxPlayer {
@@ -284,7 +302,8 @@ AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer)
 	memset(_channelsVolumeTable, 0, sizeof(_channelsVolumeTable));
 	memset(_instrumentsTable, 0, sizeof(_instrumentsTable));
 	initCard();
-	_opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 50);
+	// 1000/(10923000 ms / 1193180) ~= 109 Hz
+	_opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 109);
 }
 
 AdLibSoundDriver::~AdLibSoundDriver() {
@@ -297,7 +316,7 @@ void AdLibSoundDriver::setUpdateCallback(UpdateCallback upCb, void *ref) {
 }
 
 void AdLibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) {
-	assert(channel < 4);
+	assert(channel < MAX_ADLIB_CHANNELS);
 	if (data) {
 		volume = CLIP(volume, 0, 80);
 		volume += volume / 4;
@@ -307,8 +326,8 @@ void AdLibSoundDriver::setupChannel(int channel, const byte *data, int instrumen
 	}
 }
 
-void AdLibSoundDriver::stopChannel(int channel) {
-	assert(channel < 4);
+void AdLibSoundDriverINS::stopChannel(int channel) {
+	assert(channel < MAX_ADLIB_CHANNELS);
 	AdLibSoundInstrument *ins = &_instrumentsTable[channel];
 	if (ins->mode != 0 && ins->channel == 6) {
 		channel = 6;
@@ -322,6 +341,18 @@ void AdLibSoundDriver::stopChannel(int channel) {
 	}
 }
 
+void AdLibSoundDriverADL::stopChannel(int channel) {
+	assert(channel < MAX_ADLIB_CHANNELS);
+	AdLibSoundInstrument *ins = &_instrumentsTable[channel];
+	if (ins->mode == 0 || ins->channel == 6) {
+		_opl->writeReg(0xB0 | channel, 0);
+	}
+	if (ins->mode != 0) {
+		_vibrato &= ~(1 << (10 - ins->channel));
+		_opl->writeReg(0xBD, _vibrato);
+	}
+}
+
 void AdLibSoundDriver::stopAll() {
 	int i;
 	for (i = 0; i < 18; ++i) {
@@ -330,7 +361,12 @@ void AdLibSoundDriver::stopAll() {
 	for (i = 0; i < 9; ++i) {
 		_opl->writeReg(0xB0 | i, 0);
 	}
-	_opl->writeReg(0xBD, 0);
+	if (g_cine->getGameType() == Cine::GType_OS) {
+		_vibrato &= (1 << 5);
+		_opl->writeReg(0xBD, _vibrato);
+	} else {
+		_opl->writeReg(0xBD, 0);
+	}
 }
 
 void AdLibSoundDriver::initCard() {
@@ -363,10 +399,16 @@ void AdLibSoundDriver::onTimer() {
 	}
 }
 
-void AdLibSoundDriver::setupInstrument(const byte *data, int channel) {
-	assert(channel < 4);
+void AdLibSoundDriver::setupPreloadedInstrument(int channel) {
+	setupInstrument(NULL, channel, false);
+}
+
+void AdLibSoundDriver::setupInstrument(const byte *data, int channel, bool loadData) {
+	assert(channel < MAX_ADLIB_CHANNELS);
 	AdLibSoundInstrument *ins = &_instrumentsTable[channel];
-	loadInstrument(data, ins);
+	if (loadData && data) {
+		loadInstrument(data, ins);
+	}
 
 	int mod, car, tmp;
 	const AdLibRegisterSoundInstrument *reg;
@@ -452,7 +494,7 @@ void AdLibSoundDriverINS::loadInstrument(const byte *data, AdLibSoundInstrument
 }
 
 void AdLibSoundDriverINS::setChannelFrequency(int channel, int frequency) {
-	assert(channel < 4);
+	assert(channel < MAX_ADLIB_CHANNELS);
 	AdLibSoundInstrument *ins = &_instrumentsTable[channel];
 	if (ins->mode != 0 && ins->channel == 6) {
 		channel = 6;
@@ -476,8 +518,11 @@ void AdLibSoundDriverINS::setChannelFrequency(int channel, int frequency) {
 	}
 }
 
+void AdLibSoundDriverINS::playSample(int mode, int channel, int param3, int param4, int param5, int size) {
+}
+
 void AdLibSoundDriverINS::playSample(const byte *data, int size, int channel, int volume) {
-	assert(channel < 4);
+	assert(channel < MAX_ADLIB_CHANNELS);
 	_channelsVolumeTable[channel] = 127;
 	resetChannel(channel);
 	setupInstrument(data + 257, channel);
@@ -501,6 +546,12 @@ void AdLibSoundDriverINS::playSample(const byte *data, int size, int channel, in
 	}
 }
 
+AdLibSoundDriverADL::AdLibSoundDriverADL(Audio::Mixer *mixer)
+	: AdLibSoundDriver(mixer), _samples() {
+
+	memset(_samples, 0, sizeof(_samples));
+}
+
 void AdLibSoundDriverADL::loadInstrument(const byte *data, AdLibSoundInstrument *asi) {
 	asi->mode = *data++;
 	asi->channel = *data++;
@@ -513,8 +564,9 @@ void AdLibSoundDriverADL::loadInstrument(const byte *data, AdLibSoundInstrument
 }
 
 void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) {
-	assert(channel < 4);
+	assert(channel < MAX_ADLIB_CHANNELS);
 	AdLibSoundInstrument *ins = &_instrumentsTable[channel];
+	setupPreloadedInstrument(channel);
 	if (ins->mode != 0) {
 		channel = ins->channel;
 		if (channel == 9) {
@@ -547,8 +599,30 @@ void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) {
 	}
 }
 
+void AdLibSoundDriverADL::playSample(int mode, int channel, int param3, int param4, int param5, int size) {
+	switch (mode) {
+	case 0:
+		_instrumentsTable[(channel & 1) + 4] = _samples[param3];
+		_channelsVolumeTable[(channel & 1) + 4] = 0x7F;
+		stopChannel((channel & 1) + 4);
+		if (param5 >= 0x0C && param5 <= 0x6C) {
+			setChannelFrequency((channel & 1) + 4, param5);
+		}
+		break;
+	case 1:
+		if (channel <= 0x30) {
+			AnimData& animData = g_cine->_animDataTable[param3];
+			const byte *data = animData.data();
+			if (data && animData.size() >= 58) {
+				loadInstrument(data, &_samples[channel]);
+			}
+		}
+		break;
+	}
+}
+
 void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, int volume) {
-	assert(channel < 4);
+	assert(channel < MAX_ADLIB_CHANNELS);
 	_channelsVolumeTable[channel] = 127;
 	setupInstrument(data, channel);
 	AdLibSoundInstrument *ins = &_instrumentsTable[channel];
@@ -606,8 +680,8 @@ void MidiSoundDriverH32::setUpdateCallback(UpdateCallback upCb, void *ref) {
 		timer->removeTimerProc(_callback);
 
 	_callback = upCb;
-	if (_callback)
-		timer->installTimerProc(_callback, 1000000 / 50, ref, "MidiSoundDriverH32");
+	if (_callback) // 10923000 ms / 1193180 ~= 9155 microseconds
+		timer->installTimerProc(_callback, 9155, ref, "MidiSoundDriverH32");
 }
 
 void MidiSoundDriverH32::setupChannel(int channel, const byte *data, int instrument, int volume) {
@@ -644,6 +718,46 @@ void MidiSoundDriverH32::stopChannel(int channel) {
 	_output->send(0xB1 + channel, 0x7B, 0x00);
 }
 
+void MidiSoundDriverH32::playSample(int mode, int channel, int param3, int param4, int param5, int size) {
+	Common::StackLock lock(_mutex);
+
+	switch (mode) {
+	case 0: // play instrument
+		if (param5 >= 0x0C && param5 <= 0x6C) {
+			selectInstrument2(channel + 4, 2, param3 + 0xF);
+			selectInstrument3(channel + 4, 1, param4);
+		}
+
+		stopChannel(channel + 4);
+
+		if (param5 >= 0x0C && param5 <= 0x6C) {
+			_output->send(0x91 + channel + 4, param5, 0x7F);
+		}
+		break;
+	case 1: // load instrument
+		if (channel <= 0x30) {
+			AnimData& animData = g_cine->_animDataTable[param3];
+			const byte *data = animData.data();
+			if (data && data[0] >= 0x80 && data[0] < 0xC0) {
+				writeInstrument((channel + 15) * 512 + 0x80000, data + 1, animData.size() - 1);
+			}
+		}
+		break;
+	case 2:
+		selectInstrument3(channel + 4, param3, param4);
+		break;
+	case 3:
+		selectInstrument4(channel, param3, param4, param5);
+		break;
+	case 4: // show text in Roland MT-32 LCD display
+		// Don't display text in Roland MT-32 LCD display when loading a savegame
+		if (!runOnlyUntilCopyProtectionCheck) {
+			selectInstrument5(channel);
+		}
+		break;
+	}
+}
+
 void MidiSoundDriverH32::playSample(const byte *data, int size, int channel, int volume) {
 	Common::StackLock lock(_mutex);
 
@@ -654,7 +768,7 @@ void MidiSoundDriverH32::playSample(const byte *data, int size, int channel, int
 	if (data[0] < 0x80) {
 		selectInstrument(channel, data[0] / 0x40, data[0] % 0x40, volume);
 	} else {
-		writeInstrument(channel * 512 + 0x80000, data + 1, 256);
+		writeInstrument(channel * 512 + 0x80000, data + 1, size - 1);
 		selectInstrument(channel, 2, channel, volume);
 	}
 
@@ -667,7 +781,7 @@ void MidiSoundDriverH32::notifyInstrumentLoad(const byte *data, int size, int ch
 	// In case we specify a standard instrument or standard rhythm instrument
 	// do not do anything here. It might be noteworthy that the instrument
 	// selection client code does not support rhythm instruments!
-	if (data[0] < 0x80 || data[0] > 0xC0)
+	if (data[0] < 0x80 || data[0] >= 0xC0)
 		return;
 
 	writeInstrument(channel * 512 + 0x80000, data + 1, size - 1);
@@ -694,6 +808,151 @@ void MidiSoundDriverH32::writeInstrument(int offset, const byte *data, int size)
 	_output->sysEx(sysEx, copySize + 8);
 }
 
+void MidiSoundDriverH32::selectInstrument2(int channel, int timbreGroup, int timbreNumber) {
+	const int offset = channel * 16 + 0x30000; // 0x30000 is the start of the patch temp area
+
+	byte sysEx[14] = {
+		0x41, 0x10, 0x16, 0x12,
+		0x00, 0x00, 0x00,       // offset
+		0x00, // Timbre group   _ timbreGroup * 64 + timbreNumber should be the
+		0x00, // Timbre number /  MT-32 instrument in case timbreGroup is 0 or 1.
+		0x18, // Key shift (= 0)
+		0x32, // Fine tune (= 0)
+		0x0C, // Bender Range
+		0x03, // Assign Mode
+		0x00 // Checksum
+	};
+
+
+	sysEx[4] = (offset >> 16) & 0xFF;
+	sysEx[5] = (offset >> 8) & 0xFF;
+	sysEx[6] = (offset >> 0) & 0xFF;
+
+	sysEx[7] = timbreGroup;
+	sysEx[8] = timbreNumber;
+
+	byte checkSum = 0;
+
+	for (int i = 4; i < sizeof(sysEx) - 1; ++i)
+		checkSum += sysEx[i];
+
+	sysEx[sizeof(sysEx) - 1] = 0x80 - (checkSum & 0x7F);
+
+	_output->sysEx(sysEx, sizeof(sysEx));
+}
+
+void MidiSoundDriverH32::selectInstrument3(int channel, int offsetMode, int timbreGroup) {
+	int offset = channel * 16 + 0x30000; // 0x30000 is the start of the patch temp area
+
+	switch (offsetMode) {
+	case 1:
+		offset += 8;
+		break;
+	case 2:
+		offset += 6;
+		break;
+	case 3:
+		offset += 9;
+		break;
+	}
+
+	byte sysEx[9] = {
+		0x41, 0x10, 0x16, 0x12,
+		0x00, 0x00, 0x00,       // offset
+		0x00, // Timbre group
+		0x00 // Checksum
+	};
+
+
+	sysEx[4] = (offset >> 16) & 0xFF;
+	sysEx[5] = (offset >> 8) & 0xFF;
+	sysEx[6] = (offset >> 0) & 0xFF;
+
+	sysEx[7] = timbreGroup;
+
+	byte checkSum = 0;
+
+	for (int i = 4; i < sizeof(sysEx) - 1; ++i)
+		checkSum += sysEx[i];
+
+	sysEx[sizeof(sysEx) - 1] = 0x80 - (checkSum & 0x7F);
+
+	_output->sysEx(sysEx, sizeof(sysEx));
+}
+
+void MidiSoundDriverH32::selectInstrument4(int offsetMode, int timbreGroup, int timbreNumber, int keyShift) {
+	int offset = 0x100000;
+	const int len = (offsetMode == 2) ? 11 : 9;
+
+	switch (offsetMode) {
+	case 1:
+		offset += 0x16;
+		break;
+	case 2:
+		offset += 1;
+		break;
+	}
+
+	byte sysEx[11] = {
+		0x41, 0x10, 0x16, 0x12,
+		0x00, 0x00, 0x00,       // offset
+		0x00, // Timbre group
+		0x00, // Timbre number /  MT-32 instrument in case timbreGroup is 0 or 1.
+		0x18, // Key shift (= 0)
+		0x00 // Checksum
+	};
+
+
+	sysEx[4] = (offset >> 16) & 0xFF;
+	sysEx[5] = (offset >> 8) & 0xFF;
+	sysEx[6] = (offset >> 0) & 0xFF;
+
+	sysEx[7] = timbreGroup;
+
+	if (offsetMode == 2) {
+		sysEx[8] = timbreNumber;
+		sysEx[9] = keyShift;
+	}
+
+	byte checkSum = 0;
+
+	for (int i = 4; i < len - 1; ++i)
+		checkSum += sysEx[i];
+
+	sysEx[len - 1] = 0x80 - (checkSum & 0x7F);
+
+	_output->sysEx(sysEx, len);
+}
+
+void MidiSoundDriverH32::selectInstrument5(int messageNum) {
+	int offset = 0x200000;
+
+	byte sysEx[28] = {
+		0x41, 0x10, 0x16, 0x12,
+		0x00, 0x00, 0x00       // offset
+	};
+
+	memset(sysEx + 7, 0x20, 20);
+
+	if (messageNum >= 0 && messageNum < g_cine->_messageTable.size()) {
+		Common::String msg = g_cine->_messageTable[messageNum];
+		memcpy(sysEx + 7, msg.c_str(), MIN<int>(20, msg.size()));
+	}
+
+	sysEx[4] = (offset >> 16) & 0xFF;
+	sysEx[5] = (offset >> 8) & 0xFF;
+	sysEx[6] = (offset >> 0) & 0xFF;
+
+	byte checkSum = 0;
+
+	for (int i = 4; i < sizeof(sysEx) - 1; ++i)
+		checkSum += sysEx[i];
+
+	sysEx[sizeof(sysEx) - 1] = 0x80 - (checkSum & 0x7F);
+
+	_output->sysEx(sysEx, sizeof(sysEx));
+}
+
 void MidiSoundDriverH32::selectInstrument(int channel, int timbreGroup, int timbreNumber, int volume) {
 	const int offset = channel * 16 + 0x30000; // 0x30000 is the start of the patch temp area
 
@@ -775,7 +1034,6 @@ bool PCSoundFxPlayer::load(const char *song) {
 
 	for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
 		_instrumentsData[i] = NULL;
-
 		char instrument[64];
 		memset(instrument, 0, 64); // Clear the data first
 		memcpy(instrument, _sfxData + 20 + i * 30, 12);
@@ -788,7 +1046,16 @@ bool PCSoundFxPlayer::load(const char *song) {
 			}
 			Common::strlcat(instrument, _driver->getInstrumentExtension(), sizeof(instrument));
 			uint32 instrumentSize;
-			_instrumentsData[i] = readBundleSoundFile(instrument, &instrumentSize);
+			byte *data = readBundleSoundFile(instrument, &instrumentSize);
+			if (g_cine->getGameType() == Cine::GType_OS && _driver->musicType() == MT_MT32 &&
+				data && instrumentSize > 0x16) {
+				instrumentSize -= 0x16;
+				byte *tmp = (byte *)calloc(instrumentSize, 1);
+				memcpy(tmp, data + 0x16, instrumentSize);
+				free(data);
+				data = tmp;
+			}
+			_instrumentsData[i] = data;
 			if (!_instrumentsData[i]) {
 				warning("Unable to load soundfx instrument '%s'", instrument);
 			} else {
@@ -809,7 +1076,12 @@ void PCSoundFxPlayer::play() {
 		_currentPos = 0;
 		_currentOrder = 0;
 		_numOrders = _sfxData[470];
-		_eventsDelay = (252 - _sfxData[471]) * 50 / 1060;
+		int timerIntsPerMusicUpdate = (g_cine->getGameType() == Cine::GType_OS) ? 1 : 2;
+		if (g_cine->getGameType() == Cine::GType_OS) {
+			_eventsDelay = (244 - _sfxData[471]) * 109 * timerIntsPerMusicUpdate / 1060;
+		} else {
+			_eventsDelay = (252 - _sfxData[471]) * 55 * timerIntsPerMusicUpdate / 1060;
+		}
 		_updateTicksCounter = 0;
 		_playing = true;
 	}
@@ -820,7 +1092,8 @@ void PCSoundFxPlayer::stop() {
 	if (_playing || _fadeOutCounter != 0) {
 		_fadeOutCounter = 0;
 		_playing = false;
-		for (int i = 0; i < NUM_CHANNELS; ++i) {
+		int numChannels = (g_cine->getGameType() == Cine::GType_OS) ? 8 : 4;
+		for (int i = 0; i < numChannels; ++i) {
 			_driver->stopChannel(i);
 		}
 		_driver->stopAll();
@@ -852,7 +1125,7 @@ void PCSoundFxPlayer::update() {
 }
 
 void PCSoundFxPlayer::handleEvents() {
-	const byte *patternData = _sfxData + 600;
+	const byte *patternData = _sfxData + ((g_cine->getGameType() == Cine::GType_OS) ? 2400 : 600);
 	const byte *orderTable = _sfxData + 472;
 	uint16 patternNum = orderTable[_currentOrder] * 1024;
 
@@ -901,6 +1174,9 @@ void PCSoundFxPlayer::unload() {
 	_sfxData = NULL;
 }
 
+MusicType Sound::musicType() {
+	return _musicType;
+}
 
 PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
 	: Sound(mixer, vm), _soundDriver(0) {
@@ -908,6 +1184,7 @@ PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
 	_currentMusic = 0;
 	_currentMusicStatus = 0;
 	_currentBgSlot = 0;
+	_musicType = MT_INVALID;
 
 	const MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB);
 	const MusicType musicType = MidiDriver::getMusicType(dev);
@@ -918,6 +1195,7 @@ PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
 			if (driver && driver->open() == 0) {
 				driver->sendMT32Reset();
 				_soundDriver = new MidiSoundDriverH32(driver);
+				_musicType = MT_MT32;
 			} else {
 				warning("Could not create MIDI output, falling back to AdLib");
 			}
@@ -932,6 +1210,7 @@ PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
 		} else {
 			_soundDriver = new AdLibSoundDriverADL(_mixer);
 		}
+		_musicType = MT_ADLIB;
 	}
 
 	_player = new PCSoundFxPlayer(_soundDriver);
@@ -966,6 +1245,7 @@ static uint8 musicCDTracks[11] = {
 
 void PCSound::loadMusic(const char *name) {
 	debugC(5, kCineDebugSound, "PCSound::loadMusic('%s')", name);
+
 	if (_vm->getGameType() == GType_FW && (_vm->getFeatures() & GF_CD)) {
 		_currentMusic = 0;
 		_currentMusicStatus = 0;
@@ -1041,6 +1321,11 @@ void PCSound::fadeOutMusic() {
 	_player->fadeOut();
 }
 
+void PCSound::playSound(int mode, int channel, int param3, int param4, int param5, int size) {
+	debugC(5, kCineDebugSound, "PCSound::playSound() channel %d size %d, new", channel, size);
+	_soundDriver->playSample(mode, channel, param3, param4, param5, size);
+}
+
 void PCSound::playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) {
 	debugC(5, kCineDebugSound, "PCSound::playSound() channel %d size %d", channel, size);
 	_soundDriver->playSample(data, size, channel, volume);
@@ -1053,6 +1338,7 @@ void PCSound::stopSound(int channel) {
 
 PaulaSound::PaulaSound(Audio::Mixer *mixer, CineEngine *vm)
 	: Sound(mixer, vm), _sfxTimer(0), _musicTimer(0), _musicFadeTimer(0) {
+	_musicType = MT_AMIGA;
 	_moduleStream = 0;
 	// The original is using the following timer frequency:
 	// 0.709379Mhz / 8000 = 88.672375Hz
@@ -1147,6 +1433,10 @@ void PaulaSound::fadeOutMusic() {
 	_musicFadeTimer = 1;
 }
 
+void PaulaSound::playSound(int mode, int channel, int param3, int param4, int param5, int size) {
+	// Newer playSound from Operation Stealth might not do anything on Amiga
+}
+
 void PaulaSound::playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) {
 	debugC(5, kCineDebugSound, "PaulaSound::playSound() channel %d size %d", channel, size);
 	Common::StackLock lock(_sfxMutex);
diff --git a/engines/cine/sound.h b/engines/cine/sound.h
index 928671488b..0a6b8efefe 100644
--- a/engines/cine/sound.h
+++ b/engines/cine/sound.h
@@ -26,6 +26,7 @@
 #include "common/util.h"
 #include "common/mutex.h"
 #include "audio/mixer.h"
+#include "audio/mididrv.h"
 
 namespace Audio {
 class AudioStream;
@@ -38,14 +39,16 @@ class CineEngine;
 class Sound {
 public:
 
-	Sound(Audio::Mixer *mixer, CineEngine *vm) : _mixer(mixer), _vm(vm) {}
+	Sound(Audio::Mixer *mixer, CineEngine *vm) : _mixer(mixer), _vm(vm), _musicType(MT_INVALID) {}
 	virtual ~Sound() {}
 
+	virtual MusicType musicType();
 	virtual void loadMusic(const char *name) = 0;
 	virtual void playMusic() = 0;
 	virtual void stopMusic() = 0;
 	virtual void fadeOutMusic() = 0;
 
+	virtual void playSound(int mode, int channel, int param3, int param4, int param5, int size) = 0;
 	virtual void playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) = 0;
 	virtual void stopSound(int channel) = 0;
 	virtual void setBgMusic(int num) = 0;
@@ -54,6 +57,7 @@ protected:
 
 	Audio::Mixer *_mixer;
 	CineEngine *_vm;
+	MusicType _musicType;
 };
 
 class PCSoundDriver;
@@ -70,6 +74,7 @@ public:
 	void stopMusic() override;
 	void fadeOutMusic() override;
 
+	void playSound(int mode, int channel, int param3, int param4, int param5, int size) override;
 	void playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) override;
 	void stopSound(int channel) override;
 	void setBgMusic(int num) override;
@@ -93,6 +98,7 @@ public:
 	void stopMusic() override;
 	void fadeOutMusic() override;
 
+	void playSound(int mode, int channel, int param3, int param4, int param5, int size) override;
 	void playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) override;
 	void stopSound(int channel) override;
 	void setBgMusic(int num) override;
diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp
index 8d904cb1f2..2df51ddb08 100644
--- a/engines/cine/texte.cpp
+++ b/engines/cine/texte.cpp
@@ -28,6 +28,7 @@
 
 namespace Cine {
 
+bool allocatedFailureMessages = false;
 const char *const *failureMessages;
 const CommandeType *defaultActionCommand;
 const CommandeType *systemMenu;
@@ -539,7 +540,7 @@ void initLanguage(Common::Language lang) {
 
 	switch (lang) {
 	case Common::FR_FRA:
-		failureMessages = failureMessages_FR;
+		setFailureMessages(failureMessages_FR, false);
 		defaultActionCommand = defaultActionCommand_FR;
 		systemMenu = systemMenu_FR;
 		confirmMenu = confirmMenu_FR;
@@ -549,7 +550,7 @@ void initLanguage(Common::Language lang) {
 		break;
 
 	case Common::ES_ESP:
-		failureMessages = failureMessages_ES;
+		setFailureMessages(failureMessages_ES, false);
 		defaultActionCommand = defaultActionCommand_ES;
 		systemMenu = systemMenu_ES;
 		confirmMenu = confirmMenu_ES;
@@ -559,7 +560,7 @@ void initLanguage(Common::Language lang) {
 		break;
 
 	case Common::DE_DEU:
-		failureMessages = failureMessages_DE;
+		setFailureMessages(failureMessages_DE, false);
 		defaultActionCommand = defaultActionCommand_DE;
 		systemMenu = systemMenu_DE;
 		confirmMenu = confirmMenu_DE;
@@ -569,7 +570,7 @@ void initLanguage(Common::Language lang) {
 		break;
 
 	case Common::IT_ITA:
-		failureMessages = failureMessages_IT;
+		setFailureMessages(failureMessages_IT, false);
 		defaultActionCommand = defaultActionCommand_IT;
 		systemMenu = systemMenu_IT;
 		confirmMenu = confirmMenu_IT;
@@ -579,7 +580,7 @@ void initLanguage(Common::Language lang) {
 		break;
 
 	default:
-		failureMessages = failureMessages_EN;
+		setFailureMessages(failureMessages_EN, false);
 		defaultActionCommand = defaultActionCommand_EN;
 		systemMenu = systemMenu_EN;
 		confirmMenu = confirmMenu_EN;
@@ -604,15 +605,17 @@ void loadErrmessDat(const char *fname) {
 	in.open(fname);
 
 	if (in.isOpen()) {
-		// FIXME - This can leak in some situations in Operation Stealth
-		//         Engine Restart - multiple allocations with no free?
+		if (allocatedFailureMessages) {
+			freeErrmessDat();
+		}
+
 		char **ptr = (char **)malloc(sizeof(char *) * 6 * 4 + 60 * 6 * 4);
 
 		for (int i = 0; i < 6 * 4; i++) {
 			ptr[i] = (char *)ptr + (sizeof(char *) * 6 * 4) + 60 * i;
 			in.read(ptr[i], 60);
 		}
-		failureMessages = const_cast<const char *const *>(ptr);
+		setFailureMessages(const_cast<const char *const *>(ptr), true);
 
 		in.close();
 	} else {
@@ -620,9 +623,20 @@ void loadErrmessDat(const char *fname) {
 	}
 }
 
+void setFailureMessages(const char *const *messages, bool allocated) {
+	if (allocatedFailureMessages) {
+		freeErrmessDat();
+	}
+	failureMessages = messages;
+	allocatedFailureMessages = allocated;
+}
+
 void freeErrmessDat() {
-	free(const_cast<const char **>(failureMessages));
+	if (allocatedFailureMessages) {
+		free(const_cast<const char **>(failureMessages));
+	}
 	failureMessages = 0;
+	allocatedFailureMessages = false;
 }
 
 void loadPoldatDat(const char *fname) {
diff --git a/engines/cine/texte.h b/engines/cine/texte.h
index 1e0944c312..e073ab9dd3 100644
--- a/engines/cine/texte.h
+++ b/engines/cine/texte.h
@@ -60,6 +60,7 @@ extern const char *const *commandPrepositionTable;
 
 void loadTextData(const char *filename);
 void loadErrmessDat(const char *fname);
+void setFailureMessages(const char *const *messages, bool allocated);
 void freeErrmessDat();
 void loadPoldatDat(const char *fname);
 
diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp
index b571000962..3ac82d4e22 100644
--- a/engines/cine/various.cpp
+++ b/engines/cine/various.cpp
@@ -42,6 +42,7 @@ namespace Cine {
 
 int16 disableSystemMenu = 0;
 bool inMenu;
+bool runOnlyUntilCopyProtectionCheck = false;
 
 int16 commandVar3[4];
 int16 commandVar1;
@@ -52,18 +53,17 @@ int16 commandVar2;
 uint16 var2;
 uint16 var3;
 uint16 var4;
-uint16 var5;
+uint16 lastType20OverlayBgIdx; // Was var5
+uint16 reloadBgPalOnNextFlip;
+uint16 forbidBgPalReload;
+uint16 gfxFadeOutCompleted;
+uint16 gfxFadeInRequested;
 
 int16 buildObjectListCommand(int16 param);
 int16 canUseOnObject = 0;
 
 void waitPlayerInput() {
-	uint16 button;
-
-	do {
-		manageEvents();
-		getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
-	} while (!button && !g_cine->shouldQuit());
+	manageEvents(WAIT_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_DOWN_UP);
 }
 
 void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4) {
@@ -72,7 +72,6 @@ void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4) {
 uint16 errorVar;
 byte menuVar;
 
-bool fadeRequired;
 uint16 allowPlayerInput;
 uint16 checkForPendingDataLoadSwitch;
 uint16 isDrawCommandEnabled;
@@ -128,7 +127,7 @@ uint16 yMoveKeyb = kKeybMoveCenterY;
 
 SelectedObjStruct currentSelectedObject;
 
-CommandeType currentSaveName[10];
+CommandeType currentSaveName[kMaxSavegames];
 
 static const int16 choiceResultTable[] = { 1, 1, 1, 2, 1, 1, 1 };
 static const int16 subObjectUseTable[] = { 3, 3, 3, 3, 3, 0, 0 };
@@ -304,7 +303,8 @@ void CineEngine::resetEngine() {
 
 	g_cine->_globalVars.reset();
 
-	var2 = var3 = var4 = var5 = 0;
+	bgVar0 = 0;
+	var2 = var3 = var4 = lastType20OverlayBgIdx = 0;
 
 	strcpy(newPrcName, "");
 	strcpy(newRelName, "");
@@ -322,21 +322,16 @@ void CineEngine::resetEngine() {
 	g_cine->_globalVars[VAR_MOUSE_X_POS] = 0;
 	g_cine->_globalVars[VAR_MOUSE_Y_POS] = 0;
 
-	fadeRequired = false;
-
 	renderer->clear();
+	currentDisk = 1;
 
 	checkForPendingDataLoadSwitch = 0;
 
-	if (g_cine->getGameType() == Cine::GType_OS) {
-		g_cine->_seqList.clear();
-		currentAdditionalBgIdx = 0;
-		currentAdditionalBgIdx2 = 0;
-		// TODO: Add resetting of the following variables
-		// adBgVar1 = 0;
-		// adBgVar0 = 0;
-		// gfxFadeOutCompleted = 0;
-	}
+	g_cine->_seqList.clear();
+	reloadBgPalOnNextFlip = 0;
+	forbidBgPalReload = 0;
+	gfxFadeOutCompleted = 0;
+	gfxFadeInRequested = 0;
 }
 
 int CineEngine::scummVMSaveLoadDialog(bool isSave) {
@@ -365,7 +360,7 @@ int CineEngine::scummVMSaveLoadDialog(bool isSave) {
 	if (slot < 0)
 		return true;
 
-	Common::String saveFileName(Common::String::format("%s.%1d", _targetName.c_str(), slot));
+	Common::String saveFileName(getSaveStateName(slot));
 
 	if (isSave) {
 		Common::String tmp = Common::String::format("%s.dir", _targetName.c_str());
@@ -376,12 +371,13 @@ int CineEngine::scummVMSaveLoadDialog(bool isSave) {
 			return false;
 		}
 
-		Common::strlcpy(currentSaveName[slot], desc.c_str(), 20);
+		Common::strlcpy(currentSaveName[slot], desc.c_str(), sizeof(CommandeType));
 
-		fHandle->write(currentSaveName, 200);
+		fHandle->write(currentSaveName, sizeof(currentSaveName));
 		delete fHandle;
 
-		makeSave(saveFileName);
+		makeSave(saveFileName, getTotalPlayTime() / 1000, desc, false);
+
 		return true;
 	} else {
 		return makeLoad(saveFileName);
@@ -396,10 +392,8 @@ void CineEngine::makeSystemMenu() {
 	if (disableSystemMenu != 1) {
 		inMenu = true;
 
-		do {
-			manageEvents();
-			getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
-		} while (mouseButton);
+		manageEvents(MAKE_SYSTEM_MENU, UNTIL_MOUSE_BUTTON_UP);
+		getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
 
 		numEntry = 6;
 
@@ -407,11 +401,14 @@ void CineEngine::makeSystemMenu() {
 			numEntry--;
 		}
 
+		renderer->saveBackBuffer(BEFORE_OPENING_MENU);
+
 		systemCommand = makeMenuChoice(systemMenu, numEntry, mouseX, mouseY, 140);
 
 		switch (systemCommand) {
 		case 0: { // Pause
 			renderer->drawString(otherMessages[2], 0);
+			PauseToken pauseToken = g_cine->pauseEngine();
 			waitPlayerInput();
 			break;
 		}
@@ -441,12 +438,9 @@ void CineEngine::makeSystemMenu() {
 				}
 
 				getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
-				selectedSave = makeMenuChoice(currentSaveName, 10, mouseX, mouseY + 8, 180);
+				selectedSave = makeMenuChoice(currentSaveName, kMaxSavegames, mouseX, mouseY + 8, 180);
 
 				if (selectedSave >= 0) {
-					char saveNameBuffer[256];
-					sprintf(saveNameBuffer, "%s.%1d", _targetName.c_str(), selectedSave);
-
 					getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
 					if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
 						char loadString[256];
@@ -454,7 +448,7 @@ void CineEngine::makeSystemMenu() {
 						sprintf(loadString, otherMessages[3], currentSaveName[selectedSave]);
 						renderer->drawString(loadString, 0);
 
-						makeLoad(saveNameBuffer);
+						loadGameState(selectedSave);
 					} else {
 						renderer->drawString(otherMessages[4], 0);
 						waitPlayerInput();
@@ -481,19 +475,18 @@ void CineEngine::makeSystemMenu() {
 				return;
 			}
 
-			selectedSave = makeMenuChoice(currentSaveName, 10, mouseX, mouseY + 8, 180);
+			selectedSave = makeMenuChoice(currentSaveName, kMaxSavegames, mouseX, mouseY + 8, 180, g_cine->getAutosaveSlot() + 1);
 
 			if (selectedSave >= 0) {
-				char saveFileName[256];
-				char saveName[20];
+				CommandeType saveName;
 				saveName[0] = 0;
 
-				if (!makeTextEntryMenu(otherMessages[6], saveName, 20, 120))
+				if (!makeTextEntryMenu(otherMessages[6], saveName, sizeof(CommandeType), 120))
 					break;
 
-				Common::strlcpy(currentSaveName[selectedSave], saveName, 20);
+				Common::strlcpy(currentSaveName[selectedSave], saveName, sizeof(CommandeType));
 
-				sprintf(saveFileName, "%s.%1d", _targetName.c_str(), selectedSave);
+				Common::String saveFileName = getSaveStateName(selectedSave);
 
 				getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
 				if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
@@ -506,13 +499,13 @@ void CineEngine::makeSystemMenu() {
 						break;
 					}
 
-					fHandle->write(currentSaveName, 200);
+					fHandle->write(currentSaveName, sizeof(currentSaveName));
 					delete fHandle;
 
 					sprintf(saveString, otherMessages[3], currentSaveName[selectedSave]);
 					renderer->drawString(saveString, 0);
 
-					makeSave(saveFileName);
+					makeSave(saveFileName, getTotalPlayTime() / 1000, Common::String((const char *)currentSaveName), false);
 
 					checkDataDisk(-1);
 				} else {
@@ -544,7 +537,6 @@ void drawDoubleMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 c
 }
 
 void processInventory(int16 x, int16 y) {
-	uint16 button;
 	int menuWidth;
 	int listSize;
 	int commandParam;
@@ -572,15 +564,7 @@ void processInventory(int16 x, int16 y) {
 	delete menu;
 	menu = 0;
 
-	do {
-		manageEvents();
-		getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
-	} while (!button);
-
-	do {
-		manageEvents();
-		getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
-	} while (button);
+	manageEvents(PROCESS_INVENTORY, UNTIL_MOUSE_BUTTON_DOWN_UP);
 }
 
 int16 buildObjectListCommand(int16 param) {
@@ -611,7 +595,7 @@ int16 selectSubObject(int16 x, int16 y, int16 param) {
 	}
 
 	if (disableSystemMenu == 0) {
-		selectedObject = makeMenuChoice(objectListCommand, listSize, x, y, 140, osExtras);
+		selectedObject = makeMenuChoice(objectListCommand, listSize, x, y, 140, 0, osExtras, osExtras);
 	}
 
 	if (selectedObject == -1)
@@ -635,7 +619,6 @@ void makeCommandLine() {
 		makeOSCommandLine();
 }
 
-// TODO: Add support for using the different prepositions for different verbs (Doesn't work currently)
 void makeOSCommandLine() {
 	uint16 x, y;
 
@@ -644,6 +627,13 @@ void makeOSCommandLine() {
 
 	if (playerCommand != -1) {
 		g_cine->_commandBuffer = defaultActionCommand[playerCommand];
+
+		// This is not present in disassembly but here to implement
+		// prepositions for other than USE command.
+		if (choiceResultTable[playerCommand] != 2 && commandPrepositionTable[playerCommand][0]) {
+			g_cine->_commandBuffer += " ";
+			g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
+		}
 	} else {
 		g_cine->_commandBuffer = "";
 	}
@@ -713,7 +703,9 @@ void makeOSCommandLine() {
 
 			if (di != -1) {
 				runObjectScript(di);
-			} // TODO: else addFailureMessage(playerCommand)
+			} else {
+				addPlayerCommandMessage(playerCommand);
+			}
 
 			playerCommand = -1;
 			commandVar1 = 0;
@@ -725,7 +717,6 @@ void makeOSCommandLine() {
 	renderer->setCommand(g_cine->_commandBuffer);
 }
 
-// TODO: Add support for using the different prepositions for different verbs (Doesn't work currently)
 void makeFWCommandLine() {
 	uint16 x, y;
 
@@ -734,6 +725,13 @@ void makeFWCommandLine() {
 
 	if (playerCommand != -1) {
 		g_cine->_commandBuffer = defaultActionCommand[playerCommand];
+
+		// This is not present in disassembly but here to implement
+		// prepositions for other than USE command.
+		if (choiceResultTable[playerCommand] != 2 && commandPrepositionTable[playerCommand][0]) {
+			g_cine->_commandBuffer += " ";
+			g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
+		}
 	} else {
 		g_cine->_commandBuffer = "";
 	}
@@ -780,7 +778,7 @@ uint16 needMouseSave = 0;
 uint16 menuVar4 = 0;
 uint16 menuVar5 = 0;
 
-int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, bool recheckValue) {
+int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, int minY, bool recheckValue, bool allowEmpty) {
 	int16 paramY;
 	uint16 button;
 	int16 var_A;
@@ -790,6 +788,11 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
 	int16 var_4;
 	SelectionMenu *menu;
 
+	Common::StringArray list;
+	for (uint16 i = minY; i < height; ++i)
+		list.push_back(commandList[i]);
+	height -= minY; // Remove values before minY
+
 	paramY = (height * 9) + 10;
 
 	if (X + width > 319) {
@@ -800,32 +803,49 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
 		Y = 199 - paramY;
 	}
 
-	Common::StringArray list;
-	for (uint16 i = 0; i < height; ++i)
-		list.push_back(commandList[i]);
 	menu = new SelectionMenu(Common::Point(X, Y), width, list);
 	renderer->pushMenu(menu);
 	renderer->drawFrame();
 
-	do {
-		manageEvents();
-		getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
-	} while (button && !g_cine->shouldQuit());
+	manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_UP);
+	getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
 
 	var_A = 0;
 
-	currentSelection = 0;
+	currentSelection = allowEmpty ? -1 : 0;
 
 	menu->setSelection(currentSelection);
 	renderer->drawFrame();
 
-	manageEvents();
+	manageEvents(MAKE_MENU_CHOICE, EMPTY_EVENT_QUEUE);
 	getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY);
 
 	menuVar = 0;
 
 	do {
-		manageEvents();
+		Common::Rect all(X + 1, Y + 1, X + width, Y + paramY + 1);
+		Common::Array<Common::Rect> rects;
+
+		if (currentSelection == -1) {
+			rects.push_back(all);
+		} else if (allowEmpty) {
+			rects.push_back(Common::Rect(0, 0, 320, all.top)); // All above menu (left, top, right)
+			rects.push_back(Common::Rect(0, all.bottom, 320, 200)); // All below menu (left, bottom, right)
+			rects.push_back(Common::Rect(0, all.top, all.left, all.bottom)); // To the left of menu
+			rects.push_back(Common::Rect(all.right, all.top, 320, all.bottom)); // To the right of menu
+		}
+
+		if (currentSelection > 0) {
+			Common::Rect aboveCurr(X + 1, Y + 1, X + width, currentSelection * 9 + Y + 4);
+			rects.push_back(aboveCurr);
+		}
+
+		if (currentSelection != -1 && currentSelection < height - 1) {
+			Common::Rect belowCurr(X + 1, currentSelection * 9 + 13 + Y, X + width, Y + paramY + 1);
+			rects.push_back(belowCurr);
+		}
+
+		manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS, false, rects);
 		getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY);
 
 		if (button) {
@@ -849,7 +869,47 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
 				}
 			}
 		} else {
-			if (mouseX > X && mouseX < X + width && mouseY > Y && mouseY < Y + height * 9) {
+			int selectionValueDiff = 0;
+			while (!g_cine->_keyInputList.empty()) {
+				switch (g_cine->_keyInputList.back().keycode) {
+				case Common::KEYCODE_UP:
+					selectionValueDiff--;
+					break;
+				case Common::KEYCODE_DOWN:
+					selectionValueDiff++;
+					break;
+				}
+				g_cine->_keyInputList.pop_back();
+			}
+
+			if (selectionValueDiff != 0) {
+				if (currentSelection == -1) {
+					if (selectionValueDiff > 0) {
+						// Start going down from the top
+						currentSelection = 0;
+						selectionValueDiff--;
+					} else if (selectionValueDiff < 0) {
+						// Start going up from the bottom
+						currentSelection = height - 1;
+						selectionValueDiff++;
+					}
+				}
+
+				currentSelection = CLIP<int16>(currentSelection + selectionValueDiff, 0, height - 1);
+				
+				if (currentSelection != oldSelection) {
+					Common::Point currentSelectionCenter(X + width / 2, (currentSelection * 9) + Y + 8);
+					g_system->warpMouse(currentSelectionCenter.x, currentSelectionCenter.y);
+				}
+			} else if (mouseX > X && mouseX < X + width && mouseY > Y && mouseY < Y + paramY + 1) {
+				// Y value range for selection s:
+				// (mouseY - (Y + 4)) / 9 >= s
+				// mouseY - (Y + 4) >= s * 9 <=>
+				// mouseY >= s * 9 + Y + 4
+				//
+				// (mouseY - (Y + 4)) / 9 < (s + 1) <=>
+				// mouseY - (Y + 4) < (s + 1) * 9 <=>
+				// mouseY < s * 9 + 13 + Y
 				currentSelection = (mouseY - (Y + 4)) / 9;
 
 				if (currentSelection < 0)
@@ -857,6 +917,8 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
 
 				if (currentSelection >= height)
 					currentSelection = height - 1;
+			} else if (allowEmpty) {
+				currentSelection = -1;
 			}
 		}
 
@@ -881,15 +943,17 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
 
 	menuVar = 0;
 
-	do {
-		manageEvents();
-		getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
-	} while (button && !g_cine->shouldQuit());
+	manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_UP);
+	getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
+
+	if (currentSelection != -1) {
+		currentSelection += minY;
+	}
 
 	if (var_4 == 2) { // recheck
 		if (!recheckValue)
 			return -1;
-		else
+		else if (currentSelection != -1)
 			return currentSelection + 8000;
 	}
 
@@ -907,7 +971,7 @@ void makeActionMenu() {
 
 	if (g_cine->getGameType() == Cine::GType_OS) {
 		if (disableSystemMenu == 0) {
-			playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70, true);
+			playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70, 0, true, true);
 		}
 
 		if (playerCommand >= 8000) {
@@ -923,18 +987,138 @@ void makeActionMenu() {
 	inMenu = false;
 }
 
+void mouseLeftRightDown() {
+	// Left and right mouse buttons are down
+	g_cine->makeSystemMenu();
+}
+
+void allowPlayerInputMouseRightDown() {
+	// Player input is allowed, left mouse button is up, right mouse button is down
+	makeActionMenu();
+	makeCommandLine();
+}
+
+void playerCommandMouseLeft(uint16 &mouseButton, uint16 &mouseX, uint16 &mouseY) {
+	// A player command is given, left mouse button is down, right mouse button is up
+	int16 si;
+	manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_UP);
+	getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
+
+	si = getObjectUnderCursor(mouseX, mouseY);
+
+	if (si != -1) {
+		commandVar3[commandVar1] = si;
+		commandVar1++;
+
+		g_cine->_commandBuffer += " ";
+		g_cine->_commandBuffer += g_cine->_objectTable[si].name;
+
+		isDrawCommandEnabled = 1;
+
+		if (choiceResultTable[playerCommand] == commandVar1) {
+			int16 relEntry;
+
+			SelectedObjStruct obj;
+			obj.idx = commandVar3[0];
+			obj.param = commandVar3[1];
+
+			relEntry = getRelEntryForObject(playerCommand, commandVar1, &obj);
+
+			if (relEntry != -1) {
+				runObjectScript(relEntry);
+			} else {
+				addPlayerCommandMessage(playerCommand);
+			}
+
+			playerCommand = -1;
+
+			commandVar1 = 0;
+			g_cine->_commandBuffer = "";
+		} else if (g_cine->getGameType() == Cine::GType_OS) {
+			isDrawCommandEnabled = 1;
+			g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
+		}
+
+		renderer->setCommand(g_cine->_commandBuffer);
+	} else {
+		g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
+		if (!mouseX) {
+			g_cine->_globalVars[VAR_MOUSE_X_POS]++;
+		}
+
+		g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
+
+		if (g_cine->getGameType() == Cine::GType_OS) {
+			if (!mouseY) {
+				g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
+			}
+			g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
+			g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
+		}
+	}
+}
+
+void playerCommandMouseLeftRightUp(uint16 mouseX, uint16 mouseY) {
+	// A player command is given, left and right mouse buttons are up
+	int16 objIdx;
+
+	objIdx = getObjectUnderCursor(mouseX, mouseY);
+
+	if (g_cine->getGameType() == Cine::GType_OS || commandVar2 != objIdx) {
+		if (objIdx != -1) {
+			renderer->setCommand(g_cine->_commandBuffer + " " + g_cine->_objectTable[objIdx].name);
+		} else {
+			isDrawCommandEnabled = 1;
+		}
+	}
+
+	commandVar2 = objIdx;
+}
+
+void noPlayerCommandMouseLeft(uint16 &mouseX, uint16 &mouseY) {
+	// No player command is given, left mouse button is down, right mouse button is up
+	int16 objIdx;
+	int16 relEntry;
+
+	g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
+	if (!mouseX) {
+		g_cine->_globalVars[VAR_MOUSE_X_POS]++;
+	}
+
+	g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
+
+	if (g_cine->getGameType() == Cine::GType_OS) {
+		if (!mouseY) {
+			g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
+		}
+		g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
+		g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
+	}
+
+	objIdx = getObjectUnderCursor(mouseX, mouseY);
+
+	if (objIdx != -1) {
+		currentSelectedObject.idx = objIdx;
+		currentSelectedObject.param = -1;
+
+		relEntry = getRelEntryForObject(6, 1, &currentSelectedObject);
+
+		if (relEntry != -1) {
+			runObjectScript(relEntry);
+		}
+	}
+}
+
 uint16 executePlayerInput() {
 	uint16 var_5E;
 	uint16 var_2;
 	uint16 mouseX, mouseY, mouseButton;
-	uint16 currentEntry = 0;
-	uint16 di = 0;
-	bool limitMouseCheckCount = false;
 
 	canUseOnObject = 0;
 
 	if (isInPause) {
 		renderer->drawString(otherMessages[2], 0);
+		PauseToken pauseToken = g_cine->pauseEngine();
 		waitPlayerInput();
 		isInPause = 0;
 	}
@@ -944,148 +1128,44 @@ uint16 executePlayerInput() {
 			renderer->setCommand(g_cine->_commandBuffer);
 		}
 		isDrawCommandEnabled = 0;
-		limitMouseCheckCount = true;
+
+		// DIFFERENCE FROM DISASSEMBLY:
+		// See renderer's drawFrame function for comments related to how the waiting
+		// period from the original engine has been moved to here. In the original
+		// engine a maximum of 200 pollings of the mouse state were done here and
+		// the maximum state of the mouse buttons were read. Now we wait for
+		// g_cine->getTimerDelay() here while reading the mouse state and updating
+		// the command line.
+		manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_WAIT_ENDED, true);
+	} else {
+		manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED);
 	}
 
 	// Get mouse position and button states
-	di = 0;
-	currentEntry = 0;
 	getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
 
-	while (mouseButton && (!limitMouseCheckCount || currentEntry < 200) && !g_cine->shouldQuit()) {
-		di |= (mouseButton & (kLeftMouseButton | kRightMouseButton));
-		if (!limitMouseCheckCount) {
-			manageEvents();
-		}
-		getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
-		currentEntry++;
-	}
-
-	if (di) {
-		mouseButton = di;
-	}
-
 	if ((mouseButton & kLeftMouseButton) && (mouseButton & kRightMouseButton)) {
 		// Left and right mouse buttons are down
-		g_cine->makeSystemMenu();
+		mouseLeftRightDown();
 	} else if (allowPlayerInput) { // Player input is allowed
 		if (!(mouseButton & kLeftMouseButton) && (mouseButton & kRightMouseButton)) {
 			// Player input is allowed, left mouse button is up, right mouse button is down
-			makeActionMenu();
-			makeCommandLine();
+			allowPlayerInputMouseRightDown();
 		} else if (playerCommand != -1) { // A player command is given
 			if (mouseButton & kLeftMouseButton) { // Left mouse button is down
 				if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
 					// A player command is given, left mouse button is down, right mouse button is up
-					int16 si;
-					while (mouseButton && !g_cine->shouldQuit()) {
-						manageEvents();
-						getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
-					}
-
-					si = getObjectUnderCursor(mouseX, mouseY);
-
-					if (si != -1) {
-						commandVar3[commandVar1] = si;
-						commandVar1++;
-
-						g_cine->_commandBuffer += " ";
-						g_cine->_commandBuffer += g_cine->_objectTable[si].name;
-
-						isDrawCommandEnabled = 1;
-
-						if (choiceResultTable[playerCommand] == commandVar1) {
-							int16 relEntry;
-
-							SelectedObjStruct obj;
-							obj.idx = commandVar3[0];
-							obj.param = commandVar3[1];
-
-							relEntry = getRelEntryForObject(playerCommand, commandVar1, &obj);
-
-							if (relEntry != -1) {
-								runObjectScript(relEntry);
-							} else {
-								addPlayerCommandMessage(playerCommand);
-							}
-
-							playerCommand = -1;
-
-							commandVar1 = 0;
-							g_cine->_commandBuffer = "";
-						} else if (g_cine->getGameType() == Cine::GType_OS) {
-							isDrawCommandEnabled = 1;
-							g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
-						}
-
-						renderer->setCommand(g_cine->_commandBuffer);
-					} else {
-						g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
-						if (!mouseX) {
-							g_cine->_globalVars[VAR_MOUSE_X_POS]++;
-						}
-
-						g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
-
-						if (g_cine->getGameType() == Cine::GType_OS) {
-							if (!mouseY) {
-								g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
-							}
-							g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
-							g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
-						}
-					}
+					playerCommandMouseLeft(mouseButton, mouseX, mouseY);
 				}
 			} else if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
 				// A player command is given, left and right mouse buttons are up
-				int16 objIdx;
-
-				objIdx = getObjectUnderCursor(mouseX, mouseY);
-
-				if (g_cine->getGameType() == Cine::GType_OS || commandVar2 != objIdx) {
-					if (objIdx != -1) {
-						renderer->setCommand(g_cine->_commandBuffer + " " + g_cine->_objectTable[objIdx].name);
-					} else {
-						isDrawCommandEnabled = 1;
-					}
-				}
-
-				commandVar2 = objIdx;
+				playerCommandMouseLeftRightUp(mouseX, mouseY);
 			}
 		} else { // No player command is given
 			if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
 				if (mouseButton & kLeftMouseButton) { // Left mouse button is down
 					// No player command is given, left mouse button is down, right mouse button is up
-					int16 objIdx;
-					int16 relEntry;
-
-					g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
-					if (!mouseX) {
-						g_cine->_globalVars[VAR_MOUSE_X_POS]++;
-					}
-
-					g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
-
-					if (g_cine->getGameType() == Cine::GType_OS) {
-						if (!mouseY) {
-							g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
-						}
-						g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
-						g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
-					}
-
-					objIdx = getObjectUnderCursor(mouseX, mouseY);
-
-					if (objIdx != -1) {
-						currentSelectedObject.idx = objIdx;
-						currentSelectedObject.param = -1;
-
-						relEntry = getRelEntryForObject(6, 1, &currentSelectedObject);
-
-						if (relEntry != -1) {
-							runObjectScript(relEntry);
-						}
-					}
+					noPlayerCommandMouseLeft(mouseX, mouseY);
 				}
 			}
 		}
@@ -1290,7 +1370,7 @@ void removeMessages() {
 			// some other places too) and that's where incrementing a the overlay's
 			// last parameter by one if it's negative and testing it for positivity
 			// comes from too.
-			remove = it->type == 3 || (it->type == 2 && (it->color >= 0 || ++it->color >= 0));
+			remove = it->type == 3 || (it->type == 2 && (it->color >= 0 || ++(it->color) >= 0));
 		} else { // Future Wars
 			remove = it->type == 2 || it->type == 3;
 		}
@@ -1308,6 +1388,8 @@ uint16 processKeyboard(uint16 param) {
 }
 
 void mainLoopSub6() {
+	// (10923000 ms * 3) / 1193180 ~= 27 ms.
+	//g_system->delayMillis(27);
 }
 
 void checkForPendingDataLoad() {
@@ -1391,6 +1473,7 @@ void removeSeq(uint16 param1, uint16 param2, uint16 param3) {
 	}
 }
 
+// Checked against Operation Stealth 16 color DOS disassembly, should be correct.
 bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
 	Common::List<SeqListElement>::iterator it;
 
@@ -1497,7 +1580,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele
 	      param1, objIdx, (const void *)ptr, element.var8, element.var14, param3);
 
 	// In the original an error string is set and 0 is returned if the following doesn't hold
-	assert(ptr);
+	if (!ptr) {
+		return 0;
+	}
 
 	// We probably could just use a local variable here instead of the dummyU16 but
 	// haven't checked if this has any side-effects so keeping it this way still.
@@ -1505,7 +1590,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele
 	ptrData = ptr + dummyU16;
 
 	// In the original an error string is set and 0 is returned if the following doesn't hold
-	assert(*ptrData);
+	if (!*ptrData) {
+		return 0;
+	}
 
 	di = (g_cine->_objectTable[objIdx].costume + 1) % (*ptrData);
 	++ptrData; // Jump over the just read byte
@@ -1696,7 +1783,7 @@ void processSeqListElement(SeqListElement &element) {
 			}
 		}
 
-		if (element.var16 + element.var14 == 0) {
+		if (((element.var16 + element.var14) & 0xFFFF) == 0) {
 			if (element.var1C) {
 				if (element.var1E) {
 					g_cine->_objectTable[element.objIdx].costume = 0;
@@ -1732,7 +1819,7 @@ bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxL
 
 	int16 x = (320 - width) / 2;
 
-	getKeyData(); // clear input key
+	g_cine->_keyInputList.clear();
 
 	int quit = 0;
 	bool redraw = true;
@@ -1750,12 +1837,23 @@ bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxL
 			redraw = false;
 		}
 
+		if (g_cine->_keyInputList.empty()) {
+			manageEvents(MAKE_TEXT_ENTRY_MENU, UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT);
+		}
+
 		char ch[2];
 		memset(tempString, 0, stringMaxLength);
 		ch[1] = 0;
 
-		manageEvents();
-		int keycode = getKeyData();
+		Common::KeyState keyState = Common::KeyState();
+			
+		if (!g_cine->_keyInputList.empty()) {
+			keyState = g_cine->_keyInputList.back();
+			g_cine->_keyInputList.pop_back();
+		}
+		
+		int keycode = keyState.keycode;
+		uint16 ascii = keyState.ascii;
 		uint16 mouseButton, mouseX, mouseY;
 
 		getMouseData(0, &mouseButton, &mouseX, &mouseY);
@@ -1799,12 +1897,12 @@ bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxL
 			}
 			break;
 		default:
-			if (((keycode >= 'a') && (keycode <= 'z')) ||
-				((keycode >= '0') && (keycode <= '9')) ||
-				((keycode >= 'A') && (keycode <= 'Z')) ||
-				(keycode == ' ')) {
+			if (((ascii >= 'a') && (ascii <= 'z')) ||
+				((ascii >= '0') && (ascii <= '9')) ||
+				((ascii >= 'A') && (ascii <= 'Z')) ||
+				(ascii == ' ')) {
 				if (inputLength < stringMaxLength - 1) {
-					ch[0] = keycode;
+					ch[0] = ascii;
 					if (inputPos != 1) {
 						strncpy(tempString, inputString, inputPos - 1);
 						strcat(tempString, ch);
diff --git a/engines/cine/various.h b/engines/cine/various.h
index eb6312997d..45f091998e 100644
--- a/engines/cine/various.h
+++ b/engines/cine/various.h
@@ -25,17 +25,20 @@
 
 
 #include "common/file.h"
+#include "common/keyboard.h"
 
 #include "cine/cine.h"
 
 namespace Cine {
 
+#define kMaxSavegames 20 // 20 fit on screen using original save/load interface
+
 // Maximum size of the command buffer including the trailing zero
 #define kMaxCommandBufferSize 80
 
 void initLanguage(Common::Language lang);
 
-int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, bool recheckValue = false);
+int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, int minY = 0, bool recheckValue = false, bool allowEmpty = false);
 void makeCommandLine();
 void makeFWCommandLine();
 void makeOSCommandLine();
@@ -45,8 +48,9 @@ void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4);
 
 extern int16 disableSystemMenu;
 extern bool inMenu;
+extern bool runOnlyUntilCopyProtectionCheck;
 
-extern CommandeType currentSaveName[10];
+extern CommandeType currentSaveName[kMaxSavegames];
 
 struct SeqListElement {
 	int16 var4;
@@ -68,7 +72,11 @@ struct SeqListElement {
 extern uint16 var2;
 extern uint16 var3;
 extern uint16 var4;
-extern uint16 var5;
+extern uint16 lastType20OverlayBgIdx;
+extern uint16 reloadBgPalOnNextFlip;
+extern uint16 forbidBgPalReload;
+extern uint16 gfxFadeOutCompleted;
+extern uint16 gfxFadeInRequested;
 extern int16 commandVar1;
 extern int16 commandVar2;
 extern int16 commandVar3[4];
@@ -83,7 +91,6 @@ extern uint16 allowPlayerInput;
 
 extern uint16 checkForPendingDataLoadSwitch;
 
-extern bool fadeRequired;
 extern uint16 isDrawCommandEnabled;
 extern uint16 waitForPlayerClick;
 extern uint16 menuCommandLen;
@@ -106,6 +113,7 @@ extern char currentPartName[15];
 
 void stopSample();
 void stopMusicAfterFadeOut();
+void playerCommandMouseLeftRightUp(uint16 mouseX, uint16 mouseY);
 uint16 executePlayerInput();
 
 void drawOverlays();
@@ -114,7 +122,6 @@ extern uint16 mouseUpdateStatus;
 extern uint16 dummyU16;
 
 void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY);
-int getKeyData();
 
 uint16 processKeyboard(uint16 param);
 
@@ -147,6 +154,7 @@ void resetGfxEntityEntry(uint16 objIdx);
 
 bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y);
 void moveUsingKeyboard(int x, int y);
+int16 getObjectUnderCursor(uint16 x, uint16 y);
 
 } // End of namespace Cine
 


Commit: 9c68b62eb9d9b48d67506ef3d843cac5bd2fdea2
    https://github.com/scummvm/scummvm/commit/9c68b62eb9d9b48d67506ef3d843cac5bd2fdea2
Author: Kari Salminen (kari.salminen at gmail.com)
Date: 2020-07-25T00:33:42+02:00

Commit Message:
CINE: Fix clang compilation and DeepCode warning.

Change structure initialization from C++11 style to older style to fix
clang compilation. Add null check to fix DeepCode warning about
possible undefined behaviour.

Changed paths:
    engines/cine/pal.cpp
    engines/cine/sound.cpp


diff --git a/engines/cine/pal.cpp b/engines/cine/pal.cpp
index 21cdc068e8..ed0fd3f620 100644
--- a/engines/cine/pal.cpp
+++ b/engines/cine/pal.cpp
@@ -203,8 +203,8 @@ int Palette::findMinBrightnessColorIndex(uint minColorIndex) {
 bool Palette::ensureContrast(byte &minBrightnessColorIndex) {
 	minBrightnessColorIndex = findMinBrightnessColorIndex();
 	if (_colors.size() >= 3 && isEqual(2, minBrightnessColorIndex)) {
-		Color black{ 0, 0, 0 };
-		Color white{_format.rMax(), _format.gMax(), _format.bMax()	};
+		Color black = {0, 0, 0};
+		Color white = {_format.rMax(), _format.gMax(), _format.bMax()};
 
 		_colors[2] = white;
 		if (isEqual(2, minBrightnessColorIndex)) {
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index 0bd590d3ac..970690f79c 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -1051,6 +1051,9 @@ bool PCSoundFxPlayer::load(const char *song) {
 				data && instrumentSize > 0x16) {
 				instrumentSize -= 0x16;
 				byte *tmp = (byte *)calloc(instrumentSize, 1);
+				if (tmp == NULL) {
+					error("PCSoundFxPlayer::load('%s'): Out of memory (%d bytes)", song, instrumentSize);
+				}
 				memcpy(tmp, data + 0x16, instrumentSize);
 				free(data);
 				data = tmp;


Commit: 580a7e6568839a6505b0d8a10244a814dd16c03b
    https://github.com/scummvm/scummvm/commit/580a7e6568839a6505b0d8a10244a814dd16c03b
Author: Kari Salminen (kari.salminen at gmail.com)
Date: 2020-07-25T00:33:42+02:00

Commit Message:
CINE: Fix formatting, string copy and signedness.

Fix code formatting, string copying (strncpy -> strlcpy) and signedness (int -> uint).

Changed paths:
    engines/cine/anim.cpp
    engines/cine/detection.cpp
    engines/cine/pal.cpp


diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index bcef2ddc0d..ee6a519c0d 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -820,19 +820,18 @@ int loadResource(const char *resourceName, int16 idx, int16 frameIndex) {
 	if (g_cine->getGameType() == Cine::GType_OS &&
 		g_cine->getPlatform() == Common::kPlatformDOS &&
 		g_sound->musicType() != MT_MT32 &&
-		(strstr(resourceName, ".SPL") || strstr(resourceName, ".H32")))
-	{
+		(strstr(resourceName, ".SPL") || strstr(resourceName, ".H32"))) {
 		char base[20];
 		removeExtention(base, resourceName);
 
-		for (int i = 0; i < ARRAYSIZE(resNameMapping); i++) {
+		for (uint i = 0; i < ARRAYSIZE(resNameMapping); i++) {
 			if (scumm_stricmp(base, resNameMapping[i].from) == 0) {
-				strncpy(base, resNameMapping[i].to, sizeof(base));
+				Common::strlcpy(base, resNameMapping[i].to, sizeof(base));
 				break;
 			}
 		}
 
-		const char* ext = (g_sound->musicType() == MT_ADLIB) ? ".ADL" : ".HP";
+		const char *ext = (g_sound->musicType() == MT_ADLIB) ? ".ADL" : ".HP";
 		strcat(base, ext);
 		return loadResource(base, idx, frameIndex);
 	}
diff --git a/engines/cine/detection.cpp b/engines/cine/detection.cpp
index 79f797527f..18620e956b 100644
--- a/engines/cine/detection.cpp
+++ b/engines/cine/detection.cpp
@@ -331,7 +331,7 @@ void CineMetaEngine::removeSaveState(const char *target, int slot) const {
 	delete out;
 
 	// Delete save file
-	const char * saveFileName = getSavegameFile(slot, target);
+	const char *saveFileName = getSavegameFile(slot, target);
 
 	g_system->getSavefileManager()->removeSavefile(saveFileName);
 }
diff --git a/engines/cine/pal.cpp b/engines/cine/pal.cpp
index ed0fd3f620..9d18548b1c 100644
--- a/engines/cine/pal.cpp
+++ b/engines/cine/pal.cpp
@@ -145,7 +145,7 @@ Palette &Palette::rotateRight(byte firstIndex, byte lastIndex) {
 
 	const Color lastColor = _colors[lastIndex];
 
-	for (int i = lastIndex; i > firstIndex; i--)
+	for (uint i = lastIndex; i > firstIndex; i--)
 		_colors[i] = _colors[i - 1];
 
 	_colors[firstIndex] = lastColor;
@@ -158,7 +158,7 @@ Palette &Palette::rotateLeft(byte firstIndex, byte lastIndex) {
 
 	const Color firstColor = _colors[firstIndex];
 
-	for (int i = firstIndex; i < lastIndex; i++)
+	for (uint i = firstIndex; i < lastIndex; i++)
 		_colors[i] = _colors[i + 1];
 
 	_colors[lastIndex] = firstColor;




More information about the Scummvm-git-logs mailing list