[Scummvm-cvs-logs] scummvm master -> 0dd760724e37b70bfaade2c34c12e34fab4d4b71

m-kiewitz m_kiewitz at users.sourceforge.net
Tue Dec 29 01:44:56 CET 2015


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

Summary:
0dd760724e SCI32: split up SCI2.1 into EARLY/MIDDLE/LATE


Commit: 0dd760724e37b70bfaade2c34c12e34fab4d4b71
    https://github.com/scummvm/scummvm/commit/0dd760724e37b70bfaade2c34c12e34fab4d4b71
Author: Martin Kiewitz (m_kiewitz at users.sourceforge.net)
Date: 2015-12-29T01:44:11+01:00

Commit Message:
SCI32: split up SCI2.1 into EARLY/MIDDLE/LATE

- Detection works via signatures (couldn't find a better way)
- new kString subcalls were introduced SCI2.1 LATE
- kString now has signatures and is split via subcall table
- kString fix, so that KQ7 doesn't crash, when starting a chapter
- Sci2StringFunctionType removed, because no longer needed

Changed paths:
    engines/sci/console.cpp
    engines/sci/engine/features.cpp
    engines/sci/engine/features.h
    engines/sci/engine/kernel.cpp
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kevent.cpp
    engines/sci/engine/kmisc.cpp
    engines/sci/engine/kscripts.cpp
    engines/sci/engine/ksound.cpp
    engines/sci/engine/kstring.cpp
    engines/sci/engine/kvideo.cpp
    engines/sci/engine/message.cpp
    engines/sci/engine/object.cpp
    engines/sci/engine/object.h
    engines/sci/engine/savegame.cpp
    engines/sci/engine/script.cpp
    engines/sci/engine/static_selectors.cpp
    engines/sci/engine/vm_types.cpp
    engines/sci/graphics/compare.cpp
    engines/sci/graphics/frameout.cpp
    engines/sci/graphics/view.cpp
    engines/sci/resource.cpp
    engines/sci/resource.h
    engines/sci/resource_audio.cpp
    engines/sci/sci.cpp
    engines/sci/sci.h
    engines/sci/sound/audio.cpp
    engines/sci/sound/drivers/midi.cpp
    engines/sci/sound/midiparser_sci.cpp
    engines/sci/sound/music.cpp
    engines/sci/util.cpp



diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 1e95393..5cd7b9f 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -483,9 +483,7 @@ bool Console::cmdGetVersion(int argc, const char **argv) {
 	debugPrintf("Move count type: %s\n", (_engine->_features->handleMoveCount()) ? "increment" : "ignore");
 	debugPrintf("SetCursor type: %s\n", getSciVersionDesc(_engine->_features->detectSetCursorType()));
 #ifdef ENABLE_SCI32
-	if (getSciVersion() >= SCI_VERSION_2)
-		debugPrintf("kString type: %s\n", (_engine->_features->detectSci2StringFunctionType() == kSci2StringFunctionOld) ? "SCI2 (old)" : "SCI2.1 (new)");
-	if (getSciVersion() == SCI_VERSION_2_1)
+	if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE))
 		debugPrintf("SCI2.1 kernel table: %s\n", (_engine->_features->detectSci21KernelType() == SCI_VERSION_2) ? "modified SCI2 (old)" : "SCI2.1 (new)");
 #endif
 	debugPrintf("View type: %s\n", viewTypeDesc[g_sci->getResMan()->getViewType()]);
@@ -1046,7 +1044,7 @@ bool Console::cmdVerifyScripts(int argc, const char **argv) {
 		if (!script)
 			debugPrintf("Error: script %d couldn't be loaded\n", itr->getNumber());
 
-		if (getSciVersion() <= SCI_VERSION_2_1) {
+		if (getSciVersion() <= SCI_VERSION_2_1_LATE) {
 			heap = _engine->getResMan()->findResource(ResourceId(kResourceTypeHeap, itr->getNumber()), false);
 			if (!heap)
 				debugPrintf("Error: script %d doesn't have a corresponding heap\n", itr->getNumber());
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp
index be062db..a993506 100644
--- a/engines/sci/engine/features.cpp
+++ b/engines/sci/engine/features.cpp
@@ -40,7 +40,6 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan)
 	_moveCountType = kMoveCountUninitialized;
 #ifdef ENABLE_SCI32
 	_sci21KernelType = SCI_VERSION_NONE;
-	_sci2StringFunctionType = kSci2StringFunctionUninitialized;
 #endif
 	_usesCdTrack = Common::File::exists("cdaudio.map");
 	if (!ConfMan.getBool("use_cdaudio"))
@@ -143,8 +142,8 @@ SciVersion GameFeatures::detectDoSoundType() {
 			//  SCI0LATE. Although the last SCI0EARLY game (lsl2) uses SCI0LATE resources
 			_doSoundType = g_sci->getResMan()->detectEarlySound() ? SCI_VERSION_0_EARLY : SCI_VERSION_0_LATE;
 #ifdef ENABLE_SCI32
-		} else if (getSciVersion() >= SCI_VERSION_2_1) {
-			_doSoundType = SCI_VERSION_2_1;
+		} else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
+			_doSoundType = SCI_VERSION_2_1_EARLY;
 #endif
 		} else if (SELECTOR(nodePtr) == -1) {
 			// No nodePtr selector, so this game is definitely using newer
@@ -271,7 +270,7 @@ SciVersion GameFeatures::detectLofsType() {
 			return _lofsType;
 		}
 
-		if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+		if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 			// SCI1.1 type, i.e. we compensate for the fact that the heap is attached
 			// to the end of the script
 			_lofsType = SCI_VERSION_1_1;
@@ -475,7 +474,7 @@ bool GameFeatures::autoDetectSci21KernelType() {
 		}
 
 		warning("autoDetectSci21KernelType(): Sound object not loaded, assuming a SCI2.1 table");
-		_sci21KernelType = SCI_VERSION_2_1;
+		_sci21KernelType = SCI_VERSION_2_1_EARLY;
 		return true;
 	}
 
@@ -514,7 +513,7 @@ bool GameFeatures::autoDetectSci21KernelType() {
 				_sci21KernelType = SCI_VERSION_2;
 				return true;
 			} else if (kFuncNum == 0x75) {
-				_sci21KernelType = SCI_VERSION_2_1;
+				_sci21KernelType = SCI_VERSION_2_1_EARLY;
 				return true;
 			}
 		}
@@ -532,65 +531,6 @@ SciVersion GameFeatures::detectSci21KernelType() {
 	}
 	return _sci21KernelType;
 }
-
-Sci2StringFunctionType GameFeatures::detectSci2StringFunctionType() {
-	if (_sci2StringFunctionType == kSci2StringFunctionUninitialized) {
-		if (getSciVersion() <= SCI_VERSION_1_1) {
-			error("detectSci21StringFunctionType() called from SCI1.1 or earlier");
-		} else if (getSciVersion() == SCI_VERSION_2) {
-			// SCI2 games are always using the old type
-			_sci2StringFunctionType = kSci2StringFunctionOld;
-		} else if (getSciVersion() == SCI_VERSION_3) {
-			// SCI3 games are always using the new type
-			_sci2StringFunctionType = kSci2StringFunctionNew;
-		} else {	// SCI2.1
-			if (!autoDetectSci21StringFunctionType())
-				_sci2StringFunctionType = kSci2StringFunctionOld;
-			else
-				_sci2StringFunctionType = kSci2StringFunctionNew;
-		}
-	}
-
-	debugC(1, kDebugLevelVM, "Detected SCI2 kString type: %s", (_sci2StringFunctionType == kSci2StringFunctionOld) ? "old" : "new");
-
-	return _sci2StringFunctionType;
-}
-
-bool GameFeatures::autoDetectSci21StringFunctionType() {
-	// Look up the script address
-	reg_t addr = getDetectionAddr("Str", SELECTOR(size));
-
-	if (!addr.getSegment())
-		return false;
-
-	uint16 offset = addr.getOffset();
-	Script *script = _segMan->getScript(addr.getSegment());
-
-	while (true) {
-		int16 opparams[4];
-		byte extOpcode;
-		byte opcode;
-		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
-		opcode = extOpcode >> 1;
-
-		// Check for end of script
-		if (opcode == op_ret || offset >= script->getBufSize())
-			break;
-
-		if (opcode == op_callk) {
-			uint16 kFuncNum = opparams[0];
-
-			// SCI2.1 games which use the new kString functions call kString(8).
-			// Earlier ones call the callKernel script function, but not kString
-			// directly
-			if (_kernel->getKernelName(kFuncNum) == "String")
-				return true;
-		}
-	}
-
-	return false;	// not found a call to kString
-}
-
 #endif
 
 bool GameFeatures::autoDetectMoveCountType() {
diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h
index a4d715f..1c41026 100644
--- a/engines/sci/engine/features.h
+++ b/engines/sci/engine/features.h
@@ -34,12 +34,6 @@ enum MoveCountType {
 	kIncrementMoveCount
 };
 
-enum Sci2StringFunctionType {
-	kSci2StringFunctionUninitialized,
-	kSci2StringFunctionOld,
-	kSci2StringFunctionNew
-};
-
 class GameFeatures {
 public:
 	GameFeatures(SegManager *segMan, Kernel *kernel);
@@ -82,13 +76,6 @@ public:
 	 * @return Graphics functions type, SCI_VERSION_2 / SCI_VERSION_2_1
 	 */
 	SciVersion detectSci21KernelType();
-
-	/**
-	 * Autodetects the string subfunctions used in SCI2 - SCI3
-	 * @return string subfunctions type, kSci2StringFunctionOld / kSci2StringFunctionNew
-	 */
-	Sci2StringFunctionType detectSci2StringFunctionType();
-
 #endif
 
 	/**
@@ -132,13 +119,11 @@ private:
 	bool autoDetectMoveCountType();
 #ifdef ENABLE_SCI32
 	bool autoDetectSci21KernelType();
-	bool autoDetectSci21StringFunctionType();
 #endif
 
 	SciVersion _doSoundType, _setCursorType, _lofsType, _gfxFunctionsType, _messageFunctionType;
 #ifdef ENABLE_SCI32
 	SciVersion _sci21KernelType;
-	Sci2StringFunctionType _sci2StringFunctionType;
 #endif
 
 	MoveCountType _moveCountType;
diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp
index bfb7bfc..c0f2c64 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -118,7 +118,7 @@ void Kernel::loadSelectorNames() {
 
 	// Starting with KQ7, Mac versions have a BE name table. GK1 Mac and earlier (and all
 	// other platforms) always use LE.
-	bool isBE = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1
+	bool isBE = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY
 			&& g_sci->getGameId() != GID_GK1);
 
 	if (!r) { // No such resource?
@@ -863,7 +863,9 @@ void Kernel::loadKernelNames(GameFeatures *features) {
 		_kernelNames = Common::StringArray(sci2_default_knames, kKernelEntriesSci2);
 		break;
 
-	case SCI_VERSION_2_1:
+	case SCI_VERSION_2_1_EARLY:
+	case SCI_VERSION_2_1_MIDDLE:
+	case SCI_VERSION_2_1_LATE:
 		if (features->detectSci21KernelType() == SCI_VERSION_2) {
 			// Some early SCI2.1 games use a modified SCI2 kernel table instead of
 			// the SCI2.1 kernel table. We detect which version to use based on
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 57b4d94..5d929a3 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -414,6 +414,27 @@ reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
 reg_t kArray(EngineState *s, int argc, reg_t *argv);
 reg_t kListAt(EngineState *s, int argc, reg_t *argv);
 reg_t kString(EngineState *s, int argc, reg_t *argv);
+
+reg_t kStringNew(EngineState *s, int argc, reg_t *argv);
+reg_t kStringSize(EngineState *s, int argc, reg_t *argv);
+reg_t kStringAt(EngineState *s, int argc, reg_t *argv);
+reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv);
+reg_t kStringFree(EngineState *s, int argc, reg_t *argv);
+reg_t kStringFill(EngineState *s, int argc, reg_t *argv);
+reg_t kStringCopy(EngineState *s, int argc, reg_t *argv);
+reg_t kStringCompare(EngineState *s, int argc, reg_t *argv);
+reg_t kStringDup(EngineState *s, int argc, reg_t *argv);
+reg_t kStringGetData(EngineState *s, int argc, reg_t *argv);
+reg_t kStringLen(EngineState *s, int argc, reg_t *argv);
+reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv);
+reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv);
+reg_t kStringAtoi(EngineState *s, int argc, reg_t *argv);
+reg_t kStringTrim(EngineState *s, int argc, reg_t *argv);
+reg_t kStringUpper(EngineState *s, int argc, reg_t *argv);
+reg_t kStringLower(EngineState *s, int argc, reg_t *argv);
+reg_t kStringTrn(EngineState *s, int argc, reg_t *argv);
+reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv);
+
 reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
 reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv);
 reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 2cbd793..49dfa17 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -34,6 +34,15 @@ namespace Sci {
 // . -> any type
 // i* -> optional multiple integers
 // .* -> any parameters afterwards (or none)
+//
+// data types:
+// i - regular integer
+// o - object
+// r - reference
+// n - node
+// 0 - NULL
+// . - any
+// ! - invalid reference/offset
 
 struct SciKernelMapSubEntry {
 	SciVersion fromVersion;
@@ -56,7 +65,8 @@ struct SciKernelMapSubEntry {
 #define SIG_SCI1           SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE
 #define SIG_SCI11          SCI_VERSION_1_1, SCI_VERSION_1_1
 #define SIG_SINCE_SCI11    SCI_VERSION_1_1, SCI_VERSION_NONE
-#define SIG_SCI21          SCI_VERSION_2_1, SCI_VERSION_3
+#define SIG_SINCE_SCI21    SCI_VERSION_2_1_EARLY, SCI_VERSION_3
+#define SIG_UNTIL_SCI21MID SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_MIDDLE
 
 #define SIG_SCI16          SCI_VERSION_NONE, SCI_VERSION_1_1
 #define SIG_SCI32          SCI_VERSION_2, SCI_VERSION_NONE
@@ -65,7 +75,7 @@ struct SciKernelMapSubEntry {
 #define SIG_SOUNDSCI0      SCI_VERSION_0_EARLY, SCI_VERSION_0_LATE
 #define SIG_SOUNDSCI1EARLY SCI_VERSION_1_EARLY, SCI_VERSION_1_EARLY
 #define SIG_SOUNDSCI1LATE  SCI_VERSION_1_LATE, SCI_VERSION_1_LATE
-#define SIG_SOUNDSCI21     SCI_VERSION_2_1, SCI_VERSION_3
+#define SIG_SOUNDSCI21     SCI_VERSION_2_1_EARLY, SCI_VERSION_3
 
 #define SIGFOR_ALL   0x3f
 #define SIGFOR_DOS   1 << 0
@@ -190,7 +200,7 @@ static const SciKernelMapSubEntry kGraph_subops[] = {
 
 //    version,         subId, function-mapping,                    signature,              workarounds
 static const SciKernelMapSubEntry kPalVary_subops[] = {
-	{ SIG_SCI21,           0, MAP_CALL(PalVaryInit),               "ii(i)(i)(i)",          NULL },
+	{ SIG_SINCE_SCI21,     0, MAP_CALL(PalVaryInit),               "ii(i)(i)(i)",          NULL },
 	{ SIG_SCIALL,          0, MAP_CALL(PalVaryInit),               "ii(i)(i)",             NULL },
 	{ SIG_SCIALL,          1, MAP_CALL(PalVaryReverse),            "(i)(i)(i)",            NULL },
 	{ SIG_SCIALL,          2, MAP_CALL(PalVaryGetCurrentStep),     "",                     NULL },
@@ -246,6 +256,7 @@ static const SciKernelMapSubEntry kFileIO_subops[] = {
 
 #ifdef ENABLE_SCI32
 
+//    version,         subId, function-mapping,                    signature,              workarounds
 static const SciKernelMapSubEntry kSave_subops[] = {
 	{ SIG_SCI32,           0, MAP_CALL(SaveGame),                  "[r0]i[r0](r0)",        NULL },
 	{ SIG_SCI32,           1, MAP_CALL(RestoreGame),               "[r0]i[r0]",            NULL },
@@ -261,31 +272,73 @@ static const SciKernelMapSubEntry kSave_subops[] = {
 
 //    version,         subId, function-mapping,                    signature,              workarounds
 static const SciKernelMapSubEntry kList_subops[] = {
-	{ SIG_SCI21,           0, MAP_CALL(NewList),                   "",                     NULL },
-	{ SIG_SCI21,           1, MAP_CALL(DisposeList),               "l",                    NULL },
-	{ SIG_SCI21,           2, MAP_CALL(NewNode),                   ".(.)",                 NULL },
-	{ SIG_SCI21,           3, MAP_CALL(FirstNode),                 "[l0]",                 NULL },
-	{ SIG_SCI21,           4, MAP_CALL(LastNode),                  "l",                    NULL },
-	{ SIG_SCI21,           5, MAP_CALL(EmptyList),                 "l",                    NULL },
-	{ SIG_SCI21,           6, MAP_CALL(NextNode),                  "n",                    NULL },
-	{ SIG_SCI21,           7, MAP_CALL(PrevNode),                  "n",                    NULL },
-	{ SIG_SCI21,           8, MAP_CALL(NodeValue),                 "[n0]",                 NULL },
-	{ SIG_SCI21,           9, MAP_CALL(AddAfter),                  "lnn.",                 NULL },
-	{ SIG_SCI21,          10, MAP_CALL(AddToFront),                "ln.",                  NULL },
-	{ SIG_SCI21,          11, MAP_CALL(AddToEnd),                  "ln(.)",                NULL },
-	{ SIG_SCI21,          12, MAP_CALL(AddBefore),                 "ln.",                  NULL },
-	{ SIG_SCI21,          13, MAP_CALL(MoveToFront),               "ln",                   NULL },
-	{ SIG_SCI21,          14, MAP_CALL(MoveToEnd),                 "ln",                   NULL },
-	{ SIG_SCI21,          15, MAP_CALL(FindKey),                   "l.",                   NULL },
-	{ SIG_SCI21,          16, MAP_CALL(DeleteKey),                 "l.",                   NULL },
-	{ SIG_SCI21,          17, MAP_CALL(ListAt),                    "li",                   NULL },
-	{ SIG_SCI21,          18, MAP_CALL(ListIndexOf) ,              "l[io]",                NULL },
-	{ SIG_SCI21,          19, MAP_CALL(ListEachElementDo),         "li(.*)",               NULL },
-	{ SIG_SCI21,          20, MAP_CALL(ListFirstTrue),             "li(.*)",               NULL },
-	{ SIG_SCI21,          21, MAP_CALL(ListAllTrue),               "li(.*)",               NULL },
-	{ SIG_SCI21,          22, MAP_CALL(Sort),                      "ooo",                  NULL },
+	{ SIG_SINCE_SCI21,     0, MAP_CALL(NewList),                   "",                     NULL },
+	{ SIG_SINCE_SCI21,     1, MAP_CALL(DisposeList),               "l",                    NULL },
+	{ SIG_SINCE_SCI21,     2, MAP_CALL(NewNode),                   ".(.)",                 NULL },
+	{ SIG_SINCE_SCI21,     3, MAP_CALL(FirstNode),                 "[l0]",                 NULL },
+	{ SIG_SINCE_SCI21,     4, MAP_CALL(LastNode),                  "l",                    NULL },
+	{ SIG_SINCE_SCI21,     5, MAP_CALL(EmptyList),                 "l",                    NULL },
+	{ SIG_SINCE_SCI21,     6, MAP_CALL(NextNode),                  "n",                    NULL },
+	{ SIG_SINCE_SCI21,     7, MAP_CALL(PrevNode),                  "n",                    NULL },
+	{ SIG_SINCE_SCI21,     8, MAP_CALL(NodeValue),                 "[n0]",                 NULL },
+	{ SIG_SINCE_SCI21,     9, MAP_CALL(AddAfter),                  "lnn.",                 NULL },
+	{ SIG_SINCE_SCI21,    10, MAP_CALL(AddToFront),                "ln.",                  NULL },
+	{ SIG_SINCE_SCI21,    11, MAP_CALL(AddToEnd),                  "ln(.)",                NULL },
+	{ SIG_SINCE_SCI21,    12, MAP_CALL(AddBefore),                 "ln.",                  NULL },
+	{ SIG_SINCE_SCI21,    13, MAP_CALL(MoveToFront),               "ln",                   NULL },
+	{ SIG_SINCE_SCI21,    14, MAP_CALL(MoveToEnd),                 "ln",                   NULL },
+	{ SIG_SINCE_SCI21,    15, MAP_CALL(FindKey),                   "l.",                   NULL },
+	{ SIG_SINCE_SCI21,    16, MAP_CALL(DeleteKey),                 "l.",                   NULL },
+	{ SIG_SINCE_SCI21,    17, MAP_CALL(ListAt),                    "li",                   NULL },
+	{ SIG_SINCE_SCI21,    18, MAP_CALL(ListIndexOf) ,              "l[io]",                NULL },
+	{ SIG_SINCE_SCI21,    19, MAP_CALL(ListEachElementDo),         "li(.*)",               NULL },
+	{ SIG_SINCE_SCI21,    20, MAP_CALL(ListFirstTrue),             "li(.*)",               NULL },
+	{ SIG_SINCE_SCI21,    21, MAP_CALL(ListAllTrue),               "li(.*)",               NULL },
+	{ SIG_SINCE_SCI21,    22, MAP_CALL(Sort),                      "ooo",                  NULL },
+	SCI_SUBOPENTRY_TERMINATOR
+};
+
+//    version,         subId, function-mapping,                    signature,              workarounds
+static const SciKernelMapSubEntry kString_subops[] = {
+	{ SIG_SCI32,           0, MAP_CALL(StringNew),                 "i(i)",                 NULL },
+	{ SIG_SCI32,           1, MAP_CALL(StringSize),                "[or]",                 NULL },
+	{ SIG_SCI32,           2, MAP_CALL(StringAt),                  "[or]i",                NULL },
+	{ SIG_SCI32,           3, MAP_CALL(StringPutAt),               "[or]i(i*)",            NULL },
+	// StringFree accepts invalid references
+	{ SIG_SCI32,           4, MAP_CALL(StringFree),                "[or0!]",               NULL },
+	{ SIG_SCI32,           5, MAP_CALL(StringFill),                "[or]ii",               NULL },
+	{ SIG_SCI32,           6, MAP_CALL(StringCopy),                "[or]i[or]ii",          NULL },
+	{ SIG_SCI32,           7, MAP_CALL(StringCompare),             "[or][or](i)",          NULL },
+
+	// =SCI2.1 Early and SCI2.1 Middle=
+	{ SIG_UNTIL_SCI21MID,  8, MAP_CALL(StringDup),                 "[or]",                 NULL },
+	{ SIG_UNTIL_SCI21MID,  9, MAP_CALL(StringGetData),             "[or]",                 NULL },
+	{ SIG_UNTIL_SCI21MID, 10, MAP_CALL(StringLen),                 "[or]",                 NULL },
+	{ SIG_UNTIL_SCI21MID, 11, MAP_CALL(StringPrintf),              "[or](.*)",             NULL },
+	{ SIG_UNTIL_SCI21MID, 12, MAP_CALL(StringPrintfBuf),           "[or](.*)",             NULL },
+	{ SIG_UNTIL_SCI21MID, 13, MAP_CALL(StringAtoi),                "[or]",                 NULL },
+	// exact functionality of Trim is unknown atm
+	{ SIG_UNTIL_SCI21MID, 14, MAP_CALL(StringTrim),                "[or]",                 NULL },
+	{ SIG_UNTIL_SCI21MID, 15, MAP_CALL(StringUpper),               "[or]",                 NULL },
+	{ SIG_UNTIL_SCI21MID, 16, MAP_CALL(StringLower),               "[or]",                 NULL },
+	// the following 2 are unknown atm (happen in Phantasmagoria)
+	// possibly translate?
+	{ SIG_UNTIL_SCI21MID, 17, MAP_CALL(StringTrn),                 "[or]",                 NULL },
+	{ SIG_UNTIL_SCI21MID, 18, MAP_CALL(StringTrnExclude),          "[or]",                 NULL },
+
+	// SCI2.1 Late + SCI3 - kStringDup + kStringGetData were removed
+	{ SIG_SCI32,           8, MAP_CALL(StringLen),                 "[or]",                 NULL },
+	{ SIG_SCI32,           9, MAP_CALL(StringPrintf),              "[or](.*)",             NULL },
+	{ SIG_SCI32,          10, MAP_CALL(StringPrintfBuf),           "[or](.*)",             NULL },
+	{ SIG_SCI32,          11, MAP_CALL(StringAtoi),                "[or]",                 NULL },
+	{ SIG_SCI32,          12, MAP_CALL(StringTrim),                "[or]",                 NULL },
+	{ SIG_SCI32,          13, MAP_CALL(StringUpper),               "[or]",                 NULL },
+	{ SIG_SCI32,          14, MAP_CALL(StringLower),               "[or]",                 NULL },
+	{ SIG_SCI32,          15, MAP_CALL(StringTrn),                 "[or]",                 NULL },
+	{ SIG_SCI32,          16, MAP_CALL(StringTrnExclude),          "[or]",                 NULL },
 	SCI_SUBOPENTRY_TERMINATOR
 };
+
 #endif
 
 struct SciKernelMapEntry {
@@ -429,7 +482,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(Said),              SIG_EVERYWHERE,           "[r0]",                  NULL,            NULL },
 	{ MAP_CALL(SaveGame),          SIG_EVERYWHERE,           "[r0]i[r0](r0)",         NULL,            NULL },
 	{ MAP_CALL(ScriptID),          SIG_EVERYWHERE,           "[io](i)",               NULL,            NULL },
-	{ MAP_CALL(SetCursor),         SIG_SCI21, SIGFOR_ALL,    "i(i)([io])(i*)",        NULL,            NULL },
+	{ MAP_CALL(SetCursor),         SIG_SINCE_SCI21, SIGFOR_ALL, "i(i)([io])(i*)",     NULL,            NULL },
 	// TODO: SCI2.1 may supply an object optionally (mother goose sci21 right on startup) - find out why
 	{ MAP_CALL(SetCursor),         SIG_SCI11, SIGFOR_ALL,    "i(i)(i)(i)(iiiiii)",    NULL,            NULL },
 	{ MAP_CALL(SetCursor),         SIG_EVERYWHERE,           "i(i)(i)(i)(i)",         NULL,            kSetCursor_workarounds },
@@ -513,7 +566,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	// our garbage collector (i.e. the SCI0-SCI1.1 semantics).
 	{ "Purge", kFlushResources,    SIG_EVERYWHERE,           "i",                     NULL,            NULL },
 	{ MAP_CALL(SetShowStyle),      SIG_EVERYWHERE,           "ioiiiii([ri])(i)",      NULL,            NULL },
-	{ MAP_CALL(String),            SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
+	{ MAP_CALL(String),            SIG_EVERYWHERE,           "(.*)",                  kString_subops,  NULL },
 	{ MAP_CALL(UpdatePlane),       SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(UpdateScreenItem),  SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(ObjectIntersect),   SIG_EVERYWHERE,           "oo",                    NULL,            NULL },
@@ -570,7 +623,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	// SCI2.1 Kernel Functions
 	{ MAP_CALL(CD),                SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 	{ MAP_CALL(IsOnMe),            SIG_EVERYWHERE,           "iioi",                  NULL,            NULL },
-	{ MAP_CALL(List),              SIG_SCI21, SIGFOR_ALL,    "(.*)",                  kList_subops,    NULL },
+	{ MAP_CALL(List),              SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)",               kList_subops,    NULL },
 	{ MAP_CALL(MulDiv),            SIG_EVERYWHERE,           "iii",                   NULL,            NULL },
 	{ MAP_CALL(PlayVMD),           SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 	{ MAP_CALL(Robot),             SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index 8e16e0a..eb052dd 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -58,7 +58,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
 		// In case we use a simulated event we query the current mouse position
 		mousePos = g_sci->_gfxCursor->getPosition();
 #ifdef ENABLE_SCI32
-		if (getSciVersion() >= SCI_VERSION_2_1)
+		if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
 			g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x);
 #endif
 		// Limit the mouse cursor position, if necessary
@@ -84,7 +84,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
 	// For a real event we use its associated mouse position
 	mousePos = curEvent.mousePos;
 #ifdef ENABLE_SCI32
-	if (getSciVersion() >= SCI_VERSION_2_1)
+	if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
 		g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x);
 #endif
 	// Limit the mouse cursor position, if necessary
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index 448e641..1d9dae6 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -487,7 +487,7 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) {
 		// In SCI1, its usage is still unknown
 		// In SCI1.1, it's NOP
 		// In SCI32, it's used for remapping cursor ID's
-		if (getSciVersion() >= SCI_VERSION_2_1) // Set Mac cursor remap
+		if (getSciVersion() >= SCI_VERSION_2_1_EARLY) // Set Mac cursor remap
 			g_sci->_gfxCursor->setMacCursorRemapList(argc - 1, argv + 1);
 		else if (getSciVersion() != SCI_VERSION_1_1)
 			warning("Unknown SCI1 kMacPlatform(0) call");
diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index 5c27178..303de07 100644
--- a/engines/sci/engine/kscripts.cpp
+++ b/engines/sci/engine/kscripts.cpp
@@ -229,7 +229,7 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) {
 	uint16 address = scr->validateExportFunc(index, true);
 
 	// Point to the heap for SCI1.1 - SCI2.1 games
-	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1)
+	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE)
 		address += scr->getScriptSize();
 
 	// Bugfix for the intro speed in PQ2 version 1.002.011.
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index 6a1708d..032191e 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -185,7 +185,7 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
 		volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX);
 		debugC(kDebugLevelSound, "kDoAudio: set volume to %d", volume);
 #ifdef ENABLE_SCI32
-		if (getSciVersion() >= SCI_VERSION_2_1) {
+		if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
 			int16 volumePrev = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2;
 			volumePrev = CLIP<int16>(volumePrev, 0, AUDIO_VOLUME_MAX);
 			mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index eef758a..cd0d6af 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -674,189 +674,230 @@ reg_t kText(EngineState *s, int argc, reg_t *argv) {
 	return s->r_acc;
 }
 
-reg_t kString(EngineState *s, int argc, reg_t *argv) {
-	uint16 op = argv[0].toUint16();
+// TODO: there is an unused second argument, happens at least in LSL6 right during the intro
+reg_t kStringNew(EngineState *s, int argc, reg_t *argv) {
+	reg_t stringHandle;
+	SciString *string = s->_segMan->allocateString(&stringHandle);
+	string->setSize(argv[0].toUint16());
 
-	if (g_sci->_features->detectSci2StringFunctionType() == kSci2StringFunctionNew) {
-		if (op >= 8)	// Dup, GetData have been removed
-			op += 2;
-	}
+	// Make sure the first character is a null character
+	if (string->getSize() > 0)
+		string->setValue(0, 0);
 
-	switch (op) {
-	case 0: { // New
-		reg_t stringHandle;
-		SciString *string = s->_segMan->allocateString(&stringHandle);
-		string->setSize(argv[1].toUint16());
+	return stringHandle;
+}
 
-		// Make sure the first character is a null character
-		if (string->getSize() > 0)
-			string->setValue(0, 0);
+reg_t kStringSize(EngineState *s, int argc, reg_t *argv) {
+	return make_reg(0, s->_segMan->getString(argv[0]).size());
+}
 
-		return stringHandle;
-		}
-	case 1: // Size
-		return make_reg(0, s->_segMan->getString(argv[1]).size());
-	case 2: { // At (return value at an index)
-		// Note that values are put in bytes to avoid sign extension
-		if (argv[1].getSegment() == s->_segMan->getStringSegmentId()) {
-			SciString *string = s->_segMan->lookupString(argv[1]);
-			byte val = string->getRawData()[argv[2].toUint16()];
-			return make_reg(0, val);
-		} else {
-			Common::String string = s->_segMan->getString(argv[1]);
-			byte val = string[argv[2].toUint16()];
-			return make_reg(0, val);
-		}
+// At (return value at an index)
+reg_t kStringAt(EngineState *s, int argc, reg_t *argv) {
+	// Note that values are put in bytes to avoid sign extension
+	if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) {
+		SciString *string = s->_segMan->lookupString(argv[0]);
+		byte val = string->getRawData()[argv[1].toUint16()];
+		return make_reg(0, val);
+	} else {
+		Common::String string = s->_segMan->getString(argv[0]);
+		byte val = string[argv[1].toUint16()];
+		return make_reg(0, val);
 	}
-	case 3: { // Atput (put value at an index)
-		SciString *string = s->_segMan->lookupString(argv[1]);
+}
 
-		uint32 index = argv[2].toUint16();
-		uint32 count = argc - 3;
+// Atput (put value at an index)
+reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv) {
+	SciString *string = s->_segMan->lookupString(argv[0]);
 
-		if (index + count > 65535)
-			break;
+	uint32 index = argv[1].toUint16();
+	uint32 count = argc - 2;
 
-		if (string->getSize() < index + count)
-			string->setSize(index + count);
+	if (index + count > 65535)
+		return NULL_REG;
 
-		for (uint16 i = 0; i < count; i++)
-			string->setValue(i + index, argv[i + 3].toUint16());
+	if (string->getSize() < index + count)
+		string->setSize(index + count);
 
-		return argv[1]; // We also have to return the handle
-	}
-	case 4: // Free
-		// Freeing of strings is handled by the garbage collector
-		return s->r_acc;
-	case 5: { // Fill
-		SciString *string = s->_segMan->lookupString(argv[1]);
-		uint16 index = argv[2].toUint16();
+	for (uint16 i = 0; i < count; i++)
+		string->setValue(i + index, argv[i + 2].toUint16());
+
+	return argv[0]; // We also have to return the handle
+}
+
+reg_t kStringFree(EngineState *s, int argc, reg_t *argv) {
+	// Freeing of strings is handled by the garbage collector
+	return s->r_acc;
+}
+
+reg_t kStringFill(EngineState *s, int argc, reg_t *argv) {
+	SciString *string = s->_segMan->lookupString(argv[0]);
+	uint16 index = argv[1].toUint16();
+
+	// A count of -1 means fill the rest of the array
+	uint16 count = argv[2].toSint16() == -1 ? string->getSize() - index : argv[2].toUint16();
+	uint16 stringSize = string->getSize();
 
-		// A count of -1 means fill the rest of the array
-		uint16 count = argv[3].toSint16() == -1 ? string->getSize() - index : argv[3].toUint16();
-		uint16 stringSize = string->getSize();
+	if (stringSize < index + count)
+		string->setSize(index + count);
 
-		if (stringSize < index + count)
-			string->setSize(index + count);
+	for (uint16 i = 0; i < count; i++)
+		string->setValue(i + index, argv[3].toUint16());
 
-		for (uint16 i = 0; i < count; i++)
-			string->setValue(i + index, argv[4].toUint16());
+	return argv[0];
+}
+
+reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) {
+	const char *string2 = 0;
+	uint32 string2Size = 0;
+	Common::String string;
 
-		return argv[1];
+	if (argv[2].getSegment() == s->_segMan->getStringSegmentId()) {
+		SciString *sstr;
+		sstr = s->_segMan->lookupString(argv[2]);
+		string2 = sstr->getRawData();
+		string2Size = sstr->getSize();
+	} else {
+		string = s->_segMan->getString(argv[2]);
+		string2 = string.c_str();
+		string2Size = string.size() + 1;
 	}
-	case 6: { // Cpy
-		const char *string2 = 0;
-		uint32 string2Size = 0;
-		Common::String string;
-
-		if (argv[3].getSegment() == s->_segMan->getStringSegmentId()) {
-			SciString *sstr;
-			sstr = s->_segMan->lookupString(argv[3]);
-			string2 = sstr->getRawData();
-			string2Size = sstr->getSize();
-		} else {
-			string = s->_segMan->getString(argv[3]);
-			string2 = string.c_str();
-			string2Size = string.size() + 1;
+
+	uint32 index1 = argv[1].toUint16();
+	uint32 index2 = argv[3].toUint16();
+
+	if (argv[0] == argv[2]) {
+		// source and destination string are one and the same
+		if (index1 == index2) {
+			// even same index? ignore this call
+			// Happens in KQ7, when starting a chapter
+			return argv[0];
 		}
+		// TODO: this will crash, when setSize() is triggered later
+		// we need to exactly replicate original interpreter behavior
+		warning("kString(Copy): source is the same as destination string");
+	}
 
-		uint32 index1 = argv[2].toUint16();
-		uint32 index2 = argv[4].toUint16();
+	// The original engine ignores bad copies too
+	if (index2 > string2Size)
+		return NULL_REG;
 
-		// The original engine ignores bad copies too
-		if (index2 > string2Size)
-			break;
+	// A count of -1 means fill the rest of the array
+	uint32 count = argv[4].toSint16() == -1 ? string2Size - index2 + 1 : argv[4].toUint16();
+//	reg_t strAddress = argv[0];
 
-		// A count of -1 means fill the rest of the array
-		uint32 count = argv[5].toSint16() == -1 ? string2Size - index2 + 1 : argv[5].toUint16();
-		reg_t strAddress = argv[1];
+	SciString *string1 = s->_segMan->lookupString(argv[0]);
+	//SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress);
 
-		SciString *string1 = s->_segMan->lookupString(argv[1]);
-		//SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress);
+	if (string1->getSize() < index1 + count)
+		string1->setSize(index1 + count);
 
-		if (string1->getSize() < index1 + count)
-			string1->setSize(index1 + count);
+	// Note: We're accessing from c_str() here because the
+	// string's size ignores the trailing 0 and therefore
+	// triggers an assert when doing string2[i + index2].
+	for (uint16 i = 0; i < count; i++)
+		string1->setValue(i + index1, string2[i + index2]);
 
-		// Note: We're accessing from c_str() here because the
-		// string's size ignores the trailing 0 and therefore
-		// triggers an assert when doing string2[i + index2].
-		for (uint16 i = 0; i < count; i++)
-			string1->setValue(i + index1, string2[i + index2]);
+	return argv[0];
+}
 
-		return strAddress;
-	}
-	case 7: { // Cmp
-		Common::String string1 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]);
-		Common::String string2 = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
-
-		if (argc == 4) // Strncmp
-			return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[3].toUint16()));
-		else           // Strcmp
-			return make_reg(0, strcmp(string1.c_str(), string2.c_str()));
-	}
-	case 8: { // Dup
-		reg_t stringHandle;
+reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) {
+	Common::String string1 = argv[0].isNull() ? "" : s->_segMan->getString(argv[0]);
+	Common::String string2 = argv[1].isNull() ? "" : s->_segMan->getString(argv[1]);
 
-		SciString *dupString = s->_segMan->allocateString(&stringHandle);
+	if (argc == 3) // Strncmp
+		return make_reg(0, strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16()));
+	else           // Strcmp
+		return make_reg(0, strcmp(string1.c_str(), string2.c_str()));
+}
 
-		if (argv[1].getSegment() == s->_segMan->getStringSegmentId()) {
-			*dupString = *s->_segMan->lookupString(argv[1]);
-		} else {
-			dupString->fromString(s->_segMan->getString(argv[1]));
-		}
+// was removed for SCI2.1 Late+
+reg_t kStringDup(EngineState *s, int argc, reg_t *argv) {
+	reg_t stringHandle;
 
-		return stringHandle;
-	}
-	case 9: // Getdata
-		if (!s->_segMan->isHeapObject(argv[1]))
-			return argv[1];
-
-		return readSelector(s->_segMan, argv[1], SELECTOR(data));
-	case 10: // Stringlen
-		return make_reg(0, s->_segMan->strlen(argv[1]));
-	case 11: { // Printf
-		reg_t stringHandle;
-		s->_segMan->allocateString(&stringHandle);
-
-		reg_t *adjustedArgs = new reg_t[argc];
-		adjustedArgs[0] = stringHandle;
-		memcpy(&adjustedArgs[1], argv + 1, (argc - 1) * sizeof(reg_t));
-
-		kFormat(s, argc, adjustedArgs);
-		delete[] adjustedArgs;
-		return stringHandle;
-		}
-	case 12: // Printf Buf
-		return kFormat(s, argc - 1, argv + 1);
-	case 13: { // atoi
-		Common::String string = s->_segMan->getString(argv[1]);
-		return make_reg(0, (uint16)atoi(string.c_str()));
-	}
-	// New subops in SCI2.1 late / SCI3
-	case 14:	// unknown
-		warning("kString, subop %d", op);
-		return NULL_REG;
-	case 15: { // upper
-		Common::String string = s->_segMan->getString(argv[1]);
+	SciString *dupString = s->_segMan->allocateString(&stringHandle);
 
-		string.toUppercase();
-		s->_segMan->strcpy(argv[1], string.c_str());
-		return NULL_REG;
+	if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) {
+		*dupString = *s->_segMan->lookupString(argv[0]);
+	} else {
+		dupString->fromString(s->_segMan->getString(argv[0]));
 	}
-	case 16: { // lower
-		Common::String string = s->_segMan->getString(argv[1]);
 
-		string.toLowercase();
-		s->_segMan->strcpy(argv[1], string.c_str());
-		return NULL_REG;
-	}
-	default:
-		error("Unknown kString subop %d", argv[0].toUint16());
-	}
+	return stringHandle;
+}
+
+// was removed for SCI2.1 Late+
+reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) {
+	if (!s->_segMan->isHeapObject(argv[0]))
+		return argv[0];
+
+	return readSelector(s->_segMan, argv[0], SELECTOR(data));
+}
+
+reg_t kStringLen(EngineState *s, int argc, reg_t *argv) {
+	return make_reg(0, s->_segMan->strlen(argv[0]));
+}
+
+reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv) {
+	reg_t stringHandle;
+	s->_segMan->allocateString(&stringHandle);
+
+	reg_t *adjustedArgs = new reg_t[argc + 1];
+	adjustedArgs[0] = stringHandle;
+	memcpy(&adjustedArgs[1], argv, argc * sizeof(reg_t));
+
+	kFormat(s, argc + 1, adjustedArgs);
+	delete[] adjustedArgs;
+	return stringHandle;
+}
+
+reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv) {
+	return kFormat(s, argc, argv);
+}
 
+reg_t kStringAtoi(EngineState *s, int argc, reg_t *argv) {
+	Common::String string = s->_segMan->getString(argv[0]);
+	return make_reg(0, (uint16)atoi(string.c_str()));
+}
+
+reg_t kStringTrim(EngineState *s, int argc, reg_t *argv) {
+	warning("kStringTrim (argc = %d)", argc);
+	return NULL_REG;
+}
+
+reg_t kStringUpper(EngineState *s, int argc, reg_t *argv) {
+	Common::String string = s->_segMan->getString(argv[0]);
+
+	string.toUppercase();
+	s->_segMan->strcpy(argv[0], string.c_str());
+	return NULL_REG;
+}
+
+reg_t kStringLower(EngineState *s, int argc, reg_t *argv) {
+	Common::String string = s->_segMan->getString(argv[0]);
+
+	string.toLowercase();
+	s->_segMan->strcpy(argv[0], string.c_str());
 	return NULL_REG;
 }
 
+// Possibly kStringTranslate?
+reg_t kStringTrn(EngineState *s, int argc, reg_t *argv) {
+	warning("kStringTrn (argc = %d)", argc);
+	return NULL_REG;
+}
+
+// Possibly kStringTranslateExclude?
+reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv) {
+	warning("kStringTrnExclude (argc = %d)", argc);
+	return NULL_REG;
+}
+
+reg_t kString(EngineState *s, int argc, reg_t *argv) {
+	if (!s)
+		return make_reg(0, getSciVersion());
+	error("not supposed to call this");
+}
+
 #endif
 
 } // End of namespace Sci
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index f925111..6920466 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -181,7 +181,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
 		// for the video, so we'll just play it from there for now.
 
 #ifdef ENABLE_SCI32
-		if (getSciVersion() >= SCI_VERSION_2_1) {
+		if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
 			// SCI2.1 always has argv[0] as 1, the rest of the arguments seem to
 			// follow SCI1.1/2.
 			if (argv[0].toUint16() != 1)
diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp
index 640175b..5300b72 100644
--- a/engines/sci/engine/message.cpp
+++ b/engines/sci/engine/message.cpp
@@ -182,7 +182,7 @@ bool MessageState::getRecord(CursorStack &stack, bool recurse, MessageRecord &re
 #ifdef ENABLE_SCI32
 	case 5: // v5 seems to be compatible with v4
 		// SCI32 Mac is different than SCI32 DOS/Win here
-		if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1)
+		if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY)
 			reader = new MessageReaderV4_MacSCI32(res->data, res->size);
 		else
 #endif
diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp
index eeff451..0626c08 100644
--- a/engines/sci/engine/object.cpp
+++ b/engines/sci/engine/object.cpp
@@ -45,7 +45,7 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme
 		return false;
 	}
 	block[idx].setSegment(segment); // Perform relocation
-	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1)
+	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE)
 		block[idx].incOffset(scriptSize);
 
 	return true;
@@ -63,7 +63,7 @@ void Object::init(byte *buf, reg_t obj_pos, bool initVariables) {
 		for (int i = 0; i < _methodCount * 2 + 2; ++i) {
 			_baseMethod.push_back(READ_SCI11ENDIAN_UINT16(data + READ_LE_UINT16(data + kOffsetFunctionArea) + i * 2));
 		}
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		_variables.resize(READ_SCI11ENDIAN_UINT16(data + 2));
 		_baseVars = (const uint16 *)(buf + READ_SCI11ENDIAN_UINT16(data + 4));
 		_methodCount = READ_SCI11ENDIAN_UINT16(buf + READ_SCI11ENDIAN_UINT16(data + 6));
@@ -75,7 +75,7 @@ void Object::init(byte *buf, reg_t obj_pos, bool initVariables) {
 	}
 
 	if (initVariables) {
-		if (getSciVersion() <= SCI_VERSION_2_1) {
+		if (getSciVersion() <= SCI_VERSION_2_1_LATE) {
 			for (uint i = 0; i < _variables.size(); i++)
 				_variables[i] = make_reg(0, READ_SCI11ENDIAN_UINT16(data + (i * 2)));
 		} else {
@@ -92,7 +92,7 @@ int Object::locateVarSelector(SegManager *segMan, Selector slc) const {
 	const byte *buf = 0;
 	uint varnum = 0;
 
-	if (getSciVersion() <= SCI_VERSION_2_1) {
+	if (getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		const Object *obj = getClass(segMan);
 		varnum = getSciVersion() <= SCI_VERSION_1_LATE ? getVarCount() : obj->getVariable(1).toUint16();
 		buf = (const byte *)obj->_baseVars;
diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h
index 00fe7c6..0ae7ed2 100644
--- a/engines/sci/engine/object.h
+++ b/engines/sci/engine/object.h
@@ -79,42 +79,42 @@ public:
 	}
 
 	reg_t getSpeciesSelector() const {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			return _variables[_offset];
 		else	// SCI3
 			return _speciesSelectorSci3;
 	}
 
 	void setSpeciesSelector(reg_t value) {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			_variables[_offset] = value;
 		else	// SCI3
 			_speciesSelectorSci3 = value;
 	}
 
 	reg_t getSuperClassSelector() const {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			return _variables[_offset + 1];
 		else	// SCI3
 			return _superClassPosSci3;
 	}
 
 	void setSuperClassSelector(reg_t value) {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			_variables[_offset + 1] = value;
 		else	// SCI3
 			_superClassPosSci3 = value;
 	}
 
 	reg_t getInfoSelector() const {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			return _variables[_offset + 2];
 		else	// SCI3
 			return _infoSelectorSci3;
 	}
 
 	void setInfoSelector(reg_t info) {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			_variables[_offset + 2] = info;
 		else	// SCI3
 			_infoSelectorSci3 = info;
@@ -123,7 +123,7 @@ public:
 	// No setter for the -info- selector
 
 	reg_t getNameSelector() const {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			return _offset + 3 < (uint16)_variables.size() ? _variables[_offset + 3] : NULL_REG;
 		else	// SCI3
 			return _variables.size() ? _variables[0] : NULL_REG;
@@ -132,7 +132,7 @@ public:
 	// No setter for the name selector
 
 	reg_t getPropDictSelector() const {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			return _variables[2];
 		else
 			// This should never occur, this is called from a SCI1.1 - SCI2.1 only function
@@ -140,7 +140,7 @@ public:
 	}
 
 	void setPropDictSelector(reg_t value) {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			_variables[2] = value;
 		else
 			// This should never occur, this is called from a SCI1.1 - SCI2.1 only function
@@ -148,14 +148,14 @@ public:
 	}
 
 	reg_t getClassScriptSelector() const {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			return _variables[4];
 		else	// SCI3
 			return make_reg(0, READ_SCI11ENDIAN_UINT16(_baseObj + 6));
 	}
 
 	void setClassScriptSelector(reg_t value) {
-		if (getSciVersion() <= SCI_VERSION_2_1)
+		if (getSciVersion() < SCI_VERSION_3)
 			_variables[4] = value;
 		else	// SCI3
 			// This should never occur, this is called from a SCI1.1 - SCI2.1 only function
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 93b3a99..b464d34 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -465,7 +465,7 @@ void Script::syncStringHeap(Common::Serializer &s) {
 				break;
 		} while (1);
 
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1){
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE){
 		// Strings in SCI1.1 come after the object instances
 		byte *buf = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2;
 
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp
index 36e33cc..26a7ff5 100644
--- a/engines/sci/engine/script.cpp
+++ b/engines/sci/engine/script.cpp
@@ -84,7 +84,7 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
 
 	if (getSciVersion() == SCI_VERSION_0_EARLY) {
 		_bufSize += READ_LE_UINT16(script->data) * 2;
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		// In SCI1.1 - SCI2.1, the heap was in a separate space from the script. We append
 		// it to the end of the script, and adjust addressing accordingly.
 		// However, since we address the heap with a 16-bit pointer, the
@@ -142,7 +142,7 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
 	assert(_bufSize >= script->size);
 	memcpy(_buf, script->data, script->size);
 
-	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		Resource *heap = resMan->findResource(ResourceId(kResourceTypeHeap, _nr), 0);
 		assert(heap != 0);
 
@@ -171,7 +171,7 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP
 			_localsOffset = localsBlock - _buf + 4;
 			_localsCount = (READ_LE_UINT16(_buf + _localsOffset - 2) - 4) >> 1;	// half block size
 		}
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		if (READ_LE_UINT16(_buf + 1 + 5) > 0) {	// does the script have an export table?
 			_exportTable = (const uint16 *)(_buf + 1 + 5 + 2);
 			_numExports = READ_SCI11ENDIAN_UINT16(_exportTable - 1);
@@ -387,7 +387,7 @@ void Script::identifyOffsets() {
 			scriptDataLeft -= blockSize;
 		} while (1);
 
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		// Strings in SCI1.1 up to SCI2 come after the object instances
 		scriptDataPtr = _heapStart;
 		scriptDataLeft = _heapSize;
@@ -668,7 +668,7 @@ static bool relocateBlock(Common::Array<reg_t> &block, int block_location, Segme
 		return false;
 	}
 	block[idx].setSegment(segment); // Perform relocation
-	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1)
+	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE)
 		block[idx].incOffset(scriptSize);
 
 	return true;
@@ -702,7 +702,7 @@ void Script::relocateSci0Sci21(reg_t block) {
 	uint16 heapSize = (uint16)_bufSize;
 	uint16 heapOffset = 0;
 
-	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		heap = _heapStart;
 		heapSize = (uint16)_heapSize;
 		heapOffset = _scriptSize;
@@ -930,7 +930,7 @@ void Script::initializeClasses(SegManager *segMan) {
 	if (getSciVersion() <= SCI_VERSION_1_LATE) {
 		seeker = findBlockSCI0(SCI_OBJ_CLASS);
 		mult = 1;
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		seeker = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2;
 		mult = 2;
 	} else if (getSciVersion() == SCI_VERSION_3) {
@@ -962,7 +962,7 @@ void Script::initializeClasses(SegManager *segMan) {
 			if (isClass)
 				species = READ_SCI11ENDIAN_UINT16(seeker + 12);
 			classpos += 12;
-		} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+		} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 			isClass = (READ_SCI11ENDIAN_UINT16(seeker + 14) & kInfoFlagClass);	// -info- selector
 			species = READ_SCI11ENDIAN_UINT16(seeker + 10);
 		} else if (getSciVersion() == SCI_VERSION_3) {
@@ -1104,7 +1104,7 @@ void Script::initializeObjectsSci3(SegManager *segMan, SegmentId segmentId) {
 void Script::initializeObjects(SegManager *segMan, SegmentId segmentId) {
 	if (getSciVersion() <= SCI_VERSION_1_LATE)
 		initializeObjectsSci0(segMan, segmentId);
-	else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1)
+	else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE)
 		initializeObjectsSci11(segMan, segmentId);
 	else if (getSciVersion() == SCI_VERSION_3)
 		initializeObjectsSci3(segMan, segmentId);
diff --git a/engines/sci/engine/static_selectors.cpp b/engines/sci/engine/static_selectors.cpp
index 188da3d..8aa1697 100644
--- a/engines/sci/engine/static_selectors.cpp
+++ b/engines/sci/engine/static_selectors.cpp
@@ -114,16 +114,16 @@ static const SelectorRemap sciSelectorRemap[] = {
 	{        SCI_VERSION_1_1,        SCI_VERSION_1_1, "cantBeHere",   54 },
 	// The following are not really needed. They've only been defined to
 	// ease game debugging.
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1,    "-objID-", 4096 },
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1,     "-size-", 4097 },
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1, "-propDict-", 4098 },
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1, "-methDict-", 4099 },
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1, "-classScript-", 4100 },
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1,   "-script-", 4101 },
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1,    "-super-", 4102 },
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE,    "-objID-", 4096 },
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE,     "-size-", 4097 },
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE, "-propDict-", 4098 },
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE, "-methDict-", 4099 },
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE, "-classScript-", 4100 },
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE,   "-script-", 4101 },
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE,    "-super-", 4102 },
 	//
-	{        SCI_VERSION_1_1,        SCI_VERSION_2_1,     "-info-", 4103 },
-	{ SCI_VERSION_NONE,             SCI_VERSION_NONE,            0,    0 }
+	{        SCI_VERSION_1_1,        SCI_VERSION_2_1_LATE,     "-info-", 4103 },
+	{        SCI_VERSION_NONE,           SCI_VERSION_NONE,            0,    0 }
 };
 
 struct ClassReference {
diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp
index 65a8283..cf008c4 100644
--- a/engines/sci/engine/vm_types.cpp
+++ b/engines/sci/engine/vm_types.cpp
@@ -29,7 +29,7 @@
 namespace Sci {
 
 SegmentId reg_t::getSegment() const {
-	if (getSciVersion() <= SCI_VERSION_2_1) {
+	if (getSciVersion() < SCI_VERSION_3) {
 		return _segment;
 	} else {
 		// Return the lower 14 bits of the segment
@@ -38,7 +38,7 @@ SegmentId reg_t::getSegment() const {
 }
 
 void reg_t::setSegment(SegmentId segment) {
-	if (getSciVersion() <= SCI_VERSION_2_1) {
+	if (getSciVersion() < SCI_VERSION_3) {
 		_segment = segment;
 	} else {
 		// Set the lower 14 bits of the segment, and preserve the upper 2 ones for the offset
@@ -47,7 +47,7 @@ void reg_t::setSegment(SegmentId segment) {
 }
 
 uint32 reg_t::getOffset() const {
-	if (getSciVersion() <= SCI_VERSION_2_1) {
+	if (getSciVersion() < SCI_VERSION_3) {
 		return _offset;
 	} else {
 		// Return the lower 16 bits from the offset, and the 17th and 18th bits from the segment
@@ -56,7 +56,7 @@ uint32 reg_t::getOffset() const {
 }
 
 void reg_t::setOffset(uint32 offset) {
-	if (getSciVersion() <= SCI_VERSION_2_1) {
+	if (getSciVersion() < SCI_VERSION_3) {
 		_offset = offset;
 	} else {
 		// Store the lower 16 bits in the offset, and the 17th and 18th bits in the segment
diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp
index 3c2285a..716a366 100644
--- a/engines/sci/graphics/compare.cpp
+++ b/engines/sci/graphics/compare.cpp
@@ -130,7 +130,7 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
 #ifdef ENABLE_SCI32
 	if (view->isSci2Hires())
 		view->adjustToUpscaledCoordinates(y, x);
-	else if (getSciVersion() == SCI_VERSION_2_1)
+	else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE))
 		_coordAdjuster->fromScriptToDisplay(y, x);
 #endif
 
@@ -140,7 +140,7 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) {
 	if (view->isSci2Hires()) {
 		view->adjustBackUpscaledCoordinates(celRect.top, celRect.left);
 		view->adjustBackUpscaledCoordinates(celRect.bottom, celRect.right);
-	} else if (getSciVersion() == SCI_VERSION_2_1) {
+	} else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) {
 		_coordAdjuster->fromDisplayToScript(celRect.top, celRect.left);
 		_coordAdjuster->fromDisplayToScript(celRect.bottom, celRect.right);
 	}
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index ccc362d..9628e8a 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -727,7 +727,7 @@ void GfxFrameout::kernelFrameout() {
 				if (view && view->isSci2Hires()) {
 					view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x);
 					view->adjustToUpscaledCoordinates(itemEntry->z, dummyX);
-				} else if (getSciVersion() >= SCI_VERSION_2_1) {
+				} else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
 					_coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
 					_coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX);
 				}
@@ -782,7 +782,7 @@ void GfxFrameout::kernelFrameout() {
 						view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left);
 						view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right);
 						g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
-					} else if (getSciVersion() >= SCI_VERSION_2_1 && _resMan->detectHires()) {
+					} else if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) {
 						_coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left);
 						_coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right);
 						g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp
index da61ecf..2ee18b5 100644
--- a/engines/sci/graphics/view.cpp
+++ b/engines/sci/graphics/view.cpp
@@ -356,7 +356,7 @@ void GfxView::initData(GuiResourceId resourceId) {
 			for (loopNo = 0; loopNo < _loopCount; loopNo++)
 				for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
 					_screen->adjustBackUpscaledCoordinates(_loop[loopNo].cel[celNo].scriptWidth, _loop[loopNo].cel[celNo].scriptHeight, _sci2ScaleRes);
-		} else if (getSciVersion() == SCI_VERSION_2_1) {
+		} else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) {
 			for (loopNo = 0; loopNo < _loopCount; loopNo++)
 				for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
 					_coordAdjuster->fromDisplayToScript(_loop[loopNo].cel[celNo].scriptHeight, _loop[loopNo].cel[celNo].scriptWidth);
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 10740a8..ad40ae9 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -82,8 +82,12 @@ const char *getSciVersionDesc(SciVersion version) {
 		return "SCI1.1";
 	case SCI_VERSION_2:
 		return "SCI2";
-	case SCI_VERSION_2_1:
-		return "SCI2.1";
+	case SCI_VERSION_2_1_EARLY:
+		return "Early SCI2.1";
+	case SCI_VERSION_2_1_MIDDLE:
+		return "Middle SCI2.1";
+	case SCI_VERSION_2_1_LATE:
+		return "Late SCI2.1";
 	case SCI_VERSION_3:
 		return "SCI3";
 	default:
@@ -2121,6 +2125,79 @@ ViewType ResourceManager::detectViewType() {
 	return kViewUnknown;
 }
 
+// to detect selector "wordFail" in LE vocab resource
+static const byte detectSci21EarlySignature[] = {
+	10, // size of signature
+	0x08, 0x00, 'w', 'o', 'r', 'd', 'F', 'a', 'i', 'l'
+};
+
+// to detect selector "wordFail" in BE vocab resource (SCI2.1 Early)
+static const byte detectSci21EarlyBESignature[] = {
+	10, // size of signature
+	0x00, 0x08, 'w', 'o', 'r', 'd', 'F', 'a', 'i', 'l'
+};
+
+// to detect new kString calling to detect SCI2.1 Late
+static const byte detectSci21NewStringSignature[] = {
+	8, // size of signature
+	0x78, // push1
+	0x78, // push1
+	0x39, 0x09, // pushi 09
+	0x59, 0x01, // rest 01
+	0x43, 0x5c, // callk String
+};
+
+bool ResourceManager::checkResourceDataForSignature(Resource *resource, const byte *signature) {
+	byte signatureSize = *signature;
+	const byte *resourceData = resource->data;
+
+	signature++; // skip over size byte
+	if (signatureSize < 4)
+		error("resource signature is too small, internal error");
+	if (signatureSize > resource->size)
+		return false;
+
+	const uint32 signatureDWord = *((uint32 *)signature);
+	signature += 4; signatureSize -= 4;
+
+	const uint32 searchLimit = resource->size - signatureSize + 1;
+	uint32 DWordOffset = 0;
+	while (DWordOffset < searchLimit) {
+		if (signatureDWord == READ_UINT32(resourceData + DWordOffset)) {
+			// magic DWORD found, check if the rest matches as well
+			uint32 offset = DWordOffset + 4;
+			uint32 signaturePos  = 0;
+			while (signaturePos < signatureSize) {
+				if (resourceData[offset] != signature[signaturePos])
+					break;
+				offset++;
+				signaturePos++;
+			}
+			if (signaturePos >= signatureSize)
+				return true; // signature found
+		}
+		DWordOffset++;
+	}
+	return false;
+}
+
+bool ResourceManager::checkResourceForSignatures(ResourceType resourceType, uint16 resourceNr, const byte *signature1, const byte *signature2) {
+	Resource *resource = findResource(ResourceId(resourceType, resourceNr), false);
+
+	if (resource) {
+		// resource found and loaded, check for signatures
+		if (signature1) {
+			if (checkResourceDataForSignature(resource, signature1))
+				return true;
+		}
+		if (signature2) {
+			if (checkResourceDataForSignature(resource, signature2))
+				return true;
+		}
+	}
+	return false;
+}
+
 void ResourceManager::detectSciVersion() {
 	// We use the view compression to set a preliminary s_sciVersion for the sake of getResourceInfo
 	// Pretend we have a SCI0 game
@@ -2180,31 +2257,52 @@ void ResourceManager::detectSciVersion() {
 		// no Mac SCI2 games. Yes, that means that GK1 Mac is SCI2.1 and not SCI2.
 
 		// TODO: Decide between SCI2.1 and SCI3
-		if (res)
-			s_sciVersion = SCI_VERSION_2_1;
-		else
+		if (res) {
+			s_sciVersion = SCI_VERSION_2_1_EARLY; // we check for SCI2.1 specifics a bit later
+		} else {
 			s_sciVersion = SCI_VERSION_1_1;
-		return;
+			return;
+		}
 	}
 
 	// Handle SCI32 versions here
-	if (_volVersion >= kResVersionSci2) {
-		Common::List<ResourceId> heaps = listResources(kResourceTypeHeap);
-		bool hasHeapResources = !heaps.empty();
-
-		// SCI2.1/3 and SCI1 Late resource maps are the same, except that
-		// SCI1 Late resource maps have the resource types or'd with
-		// 0x80. We differentiate between SCI2 and SCI2.1/3 based on that.
-		if (_mapVersion == kResVersionSci1Late) {
-			s_sciVersion = SCI_VERSION_2;
-			return;
-		} else if (hasHeapResources) {
-			s_sciVersion = SCI_VERSION_2_1;
+	if (s_sciVersion != SCI_VERSION_2_1_EARLY) {
+		if (_volVersion >= kResVersionSci2) {
+			Common::List<ResourceId> heaps = listResources(kResourceTypeHeap);
+			bool hasHeapResources = !heaps.empty();
+
+			// SCI2.1/3 and SCI1 Late resource maps are the same, except that
+			// SCI1 Late resource maps have the resource types or'd with
+			// 0x80. We differentiate between SCI2 and SCI2.1/3 based on that.
+			if (_mapVersion == kResVersionSci1Late) {
+				s_sciVersion = SCI_VERSION_2;
+				return;
+			} else if (hasHeapResources) {
+				s_sciVersion = SCI_VERSION_2_1_EARLY; // exact SCI2.1 version is checked a bit later
+			} else {
+				s_sciVersion = SCI_VERSION_3;
+				return;
+			}
+		}
+	}
+
+	if (s_sciVersion == SCI_VERSION_2_1_EARLY) {
+		// we only know that it's SCI2.1, not which exact version it is
+
+		// check, if selector "wordFail" inside vocab 997 exists, if it does it's SCI2.1 Early
+		if ((checkResourceForSignatures(kResourceTypeVocab, 997, detectSci21EarlySignature, detectSci21EarlyBESignature))) {
+			// found -> it is SCI2.1 early
 			return;
-		} else {
-			s_sciVersion = SCI_VERSION_3;
+		}
+
+		s_sciVersion = SCI_VERSION_2_1_MIDDLE;
+		if (checkResourceForSignatures(kResourceTypeScript, 64918, detectSci21NewStringSignature, nullptr)) {
+			// new kString call detected, it's SCI2.1 late
+			// TODO: this call seems to be different on Mac
+			s_sciVersion = SCI_VERSION_2_1_LATE;
 			return;
 		}
+		return;
 	}
 
 	// Check for transitive SCI1/SCI1.1 games, like PQ1 here
@@ -2537,7 +2635,7 @@ reg_t ResourceManager::findGameObject(bool addSci11ScriptOffset) {
 
 		int16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr);
 		return make_reg(1, offset);
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		offsetPtr = script->data + 4 + 2 + 2;
 
 		// In SCI1.1 - SCI2.1, the heap is appended at the end of the script,
@@ -2565,7 +2663,7 @@ Common::String ResourceManager::findSierraGameId() {
 
 	if (getSciVersion() < SCI_VERSION_1_1) {
 		heap = findResource(ResourceId(kResourceTypeScript, 0), false);
-	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) {
+	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE) {
 		heap = findResource(ResourceId(kResourceTypeHeap, 0), false);
 		nameSelector += 5;
 	} else if (getSciVersion() == SCI_VERSION_3) {
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index ef48998..eb5b508 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -559,6 +559,8 @@ protected:
 	ViewType detectViewType();
 	bool hasSci0Voc999();
 	bool hasSci1Voc900();
+	bool checkResourceDataForSignature(Resource *resource, const byte *signature);
+	bool checkResourceForSignatures(ResourceType resourceType, uint16 resourceNr, const byte *signature1, const byte *signature2);
 	void detectSciVersion();
 };
 
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 3a43774..6869e63 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -637,7 +637,7 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
 
 	case SCI_VERSION_1_EARLY:
 	case SCI_VERSION_1_LATE:
-	case SCI_VERSION_2_1:
+	case SCI_VERSION_2_1_EARLY:
 		data = resource->data;
 		// Count # of tracks
 		_trackCount = 0;
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 75f485d..f1ab65e 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -897,7 +897,9 @@ void SciEngine::syncIngameAudioOptions() {
 			break;
 #ifdef ENABLE_SCI32
 		case SCI_VERSION_2:
-		case SCI_VERSION_2_1:
+		case SCI_VERSION_2_1_EARLY:
+		case SCI_VERSION_2_1_MIDDLE:
+		case SCI_VERSION_2_1_LATE:
 			// Only use global 90 for some specific games, not all SCI32 games used this method
 			switch (_gameId) {
 			case GID_KQ7: // SCI2.1
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index c6813aa..15034d5 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -200,7 +200,9 @@ enum SciVersion {
 	SCI_VERSION_1_LATE, // Dr. Brain 1, EcoQuest 1, Longbow, PQ3, SQ1, LSL5, KQ5 CD
 	SCI_VERSION_1_1, // Dr. Brain 2, EcoQuest 1 CD, EcoQuest 2, KQ6, QFG3, SQ4CD, XMAS 1992 and many more
 	SCI_VERSION_2, // GK1, PQ4 floppy, QFG4 floppy
-	SCI_VERSION_2_1, // GK2, KQ7, LSL6 hires, MUMG Deluxe, Phantasmagoria 1, PQ4CD, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin
+	SCI_VERSION_2_1_EARLY, // GK2 demo, KQ7, LSL6 hires, PQ4, QFG4 floppy
+	SCI_VERSION_2_1_MIDDLE, // GK2, KQ7, MUMG Deluxe, Phantasmagoria 1, PQ4CD, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin
+	SCI_VERSION_2_1_LATE, // demos of LSL7, Lighthouse, RAMA
 	SCI_VERSION_3 // LSL7, Lighthouse, RAMA, Phantasmagoria 2
 };
 
diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp
index fb9a3f1..8c029cf 100644
--- a/engines/sci/sound/audio.cpp
+++ b/engines/sci/sound/audio.cpp
@@ -217,7 +217,7 @@ static void deDPCM8Nibble(byte *soundBuf, int32 &s, byte b) {
 	if (b & 8) {
 #ifdef ENABLE_SCI32
 		// SCI2.1 reverses the order of the table values here
-		if (getSciVersion() >= SCI_VERSION_2_1)
+		if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
 			s -= tableDPCM8[b & 7];
 		else
 #endif
diff --git a/engines/sci/sound/drivers/midi.cpp b/engines/sci/sound/drivers/midi.cpp
index baf85de..aa464cd 100644
--- a/engines/sci/sound/drivers/midi.cpp
+++ b/engines/sci/sound/drivers/midi.cpp
@@ -1019,7 +1019,7 @@ int MidiPlayer_Midi::open(ResourceManager *resMan) {
 				if (!isMt32GmPatch(res->data, res->size)) {
 					mapMt32ToGm(res->data, res->size);
 				} else {
-					if (getSciVersion() <= SCI_VERSION_2_1) {
+					if (getSciVersion() < SCI_VERSION_3) {
 						error("MT-32 patch has wrong type");
 					} else {
 						// Happens in the SCI3 interactive demo of Lighthouse
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
index 9f0d8d1..21f95f1 100644
--- a/engines/sci/sound/midiparser_sci.cpp
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -717,7 +717,7 @@ bool MidiParser_SCI::processEvent(const EventInfo &info, bool fireEvents) {
 						break;
 					case SCI_VERSION_1_EARLY:
 					case SCI_VERSION_1_LATE:
-					case SCI_VERSION_2_1:
+					case SCI_VERSION_2_1_EARLY:
 						inc = 1;
 						break;
 					default:
@@ -862,7 +862,7 @@ void MidiParser_SCI::setMasterVolume(byte masterVolume) {
 
 	case SCI_VERSION_1_EARLY:
 	case SCI_VERSION_1_LATE:
-	case SCI_VERSION_2_1:
+	case SCI_VERSION_2_1_EARLY:
 		// directly set master volume (global volume is merged with channel volumes)
 		((MidiPlayer *)_driver)->setVolume(masterVolume);
 		break;
@@ -887,7 +887,7 @@ void MidiParser_SCI::setVolume(byte volume) {
 
 	case SCI_VERSION_1_EARLY:
 	case SCI_VERSION_1_LATE:
-	case SCI_VERSION_2_1:
+	case SCI_VERSION_2_1_EARLY:
 		// Send previous channel volumes again to actually update the volume
 		for (int i = 0; i < 15; i++)
 			if (_channelRemap[i] != -1)
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index dca73c3..31cf27f 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -73,7 +73,7 @@ void SciMusic::init() {
 	// Default to MIDI in SCI2.1+ games, as many don't have AdLib support.
 	// Also, default to MIDI for Windows versions of SCI1.1 games, as their
 	// soundtrack is written for GM.
-	if (getSciVersion() >= SCI_VERSION_2_1 || g_sci->_features->useAltWinGMSound())
+	if (getSciVersion() >= SCI_VERSION_2_1_EARLY || g_sci->_features->useAltWinGMSound())
 		deviceFlags |= MDT_PREFER_GM;
 
 	// Currently our CMS implementation only supports SCI1(.1)
diff --git a/engines/sci/util.cpp b/engines/sci/util.cpp
index 3a9ed9c..c72d3be 100644
--- a/engines/sci/util.cpp
+++ b/engines/sci/util.cpp
@@ -49,7 +49,7 @@ uint16 READ_SCI11ENDIAN_UINT16(const void *ptr) {
 }
 
 uint16 READ_SCI32ENDIAN_UINT16(const void *ptr) {
-	if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1)
+	if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_2_1_EARLY)
 		return READ_BE_UINT16(ptr);
 	else
 		return READ_LE_UINT16(ptr);






More information about the Scummvm-git-logs mailing list