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

csnover csnover at users.noreply.github.com
Fri Sep 30 02:48:32 CEST 2016


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

Summary:
5ab363a436 SCI32: Fix potential null pointer dereference
2fcec86696 SCI32: Fix typo
4d3752f9af SCI32: Fix potential null pointer dereference
327f8f8299 SCI32: Fix potential null pointer dereference
181676a0d5 SCI32: Fix memory leak
78849053ca SCI32: Fix off-by-one error
21ffb62f80 SCI32: Guard against dynamic_cast failure
63345b2b70 SCI32: Fix kShowStyleNone transitions
d0517f515e SCI32: Update screen on frameout, instead of in the event loop
3cf16c0a7f SCI32: Explicitly instantiate MIN/MAX templates
bf7adaa58f SCI32: Fix incorrect warnings about unlocking type 140
560af83300 SCI: Move SciEngine::sleep to correct source file
209fe0f92a SCI32: Fix incorrect use of speedThrottler in Video32
39a3b67522 SCI32: Skip SEQ playback if file not found
22e1f1f015 SCI: Put CD flag on CD games
658fb7f8e4 SCI32: Improved game resolution detection
84d8ac4c38 SCI32: Fix buffer overflow when drawing border to a tiny text bitmap
3cc5e55201 SCI: Fix typo in debugger help
b784357f5d SCI: Fix whitespace
3201440d11 SCI: Generalize code for getting information on the current call
1de74fa514 SCI: Improve debugging output for selector failures
240b0ca348 SCI32: Add a trap for invalid calls to kNumCels
3f91726765 SCI32: Rewrite kArray & kString
88de81a72e SCI32: Patch bad calls to IntArray::newWith in Phant1 and SQ6
2be2629a3b SCI32: Fix kFileIOOpen signature
44dd029cb1 SCI32: Implement kSetHotRectangles
02540b8d38 SCI32: Add support for kPrintDebug
c7dacf273e SCI32: Clean up and fix some SCI32-only kFileIO operations
64dc37cfe2 SCI32: Start implementing kSave for SCI32
90ee2d799d SCI: Split savegame descriptor fill code from savegame list code
57c62c59f9 SCI32: Add constants documentation
a22bfb0db2 SCI32: Fix "new game" and auto-save functionality
10f450917c SCI32: Implement kSave for standard-save SCI32 games
a96dc1a7b8 SCI32: Correct return values for kFileIO operations
b5d0fffb8b SCI: Replace magic numbers for globals with named constants
1eb6d90eec SCI32: Implement ScummVM save/load
c8516ff9eb SCI32: Add handling for save catalogues
b3cfe699f6 SCI32: Fix read overflow when there are no save games
0e53c8e9f3 SCI32: Improve MGDX support
4072714143 SCI32: Increase allowed number of save games in native save/load
25c874b9cd SCI32: Add more debugger support for dumping SCI32 arrays
2629269212 SCI32: Emulate Shivers 1 game score metadata
fba8568484 SCI32: Fix multiple bugs in kSave
aa720ca9cd SCI32: Fix deleting save games in KQ7
0c9d423497 SCI32: Emulate MGDX ego view metadata
c644f817e3 SCI32: Add mark for KQ7 in script patches
7bae02e558 SCI32: Put brackets around rest parameters
3e75f42d85 SCI: Rename "until" to "thru" in kernel call signatures
1847b0f705 SCI32: Fix warnings and incompatible save games when built without SCI32
4d73736d1f SCI32: Fix compilation warnings
2c61569c62 SCI32: Disable "change directory" button in native save/load dialogue
cb66bfead0 SCI32: Fix CelObj scaling in games with hi-res scripts
7011ee8f50 SCI32: Fix Torin inventory crash caused by wrong array type
2a27f27bb4 SCI32: Clean up scaling flags
9a64c0587b SCI32: Always build scaler tables to the maximum possible size
3208f063b5 SCI32: Extra bounds checking in CelObj renderer
9d2397e1e9 SCI32: Implement kBitmapCreateFromView and kBitmapGetInfo
a8009fb0a9 SCI32: Fix crashes in line drawing code
3f6cfff225 SCI32: Fix SQ6 crash in Holocabana control panel
5877542d7e SCI32: Ease debugging of wrong array types
07e1dc3ca5 SCI32: Fix SQ6 crash using the special button in Stooge Fighter 3
2035e21667 SCI32: Add workaround for kNumCels
015a62976c SCI32: Fix whitespace
cf07e0cb7c SCI32: Ensure break to debugger works during transitions
43f908d68b SCI32: Break into debugger after frameout, not before
bde7090708 SCI32: Fix kArray signature
8cb994b801 SCI32: Increase resource manager LRU cache size
6290f1e5fc SCI: Add prefix to global variable constants
8fd19f84c8 SCI: Deduplicate call origin formatting
7ad9418583 SCI32: Fix off-by-one error in palette fades
e412eaffbc SCI32: Clean up and document GfxPalette32
b7f93623c4 SCI32: Fix broken palette cycling in SCI2/2.1early games


Commit: 5ab363a436151090d4ef1701c97ed7ed4f1e5c93
    https://github.com/scummvm/scummvm/commit/5ab363a436151090d4ef1701c97ed7ed4f1e5c93
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix potential null pointer dereference

CID 1351620.

Changed paths:
    engines/sci/graphics/plane32.cpp



diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index 1cd88d6..e7da8b8 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -527,14 +527,8 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force
 
 		if (item != nullptr) {
 			// update item in visiblePlane if item is updated
-			if (
-				item->_updated ||
-				(
-					forceUpdate &&
-					visiblePlane != nullptr &&
-					visiblePlane->_screenItemList.findByObject(item->_object) != nullptr
-				)
-			) {
+			if (visiblePlane != nullptr && (
+				item->_updated || (forceUpdate && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr))) {
 				*visiblePlane->_screenItemList[i] = *item;
 			}
 


Commit: 2fcec86696ae9deaa1eee432ca8615169694bcf0
    https://github.com/scummvm/scummvm/commit/2fcec86696ae9deaa1eee432ca8615169694bcf0
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix typo

CID 1361007.

Changed paths:
    engines/sci/graphics/transitions32.cpp



diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
index 37f608d..c0b1c07 100644
--- a/engines/sci/graphics/transitions32.cpp
+++ b/engines/sci/graphics/transitions32.cpp
@@ -953,7 +953,7 @@ bool GfxTransitions32::processScroll(PlaneScroll &scroll) {
 
 	int deltaX = scroll.deltaX;
 	int deltaY = scroll.deltaY;
-	if (((scroll.x + deltaX) * scroll.y) <= 0) {
+	if (((scroll.x + deltaX) * scroll.x) <= 0) {
 		deltaX = -scroll.x;
 	}
 	if (((scroll.y + deltaY) * scroll.y) <= 0) {


Commit: 4d3752f9af23b64bb477cb858f7035e299fc0b15
    https://github.com/scummvm/scummvm/commit/4d3752f9af23b64bb477cb858f7035e299fc0b15
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix potential null pointer dereference

CID 1357041.

Changed paths:
    engines/sci/graphics/controls32.cpp



diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index 7689655..bdc7248 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -441,6 +441,11 @@ void ScrollWindow::show() {
 	}
 
 	Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
+
+	if (plane == nullptr) {
+		error("[ScrollWindow::show]: Plane %04x:%04x not found", PRINT_REG(_plane));
+	}
+
 	plane->_screenItemList.add(_screenItem);
 
 	_visible = true;


Commit: 327f8f829941e544ef6d97f5ff816d6190c499d1
    https://github.com/scummvm/scummvm/commit/327f8f829941e544ef6d97f5ff816d6190c499d1
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix potential null pointer dereference

CID 1361026.

Changed paths:
    engines/sci/graphics/transitions32.cpp



diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
index c0b1c07..b93f0f7 100644
--- a/engines/sci/graphics/transitions32.cpp
+++ b/engines/sci/graphics/transitions32.cpp
@@ -965,6 +965,10 @@ bool GfxTransitions32::processScroll(PlaneScroll &scroll) {
 
 	Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(scroll.plane);
 
+	if (plane == nullptr) {
+		error("[GfxTransitions32::processScroll]: Plane %04x:%04x not found", PRINT_REG(scroll.plane));
+	}
+
 	if ((scroll.x == 0) && (scroll.y == 0)) {
 		plane->deletePic(scroll.oldPictureId, scroll.newPictureId);
 		finished = true;


Commit: 181676a0d515ab2ae4e7930bea7658e00f51d5d0
    https://github.com/scummvm/scummvm/commit/181676a0d515ab2ae4e7930bea7658e00f51d5d0
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix memory leak

CID 1361032.

Changed paths:
    engines/sci/graphics/transitions32.cpp



diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
index b93f0f7..361c38e 100644
--- a/engines/sci/graphics/transitions32.cpp
+++ b/engines/sci/graphics/transitions32.cpp
@@ -940,8 +940,9 @@ void GfxTransitions32::kernelSetScroll(const reg_t planeId, const int16 deltaX,
 			g_sci->_gfxFrameout->frameOut(true);
 			throttle();
 		}
-		delete scroll;
 	}
+
+	delete scroll;
 }
 
 bool GfxTransitions32::processScroll(PlaneScroll &scroll) {


Commit: 78849053ca8abf203fe3f17972581416e86d334d
    https://github.com/scummvm/scummvm/commit/78849053ca8abf203fe3f17972581416e86d334d
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix off-by-one error

CID 1361599.

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



diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp
index e780d3c..db6ffb1 100644
--- a/engines/sci/engine/klists.cpp
+++ b/engines/sci/engine/klists.cpp
@@ -554,7 +554,7 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) {
 
 	++list->numRecursions;
 
-	if (list->numRecursions > ARRAYSIZE(list->nextNodes)) {
+	if (list->numRecursions >= ARRAYSIZE(list->nextNodes)) {
 		error("Too much recursion in kListEachElementDo");
 	}
 


Commit: 21ffb62f806b556a7faae84c5a221f51baa86087
    https://github.com/scummvm/scummvm/commit/21ffb62f806b556a7faae84c5a221f51baa86087
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Guard against dynamic_cast failure

CID 1361762, 1361763, 1361764, 1361765.

Changed paths:
    engines/sci/sound/audio32.cpp



diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
index 4af474b..43c2193 100644
--- a/engines/sci/sound/audio32.cpp
+++ b/engines/sci/sound/audio32.cpp
@@ -183,6 +183,9 @@ int Audio32::writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::R
 	do {
 		if (loop && sourceStream->endOfStream()) {
 			Audio::RewindableAudioStream *rewindableStream = dynamic_cast<Audio::RewindableAudioStream *>(sourceStream);
+			if (rewindableStream == nullptr) {
+				error("[Audio32::writeAudioInternal]: Unable to cast stream");
+			}
 			rewindableStream->rewind();
 		}
 
@@ -453,7 +456,11 @@ void Audio32::freeUnusedChannels() {
 		const AudioChannel &channel = getChannel(channelIndex);
 		if (!channel.robot && channel.stream->endOfStream()) {
 			if (channel.loop) {
-				dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->rewind();
+				Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream);
+				if (stream == nullptr) {
+					error("[Audio32::freeUnusedChannels]: Unable to cast stream for resource %s", channel.id.toString().c_str());
+				}
+				stream->rewind();
 			} else {
 				stop(channelIndex--);
 			}
@@ -658,6 +665,9 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool
 	if (channelIndex != kNoExistingChannel) {
 		AudioChannel &channel = getChannel(channelIndex);
 		Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream);
+		if (stream == nullptr) {
+			error("[Audio32::play]: Unable to cast stream for resource %s", resourceId.toString().c_str());
+		}
 
 		if (channel.pausedAtTick) {
 			resume(channelIndex);
@@ -764,7 +774,12 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool
 	// use audio streams, and allocate and fill the monitoring buffer
 	// when reading audio data from the stream.
 
-	channel.duration = /* round up */ 1 + (dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->getLength().msecs() * 60 / 1000);
+	Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream);
+	if (stream == nullptr) {
+		error("[Audio32::play]: Unable to cast stream for resource %s", resourceId.toString().c_str());
+	}
+
+	channel.duration = /* round up */ 1 + (stream->getLength().msecs() * 60 / 1000);
 
 	const uint32 now = g_sci->getTickCount();
 	channel.pausedAtTick = autoPlay ? 0 : now;


Commit: 63345b2b70e72330b4f2845bd45e13117be41dc4
    https://github.com/scummvm/scummvm/commit/63345b2b70e72330b4f2845bd45e13117be41dc4
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix kShowStyleNone transitions

Changed paths:
    engines/sci/graphics/transitions32.cpp



diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
index 361c38e..e98f783 100644
--- a/engines/sci/graphics/transitions32.cpp
+++ b/engines/sci/graphics/transitions32.cpp
@@ -233,92 +233,98 @@ void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeOb
 		}
 	}
 
-	if (type > 0) {
-		if (createNewEntry) {
-			entry = new PlaneShowStyle;
-			// NOTE: SCI2.1 engine tests if allocation returned a null pointer
-			// but then only avoids setting currentStep if this is so. Since
-			// this is a nonsensical approach, we do not do that here
-			entry->currentStep = 0;
-			entry->processed = false;
-			entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
-			entry->plane = planeObj;
-			entry->fadeColorRangesCount = 0;
-
-			if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
-				// for pixel dissolve
-				entry->bitmap = NULL_REG;
-				entry->bitmapScreenItem = nullptr;
-
-				// for wipe
-				entry->screenItems.clear();
-				entry->width = plane->_gameRect.width();
-				entry->height = plane->_gameRect.height();
-			} else {
-				entry->fadeColorRanges = nullptr;
-				if (hasFadeArray) {
-					// NOTE: SCI2.1mid engine does no check to verify that an array is
-					// successfully retrieved, and SegMan will cause a fatal error
-					// if we try to use a memory segment that is not an array
-					SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
-
-					uint32 rangeCount = table->getSize();
-					entry->fadeColorRangesCount = rangeCount;
-
-					// NOTE: SCI engine code always allocates memory even if the range
-					// table has no entries, but this does not really make sense, so
-					// we avoid the allocation call in this case
-					if (rangeCount > 0) {
-						entry->fadeColorRanges = new uint16[rangeCount];
-						for (size_t i = 0; i < rangeCount; ++i) {
-							entry->fadeColorRanges[i] = table->getValue(i).toUint16();
-						}
+	if (type == kShowStyleNone) {
+		if (createNewEntry == false) {
+			deleteShowStyle(findIteratorForPlane(planeObj));
+		}
+
+		return;
+	}
+
+	if (createNewEntry) {
+		entry = new PlaneShowStyle;
+		// NOTE: SCI2.1 engine tests if allocation returned a null pointer
+		// but then only avoids setting currentStep if this is so. Since
+		// this is a nonsensical approach, we do not do that here
+		entry->currentStep = 0;
+		entry->processed = false;
+		entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
+		entry->plane = planeObj;
+		entry->fadeColorRangesCount = 0;
+
+		if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+			// for pixel dissolve
+			entry->bitmap = NULL_REG;
+			entry->bitmapScreenItem = nullptr;
+
+			// for wipe
+			entry->screenItems.clear();
+			entry->width = plane->_gameRect.width();
+			entry->height = plane->_gameRect.height();
+		} else {
+			entry->fadeColorRanges = nullptr;
+			if (hasFadeArray) {
+				// NOTE: SCI2.1mid engine does no check to verify that an array is
+				// successfully retrieved, and SegMan will cause a fatal error
+				// if we try to use a memory segment that is not an array
+				SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
+
+				uint32 rangeCount = table->getSize();
+				entry->fadeColorRangesCount = rangeCount;
+
+				// NOTE: SCI engine code always allocates memory even if the range
+				// table has no entries, but this does not really make sense, so
+				// we avoid the allocation call in this case
+				if (rangeCount > 0) {
+					entry->fadeColorRanges = new uint16[rangeCount];
+					for (size_t i = 0; i < rangeCount; ++i) {
+						entry->fadeColorRanges[i] = table->getValue(i).toUint16();
 					}
 				}
 			}
 		}
+	}
 
-		// NOTE: The original engine had no nullptr check and would just crash
-		// if it got to here
-		if (entry == nullptr) {
-			error("Cannot edit non-existing ShowStyle entry");
-		}
+	// NOTE: The original engine had no nullptr check and would just crash
+	// if it got to here
+	if (entry == nullptr) {
+		error("Cannot edit non-existing ShowStyle entry");
+	}
 
-		entry->fadeUp = isFadeUp;
-		entry->color = color;
-		entry->nextTick = g_sci->getTickCount();
-		entry->type = type;
-		entry->animate = animate;
-		entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
+	entry->fadeUp = isFadeUp;
+	entry->color = color;
+	entry->nextTick = g_sci->getTickCount();
+	entry->type = type;
+	entry->animate = animate;
+	entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
 
-		if (entry->delay == 0) {
-			error("ShowStyle has no duration");
-		}
+	if (entry->delay == 0) {
+		error("ShowStyle has no duration");
+	}
 
-		if (frameOutNow) {
-			// Creates a reference frame for the pixel dissolves to use
-			g_sci->_gfxFrameout->frameOut(false);
-		}
+	if (frameOutNow) {
+		// Creates a reference frame for the pixel dissolves to use
+		g_sci->_gfxFrameout->frameOut(false);
+	}
 
-		if (createNewEntry) {
-			if (getSciVersion() <= SCI_VERSION_2_1_EARLY) {
-				switch (entry->type) {
-				case kShowStyleIrisOut:
-				case kShowStyleIrisIn:
-					configure21EarlyIris(*entry, priority);
-				break;
-				case kShowStyleDissolve:
-					configure21EarlyDissolve(*entry, priority, plane->_gameRect);
-				break;
-				default:
-					// do nothing
-				break;
-				}
+	if (createNewEntry) {
+		if (getSciVersion() <= SCI_VERSION_2_1_EARLY) {
+			switch (entry->type) {
+			case kShowStyleIrisOut:
+			case kShowStyleIrisIn:
+				configure21EarlyIris(*entry, priority);
+			break;
+			case kShowStyleDissolve:
+				configure21EarlyDissolve(*entry, priority, plane->_gameRect);
+			break;
+			default:
+				// do nothing
+			break;
 			}
-
-			_showStyles.push_back(*entry);
-			delete entry;
 		}
+
+		_showStyles.push_back(*entry);
+		delete entry;
 	}
 }
 


Commit: d0517f515eb7f36ec7222f3d50f4917fbd7df5e7
    https://github.com/scummvm/scummvm/commit/d0517f515eb7f36ec7222f3d50f4917fbd7df5e7
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Update screen on frameout, instead of in the event loop

Changed paths:
    engines/sci/engine/kevent.cpp
    engines/sci/event.cpp
    engines/sci/graphics/cursor32.cpp
    engines/sci/graphics/frameout.cpp
    engines/sci/graphics/palette32.cpp



diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index 9250e0f..600dce3 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -236,7 +236,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
 	// check bugs #3058865 and #3127824
 	if (s->_gameIsBenchmarking) {
 		// Game is benchmarking, don't add a delay
-	} else {
+	} else if (getSciVersion() < SCI_VERSION_2) {
 		g_system->delayMillis(10);
 	}
 
diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index b267d2e..550cd46 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -370,7 +370,9 @@ SciEvent EventManager::getSciEvent(uint32 mask) {
 	SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point() };
 #endif
 
-	EventManager::updateScreen();
+	if (getSciVersion() < SCI_VERSION_2) {
+		updateScreen();
+	}
 
 	// Get all queued events from graphics driver
 	do {
diff --git a/engines/sci/graphics/cursor32.cpp b/engines/sci/graphics/cursor32.cpp
index 88150db..7889d61 100644
--- a/engines/sci/graphics/cursor32.cpp
+++ b/engines/sci/graphics/cursor32.cpp
@@ -122,6 +122,7 @@ void GfxCursor32::drawToHardware(const DrawRegion &source) {
 	byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset;
 
 	g_system->copyRectToScreen(sourcePixel, source.rect.width(), drawRect.left, drawRect.top, drawRect.width(), drawRect.height());
+	g_system->updateScreen();
 }
 
 void GfxCursor32::unhide() {
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 4e0aa22..e886193 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -1108,6 +1108,7 @@ void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showLi
 
 void GfxFrameout::showBits() {
 	if (!_showList.size()) {
+		g_system->updateScreen();
 		return;
 	}
 
@@ -1146,6 +1147,7 @@ void GfxFrameout::showBits() {
 	_cursor->donePainting();
 
 	_showList.clear();
+	g_system->updateScreen();
 }
 
 void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) {
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index c7098bc..db81669 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -295,7 +295,7 @@ void GfxPalette32::updateHardware(const bool updateScreen) {
 
 	g_system->getPaletteManager()->setPalette(bpal, 0, 256);
 	if (updateScreen) {
-		g_sci->getEventManager()->updateScreen();
+		g_system->updateScreen();
 	}
 }
 


Commit: 3cf16c0a7f9731a88914531392628b2dc599d787
    https://github.com/scummvm/scummvm/commit/3cf16c0a7f9731a88914531392628b2dc599d787
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Explicitly instantiate MIN/MAX templates

Changed paths:
    engines/sci/engine/kvideo.cpp
    engines/sci/graphics/paint32.cpp
    engines/sci/graphics/video32.cpp
    engines/sci/sound/audio32.cpp



diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index b539c84..46829cc 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -446,10 +446,10 @@ reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv) {
 	const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
 
 	Common::Rect blackoutArea;
-	blackoutArea.left = MAX((int16)0, argv[0].toSint16());
-	blackoutArea.top = MAX((int16)0, argv[1].toSint16());
-	blackoutArea.right = MIN(scriptWidth, (int16)(argv[2].toSint16() + 1));
-	blackoutArea.bottom = MIN(scriptHeight, (int16)(argv[3].toSint16() + 1));
+	blackoutArea.left = MAX<int16>(0, argv[0].toSint16());
+	blackoutArea.top = MAX<int16>(0, argv[1].toSint16());
+	blackoutArea.right = MIN<int16>(scriptWidth, argv[2].toSint16() + 1);
+	blackoutArea.bottom = MIN<int16>(scriptHeight, argv[3].toSint16() + 1);
 	g_sci->_video32->getVMDPlayer().setBlackoutArea(blackoutArea);
 	return s->r_acc;
 }
diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp
index 338b709..509df7c 100644
--- a/engines/sci/graphics/paint32.cpp
+++ b/engines/sci/graphics/paint32.cpp
@@ -121,7 +121,7 @@ reg_t GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::
 	const uint8 skipColor = color != kDefaultSkipColor ? kDefaultSkipColor : 0;
 
 	// Thickness is expected to be 2n+1
-	thickness = ((MAX((uint8)1, thickness) - 1) | 1);
+	thickness = ((MAX<uint8>(1, thickness) - 1) | 1);
 	const uint8 halfThickness = thickness >> 1;
 
 	outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness;
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index 8b1d4ef..ab32a11 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -635,7 +635,7 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co
 	const int32 maxFrameNo = (int32)(_decoder->getFrameCount() - 1);
 
 	if ((flags & kEventFlagToFrame) && lastFrameNo > 0) {
-		_decoder->setEndFrame(MIN((int32)lastFrameNo, maxFrameNo));
+		_decoder->setEndFrame(MIN<int32>(lastFrameNo, maxFrameNo));
 	} else {
 		_decoder->setEndFrame(maxFrameNo);
 	}
@@ -645,7 +645,7 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co
 		if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) {
 			_yieldInterval = lastFrameNo;
 		} else if (yieldInterval != -1) {
-			_yieldInterval = MIN((int32)yieldInterval, maxFrameNo);
+			_yieldInterval = MIN<int32>(yieldInterval, maxFrameNo);
 		}
 	} else {
 		_yieldInterval = maxFrameNo;
@@ -891,7 +891,7 @@ void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) {
 	// At least GK2 sends 256 as the end color, which is wrong,
 	// but works in the original engine as the storage size is 4 bytes
 	// and used values are clamped to 0-255
-	_endColor = MIN((int16)255, endColor);
+	_endColor = MIN<int16>(255, endColor);
 }
 
 } // End of namespace Sci
diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
index 43c2193..659a526 100644
--- a/engines/sci/sound/audio32.cpp
+++ b/engines/sci/sound/audio32.cpp
@@ -1027,7 +1027,7 @@ int16 Audio32::getVolume(const int16 channelIndex) const {
 }
 
 void Audio32::setVolume(const int16 channelIndex, int16 volume) {
-	volume = MIN((int16)kMaxVolume, volume);
+	volume = MIN<int16>(kMaxVolume, volume);
 	if (channelIndex == kAllChannels) {
 		ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
 		ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);


Commit: bf7adaa58fcb9350add66ec0fc46139913d78eb5
    https://github.com/scummvm/scummvm/commit/bf7adaa58fcb9350add66ec0fc46139913d78eb5
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix incorrect warnings about unlocking type 140

This type in SSCI corresponds to Wave resources, but since ScummVM
does not differentiate between Wave and Audio resources, just say
it's an Audio resource type, not an Invalid resource type.

Changed paths:
    engines/sci/resource.cpp



diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 2e69932..fb93c3e 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -159,7 +159,7 @@ static const ResourceType s_resTypeMapSci21[] = {
 	kResourceTypeView, kResourceTypePic, kResourceTypeScript, kResourceTypeAnimation,     // 0x00-0x03
 	kResourceTypeSound, kResourceTypeEtc, kResourceTypeVocab, kResourceTypeFont,          // 0x04-0x07
 	kResourceTypeCursor, kResourceTypePatch, kResourceTypeBitmap, kResourceTypePalette,   // 0x08-0x0B
-	kResourceTypeInvalid, kResourceTypeAudio, kResourceTypeSync, kResourceTypeMessage,    // 0x0C-0x0F
+	kResourceTypeAudio, kResourceTypeAudio, kResourceTypeSync, kResourceTypeMessage,      // 0x0C-0x0F
 	kResourceTypeMap, kResourceTypeHeap, kResourceTypeChunk, kResourceTypeAudio36,        // 0x10-0x13
 	kResourceTypeSync36, kResourceTypeTranslation, kResourceTypeRobot, kResourceTypeVMD,  // 0x14-0x17
 	kResourceTypeDuck, kResourceTypeClut, kResourceTypeTGA, kResourceTypeZZZ              // 0x18-0x1B


Commit: 560af833007cd3583ceae51b58cf015390ad14ce
    https://github.com/scummvm/scummvm/commit/560af833007cd3583ceae51b58cf015390ad14ce
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Move SciEngine::sleep to correct source file

Changed paths:
    engines/sci/event.cpp
    engines/sci/sci.cpp



diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index 550cd46..43db00c 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -402,25 +402,4 @@ SciEvent EventManager::getSciEvent(uint32 mask) {
 
 	return event;
 }
-
-void SciEngine::sleep(uint32 msecs) {
-	uint32 time;
-	const uint32 wakeUpTime = g_system->getMillis() + msecs;
-
-	while (true) {
-		// let backend process events and update the screen
-		_eventMan->getSciEvent(SCI_EVENT_PEEK);
-		time = g_system->getMillis();
-		if (time + 10 < wakeUpTime) {
-			g_system->delayMillis(10);
-		} else {
-			if (time < wakeUpTime)
-				g_system->delayMillis(wakeUpTime - time);
-			break;
-		}
-
-	}
-}
-
-
 } // End of namespace Sci
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 86c0cff..8e330f2 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -955,6 +955,25 @@ int SciEngine::inQfGImportRoom() const {
 	return 0;
 }
 
+void SciEngine::sleep(uint32 msecs) {
+	uint32 time;
+	const uint32 wakeUpTime = g_system->getMillis() + msecs;
+
+	for (;;) {
+		// let backend process events and update the screen
+		_eventMan->getSciEvent(SCI_EVENT_PEEK);
+		time = g_system->getMillis();
+		if (time + 10 < wakeUpTime) {
+			g_system->delayMillis(10);
+		} else {
+			if (time < wakeUpTime)
+				g_system->delayMillis(wakeUpTime - time);
+			break;
+		}
+
+	}
+}
+
 void SciEngine::setLauncherLanguage() {
 	if (_gameDescription->flags & ADGF_ADDENGLISH) {
 		// If game is multilingual


Commit: 209fe0f92afea74e170d97c9ff0f697242fcac36
    https://github.com/scummvm/scummvm/commit/209fe0f92afea74e170d97c9ff0f697242fcac36
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix incorrect use of speedThrottler in Video32

The sleep values for video playback are the amount of time until
the next frame needs to be displayed, whereas speedThrottler is
used to ensure that a given amount of time always elapses between
two triggered speedThrottler calls.

Changed paths:
    engines/sci/graphics/video32.cpp



diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index ab32a11..dbbe32e 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -86,9 +86,8 @@ void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const
 	_decoder->start();
 
 	while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
+		g_sci->sleep(_decoder->getTimeToNextFrame());
 		renderFrame();
-		g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
-		g_sci->getEngineState()->_throttleTrigger = true;
 	}
 
 	_segMan->freeBitmap(_screenItem->_celInfo.bitmap);
@@ -311,9 +310,8 @@ AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int1
 void AVIPlayer::renderVideo() const {
 	_decoder->start();
 	while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
-		g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
-		g_sci->getEngineState()->_throttleTrigger = true;
-		if (_decoder->needsUpdate()) {
+		g_sci->sleep(_decoder->getTimeToNextFrame());
+		while (_decoder->needsUpdate()) {
 			renderFrame();
 		}
 	}
@@ -457,9 +455,8 @@ AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) {
 			break;
 		}
 
-		g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
-		g_sci->getEngineState()->_throttleTrigger = true;
-		if (_decoder->needsUpdate()) {
+		g_sci->sleep(_decoder->getTimeToNextFrame());
+		while (_decoder->needsUpdate()) {
 			renderFrame();
 		}
 


Commit: 39a3b67522dba724d0d7a6301759d3fcf5cf064e
    https://github.com/scummvm/scummvm/commit/39a3b67522dba724d0d7a6301759d3fcf5cf064e
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Skip SEQ playback if file not found

CID 1361588.

Changed paths:
    engines/sci/graphics/video32.cpp



diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index dbbe32e..506cde7 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -58,7 +58,10 @@ SEQPlayer::SEQPlayer(SegManager *segMan) :
 void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) {
 	delete _decoder;
 	_decoder = new SEQDecoder(numTicks);
-	_decoder->loadFile(fileName);
+	if (!_decoder->loadFile(fileName)) {
+		warning("[SEQPlayer::play]: Failed to load %s", fileName.c_str());
+		return;
+	}
 
 	// NOTE: In the original engine, video was output directly to the hardware,
 	// bypassing the game's rendering engine. Instead of doing this, we use a


Commit: 22e1f1f015e38e7ceb0d3b29c61d362d2d906634
    https://github.com/scummvm/scummvm/commit/22e1f1f015e38e7ceb0d3b29c61d362d2d906634
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Put CD flag on CD games

Changed paths:
    engines/sci/detection_tables.h



diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index fafbf60..22d2b6f 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -866,7 +866,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "b996fa1e57389a1e179a00a0049de1f4", 8110},
 		{"ressci.000", 0, "a19fc3604c6e5407abcf03d59ee87217", 168522221},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK2 },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK2 },
 
 	// Gabriel Knight 2 - English DOS (from jvprat)
 	// Executable scanning reports "2.100.002", VERSION file reports "1.1"
@@ -884,7 +884,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.006", 0, "ce9359037277b7d7976da185c2fa0aad", 2977},
 		{"ressci.006", 0, "8e44e03890205a7be12f45aaba9644b4", 60659424},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK2 },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK2 },
 
 	// Gabriel Knight 2 - French DOS (6-CDs Sierra Originals reedition)
 	// Executable scanning reports "2.100.002", VERSION file reports "1.0"
@@ -902,7 +902,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.006", 0, "11b2e722170b8c93fdaa5428e2c7676f", 3001},
 		{"ressci.006", 0, "4037d941aec39d2e654e20960429aefc", 60568486},
 		AD_LISTEND},
-		Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_GK2 },
+		Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_GK2 },
 
 	// Gabriel Knight 2 - English Macintosh
 	// NOTE: This only contains disc 1 files (as well as the persistent file:
@@ -914,7 +914,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"Data4", 0, "8b843c62eb53136a855d6e0087e3cb0d", 5889553},
 		{"Data5", 0, "f9fcf9ab2eb13b2125c33a1cda03a093", 14349984},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_GK2_MAC },
+		Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_CD | ADGF_UNSTABLE, GUIO_GK2_MAC },
 
 #endif // ENABLE_SCI32
 
@@ -2580,7 +2580,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resource.map", 0, "0c0804434ea62278dd15032b1947426c", 8872},
 		{"resource.000", 0, "9a9f4870504444cda863dd14d077a680", 18520872},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL6HIRES },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL6HIRES },
 
 	// Larry 6 - German DOS CD - HIRES (provided by richiefs in bug report #2670691)
 	// SCI interpreter version 2.100.002
@@ -2588,7 +2588,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resource.map", 0, "badfdf446ffed569a310d2c63a249421", 8896},
 		{"resource.000", 0, "bd944d2b06614a5b39f1586906f0ee88", 18534274},
 		AD_LISTEND},
-		Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL6HIRES },
+		Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL6HIRES },
 
 	// Larry 6 - French DOS CD - HIRES (provided by richiefs in bug report #2670691)
 	// SCI interpreter version 2.100.002
@@ -2596,7 +2596,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resource.map", 0, "d184e9aa4f2d4b5670ddb3669db82cda", 8896},
 		{"resource.000", 0, "bd944d2b06614a5b39f1586906f0ee88", 18538987},
 		AD_LISTEND},
-		Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL6HIRES },
+		Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL6HIRES },
 
 #define GUIO_LSL7_DEMO GUIO5(GUIO_NOSPEECH, \
                              GUIO_NOASPECT, \
@@ -2624,7 +2624,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "eae93e1b1d1ccc58b4691c371281c95d", 8188},
 		{"ressci.000", 0, "89353723488219e25589165d73ed663e", 66965678},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL7 },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
 
 	// Larry 7 - German DOS (from Tobis87)
 	// SCI interpreter version 3.000.000
@@ -2632,7 +2632,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "c11e6bfcfc2f2d05da47e5a7df3e9b1a", 8188},
 		{"ressci.000", 0, "a8c6817bb94f332ff498a71c8b47f893", 66971724},
 		AD_LISTEND},
-		Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL7 },
+		Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
 
 	// Larry 7 - French DOS (provided by richiefs in bug report #2670691)
 	// SCI interpreter version 3.000.000
@@ -2640,7 +2640,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "4407849fd52fe3efb0c30fba60cd5cd4", 8206},
 		{"ressci.000", 0, "dc37c3055fffbefb494ff22b145d377b", 66964472},
 		AD_LISTEND},
-		Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL7 },
+		Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
 
 	// Larry 7 - Italian DOS CD (from glorifindel)
 	// SCI interpreter version 3.000.000
@@ -2648,7 +2648,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "9852a97141f789413f29bf956052acdb", 8212},
 		{"ressci.000", 0, "440b9fed89590abb4e4386ed6f948ee2", 67140181},
 		AD_LISTEND},
-		Common::IT_ITA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL7 },
+		Common::IT_ITA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
 
 	// Larry 7 - Spanish DOS (from the Leisure Suit Larry Collection)
 	// Executable scanning reports "3.000.000", VERSION file reports "1.0s"
@@ -2656,7 +2656,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "8f3d603e1acc834a5d598b30cdfc93f3", 8188},
 		{"ressci.000", 0, "32792f9bc1bf3633a88b382bb3f6e40d", 67071418},
 		AD_LISTEND},
-		Common::ES_ESP, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LSL7 },
+		Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LSL7 },
 #endif
 
 #define GUIO_LIGHTHOUSE_DEMO GUIO5(GUIO_NOSPEECH, \
@@ -2696,7 +2696,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.002", 0, "c68db5333f152fea6ca2dfc75cad8b34", 7573},
 		{"ressci.002", 0, "175468431a979b9f317c294ce3bc1430", 94628315},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
 
 	// Lighthouse - Japanese DOS (from m_kiewitz)
 	// Executable scanning reports "3.000.000", VERSION file reports "1.0C"
@@ -2706,7 +2706,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.002", 0, "723fc742c623d8933e5753a264324cb0", 7657},
 		{"ressci.002", 0, "175468431a979b9f317c294ce3bc1430", 94627469},
 		AD_LISTEND},
-		Common::JA_JPN, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
+		Common::JA_JPN, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
 
 	// Lighthouse - Spanish DOS (from jvprat)
 	// Executable scanning reports "3.000.000", VERSION file reports "1.1"
@@ -2716,7 +2716,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.002", 0, "e7dc85884a2417e2eff9de0c63dd65fa", 7630},
 		{"ressci.002", 0, "3c8d627c555b0e3e4f1d9955bc0f0df4", 94631127},
 		AD_LISTEND},
-		Common::ES_ESP, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
+		Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_LIGHTHOUSE },
 #endif	// ENABLE_SCI3_GAMES
 
 #endif // ENABLE_SCI32
@@ -2850,7 +2850,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resource.map", 0, "5159a1578c4306bfe070a3e4d8c2e1d3", 4741},
 		{"resource.000", 0, "1926925c95d82f0999590e93b02887c5", 15150768},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES },
 
 	// Mixed-Up Mother Goose Deluxe - Multilingual Windows CD (English/French/German/Spanish)
 	// Executable scanning reports "2.100.002"
@@ -2858,7 +2858,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "ef611af561898dcfea87846919ebf3eb", 4969},
 		{"ressci.000", 0, "227685bc59d90821978d330713e44a7a", 17205800},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_MOTHERGOOSEHIRES },
 #endif // ENABLE_SCI32
 
 	// Ms. Astro Chicken - English DOS
@@ -2904,7 +2904,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.007", 0, "aa8175cfc93242af6f5e65bdceaafc0d", 7972},
 		//{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 25859038},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
 
 	// Phantasmagoria - English DOS (from jvprat)
 	// Executable scanning reports "2.100.002", VERSION file reports "1.100.000UK"
@@ -2924,7 +2924,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.007", 0, "afbd16ea77869a720afa1c5371de107d", 7972},
 		//{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 25859038},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
 
 	// Phantasmagoria - German DOS/Windows
 	// Windows executable scanning reports "unknown" - "Sep 19 1995 09:39:48"
@@ -2947,7 +2947,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.007", 0, "06309b8043aecb85bd507b15d16cb544", 7984},
 		//{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 26898681},
 		AD_LISTEND},
-		Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
+		Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
 
 	// Phantasmagoria - French DOS
 	// Supplied by Kervala in bug #6574
@@ -2966,7 +2966,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 85415107},
 		{"resmap.007", 0, "5633960bc106c39ca91d2d8fce18fd2d", 7984},
 		AD_LISTEND},
-		Common::FR_FRA, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
+		Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
 
 	// Phantasmagoria - English DOS Demo
 	// Executable scanning reports "2.100.002"
@@ -2985,7 +2985,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"ressci.000", 0, "cd5967f9b9586e3380645961c0765be3", 116822037},
 		{"resmap.000", 0, "3cafc1c6a53945c1f3babbfd6380c64c", 16468},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA },
 
 	// Phantasmagoria - English Macintosh
 	// NOTE: This only contains disc 1 files (as well as the two persistent files:
@@ -3001,7 +3001,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		// Data8-12 are empty
 		{"Data13", 0, "6d2c450fca19a69b5af74ed5b03c0a17", 14923328},
 		AD_LISTEND},
-	 Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA_MAC },
+	 Common::EN_ANY, Common::kPlatformMacintosh, ADGF_CD | ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_PHANTASMAGORIA_MAC },
 
 #ifdef ENABLE_SCI3_GAMES
 
@@ -3368,7 +3368,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.000", 0, "1c2563fee189885e29d9348f37306d94", 12175},
 		{"ressci.000", 0, "b2e1826ca81ce2e7e764587f5a14eee9", 127149181},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO_PQSWAT },
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO_PQSWAT },
 
 	// Police Quest: SWAT - English Windows (from the Police Quest Collection)
 	// Executable scanning reports "2.100.002", VERSION file reports "1.0c"
@@ -3383,7 +3383,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resmap.004", 0, "4228038906f041623e65789500b22285", 6835},
 		{"ressci.004", 0, "b7e619e6ecf62fe65d5116a3a422e5f0", 46223872},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO_PQSWAT },
+		Common::EN_ANY, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_PQSWAT },
 #endif // ENABLE_SCI32
 
 	// Quest for Glory 1 / Hero's Quest - English DOS 3.5" Floppy (supplied by merkur in bug report #2718784)


Commit: 658fb7f8e4c9a43f3f5e21f6b572b2ff2c1cc974
    https://github.com/scummvm/scummvm/commit/658fb7f8e4c9a43f3f5e21f6b572b2ff2c1cc974
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Improved game resolution detection

Changed paths:
    engines/sci/graphics/frameout.cpp
    engines/sci/graphics/frameout.h



diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index e886193..7d38d01 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -58,7 +58,7 @@
 namespace Sci {
 
 GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor) :
-	_isHiRes(ConfMan.getBool("enable_high_resolution_graphics")),
+	_isHiRes(gameIsHiRes()),
 	_palette(palette),
 	_cursor(cursor),
 	_segMan(segMan),
@@ -71,11 +71,6 @@ GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitio
 	_overdrawThreshold(0),
 	_palMorphIsOn(false) {
 
-	// QFG4 is the only SCI32 game that doesn't have a high-resolution version
-	if (g_sci->getGameId() == GID_QFG4) {
-		_isHiRes = false;
-	}
-
 	if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
 		_currentBuffer = Buffer(630, 450, nullptr);
 	} else if (_isHiRes) {
@@ -213,6 +208,26 @@ void GfxFrameout::syncWithScripts(bool addElements) {
 	}
 }
 
+bool GfxFrameout::gameIsHiRes() const {
+	// QFG4 is always low resolution
+	if (g_sci->getGameId() == GID_QFG4) {
+		return false;
+	}
+
+	// GK1 DOS floppy is low resolution only, but GK1 Mac floppy is high
+	// resolution only
+	if (g_sci->getGameId() == GID_GK1 &&
+		!g_sci->isCD() &&
+		g_sci->getPlatform() != Common::kPlatformMacintosh) {
+
+		return false;
+	}
+
+	// All other games are either high resolution by default, or have a
+	// user-defined toggle
+	return ConfMan.getBool("enable_high_resolution_graphics");
+}
+
 #pragma mark -
 #pragma mark Benchmarking
 
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index e4caffd..9481b0e 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -44,6 +44,12 @@ private:
 	GfxPalette32 *_palette;
 	SegManager *_segMan;
 
+	/**
+	 * Determines whether the current game should be rendered in
+	 * high resolution.
+	 */
+	bool gameIsHiRes() const;
+
 public:
 	GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor);
 	~GfxFrameout();


Commit: 84d8ac4c38bd2aabbbc7ad85ae257db69ba33574
    https://github.com/scummvm/scummvm/commit/84d8ac4c38bd2aabbbc7ad85ae257db69ba33574
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix buffer overflow when drawing border to a tiny text bitmap

Changed paths:
    engines/sci/graphics/text32.cpp



diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index 1157258..254c7d9 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -183,13 +183,15 @@ void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint
 
 	// NOTE: Not fully disassembled, but this should be right
 	int16 rectWidth = targetRect.width();
-	int16 sidesHeight = targetRect.height() - size * 2;
+	int16 heightRemaining = targetRect.height();
+	int16 sidesHeight = heightRemaining - size * 2;
 	int16 centerWidth = rectWidth - size * 2;
 	int16 stride = _width - rectWidth;
 
-	for (int16 y = 0; y < size; ++y) {
+	for (int16 y = 0; y < size && y < heightRemaining; ++y) {
 		memset(pixels, color, rectWidth);
 		pixels += _width;
+		--heightRemaining;
 	}
 	for (int16 y = 0; y < sidesHeight; ++y) {
 		for (int16 x = 0; x < size; ++x) {
@@ -201,9 +203,10 @@ void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint
 		}
 		pixels += stride;
 	}
-	for (int16 y = 0; y < size; ++y) {
+	for (int16 y = 0; y < size && y < heightRemaining; ++y) {
 		memset(pixels, color, rectWidth);
 		pixels += _width;
+		--heightRemaining;
 	}
 }
 


Commit: 3cc5e55201fd1a657cf6a0e626e7666358cd396d
    https://github.com/scummvm/scummvm/commit/3cc5e55201fd1a657cf6a0e626e7666358cd396d
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Fix typo in debugger help

Changed paths:
    engines/sci/console.cpp



diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index b20ed3f..3f65ca7 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -3778,7 +3778,7 @@ bool Console::cmdBreakpointKernel(int argc, const char **argv) {
 bool Console::cmdBreakpointFunction(int argc, const char **argv) {
 	if (argc != 3) {
 		debugPrintf("Sets a breakpoint on the execution of the specified exported function.\n");
-		debugPrintf("Usage: %s <script number> <export number\n", argv[0]);
+		debugPrintf("Usage: %s <script number> <export number>\n", argv[0]);
 		return true;
 	}
 


Commit: b784357f5df98a3998ef0728b75b446e0e905621
    https://github.com/scummvm/scummvm/commit/b784357f5df98a3998ef0728b75b446e0e905621
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Fix whitespace

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



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 6e141e7..eafbe12 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -632,7 +632,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(DisposeWindow),     SIG_EVERYWHERE,           "i(i)",                  NULL,            NULL },
 	{ MAP_CALL(DoAudio),           SCI_VERSION_NONE, SCI_VERSION_2, SIGFOR_ALL, "i(.*)", NULL,         NULL }, // subop
 #ifdef ENABLE_SCI32
-	{ "DoAudio", kDoAudio32,       SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)",          kDoAudio_subops, NULL },
+	{ "DoAudio", kDoAudio32,       SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)",               kDoAudio_subops, NULL },
 #endif
 	{ MAP_CALL(DoAvoider),         SIG_EVERYWHERE,           "o(i)",                  NULL,            NULL },
 	{ MAP_CALL(DoBresen),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },
@@ -921,10 +921,10 @@ static SciKernelMapEntry s_kernelMap[] = {
 
 	// SetWindowsOption is used to set Windows specific options, like for example the title bar visibility of
 	// the game window in Phantasmagoria 2. We ignore these settings completely.
-	{ MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE,             "ii",                  NULL,            NULL },
+	{ MAP_EMPTY(SetWindowsOption), SIG_EVERYWHERE,           "ii",                    NULL,            NULL },
 
 	// Debug function called whenever the current room changes
-	{ MAP_EMPTY(NewRoom),           SIG_EVERYWHERE,          "(.*)",                  NULL,            NULL },
+	{ MAP_EMPTY(NewRoom),          SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 
 	// Unused / debug SCI2.1 unused functions, always mapped to kDummy
 


Commit: 3201440d11dde487e0ef5434cfa30066f261660f
    https://github.com/scummvm/scummvm/commit/3201440d11dde487e0ef5434cfa30066f261660f
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Generalize code for getting information on the current call

Changed paths:
    engines/sci/engine/state.cpp
    engines/sci/engine/state.h
    engines/sci/engine/vm.cpp
    engines/sci/engine/vm_types.cpp
    engines/sci/engine/workarounds.cpp
    engines/sci/engine/workarounds.h
    engines/sci/graphics/paint16.cpp



diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index a338bef..d253d31 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -380,4 +380,46 @@ void SciEngine::checkVocabularySwitch() {
 	}
 }
 
+SciCallOrigin EngineState::getCurrentCallOrigin() const {
+	// IMPORTANT: This method must always return values that match *exactly* the
+	// values in the workaround tables in workarounds.cpp, or workarounds will
+	// be broken
+
+	Common::String curObjectName = _segMan->getObjectName(xs->sendp);
+	Common::String curMethodName;
+	const Script *localScript = _segMan->getScriptIfLoaded(xs->local_segment);
+	int curScriptNr = localScript->getScriptNumber();
+
+	if (xs->debugLocalCallOffset != -1) {
+		// if lastcall was actually a local call search back for a real call
+		Common::List<ExecStack>::const_iterator callIterator = _executionStack.end();
+		while (callIterator != _executionStack.begin()) {
+			callIterator--;
+			const ExecStack &loopCall = *callIterator;
+			if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) {
+				xs->debugSelector = loopCall.debugSelector;
+				xs->debugExportId = loopCall.debugExportId;
+				break;
+			}
+		}
+	}
+
+	if (xs->type == EXEC_STACK_TYPE_CALL) {
+		if (xs->debugSelector != -1) {
+			curMethodName = g_sci->getKernel()->getSelectorName(xs->debugSelector);
+		} else if (xs->debugExportId != -1) {
+			curObjectName = "";
+			curMethodName = Common::String::format("export %d", xs->debugExportId);
+		}
+	}
+
+	SciCallOrigin reply;
+	reply.objectName = curObjectName;
+	reply.methodName = curMethodName;
+	reply.scriptNr = curScriptNr;
+	reply.localCallOffset = xs->debugLocalCallOffset;
+	reply.roomNr = currentRoomNumber();
+	return reply;
+}
+
 } // End of namespace Sci
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index baa912b..f1ec5d3 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -99,6 +99,17 @@ struct VideoState {
 	}
 };
 
+/**
+ * Trace information about a VM function call.
+ */
+struct SciCallOrigin {
+	int scriptNr; //< The source script of the function
+	Common::String objectName; //< The name of the object being called
+	Common::String methodName; //< The name of the method being called
+	int localCallOffset; //< The byte offset of a local script subroutine called by the origin method. -1 if not in a local subroutine.
+	int roomNr; //< The room that was loaded at the time of the call
+};
+
 struct EngineState : public Common::Serializable {
 public:
 	EngineState(SegManager *segMan);
@@ -209,6 +220,11 @@ public:
 	 * Resets the engine state.
 	 */
 	void reset(bool isRestoring);
+
+	/**
+	 * Finds and returns the origin of the current call.
+	 */
+	SciCallOrigin getCurrentCallOrigin() const;
 };
 
 } // End of namespace Sci
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 548fd47..72e340d 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -124,7 +124,7 @@ static reg_t read_var(EngineState *s, int type, int index) {
 			case VAR_TEMP: {
 				// Uninitialized read on a temp
 				//  We need to find correct replacements for each situation manually
-				SciTrackOriginReply originReply;
+				SciCallOrigin originReply;
 				SciWorkaroundSolution solution = trackOriginAndFindWorkaround(index, uninitializedReadWorkarounds, &originReply);
 				if (solution.type == WORKAROUND_NONE) {
 #ifdef RELEASE_BUILD
@@ -361,7 +361,7 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
 	if (kernelCall.signature
 			&& !kernel->signatureMatch(kernelCall.signature, argc, argv)) {
 		// signature mismatch, check if a workaround is available
-		SciTrackOriginReply originReply;
+		SciCallOrigin originReply;
 		SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelCall.workarounds, &originReply);
 		switch (solution.type) {
 		case WORKAROUND_NONE: {
@@ -429,7 +429,7 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
 		const KernelSubFunction &kernelSubCall = kernelCall.subFunctions[subId];
 		if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) {
 			// Signature mismatch
-			SciTrackOriginReply originReply;
+			SciCallOrigin originReply;
 			SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kernelSubCall.workarounds, &originReply);
 			switch (solution.type) {
 			case WORKAROUND_NONE: {
diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp
index d74e2b1..1076863 100644
--- a/engines/sci/engine/vm_types.cpp
+++ b/engines/sci/engine/vm_types.cpp
@@ -66,7 +66,7 @@ void reg_t::setOffset(uint32 offset) {
 }
 
 reg_t reg_t::lookForWorkaround(const reg_t right, const char *operation) const {
-	SciTrackOriginReply originReply;
+	SciCallOrigin originReply;
 	SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply);
 	if (solution.type == WORKAROUND_NONE)
 		error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from method %s::%s (room %d, script %d, localCall %x)",
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 7aaea09..68f5f4c 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -776,8 +776,7 @@ const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = {
 	{ GID_PHANTASMAGORIA, 45, 64907,  0,   "ScrollableWindow", "addString",                  NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice
 };
 
-
-SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) {
+SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin) {
 	// HACK for SCI3: Temporarily ignore this
 	if (getSciVersion() == SCI_VERSION_3) {
 		warning("SCI3 HACK: trackOriginAndFindWorkaround() called, ignoring");
@@ -789,37 +788,14 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun
 
 	const EngineState *state = g_sci->getEngineState();
 	ExecStack *lastCall = state->xs;
-	const Script *localScript = state->_segMan->getScriptIfLoaded(lastCall->local_segment);
-	int curScriptNr = localScript->getScriptNumber();
-	int curLocalCallOffset = lastCall->debugLocalCallOffset;
-
-	if (curLocalCallOffset != -1) {
-		// if lastcall was actually a local call search back for a real call
-		Common::List<ExecStack>::const_iterator callIterator = state->_executionStack.end();
-		while (callIterator != state->_executionStack.begin()) {
-			callIterator--;
-			const ExecStack &loopCall = *callIterator;
-			if ((loopCall.debugSelector != -1) || (loopCall.debugExportId != -1)) {
-				lastCall->debugSelector = loopCall.debugSelector;
-				lastCall->debugExportId = loopCall.debugExportId;
-				break;
-			}
-		}
-	}
-
-	Common::String curObjectName = state->_segMan->getObjectName(lastCall->sendp);
-	Common::String curMethodName;
 	const SciGameId gameId = g_sci->getGameId();
-	const int curRoomNumber = state->currentRoomNumber();
-
-	if (lastCall->type == EXEC_STACK_TYPE_CALL) {
-		if (lastCall->debugSelector != -1) {
-			curMethodName = g_sci->getKernel()->getSelectorName(lastCall->debugSelector);
-		} else if (lastCall->debugExportId != -1) {
-			curObjectName = "";
-			curMethodName = Common::String::format("export %d", lastCall->debugExportId);
-		}
-	}
+
+	*trackOrigin = state->getCurrentCallOrigin();
+	const Common::String &curObjectName = trackOrigin->objectName;
+	const Common::String &curMethodName = trackOrigin->methodName;
+	const int &curRoomNumber = trackOrigin->roomNr;
+	const int &curScriptNr = trackOrigin->scriptNr;
+	const int &curLocalCallOffset = trackOrigin->localCallOffset;
 
 	if (workaroundList) {
 		// Search if there is a workaround for this one
@@ -895,12 +871,6 @@ SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroun
 		} while (!searchObject.isNull()); // no parent left?
 	}
 
-	// give caller origin data
-	trackOrigin->objectName = curObjectName;
-	trackOrigin->methodName = curMethodName;
-	trackOrigin->scriptNr = curScriptNr;
-	trackOrigin->localCallOffset = lastCall->debugLocalCallOffset;
-
 	SciWorkaroundSolution noneFound;
 	noneFound.type = WORKAROUND_NONE;
 	noneFound.value = 0;
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 2cccd05..2f7a9e5 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -24,6 +24,7 @@
 #define SCI_ENGINE_WORKAROUNDS_H
 
 #include "sci/engine/vm_types.h"
+#include "sci/engine/state.h"
 #include "sci/sci.h"
 
 namespace Sci {
@@ -35,13 +36,6 @@ enum SciWorkaroundType {
 	WORKAROUND_FAKE       // fake kernel call / replace temp value / fake opcode
 };
 
-struct SciTrackOriginReply {
-	int scriptNr;
-	Common::String objectName;
-	Common::String methodName;
-	int localCallOffset;
-};
-
 struct SciWorkaroundSolution {
 	SciWorkaroundType type;
 	uint16 value;
@@ -103,7 +97,7 @@ extern const SciWorkaroundEntry kUnLoad_workarounds[];
 extern const SciWorkaroundEntry kStringPutAt_workarounds[];
 extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[];
 
-extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin);
+extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin);
 
 } // End of namespace Sci
 
diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp
index 91817d4..53449fb 100644
--- a/engines/sci/graphics/paint16.cpp
+++ b/engines/sci/graphics/paint16.cpp
@@ -541,7 +541,7 @@ reg_t GfxPaint16::kernelDisplay(const char *text, uint16 languageSplitter, int a
 			break;
 
 		default:
-			SciTrackOriginReply originReply;
+			SciCallOrigin originReply;
 			SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kDisplay_workarounds, &originReply);
 			if (solution.type == WORKAROUND_NONE)
 				error("Unknown kDisplay argument (%04x:%04x) from method %s::%s (script %d, localCall %x)",


Commit: 1de74fa514a59fbc41fa9024d91b94ee2cc1ebb7
    https://github.com/scummvm/scummvm/commit/1de74fa514a59fbc41fa9024d91b94ee2cc1ebb7
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Improve debugging output for selector failures

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



diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index ac621f5..7912655 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -226,15 +226,14 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t
 	ObjVarRef address;
 
 	if ((selectorId < 0) || (selectorId > (int)g_sci->getKernel()->getSelectorNamesSize())) {
-		error("Attempt to write to invalid selector %d of"
-		         " object at %04x:%04x.", selectorId, PRINT_REG(object));
-		return;
+		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+		error("Attempt to write to invalid selector %d. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", selectorId, PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
 	}
 
-	if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable)
-		error("Selector '%s' of object at %04x:%04x could not be"
-		         " written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
-	else {
+	if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) {
+		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+		error("Selector '%s' of object could not be written to. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+	} else {
 		*address.getPointer(segMan) = value;
 #ifdef ENABLE_SCI32
 		updateInfoFlagViewVisible(segMan->getObject(object), selectorId);
@@ -255,12 +254,12 @@ void invokeSelector(EngineState *s, reg_t object, int selectorId,
 	slc_type = lookupSelector(s->_segMan, object, selectorId, NULL, NULL);
 
 	if (slc_type == kSelectorNone) {
-		error("Selector '%s' of object at %04x:%04x could not be invoked",
-		         g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
+		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+		error("invokeSelector: Selector '%s' could not be invoked. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
 	}
 	if (slc_type == kSelectorVariable) {
-		error("Attempting to invoke variable selector %s of object %04x:%04x",
-			g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
+		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+		error("invokeSelector: Attempting to invoke variable selector %s. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
 	}
 
 	for (i = 0; i < argc; i++)
@@ -288,8 +287,8 @@ SelectorType lookupSelector(SegManager *segMan, reg_t obj_location, Selector sel
 		selectorId &= ~1;
 
 	if (!obj) {
-		error("lookupSelector(): Attempt to send to non-object or invalid script. Address was %04x:%04x",
-				PRINT_REG(obj_location));
+		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+		error("lookupSelector: Attempt to send to non-object or invalid script. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", PRINT_REG(obj_location), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
 	}
 
 	index = obj->locateVarSelector(segMan, selectorId);


Commit: 240b0ca3488231e0cfb9c56b1c21ccdd309f0908
    https://github.com/scummvm/scummvm/commit/240b0ca3488231e0cfb9c56b1c21ccdd309f0908
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Add a trap for invalid calls to kNumCels

Changed paths:
    engines/sci/graphics/celobj32.cpp



diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index d67a4dc..9019517 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -812,7 +812,21 @@ int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) {
 	const byte *const data = resource->data;
 
 	const uint16 loopCount = data[2];
-	if (loopNo >= loopCount || loopNo < 0) {
+
+	// Every version of SCI32 has a logic error in this function that causes
+	// random memory to be read if a script requests the cel count for one
+	// past the maximum loop index. At least GK1 room 800 does this, and gets
+	// stuck in an infinite loop because the game script expects this method
+	// to return a non-zero value.
+	// The scope of this bug means it is likely to pop up in other games, so we
+	// explicitly trap the bad condition here and report it so that any other
+	// game scripts relying on this broken behavior can be fixed as well
+	if (loopNo == loopCount) {
+		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
+		error("[CelObjView::getNumCels]: loop number is equal to loop count in method %s::%s (room %d, script %d, localCall %x)", origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+	}
+
+	if (loopNo > loopCount || loopNo < 0) {
 		return 0;
 	}
 


Commit: 3f9172676526aa04983f148d1262af6ea9fb53ef
    https://github.com/scummvm/scummvm/commit/3f9172676526aa04983f148d1262af6ea9fb53ef
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Rewrite kArray & kString

This change invalidates earlier SCI32 save games, which separated
arrays and strings in an incompatible manner. Old save games
contain invalid references to a string segment which no longer
exists, and contain incompatible array structures that lack
critical type information.

Changed paths:
    engines/sci/console.cpp
    engines/sci/engine/gc.cpp
    engines/sci/engine/kernel.cpp
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kfile.cpp
    engines/sci/engine/kgraphics32.cpp
    engines/sci/engine/klists.cpp
    engines/sci/engine/kpathing.cpp
    engines/sci/engine/kstring.cpp
    engines/sci/engine/kvideo.cpp
    engines/sci/engine/message.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/savegame.h
    engines/sci/engine/seg_manager.cpp
    engines/sci/engine/seg_manager.h
    engines/sci/engine/segment.cpp
    engines/sci/engine/segment.h
    engines/sci/engine/workarounds.cpp
    engines/sci/engine/workarounds.h
    engines/sci/graphics/controls32.cpp
    engines/sci/graphics/transitions32.cpp



diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 3f65ca7..6d04b82 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -2075,10 +2075,6 @@ bool Console::cmdPrintSegmentTable(int argc, const char **argv) {
 				debugPrintf("A  SCI32 arrays (%d)", (*(ArrayTable *)mobj).entries_used);
 				break;
 
-			case SEG_TYPE_STRING:
-				debugPrintf("T  SCI32 strings (%d)", (*(StringTable *)mobj).entries_used);
-				break;
-
 			case SEG_TYPE_BITMAP:
 				debugPrintf("T  SCI32 bitmaps (%d)", (*(BitmapTable *)mobj).entries_used);
 				break;
@@ -2210,9 +2206,6 @@ bool Console::segmentInfo(int nr) {
 	break;
 
 #ifdef ENABLE_SCI32
-	case SEG_TYPE_STRING:
-		debugPrintf("SCI32 strings\n");
-		break;
 	case SEG_TYPE_ARRAY:
 		debugPrintf("SCI32 arrays\n");
 		break;
@@ -2808,16 +2801,11 @@ bool Console::cmdViewReference(int argc, const char **argv) {
 		case SIG_TYPE_REFERENCE: {
 			switch (_engine->_gamestate->_segMan->getSegmentType(reg.getSegment())) {
 #ifdef ENABLE_SCI32
-				case SEG_TYPE_STRING: {
-					debugPrintf("SCI32 string\n");
-					const SciString *str = _engine->_gamestate->_segMan->lookupString(reg);
-					Common::hexdump((const byte *) str->getRawData(), str->getSize(), 16, 0);
-					break;
-				}
 				case SEG_TYPE_ARRAY: {
 					debugPrintf("SCI32 array:\n");
-					const SciArray<reg_t> *array = _engine->_gamestate->_segMan->lookupArray(reg);
-					hexDumpReg(array->getRawData(), array->getSize(), 4, 0, true);
+					// TODO: Different prints for different types
+					const SciArray *array = _engine->_gamestate->_segMan->lookupArray(reg);
+					hexDumpReg((reg_t *)array->getRawData(), array->size(), 4, 0, true);
 					break;
 				}
 				case SEG_TYPE_BITMAP: {
diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp
index 6c1713b..50f7709 100644
--- a/engines/sci/engine/gc.cpp
+++ b/engines/sci/engine/gc.cpp
@@ -46,7 +46,7 @@ const char *segmentTypeNames[] = {
 	"dynmem",    // 9
 	"obsolete",  // 10: obsolete string fragments
 	"array",     // 11: SCI32 arrays
-	"string"     // 12: SCI32 strings
+	"obsolete"   // 12: obsolete SCI32 strings
 };
 #endif
 
diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp
index c03504d..85cad99 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -410,7 +410,6 @@ uint16 Kernel::findRegType(reg_t reg) {
 	case SEG_TYPE_HUNK:
 #ifdef ENABLE_SCI32
 	case SEG_TYPE_ARRAY:
-	case SEG_TYPE_STRING:
 	case SEG_TYPE_BITMAP:
 #endif
 		result |= SIG_TYPE_REFERENCE;
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 45477e1..bd9d1bb 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -484,29 +484,36 @@ reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
 reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
 
 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 kArray(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayNew(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv);
+reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayFree(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayFill(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayCompare(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv);
+reg_t kArrayByteCopy(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 kStringGetChar(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 kStringLength(EngineState *s, int argc, reg_t *argv);
+reg_t kStringFormat(EngineState *s, int argc, reg_t *argv);
+reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv);
+reg_t kStringToInteger(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 kStringToUpperCase(EngineState *s, int argc, reg_t *argv);
+reg_t kStringToLowerCase(EngineState *s, int argc, reg_t *argv);
+reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv);
+reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv);
 
 reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv);
 reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index eafbe12..6630f1a 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -501,44 +501,56 @@ static const SciKernelMapSubEntry kRemapColors_subops[] = {
 };
 
 //    version,         subId, function-mapping,                    signature,              workarounds
+static const SciKernelMapSubEntry kArray_subops[] = {
+	{ SIG_SCI32,           0, MAP_CALL(ArrayNew),                  "ii",                   NULL },
+	{ SIG_SCI32,           1, MAP_CALL(ArrayGetSize),              "r",                    NULL },
+	{ SIG_SCI32,           2, MAP_CALL(ArrayGetElement),           "ri",                   NULL },
+	{ SIG_SCI32,           3, MAP_CALL(ArraySetElements),          "ri.*",                 kArraySetElements_workarounds },
+	{ SIG_SCI32,           4, MAP_CALL(ArrayFree),                 "r",                    NULL },
+	{ SIG_SCI32,           5, MAP_CALL(ArrayFill),                 "riii",                 NULL },
+	{ SIG_SCI32,           6, MAP_CALL(ArrayCopy),                 "ririi",                NULL },
+	// there is no subop 7
+	{ SIG_SCI32,           8, MAP_CALL(ArrayDuplicate),            "r",                    NULL },
+	{ SIG_SCI32,           9, MAP_CALL(ArrayGetData),              "[or]",                 NULL },
+	{ SIG_SCI3,           10, MAP_CALL(ArrayByteCopy),             "ririi",                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*)",            kStringPutAt_workarounds },
-	// 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, SCI2.1 Early and SCI2.1 Middle=
-	{ SIG_UNTIL_SCI21MID,  8, MAP_CALL(StringDup),                 "[or]",                 NULL },
-	// TODO: This gets called with null references in Torin. Check if this is correct, or it's
-	// caused by missing functionality
-	{ SIG_UNTIL_SCI21MID,  9, MAP_CALL(StringGetData),             "[or0]",                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 },
-	{ SIG_UNTIL_SCI21MID, 14, MAP_CALL(StringTrim),                "[or]i",                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_SINCE_SCI21LATE, 8, MAP_CALL(StringLen),                 "[or]",                 NULL },
-	{ SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringPrintf),              "[or](.*)",             NULL },
-	{ SIG_SINCE_SCI21LATE,10, MAP_CALL(StringPrintfBuf),           "[or](.*)",             NULL },
-	{ SIG_SINCE_SCI21LATE,11, MAP_CALL(StringAtoi),                "[or]",                 NULL },
-	{ SIG_SINCE_SCI21LATE,12, MAP_CALL(StringTrim),                "[or]i",                NULL },
-	{ SIG_SINCE_SCI21LATE,13, MAP_CALL(StringUpper),               "[or]",                 NULL },
-	{ SIG_SINCE_SCI21LATE,14, MAP_CALL(StringLower),               "[or]",                 NULL },
-	{ SIG_SINCE_SCI21LATE,15, MAP_CALL(StringTrn),                 "[or]",                 NULL },
-	{ SIG_SINCE_SCI21LATE,16, MAP_CALL(StringTrnExclude),          "[or]",                 NULL },
+	// every single copy of script 64918 in SCI2 through 2.1mid calls StringNew
+	// with a second type argument which is unused (new strings are always type
+	// 3)
+	{ SIG_UNTIL_SCI21MID,  0, MAP_CALL(StringNew),                 "i(i)",                 NULL },
+	{ SIG_UNTIL_SCI21MID,  1, MAP_CALL(ArrayGetSize),              "r",                    NULL },
+	{ SIG_UNTIL_SCI21MID,  2, MAP_CALL(StringGetChar),             "ri",                   NULL },
+	{ SIG_UNTIL_SCI21MID,  3, MAP_CALL(ArraySetElements),          "rii*",                 kArraySetElements_workarounds },
+	{ SIG_UNTIL_SCI21MID,  4, MAP_CALL(StringFree),                "[0r]",                 NULL },
+	{ SIG_UNTIL_SCI21MID,  5, MAP_CALL(ArrayFill),                 "rii",                  NULL },
+	{ SIG_UNTIL_SCI21MID,  6, MAP_CALL(ArrayCopy),                 "ririi",                NULL },
+	{ SIG_SCI32,           7, MAP_CALL(StringCompare),             "rr(i)",                NULL },
+
+	{ SIG_UNTIL_SCI21MID,  8, MAP_CALL(ArrayDuplicate),            "r",                    NULL },
+	{ SIG_UNTIL_SCI21MID,  9, MAP_CALL(StringGetData),             "[0or]",                NULL },
+	{ SIG_UNTIL_SCI21MID, 10, MAP_CALL(StringLength),              "r",                    NULL },
+	{ SIG_UNTIL_SCI21MID, 11, MAP_CALL(StringFormat),              "r.*",                  NULL },
+	{ SIG_UNTIL_SCI21MID, 12, MAP_CALL(StringFormatAt),            "r[ro].*",              NULL },
+	{ SIG_UNTIL_SCI21MID, 13, MAP_CALL(StringToInteger),           "r",                    NULL },
+	{ SIG_UNTIL_SCI21MID, 14, MAP_CALL(StringTrim),                "ri(i)",                NULL },
+	{ SIG_UNTIL_SCI21MID, 15, MAP_CALL(StringToUpperCase),         "r",                    NULL },
+	{ SIG_UNTIL_SCI21MID, 16, MAP_CALL(StringToLowerCase),         "r",                    NULL },
+	{ SIG_UNTIL_SCI21MID, 17, MAP_CALL(StringReplaceSubstring),    "rrrr",                 NULL },
+	{ SIG_UNTIL_SCI21MID, 18, MAP_CALL(StringReplaceSubstringEx),  "rrrr",                 NULL },
+
+	{ SIG_SINCE_SCI21LATE, 8, MAP_CALL(StringLength),              "r",                    NULL },
+	{ SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringFormat),              "r.*",                  NULL },
+	{ SIG_SINCE_SCI21LATE,10, MAP_CALL(StringFormatAt),            "rr.*",                 NULL },
+	{ SIG_SINCE_SCI21LATE,11, MAP_CALL(StringToInteger),           "r",                    NULL },
+	{ SIG_SINCE_SCI21LATE,12, MAP_CALL(StringTrim),                "ri(i)",                NULL },
+	{ SIG_SINCE_SCI21LATE,13, MAP_CALL(StringToUpperCase),         "r",                    NULL },
+	{ SIG_SINCE_SCI21LATE,14, MAP_CALL(StringToLowerCase),         "r",                    NULL },
+	{ SIG_SINCE_SCI21LATE,15, MAP_CALL(StringReplaceSubstring),    "rrrr",                 NULL },
+	{ SIG_SINCE_SCI21LATE,16, MAP_CALL(StringReplaceSubstringEx),  "rrrr",                 NULL },
 	SCI_SUBOPENTRY_TERMINATOR
 };
 
@@ -811,7 +823,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 
 	{ MAP_CALL(AddPlane),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(AddScreenItem),     SIG_EVERYWHERE,           "o",                     NULL,            NULL },
-	{ MAP_CALL(Array),             SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
+	{ MAP_CALL(Array),             SIG_EVERYWHERE,           "(.*)",                  kArray_subops,   NULL },
 	{ MAP_CALL(CreateTextBitmap),  SIG_EVERYWHERE,           "i(.*)",                 NULL,            NULL },
 	{ MAP_CALL(DeletePlane),       SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(DeleteScreenItem),  SIG_EVERYWHERE,           "o",                     NULL,            NULL },
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index e8b9d04..324eaf2 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -693,8 +693,8 @@ reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) {
 	// We don't really use or need any of this...
 
 	uint16 saveSlot = argv[0].toUint16();
-	char* fileName = s->_segMan->lookupString(argv[1])->getRawData();
-	warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName);
+	const SciArray &fileName = *s->_segMan->lookupArray(argv[1]);
+	warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName.toString().c_str());
 
 	return TRUE_REG;	// slot creation was successful
 }
@@ -1051,7 +1051,7 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
 	// Param 1: a string with game parameters, ignored
 	// Param 2: the selected slot
 
-	SciString *resultString = s->_segMan->lookupString(argv[0]);
+	SciArray *resultString = s->_segMan->lookupArray(argv[0]);
 	uint16 virtualId = argv[2].toUint16();
 	if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
 		error("kMakeSaveFileName: invalid savegame ID specified");
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index a33fcf3..3c7a3c2 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -317,7 +317,7 @@ reg_t kText(EngineState *s, int argc, reg_t *argv) {
 reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) {
 	g_sci->_gfxText32->setFont(argv[2].toUint16());
 
-	reg_t *rect = s->_segMan->derefRegPtr(argv[0], 4);
+	SciArray *rect = s->_segMan->lookupArray(argv[0]);
 	if (rect == nullptr) {
 		error("kTextSize: %04x:%04x cannot be dereferenced", PRINT_REG(argv[0]));
 	}
@@ -327,10 +327,14 @@ reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) {
 	bool doScaling = argc > 4 ? argv[4].toSint16() : true;
 
 	Common::Rect textRect = g_sci->_gfxText32->getTextSize(text, maxWidth, doScaling);
-	rect[0] = make_reg(0, textRect.left);
-	rect[1] = make_reg(0, textRect.top);
-	rect[2] = make_reg(0, textRect.right - 1);
-	rect[3] = make_reg(0, textRect.bottom - 1);
+
+	reg_t value[4] = {
+		make_reg(0, textRect.left),
+		make_reg(0, textRect.top),
+		make_reg(0, textRect.right - 1),
+		make_reg(0, textRect.bottom - 1) };
+
+	rect->setElements(0, 4, value);
 	return s->r_acc;
 }
 
diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp
index db6ffb1..5cd9c36 100644
--- a/engines/sci/engine/klists.cpp
+++ b/engines/sci/engine/klists.cpp
@@ -683,197 +683,105 @@ reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kArray(EngineState *s, int argc, reg_t *argv) {
-	uint16 op = argv[0].toUint16();
-
-	// Use kString when accessing strings
-	// This is possible, as strings inherit from arrays
-	// and in this case (type 3) arrays are of type char *.
-	// kString is almost exactly the same as kArray, so
-	// this call is possible
-	// TODO: we need to either merge SCI2 strings and
-	// arrays together, and in the future merge them with
-	// the SCI1 strings and arrays in the segment manager
-	bool callStringFunc = false;
-	if (op == 0) {
-		// New, check if the target type is 3 (string)
-		if (argv[2].toUint16() == 3)
-			callStringFunc = true;
-	} else {
-		if (s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_STRING ||
-			s->_segMan->getSegmentType(argv[1].getSegment()) == SEG_TYPE_SCRIPT) {
-			callStringFunc = true;
-		}
-
-#if 0
-		if (op == 6) {
-			if (s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_STRING ||
-				s->_segMan->getSegmentType(argv[3].getSegment()) == SEG_TYPE_SCRIPT) {
-				callStringFunc = true;
-			}
-		}
-#endif
-	}
+	if (!s)
+		return make_reg(0, getSciVersion());
+	error("not supposed to call this");
+}
 
-	if (callStringFunc) {
-		Kernel *kernel = g_sci->getKernel();
-		uint16 kernelStringFuncId = kernel->_kernelFunc_StringId;
-		if (kernelStringFuncId) {
-			const KernelFunction *kernelStringFunc = &kernel->_kernelFuncs[kernelStringFuncId];
-
-			if (op < kernelStringFunc->subFunctionCount) {
-				// subfunction-id is valid
-				const KernelSubFunction *kernelStringSubCall = &kernelStringFunc->subFunctions[op];
-				argc--;
-				argv++; // remove subfunction-id from arguments
-				// and call the kString subfunction
-				return kernelStringSubCall->function(s, argc, argv);
-			}
-		}
-	}
+reg_t kArrayNew(EngineState *s, int argc, reg_t *argv) {
+	uint16 size = argv[0].toUint16();
+	const SciArrayType type = (SciArrayType)argv[1].toUint16();
 
-	switch (op) {
-	case 0: { // New
-		reg_t arrayHandle;
-		SciArray<reg_t> *array = s->_segMan->allocateArray(&arrayHandle);
-		array->setType(argv[2].toUint16());
-		array->setSize(argv[1].toUint16());
-		return arrayHandle;
-	}
-	case 1: { // Size
-		SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
-		return make_reg(0, array->getSize());
+	if (type == kArrayTypeString) {
+		++size;
 	}
-	case 2: { // At (return value at an index)
-		SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
-		if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
-			// HACK: Phantasmagoria 2 keeps trying to access past the end of an
-			// array when it starts. I'm assuming it's trying to see where the
-			// array ends, or tries to resize it. Adjust the array size
-			// accordingly, and return NULL for now.
-			if (array->getSize() == argv[2].toUint16()) {
-				array->setSize(argv[2].toUint16());
-				return NULL_REG;
-			}
-		}
-		return array->getValue(argv[2].toUint16());
-	}
-	case 3: { // Atput (put value at an index)
-		SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
-
-		uint32 index = argv[2].toUint16();
-		uint32 count = argc - 3;
 
-		if (index + count > 65535)
-			break;
+	reg_t arrayHandle;
+	s->_segMan->allocateArray(type, size, &arrayHandle);
+	return arrayHandle;
+}
 
-		if (array->getSize() < index + count)
-			array->setSize(index + count);
+reg_t kArrayGetSize(EngineState *s, int argc, reg_t *argv) {
+	const SciArray &array = *s->_segMan->lookupArray(argv[0]);
+	return make_reg(0, array.size());
+}
 
-		for (uint16 i = 0; i < count; i++)
-			array->setValue(i + index, argv[i + 3]);
+reg_t kArrayGetElement(EngineState *s, int argc, reg_t *argv) {
+	SciArray &array = *s->_segMan->lookupArray(argv[0]);
+	return array.getAsID(argv[1].toUint16());
+}
 
-		return argv[1]; // We also have to return the handle
-	}
-	case 4: // Free
-		// Freeing of arrays is handled by the garbage collector
-		return s->r_acc;
-	case 5: { // Fill
-		SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
-		uint16 index = argv[2].toUint16();
+reg_t kArraySetElements(EngineState *s, int argc, reg_t *argv) {
+	SciArray &array = *s->_segMan->lookupArray(argv[0]);
+	array.setElements(argv[1].toUint16(), argc - 2, argv + 2);
+	return argv[0];
+}
 
-		// A count of -1 means fill the rest of the array
-		uint16 count = argv[3].toSint16() == -1 ? array->getSize() - index : argv[3].toUint16();
-		uint16 arraySize = array->getSize();
+reg_t kArrayFree(EngineState *s, int argc, reg_t *argv) {
+	s->_segMan->freeArray(argv[0]);
+	return s->r_acc;
+}
 
-		if (arraySize < index + count)
-			array->setSize(index + count);
+reg_t kArrayFill(EngineState *s, int argc, reg_t *argv) {
+	SciArray &array = *s->_segMan->lookupArray(argv[0]);
+	array.fill(argv[1].toUint16(), argv[2].toUint16(), argv[3]);
+	return argv[0];
+}
 
-		for (uint16 i = 0; i < count; i++)
-			array->setValue(i + index, argv[4]);
+reg_t kArrayCopy(EngineState *s, int argc, reg_t *argv) {
+	SciArray &target = *s->_segMan->lookupArray(argv[0]);
+	const uint16 targetIndex = argv[1].toUint16();
 
-		return argv[1];
+	SciArray source;
+	// String copies may be made from static script data
+	if (!s->_segMan->isArray(argv[2])) {
+		source.setType(kArrayTypeString);
+		source.fromString(s->_segMan->getString(argv[2]));
+	} else {
+		source = *s->_segMan->lookupArray(argv[2]);
 	}
-	case 6: { // Cpy
-		if (argv[1].isNull() || argv[3].isNull()) {
-			if (getSciVersion() == SCI_VERSION_3) {
-				// FIXME: Happens in SCI3, probably because of a missing kernel function.
-				warning("kArray(Cpy): Request to copy from or to a null pointer");
-				return NULL_REG;
-			} else {
-				// SCI2-2.1: error out
-				error("kArray(Cpy): Request to copy from or to a null pointer");
-			}
-		}
+	const uint16 sourceIndex = argv[3].toUint16();
+	const uint16 count = argv[4].toUint16();
 
-		reg_t arrayHandle = argv[1];
-		SciArray<reg_t> *array1 = s->_segMan->lookupArray(argv[1]);
-		SciArray<reg_t> *array2 = s->_segMan->lookupArray(argv[3]);
-		uint32 index1 = argv[2].toUint16();
-		uint32 index2 = argv[4].toUint16();
-
-		// The original engine ignores bad copies too
-		if (index2 > array2->getSize())
-			break;
-
-		// A count of -1 means fill the rest of the array
-		uint32 count = argv[5].toSint16() == -1 ? array2->getSize() - index2 : argv[5].toUint16();
-
-		if (array1->getSize() < index1 + count)
-			array1->setSize(index1 + count);
+	target.copy(source, sourceIndex, targetIndex, count);
+	return argv[0];
+}
 
-		for (uint16 i = 0; i < count; i++)
-			array1->setValue(i + index1, array2->getValue(i + index2));
+reg_t kArrayDuplicate(EngineState *s, int argc, reg_t *argv) {
+	reg_t targetHandle;
 
-		return arrayHandle;
+	// String duplicates may be made from static script data
+	if (!s->_segMan->isArray(argv[0])) {
+		const Common::String source = s->_segMan->getString(argv[0]);
+		SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, source.size(), &targetHandle);
+		target.fromString(source);
+	} else {
+		SciArray &source = *s->_segMan->lookupArray(argv[0]);
+		SciArray &target = *s->_segMan->allocateArray(source.getType(), source.size(), &targetHandle);
+		target = source;
 	}
-	case 7: // Cmp
-		// Not implemented in SSCI
-		warning("kArray(Cmp) called");
-		return s->r_acc;
-	case 8: { // Dup
-		if (argv[1].isNull()) {
-			warning("kArray(Dup): Request to duplicate a null pointer");
-#if 0
-			// Allocate an array anyway
-			reg_t arrayHandle;
-			SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle);
-			dupArray->setType(3);
-			dupArray->setSize(0);
-			return arrayHandle;
-#endif
-			return NULL_REG;
-		}
-		SegmentObj *sobj = s->_segMan->getSegmentObj(argv[1].getSegment());
-		if (!sobj || sobj->getType() != SEG_TYPE_ARRAY)
-			error("kArray(Dup): Request to duplicate a segment which isn't an array");
-
-		reg_t arrayHandle;
-		SciArray<reg_t> *dupArray = s->_segMan->allocateArray(&arrayHandle);
-		// This must occur after allocateArray, as inserting a new object
-		// in the heap object list might invalidate this pointer. Also refer
-		// to the same issue in kClone()
-		SciArray<reg_t> *array = s->_segMan->lookupArray(argv[1]);
-
-		dupArray->setType(array->getType());
-		dupArray->setSize(array->getSize());
 
-		for (uint32 i = 0; i < array->getSize(); i++)
-			dupArray->setValue(i, array->getValue(i));
-
-		return arrayHandle;
-	}
-	case 9: // Getdata
-		if (!s->_segMan->isHeapObject(argv[1]))
-			return argv[1];
+	return targetHandle;
+}
 
-		return readSelector(s->_segMan, argv[1], SELECTOR(data));
-	default:
-		error("Unknown kArray subop %d", op);
+reg_t kArrayGetData(EngineState *s, int argc, reg_t *argv) {
+	if (s->_segMan->isObject(argv[0])) {
+		return readSelector(s->_segMan, argv[0], SELECTOR(data));
 	}
 
-	return NULL_REG;
+	return argv[0];
 }
 
+reg_t kArrayByteCopy(EngineState *s, int argc, reg_t *argv) {
+	SciArray &target = *s->_segMan->lookupArray(argv[0]);
+	const uint16 targetOffset = argv[1].toUint16();
+	const SciArray &source = *s->_segMan->lookupArray(argv[2]);
+	const uint16 sourceOffset = argv[3].toUint16();
+	const uint16 count = argv[4].toUint16();
+
+	target.byteCopy(source, sourceOffset, targetOffset, count);
+	return argv[0];
+}
 #endif
 
 } // End of namespace Sci
diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp
index 06f1629..667f77f 100644
--- a/engines/sci/engine/kpathing.cpp
+++ b/engines/sci/engine/kpathing.cpp
@@ -285,6 +285,13 @@ struct PathfindingState {
 static Common::Point readPoint(SegmentRef list_r, int offset) {
 	Common::Point point;
 
+#ifdef ENABLE_SCI32
+	if (getSciVersion() >= SCI_VERSION_2) {
+		point.x = READ_UINT16(list_r.raw + offset * POLY_POINT_SIZE + 0);
+		point.y = READ_UINT16(list_r.raw + offset * POLY_POINT_SIZE + 2);
+	} else
+#endif
+
 	if (list_r.isRaw) {	// dynmem blocks are raw
 		point.x = (int16)READ_SCIENDIAN_UINT16(list_r.raw + offset * POLY_POINT_SIZE);
 		point.y = (int16)READ_SCIENDIAN_UINT16(list_r.raw + offset * POLY_POINT_SIZE + 2);
@@ -296,6 +303,12 @@ static Common::Point readPoint(SegmentRef list_r, int offset) {
 }
 
 static void writePoint(SegmentRef ref, int offset, const Common::Point &point) {
+#ifdef ENABLE_SCI32
+	if (getSciVersion() >= SCI_VERSION_2) {
+		WRITE_UINT16(ref.raw + offset * POLY_POINT_SIZE + 0, point.x);
+		WRITE_UINT16(ref.raw + offset * POLY_POINT_SIZE + 2, point.y);
+	} else
+#endif
 	if (ref.isRaw) {	// dynmem blocks are raw
 		WRITE_SCIENDIAN_UINT16(ref.raw + offset * POLY_POINT_SIZE, point.x);
 		WRITE_SCIENDIAN_UINT16(ref.raw + offset * POLY_POINT_SIZE + 2, point.y);
@@ -1397,10 +1410,8 @@ static reg_t allocateOutputArray(SegManager *segMan, int size) {
 
 #ifdef ENABLE_SCI32
 	if (getSciVersion() >= SCI_VERSION_2) {
-		SciArray<reg_t> *array = segMan->allocateArray(&addr);
+		SciArray *array = segMan->allocateArray(kArrayTypeInt16, size * 2, &addr);
 		assert(array);
-		array->setType(0);
-		array->setSize(size * 2);
 		return addr;
 	}
 #endif
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index 1c08bf5..4d96ddc 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -202,11 +202,6 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) {
 }
 
 
-#define ALIGN_NONE 0
-#define ALIGN_RIGHT 1
-#define ALIGN_LEFT -1
-#define ALIGN_CENTER 2
-
 /*  Format(targ_address, textresnr, index_inside_res, ...)
 ** or
 **  Format(targ_address, heap_text_addr, ...)
@@ -214,6 +209,13 @@ reg_t kReadNumber(EngineState *s, int argc, reg_t *argv) {
 ** the supplied parameters and writes it to the targ_address.
 */
 reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
+	enum {
+		ALIGN_NONE   = 0,
+		ALIGN_RIGHT  = 1,
+		ALIGN_LEFT   = -1,
+		ALIGN_CENTER = 2
+	};
+
 	uint16 *arguments;
 	reg_t dest = argv[0];
 	int maxsize = 4096; /* Arbitrary... */
@@ -301,12 +303,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
 			case 's': { /* Copy string */
 				reg_t reg = argv[startarg + paramindex];
 
-#ifdef ENABLE_SCI32
-				// If the string is a string object, get to the actual string in the data selector
-				if (s->_segMan->isObject(reg))
-					reg = readSelector(s->_segMan, reg, SELECTOR(data));
-#endif
-
 				Common::String tempsource = g_sci->getKernel()->lookupText(reg,
 				                                  arguments[paramindex + 1]);
 				int slen = strlen(tempsource.c_str());
@@ -379,12 +375,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
 			case 'u':
 				unsignedVar = true;
 			case 'd': { /* Copy decimal */
-				// In the new SCI2 kString function, %d is used for unsigned
-				// integers. An example is script 962 in Shivers - it uses %d
-				// to create file names.
-				if (getSciVersion() >= SCI_VERSION_2)
-					unsignedVar = true;
-
 				/* int templen; -- unused atm */
 				const char *format_string = "%d";
 
@@ -437,14 +427,6 @@ reg_t kFormat(EngineState *s, int argc, reg_t *argv) {
 
 	*target = 0; /* Terminate string */
 
-#ifdef ENABLE_SCI32
-	// Resize SCI32 strings if necessary
-	if (getSciVersion() >= SCI_VERSION_2) {
-		SciString *string = s->_segMan->lookupString(dest);
-		string->setSize(strlen(targetbuf) + 1);
-	}
-#endif
-
 	s->_segMan->strcpy(dest, targetbuf);
 
 	return dest; /* Return target addr */
@@ -661,238 +643,236 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) {
 
 #ifdef ENABLE_SCI32
 
-// TODO: there is an unused second argument, happens at least in LSL6 right during the intro
+reg_t kString(EngineState *s, int argc, reg_t *argv) {
+	if (!s)
+		return make_reg(0, getSciVersion());
+	error("not supposed to call this");
+}
+
 reg_t kStringNew(EngineState *s, int argc, reg_t *argv) {
 	reg_t stringHandle;
-	SciString *string = s->_segMan->allocateString(&stringHandle);
-	string->setSize(argv[0].toUint16());
-
-	// Make sure the first character is a null character
-	if (string->getSize() > 0)
-		string->setValue(0, 0);
-
+	const uint16 size = argv[0].toUint16();
+	s->_segMan->allocateArray(kArrayTypeString, size, &stringHandle);
 	return stringHandle;
 }
 
-reg_t kStringSize(EngineState *s, int argc, reg_t *argv) {
-	return make_reg(0, s->_segMan->getString(argv[0]).size());
-}
-
-// 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);
-	}
-}
-
-// Atput (put value at an index)
-reg_t kStringPutAt(EngineState *s, int argc, reg_t *argv) {
-	SciString *string = s->_segMan->lookupString(argv[0]);
+reg_t kStringGetChar(EngineState *s, int argc, reg_t *argv) {
+	const uint16 index = argv[1].toUint16();
 
-	uint32 index = argv[1].toUint16();
-	uint32 count = argc - 2;
+	// Game scripts may contain static raw string data
+	if (!s->_segMan->isArray(argv[0])) {
+		const Common::String string = s->_segMan->getString(argv[0]);
+		if (index >= string.size()) {
+			return make_reg(0, 0);
+		}
 
-	if (index + count > 65535)
-		return NULL_REG;
+		return make_reg(0, (byte)string[index]);
+	}
 
-	if (string->getSize() < index + count)
-		string->setSize(index + count);
+	SciArray &array = *s->_segMan->lookupArray(argv[0]);
 
-	for (uint16 i = 0; i < count; i++)
-		string->setValue(i + index, argv[i + 2].toUint16());
+	if (index >= array.size()) {
+		return make_reg(0, 0);
+	}
 
-	return argv[0]; // We also have to return the handle
+	return array.getAsID(index);
 }
 
 reg_t kStringFree(EngineState *s, int argc, reg_t *argv) {
-	// Freeing of strings is handled by the garbage collector
+	if (!argv[0].isNull()) {
+		s->_segMan->freeArray(argv[0]);
+	}
 	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();
+reg_t kStringCompare(EngineState *s, int argc, reg_t *argv) {
+	const Common::String string1 = s->_segMan->getString(argv[0]);
+	const Common::String string2 = s->_segMan->getString(argv[1]);
 
-	// 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();
+	int result;
+	if (argc == 3) {
+		result = strncmp(string1.c_str(), string2.c_str(), argv[2].toUint16());
+	} else {
+		result = strcmp(string1.c_str(), string2.c_str());
+	}
 
-	if (stringSize < index + count)
-		string->setSize(index + count);
+	return make_reg(0, (result > 0) - (result < 0));
+}
 
-	for (uint16 i = 0; i < count; i++)
-		string->setValue(i + index, argv[3].toUint16());
+reg_t kStringGetData(EngineState *s, int argc, reg_t *argv) {
+	if (s->_segMan->isObject(argv[0])) {
+		return readSelector(s->_segMan, argv[0], SELECTOR(data));
+	}
 
 	return argv[0];
 }
 
-reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) {
-	const char *string2 = 0;
-	uint32 string2Size = 0;
-	Common::String string;
+reg_t kStringLength(EngineState *s, int argc, reg_t *argv) {
+	return make_reg(0, s->_segMan->getString(argv[0]).size());
+}
 
-	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;
+namespace {
+	bool isFlag(const char c) {
+		return strchr("-+ 0#", c);
 	}
 
-	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");
+	bool isPrecision(const char c) {
+		return strchr(".0123456789*", c);
 	}
 
-	// The original engine ignores bad copies too
-	if (index2 >= string2Size)
-		return NULL_REG;
+	bool isWidth(const char c) {
+		return strchr("0123456789*", c);
+	}
 
-	// A count of -1 means fill the rest of the array
-	uint32 count = string2Size - index2;
-	if (argv[4].toSint16() != -1) {
-		count = MIN(count, (uint32)argv[4].toUint16());
+	bool isLength(const char c) {
+		return strchr("hjlLtz", c);
 	}
-//	reg_t strAddress = argv[0];
 
-	SciString *string1 = s->_segMan->lookupString(argv[0]);
-	//SciString *string1 = !argv[1].isNull() ? s->_segMan->lookupString(argv[1]) : s->_segMan->allocateString(&strAddress);
+	bool isType(const char c) {
+		return strchr("dsuxXaAceEfFgGinop", c);
+	}
 
-	if (string1->getSize() < index1 + count)
-		string1->setSize(index1 + count);
+	bool isSignedType(const char type) {
+		return type == 'd' || type == 'i';
+	}
 
-	// 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]);
+	bool isUnsignedType(const char type) {
+		return strchr("uxXoc", type);
+	}
 
-	return argv[0];
-}
+	bool isStringType(const char type) {
+		return type == 's';
+	}
 
-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]);
+	Common::String readPlaceholder(const char *&in, reg_t arg) {
+		const char *const start = in;
 
-	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()));
-}
+		assert(*in == '%');
+		++in;
 
-// was removed for SCI2.1 Late+
-reg_t kStringDup(EngineState *s, int argc, reg_t *argv) {
-	reg_t stringHandle;
-
-	SciString *dupString = s->_segMan->allocateString(&stringHandle);
+		while (isFlag(*in)) {
+			++in;
+		}
+		while (isWidth(*in)) {
+			++in;
+		}
+		while (isPrecision(*in)) {
+			++in;
+		}
+		while (isLength(*in)) {
+			++in;
+		}
 
-	if (argv[0].getSegment() == s->_segMan->getStringSegmentId()) {
-		*dupString = *s->_segMan->lookupString(argv[0]);
-	} else {
-		dupString->fromString(s->_segMan->getString(argv[0]));
+		char format[64];
+		format[0] = '\0';
+		const char type = *in++;
+		Common::strlcpy(format, start, MIN<size_t>(64, in - start + 1));
+
+		if (isType(type)) {
+			if (isSignedType(type)) {
+				const int value = arg.toSint16();
+				return Common::String::format(format, value);
+			} else if (isUnsignedType(type)) {
+				const uint value = arg.toUint16();
+				return Common::String::format(format, value);
+			} else if (isStringType(type)) {
+				Common::String value;
+				SegManager *segMan = g_sci->getEngineState()->_segMan;
+				if (segMan->isObject(arg)) {
+					value = segMan->getString(readSelector(segMan, arg, SELECTOR(data)));
+				} else {
+					value = segMan->getString(arg);
+				}
+				return Common::String::format(format, value.c_str());
+			} else {
+				error("Unsupported format type %c", type);
+			}
+		} else {
+			return Common::String::format("%s", format);
+		}
 	}
+};
 
-	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];
+Common::String format(const Common::String &source, int argc, const reg_t *argv) {
+	Common::String out;
+	const char *in = source.c_str();
+	int argIndex = 0;
+	while (*in != '\0') {
+		if (*in == '%') {
+			if (in[1] == '%') {
+				in += 2;
+				out += "%";
+				continue;
+			}
 
-	return readSelector(s->_segMan, argv[0], SELECTOR(data));
-}
+			assert(argIndex < argc);
+			out += readPlaceholder(in, argv[argIndex++]);
+		} else {
+			out += *in++;
+		}
+	}
 
-reg_t kStringLen(EngineState *s, int argc, reg_t *argv) {
-	return make_reg(0, s->_segMan->strlen(argv[0]));
+	return out;
 }
 
-reg_t kStringPrintf(EngineState *s, int argc, reg_t *argv) {
+reg_t kStringFormat(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;
+	SciArray &target = *s->_segMan->allocateArray(kArrayTypeString, 0, &stringHandle);
+	reg_t source = argv[0];
+	// Str objects may be passed in place of direct references to string data
+	if (s->_segMan->isObject(argv[0])) {
+		source = readSelector(s->_segMan, argv[0], SELECTOR(data));
+	}
+	target.fromString(format(s->_segMan->getString(source), argc - 1, argv + 1));
 	return stringHandle;
 }
 
-reg_t kStringPrintfBuf(EngineState *s, int argc, reg_t *argv) {
-	return kFormat(s, argc, argv);
+reg_t kStringFormatAt(EngineState *s, int argc, reg_t *argv) {
+	SciArray &target = *s->_segMan->lookupArray(argv[0]);
+	reg_t source = argv[1];
+	// Str objects may be passed in place of direct references to string data
+	if (s->_segMan->isObject(argv[1])) {
+		source = readSelector(s->_segMan, argv[1], SELECTOR(data));
+	}
+	target.fromString(format(s->_segMan->getString(source), argc - 2, argv + 2));
+	return argv[0];
 }
 
-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 kStringToInteger(EngineState *s, int argc, reg_t *argv) {
+	return make_reg(0, (uint16)s->_segMan->getString(argv[0]).asUint64());
 }
 
 reg_t kStringTrim(EngineState *s, int argc, reg_t *argv) {
-	Common::String string = s->_segMan->getString(argv[0]);
-
-	string.trim();
-	// TODO: Second parameter (bitfield, trim from left, right, center)
-	warning("kStringTrim (%d)", argv[1].getOffset());
-	s->_segMan->strcpy(argv[0], string.c_str());
-	return NULL_REG;
+	SciArray &array = *s->_segMan->lookupArray(argv[0]);
+	const int8 flags = argv[1].toSint16();
+	const char showChar = argc > 2 ? argv[2].toSint16() : '\0';
+	array.trim(flags, showChar);
+	return s->r_acc;
 }
 
-reg_t kStringUpper(EngineState *s, int argc, reg_t *argv) {
+reg_t kStringToUpperCase(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;
+	return argv[0];
 }
 
-reg_t kStringLower(EngineState *s, int argc, reg_t *argv) {
+reg_t kStringToLowerCase(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;
+	return argv[0];
 }
 
-// Possibly kStringTranslateExclude?
-reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv) {
-	warning("kStringTrnExclude (argc = %d)", argc);
-	return NULL_REG;
+reg_t kStringReplaceSubstring(EngineState *s, int argc, reg_t *argv) {
+	error("TODO: kStringReplaceSubstring not implemented");
+	return argv[3];
 }
 
-reg_t kString(EngineState *s, int argc, reg_t *argv) {
-	if (!s)
-		return make_reg(0, getSciVersion());
-	error("not supposed to call this");
+reg_t kStringReplaceSubstringEx(EngineState *s, int argc, reg_t *argv) {
+	error("TODO: kStringReplaceSubstringEx not implemented");
+	return argv[3];
 }
-
 #endif
 
 } // End of namespace Sci
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index 46829cc..ab216c51 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -253,11 +253,13 @@ reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) {
 	Common::Rect frameRect;
 	const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect);
 
-	reg_t *outRect = s->_segMan->derefRegPtr(argv[0], 4);
-	outRect[0] = make_reg(0, frameRect.left);
-	outRect[1] = make_reg(0, frameRect.top);
-	outRect[2] = make_reg(0, frameRect.right - 1);
-	outRect[3] = make_reg(0, frameRect.bottom - 1);
+	SciArray *outRect = s->_segMan->lookupArray(argv[0]);
+	reg_t values[4] = {
+		make_reg(0, frameRect.left),
+		make_reg(0, frameRect.top),
+		make_reg(0, frameRect.right - 1),
+		make_reg(0, frameRect.bottom - 1) };
+	outRect->setElements(0, 4, values);
 
 	return make_reg(0, numFramesTotal);
 }
diff --git a/engines/sci/engine/message.cpp b/engines/sci/engine/message.cpp
index 5300b72..b0615b4 100644
--- a/engines/sci/engine/message.cpp
+++ b/engines/sci/engine/message.cpp
@@ -439,21 +439,8 @@ Common::String MessageState::processString(const char *s) {
 void MessageState::outputString(reg_t buf, const Common::String &str) {
 #ifdef ENABLE_SCI32
 	if (getSciVersion() >= SCI_VERSION_2) {
-		if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_STRING) {
-			SciString *sciString = _segMan->lookupString(buf);
-			sciString->setSize(str.size() + 1);
-			for (uint32 i = 0; i < str.size(); i++)
-				sciString->setValue(i, str.c_str()[i]);
-			sciString->setValue(str.size(), 0);
-		} else if (_segMan->getSegmentType(buf.getSegment()) == SEG_TYPE_ARRAY) {
-			// Happens in the intro of LSL6, we are asked to write the string
-			// into an array
-			SciArray<reg_t> *sciString = _segMan->lookupArray(buf);
-			sciString->setSize(str.size() + 1);
-			for (uint32 i = 0; i < str.size(); i++)
-				sciString->setValue(i, make_reg(0, str.c_str()[i]));
-			sciString->setValue(str.size(), NULL_REG);
-		}
+		SciArray *sciString = _segMan->lookupArray(buf);
+		sciString->fromString(str);
 	} else {
 #endif
 		SegmentRef buffer_r = _segMan->dereference(buf);
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index be2d766..9def79c 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -102,66 +102,6 @@ void syncWithSerializer(Common::Serializer &s, Node &obj) {
 	syncWithSerializer(s, obj.value);
 }
 
-#ifdef ENABLE_SCI32
-void syncWithSerializer(Common::Serializer &s, SciArray<reg_t> &obj) {
-	byte type = 0;
-	uint32 size = 0;
-
-	if (s.isSaving()) {
-		type = (byte)obj.getType();
-		size = obj.getSize();
-	}
-	s.syncAsByte(type);
-	s.syncAsUint32LE(size);
-	if (s.isLoading()) {
-		obj.setType((int8)type);
-
-		// HACK: Skip arrays that have a negative type
-		if ((int8)type < 0)
-			return;
-
-		obj.setSize(size);
-	}
-
-	for (uint32 i = 0; i < size; i++) {
-		reg_t value;
-
-		if (s.isSaving())
-			value = obj.getValue(i);
-
-		syncWithSerializer(s, value);
-
-		if (s.isLoading())
-			obj.setValue(i, value);
-	}
-}
-
-void syncWithSerializer(Common::Serializer &s, SciString &obj) {
-	uint32 size = 0;
-
-	if (s.isSaving()) {
-		size = obj.getSize();
-		s.syncAsUint32LE(size);
-	} else {
-		s.syncAsUint32LE(size);
-		obj.setSize(size);
-	}
-
-	for (uint32 i = 0; i < size; i++) {
-		char value = 0;
-
-		if (s.isSaving())
-			value = obj.getValue(i);
-
-		s.syncAsByte(value);
-
-		if (s.isLoading())
-			obj.setValue(i, value);
-	}
-}
-
-#endif
-
 #pragma mark -
 
 // By default, sync using syncWithSerializer, which in turn can easily be overloaded.
@@ -292,9 +232,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
 		} else if (type == SEG_TYPE_ARRAY) {
 			// Set the correct segment for SCI32 arrays
 			_arraysSegId = i;
-		} else if (type == SEG_TYPE_STRING) {
-			// Set the correct segment for SCI32 strings
-			_stringSegId = i;
 		} else if (s.getVersion() >= 36 && type == SEG_TYPE_BITMAP) {
 			_bitmapSegId = i;
 #endif
@@ -707,11 +644,39 @@ void ArrayTable::saveLoadWithSerializer(Common::Serializer &ser) {
 	sync_Table<ArrayTable>(ser, *this);
 }
 
-void StringTable::saveLoadWithSerializer(Common::Serializer &ser) {
-	if (ser.getVersion() < 18)
-		return;
+void SciArray::saveLoadWithSerializer(Common::Serializer &s) {
+	uint16 size;
+
+	if (s.isSaving()) {
+		size = _size;
+	}
+
+	s.syncAsByte(_type);
+	s.syncAsByte(_elementSize);
+	s.syncAsUint16LE(size);
+
+	if (s.isLoading()) {
+		resize(size);
+	}
 
-	sync_Table<StringTable>(ser, *this);
+	switch (_type) {
+	case kArrayTypeByte:
+	case kArrayTypeString:
+		s.syncBytes((byte *)_data, size);
+		break;
+	case kArrayTypeInt16:
+		for (int i = 0; i < size; ++i) {
+			s.syncAsUint16LE(((int16 *)_data)[i]);
+		}
+		break;
+	case kArrayTypeID:
+		for (int i = 0; i < size; ++i) {
+			syncWithSerializer(s, ((reg_t *)_data)[i]);
+		}
+		break;
+	default:
+		error("Attempt to sync invalid SciArray type %d", _type);
+	}
 }
 
 void BitmapTable::saveLoadWithSerializer(Common::Serializer &ser) {
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 6616081..5995de7 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,7 +37,7 @@ struct EngineState;
  *
  * Version - new/changed feature
  * =============================
- *      38 - SCI32 cursor
+ *      38 - SCI32 cursor, accurate SCI32 arrays/strings
  *      37 - Segment entry data changed to pointers
  *      36 - SCI32 bitmap segment
  *      35 - SCI32 remap
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index 5cf8d61..ced3830 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -42,7 +42,6 @@ SegManager::SegManager(ResourceManager *resMan, ScriptPatcher *scriptPatcher)
 
 #ifdef ENABLE_SCI32
 	_arraysSegId = 0;
-	_stringSegId = 0;
 	_bitmapSegId = 0;
 #endif
 
@@ -72,7 +71,6 @@ void SegManager::resetSegMan() {
 
 #ifdef ENABLE_SCI32
 	_arraysSegId = 0;
-	_stringSegId = 0;
 	_bitmapSegId = 0;
 #endif
 
@@ -88,9 +86,8 @@ void SegManager::initSysStrings() {
 		_parserPtr = make_reg(_saveDirPtr.getSegment(), _saveDirPtr.getOffset() + 256);
 #ifdef ENABLE_SCI32
 	} else {
-		SciString *saveDirString = allocateString(&_saveDirPtr);
-		saveDirString->setSize(256);
-		saveDirString->setValue(0, 0);
+		SciArray *saveDirString = allocateArray(kArrayTypeString, 256, &_saveDirPtr);
+		saveDirString->byteAt(0) = '\0';
 
 		_parserPtr = NULL_REG;	// no SCI2 game had a parser
 #endif
@@ -863,7 +860,10 @@ bool SegManager::freeDynmem(reg_t addr) {
 }
 
 #ifdef ENABLE_SCI32
-SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) {
+#pragma mark -
+#pragma mark Arrays
+
+SciArray *SegManager::allocateArray(SciArrayType type, uint16 size, reg_t *addr) {
 	ArrayTable *table;
 	int offset;
 
@@ -875,10 +875,14 @@ SciArray<reg_t> *SegManager::allocateArray(reg_t *addr) {
 	offset = table->allocEntry();
 
 	*addr = make_reg(_arraysSegId, offset);
-	return &table->at(offset);
+
+	SciArray *array = &table->at(offset);
+	array->setType(type);
+	array->resize(size);
+	return array;
 }
 
-SciArray<reg_t> *SegManager::lookupArray(reg_t addr) {
+SciArray *SegManager::lookupArray(reg_t addr) {
 	if (_heap[addr.getSegment()]->getType() != SEG_TYPE_ARRAY)
 		error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
 
@@ -899,48 +903,11 @@ void SegManager::freeArray(reg_t addr) {
 	if (!arrayTable.isValidEntry(addr.getOffset()))
 		error("Attempt to use non-array %04x:%04x as array", PRINT_REG(addr));
 
-	arrayTable[addr.getOffset()].destroy();
 	arrayTable.freeEntry(addr.getOffset());
 }
 
-SciString *SegManager::allocateString(reg_t *addr) {
-	StringTable *table;
-	int offset;
-
-	if (!_stringSegId) {
-		table = (StringTable *)allocSegment(new StringTable(), &(_stringSegId));
-	} else
-		table = (StringTable *)_heap[_stringSegId];
-
-	offset = table->allocEntry();
-
-	*addr = make_reg(_stringSegId, offset);
-	return &table->at(offset);
-}
-
-SciString *SegManager::lookupString(reg_t addr) {
-	if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING)
-		error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
-
-	StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()];
-
-	if (!stringTable.isValidEntry(addr.getOffset()))
-		error("lookupString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
-
-	return &(stringTable[addr.getOffset()]);
-}
-
-void SegManager::freeString(reg_t addr) {
-	if (_heap[addr.getSegment()]->getType() != SEG_TYPE_STRING)
-		error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
-
-	StringTable &stringTable = *(StringTable *)_heap[addr.getSegment()];
-
-	if (!stringTable.isValidEntry(addr.getOffset()))
-		error("freeString: Attempt to use non-string %04x:%04x as string", PRINT_REG(addr));
-
-	stringTable[addr.getOffset()].destroy();
-	stringTable.freeEntry(addr.getOffset());
+bool SegManager::isArray(reg_t addr) const {
+	return addr.getSegment() == _arraysSegId;
 }
 
 #pragma mark -
diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h
index 8ed1c3a..60d0ee5 100644
--- a/engines/sci/engine/seg_manager.h
+++ b/engines/sci/engine/seg_manager.h
@@ -433,14 +433,10 @@ public:
 	reg_t getParserPtr() const { return _parserPtr; }
 
 #ifdef ENABLE_SCI32
-	SciArray<reg_t> *allocateArray(reg_t *addr);
-	SciArray<reg_t> *lookupArray(reg_t addr);
+	SciArray *allocateArray(SciArrayType type, uint16 size, reg_t *addr);
+	SciArray *lookupArray(reg_t addr);
 	void freeArray(reg_t addr);
-
-	SciString *allocateString(reg_t *addr);
-	SciString *lookupString(reg_t addr);
-	void freeString(reg_t addr);
-	SegmentId getStringSegmentId() { return _stringSegId; }
+	bool isArray(reg_t addr) const;
 
 	SciBitmap *allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor = kDefaultSkipColor, const int16 displaceX = 0, const int16 displaceY = 0, const int16 scaledWidth = kLowResX, const int16 scaledHeight = kLowResY, const uint32 paletteSize = 0, const bool remap = false, const bool gc = true);
 	SciBitmap *lookupBitmap(reg_t addr);
@@ -469,7 +465,6 @@ private:
 
 #ifdef ENABLE_SCI32
 	SegmentId _arraysSegId;
-	SegmentId _stringSegId;
 	SegmentId _bitmapSegId;
 #endif
 
diff --git a/engines/sci/engine/segment.cpp b/engines/sci/engine/segment.cpp
index 7943946..11331f0 100644
--- a/engines/sci/engine/segment.cpp
+++ b/engines/sci/engine/segment.cpp
@@ -67,9 +67,6 @@ SegmentObj *SegmentObj::createSegmentObj(SegmentType type) {
 	case SEG_TYPE_ARRAY:
 		mem = new ArrayTable();
 		break;
-	case SEG_TYPE_STRING:
-		mem = new StringTable();
-		break;
 	case SEG_TYPE_BITMAP:
 		mem = new BitmapTable();
 		break;
@@ -254,63 +251,39 @@ SegmentRef DynMem::dereference(reg_t pointer) {
 
 SegmentRef ArrayTable::dereference(reg_t pointer) {
 	SegmentRef ret;
-	ret.isRaw = false;
-	ret.maxSize = at(pointer.getOffset()).getSize() * 2;
-	ret.reg = at(pointer.getOffset()).getRawData();
-	return ret;
-}
 
-void ArrayTable::freeAtAddress(SegManager *segMan, reg_t sub_addr) {
-	at(sub_addr.getOffset()).destroy();
-	freeEntry(sub_addr.getOffset());
+	SciArray &array = at(pointer.getOffset());
+	const bool isRaw = array.getType() != kArrayTypeID;
+
+	ret.isRaw = isRaw;
+	ret.maxSize = isRaw ? array.byteSize() : array.size();
+	if (isRaw) {
+		ret.raw = (byte *)array.getRawData();
+	} else {
+		ret.reg = (reg_t *)array.getRawData();
+	}
+	return ret;
 }
 
 Common::Array<reg_t> ArrayTable::listAllOutgoingReferences(reg_t addr) const {
-	Common::Array<reg_t> tmp;
+	Common::Array<reg_t> refs;
 	if (!isValidEntry(addr.getOffset())) {
-		error("Invalid array referenced for outgoing references: %04x:%04x", PRINT_REG(addr));
+		// Scripts may still hold references to array memory that has been
+		// explicitly freed; ignore these references
+		return refs;
 	}
 
-	const SciArray<reg_t> *array = &at(addr.getOffset());
-
-	for (uint32 i = 0; i < array->getSize(); i++) {
-		reg_t value = array->getValue(i);
-		if (value.getSegment() != 0)
-			tmp.push_back(value);
+	SciArray &array = const_cast<SciArray &>(at(addr.getOffset()));
+	if (array.getType() == kArrayTypeID) {
+		for (uint16 i = 0; i < array.size(); ++i) {
+			const reg_t value = array.getAsID(i);
+			if (value.isPointer()) {
+				refs.push_back(value);
+			}
+		}
 	}
 
-	return tmp;
-}
-
-Common::String SciString::toString() const {
-	if (_type != 3)
-		error("SciString::toString(): Array is not a string");
-
-	Common::String string;
-	for (uint32 i = 0; i < _size && _data[i] != 0; i++)
-		string += _data[i];
-
-	return string;
-}
-
-void SciString::fromString(const Common::String &string) {
-	if (_type != 3)
-		error("SciString::fromString(): Array is not a string");
-
-	setSize(string.size() + 1);
-
-	for (uint32 i = 0; i < string.size(); i++)
-		_data[i] = string[i];
-
-	_data[string.size()] = 0;
-}
-
-SegmentRef StringTable::dereference(reg_t pointer) {
-	SegmentRef ret;
-	ret.isRaw = true;
-	ret.maxSize = at(pointer.getOffset()).getSize();
-	ret.raw = (byte *)at(pointer.getOffset()).getRawData();
-	return ret;
+	return refs;
 }
 
 #endif
diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h
index add5f4c..39376e3 100644
--- a/engines/sci/engine/segment.h
+++ b/engines/sci/engine/segment.h
@@ -24,7 +24,7 @@
 #define SCI_ENGINE_SEGMENT_H
 
 #include "common/serializer.h"
-
+#include "common/str.h"
 #include "sci/engine/object.h"
 #include "sci/engine/vm.h"
 #include "sci/engine/vm_types.h"	// for reg_t
@@ -70,7 +70,7 @@ enum SegmentType {
 
 #ifdef ENABLE_SCI32
 	SEG_TYPE_ARRAY = 11,
-	SEG_TYPE_STRING = 12,
+	// 12 used to be string, now obsolete
 	SEG_TYPE_BITMAP = 13,
 #endif
 
@@ -408,142 +408,459 @@ public:
 
 #ifdef ENABLE_SCI32
 
-template<typename T>
-class SciArray {
+#pragma mark -
+#pragma mark Arrays
+
+enum SciArrayType {
+	kArrayTypeInt16   = 0,
+	kArrayTypeID      = 1,
+	kArrayTypeByte    = 2,
+	kArrayTypeString  = 3,
+	// Type 4 was for 32-bit integers; never used
+	kArrayTypeInvalid = 5
+};
+
+enum SciArrayTrim {
+	kArrayTrimRight  = 1, ///< Trim whitespace after the last non-whitespace character
+	kArrayTrimCenter = 2, ///< Trim whitespace between non-whitespace characters
+	kArrayTrimLeft   = 4  ///< Trim whitespace before the first non-whitespace character
+};
+
+class SciArray : public Common::Serializable {
 public:
-	SciArray() : _type(-1), _data(NULL), _size(0), _actualSize(0) { }
+	SciArray() :
+		_type(kArrayTypeInvalid),
+		_size(0),
+		_data(nullptr) {}
 
-	SciArray(const SciArray<T> &array) {
+	SciArray(const SciArray &array) {
 		_type = array._type;
 		_size = array._size;
-		_actualSize = array._actualSize;
-		_data = new T[_actualSize];
+		_elementSize = array._elementSize;
+		_data = malloc(_elementSize * _size);
 		assert(_data);
-		memcpy(_data, array._data, _size * sizeof(T));
+		memcpy(_data, array._data, _elementSize * _size);
 	}
 
-	SciArray<T>& operator=(const SciArray<T> &array) {
+	SciArray &operator=(const SciArray &array) {
 		if (this == &array)
 			return *this;
 
-		delete[] _data;
+		free(_data);
 		_type = array._type;
 		_size = array._size;
-		_actualSize = array._actualSize;
-		_data = new T[_actualSize];
+		_elementSize = array._elementSize;
+		_data = malloc(_elementSize * _size);
 		assert(_data);
-		memcpy(_data, array._data, _size * sizeof(T));
+		memcpy(_data, array._data, _elementSize * _size);
 
 		return *this;
 	}
 
 	virtual ~SciArray() {
-		destroy();
+		free(_data);
+		_size = 0;
+		_type = kArrayTypeInvalid;
 	}
 
-	virtual void destroy() {
-		delete[] _data;
-		_data = NULL;
-		_type = -1;
-		_size = _actualSize = 0;
-	}
+	void saveLoadWithSerializer(Common::Serializer &s);
 
-	void setType(byte type) {
-		if (_type >= 0)
-			error("SciArray::setType(): Type already set");
+	/**
+	 * Returns the type of this array.
+	 */
+	SciArrayType getType() const {
+		return _type;
+	}
 
+	/**
+	 * Sets the type of this array. The type of the array may only be set once.
+	 */
+	void setType(SciArrayType type) {
+		assert(_type == kArrayTypeInvalid);
+		switch(type) {
+		case kArrayTypeID:
+			_elementSize = sizeof(reg_t);
+			break;
+		case kArrayTypeInt16:
+			_elementSize = sizeof(int16);
+			break;
+		case kArrayTypeString:
+			_elementSize = sizeof(char);
+			break;
+		case kArrayTypeByte:
+			_elementSize = sizeof(byte);
+			break;
+		default:
+			error("Invalid array type %d", type);
+		}
 		_type = type;
 	}
 
-	void setSize(uint32 size) {
-		if (_type < 0)
-			error("SciArray::setSize(): No type set");
+	/**
+	 * Returns the size of the array, in elements.
+	 */
+	uint16 size() const {
+		return _size;
+	}
 
-		// Check if we don't have to do anything
-		if (_size == size)
-			return;
+	/**
+	 * Returns the size of the array, in bytes.
+	 */
+	uint16 byteSize() const {
+		return _size * _elementSize;
+	}
 
-		// Check if we don't have to expand the array
-		if (size <= _actualSize) {
-			_size = size;
-			return;
+	/**
+	 * Ensures the array is large enough to store at least the given number of
+	 * values given in `newSize`. If `force` is true, the array will be resized
+	 * to store exactly `newSize` values. New values are initialized to zero.
+	 */
+	void resize(uint16 newSize, const bool force = false) {
+		if (force || newSize > _size) {
+			_data = realloc(_data, _elementSize * newSize);
+			if (newSize > _size) {
+				memset((byte *)_data + _elementSize * _size, 0, (newSize - _size) * _elementSize);
+			}
+			_size = newSize;
 		}
+	}
+
+	/**
+	 * Shrinks a string array to its optimal size.
+	 */
+	void snug() {
+		assert(_type == kArrayTypeString || _type == kArrayTypeByte);
+		resize(strlen((char *)_data) + 1, true);
+	}
 
-		// So, we're going to have to create an array of some sort
-		T *newArray = new T[size];
-		memset(newArray, 0, size * sizeof(T));
+	/**
+	 * Returns a pointer to the array's raw data storage.
+	 */
+	void *getRawData() { return _data; }
+	const void *getRawData() const { return _data; }
 
-		// Check if we never created an array before
-		if (!_data) {
-			_size = _actualSize = size;
-			_data = newArray;
-			return;
+	/**
+	 * Gets the value at the given index as a reg_t.
+	 */
+	reg_t getAsID(const uint16 index) {
+		if (getSciVersion() >= SCI_VERSION_3) {
+			resize(index);
+		} else {
+			assert(index < _size);
 		}
 
-		// Copy data from the old array to the new
-		memcpy(newArray, _data, _size * sizeof(T));
+		switch(_type) {
+		case kArrayTypeInt16:
+			return make_reg(0, ((int16 *)_data)[index]);
+		case kArrayTypeByte:
+		case kArrayTypeString:
+			return make_reg(0, ((byte *)_data)[index]);
+		case kArrayTypeID:
+			return ((reg_t *)_data)[index];
+		default:
+			error("Invalid array type %d", _type);
+		}
+	}
+
+	/**
+	 * Sets the value at the given index from a reg_t.
+	 */
+	void setFromID(const uint16 index, const reg_t value) {
+		if (getSciVersion() >= SCI_VERSION_3) {
+			resize(index);
+		} else {
+			assert(index < _size);
+		}
 
-		// Now set the new array to the old and set the sizes
-		delete[] _data;
-		_data = newArray;
-		_size = _actualSize = size;
+		switch(_type) {
+		case kArrayTypeInt16:
+			((int16 *)_data)[index] = value.toSint16();
+			break;
+		case kArrayTypeByte:
+		case kArrayTypeString:
+			((byte *)_data)[index] = value.toSint16();
+			break;
+		case kArrayTypeID:
+			((reg_t *)_data)[index] = value;
+			break;
+		default:
+			error("Invalid array type %d", _type);
+		}
 	}
 
-	T getValue(uint16 index) const {
-		if (index >= _size)
-			error("SciArray::getValue(): %d is out of bounds (%d)", index, _size);
+	/**
+	 * Returns a reference to the byte at the given index. Only valid for
+	 * string and byte arrays.
+	 */
+	byte &byteAt(const uint16 index) {
+		assert(_type == kArrayTypeString || _type == kArrayTypeByte);
 
-		return _data[index];
+		if (getSciVersion() >= SCI_VERSION_3) {
+			resize(index);
+		} else {
+			assert(index < _size);
+		}
+
+		return ((byte *)_data)[index];
 	}
 
-	void setValue(uint16 index, T value) {
-		if (index >= _size)
-			error("SciArray::setValue(): %d is out of bounds (%d)", index, _size);
+	/**
+	 * Returns a reference to the char at the given index. Only valid for
+	 * string and byte arrays.
+	 */
+	char &charAt(const uint16 index) {
+		assert(_type == kArrayTypeString || _type == kArrayTypeByte);
 
-		_data[index] = value;
+		if (getSciVersion() >= SCI_VERSION_3) {
+			resize(index);
+		} else {
+			assert(index < _size);
+		}
+
+		return ((char *)_data)[index];
 	}
 
-	byte getType() const { return _type; }
-	uint32 getSize() const { return _size; }
-	T *getRawData() { return _data; }
-	const T *getRawData() const { return _data; }
+	/**
+	 * Returns a reference to the int16 at the given index. Only valid for int16
+	 * arrays.
+	 */
+	int16 &int16At(const uint16 index) {
+		assert(_type == kArrayTypeInt16);
 
-protected:
-	int8 _type;
-	T *_data;
-	uint32 _size; // _size holds the number of entries that the scripts have requested
-	uint32 _actualSize; // _actualSize is the actual numbers of entries allocated
-};
+		if (getSciVersion() >= SCI_VERSION_3) {
+			resize(index);
+		} else {
+			assert(index < _size);
+		}
 
-class SciString : public SciArray<char> {
-public:
-	SciString() : SciArray<char>() { setType(3); }
+		return ((int16 *)_data)[index];
+	}
+
+	/**
+	 * Returns a reference to the reg_t at the given index. Only valid for ID
+	 * arrays.
+	 */
+	reg_t &IDAt(const uint16 index) {
+		assert(_type == kArrayTypeID);
 
-	// We overload destroy to ensure the string type is 3 after destroying
-	void destroy() { SciArray<char>::destroy(); _type = 3; }
+		if (getSciVersion() >= SCI_VERSION_3) {
+			resize(index);
+		} else {
+			assert(index < _size);
+		}
 
-	Common::String toString() const;
-	void fromString(const Common::String &string);
-};
+		return ((reg_t *)_data)[index];
+	}
 
-struct ArrayTable : public SegmentObjTable<SciArray<reg_t> > {
-	ArrayTable() : SegmentObjTable<SciArray<reg_t> >(SEG_TYPE_ARRAY) {}
+	/**
+	 * Reads values from the given reg_t pointer and sets them in the array,
+	 * growing the array if needed to store all values.
+	 */
+	void setElements(const uint16 index, uint16 count, const reg_t *values) {
+		resize(index + count);
+
+		switch (_type) {
+		case kArrayTypeInt16: {
+			const reg_t *source = values;
+			int16 *target = (int16 *)_data + index;
+			while (count--) {
+				assert(source->isNumber());
+				*target++ = source->toSint16();
+				++source;
+			}
+			break;
+		}
+		case kArrayTypeID: {
+			const reg_t *source = values;
+			reg_t *target = (reg_t *)_data + index;
+			while (count--) {
+				*target++ = *source++;
+			}
+			break;
+		}
+		case kArrayTypeByte:
+		case kArrayTypeString: {
+			const reg_t *source = values;
+			byte *target = (byte *)_data + index;
+			while (count--) {
+				assert(source->isNumber());
+				*target++ = source->getOffset();
+				++source;
+			}
+			break;
+		}
+		default:
+			error("Attempted write to SciArray with invalid type %d", _type);
+		}
+	}
 
-	virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr);
-	virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const;
+	/**
+	 * Fills the array with the given value. Existing values will be
+	 * overwritten. The array will be grown if needed to store all values.
+	 */
+	void fill(const uint16 index, uint16 count, const reg_t value) {
+		if (count == 65535 /* -1 */) {
+			count = size() - index;
+		}
 
-	void saveLoadWithSerializer(Common::Serializer &ser);
-	SegmentRef dereference(reg_t pointer);
-};
+		if (!count) {
+			return;
+		}
 
-struct StringTable : public SegmentObjTable<SciString> {
-	StringTable() : SegmentObjTable<SciString>(SEG_TYPE_STRING) {}
+		resize(index + count);
 
-	virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) {
-		at(sub_addr.getOffset()).destroy();
-		freeEntry(sub_addr.getOffset());
+		switch (_type) {
+		case kArrayTypeInt16: {
+			const int16 fillValue = value.toSint16();
+			int16 *target = (int16 *)_data + index;
+			while (count--) {
+				*target++ = fillValue;
+			}
+			break;
+		}
+		case kArrayTypeID: {
+			reg_t *target = (reg_t *)_data + index;
+			while (count--) {
+				*target = value;
+			}
+			break;
+		}
+		case kArrayTypeByte:
+		case kArrayTypeString: {
+			byte *target = (byte *)_data + index;
+			const byte fillValue = value.getOffset();
+			while (count--) {
+				*target = fillValue;
+			}
+			break;
+		}
+		case kArrayTypeInvalid:
+			error("Attempted write to uninitialized SciArray");
+		}
+	}
+
+	/**
+	 * Copies values from the source array. Both arrays will be grown if needed
+	 * to prevent out-of-bounds reads/writes.
+	 */
+	void copy(SciArray &source, const uint16 sourceIndex, const uint16 targetIndex, uint16 count) {
+		if (count == 65535 /* -1 */) {
+			count = source.size() - sourceIndex;
+		}
+
+		if (!count) {
+			return;
+		}
+
+		resize(targetIndex + count);
+		source.resize(sourceIndex + count);
+
+		assert(source._elementSize == _elementSize);
+
+		const byte *sourceData = (byte *)source._data + sourceIndex * source._elementSize;
+		byte *targetData = (byte *)_data + targetIndex * _elementSize;
+		memmove(targetData, sourceData, count * _elementSize);
+	}
+
+	void byteCopy(const SciArray &source, const uint16 sourceOffset, const uint16 targetOffset, const uint16 count) {
+		error("SciArray::byteCopy not implemented");
 	}
 
+	/**
+	 * Removes whitespace from string data held in this array.
+	 */
+	void trim(const int8 flags, const char showChar) {
+		enum {
+			kWhitespaceBoundary = 32,
+			kAsciiBoundary = 128
+		};
+
+		byte *data = (byte *)_data;
+		byte *source;
+		byte *target;
+
+		if (flags & kArrayTrimLeft) {
+			target = data;
+			source = data;
+			while (*source != '\0' && *source != showChar && *source <= kWhitespaceBoundary) {
+				++source;
+			}
+			strcpy((char *)target, (char *)source);
+		}
+
+		if (flags & kArrayTrimRight) {
+			source = data + strlen((char *)data) - 1;
+			while (source > data && *source != showChar && *source <= kWhitespaceBoundary) {
+				--source;
+			}
+			*source = '\0';
+		}
+
+		if (flags & kArrayTrimCenter) {
+			target = data;
+			while (*target && *target <= kWhitespaceBoundary && *target != showChar) {
+				++target;
+			}
+
+			if (*target) {
+				while (*target && (*target > kWhitespaceBoundary || *target == showChar)) {
+					++target;
+				}
+
+				if (*target) {
+					source = target;
+					while (*source) {
+						while (*source && *source <= kWhitespaceBoundary && *source != showChar) {
+							++source;
+						}
+
+						while (*source && (*source > kWhitespaceBoundary || *source == showChar)) {
+							*target++ = *source++;
+						}
+					}
+
+					--source;
+					while (source > target && (*source <= kWhitespaceBoundary || *source >= kAsciiBoundary) && *source != showChar) {
+						--source;
+					}
+					++source;
+
+					memmove(target, source, strlen((char *)source) + 1);
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Copies the string data held by this array into a new Common::String.
+	 */
+	Common::String toString() const {
+		assert(_type == kArrayTypeString);
+		return Common::String((char *)_data);
+	}
+
+	/**
+	 * Copies the string from the given Common::String into this array.
+	 */
+	void fromString(const Common::String &string) {
+		// At least LSL6hires uses a byte-type array to hold string data
+		assert(_type == kArrayTypeString || _type == kArrayTypeByte);
+		resize(string.size() + 1, true);
+		Common::strlcpy((char *)_data, string.c_str(), string.size() + 1);
+	}
+
+protected:
+	void *_data;
+	SciArrayType _type;
+	uint16 _size;
+	uint8 _elementSize;
+};
+
+struct ArrayTable : public SegmentObjTable<SciArray> {
+	ArrayTable() : SegmentObjTable<SciArray>(SEG_TYPE_ARRAY) {}
+
+	virtual Common::Array<reg_t> listAllOutgoingReferences(reg_t object) const;
+
 	void saveLoadWithSerializer(Common::Serializer &ser);
 	SegmentRef dereference(reg_t pointer);
 };
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 68f5f4c..71ffda5 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -396,6 +396,12 @@ const SciWorkaroundEntry kAbs_workarounds[] = {
 };
 
 //    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
+const SciWorkaroundEntry kArraySetElements_workarounds[] = {
+	{ GID_PHANTASMAGORIA,902, 64918,  0,                "Str", "callKernel",                NULL,     0, { WORKAROUND_FAKE, 0 } }, // tries to set an element of a string array to the ego object when starting a new game and selecting a chapter above 1
+	SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+//    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
 const SciWorkaroundEntry kCelHigh_workarounds[] = {
 	{ GID_KQ5,            -1,   255,  0,          "deathIcon", "setSize",                   NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when getting beaten up in the inn and probably more, called with 2nd parameter as object - bug #5049
 	{ GID_PQ2,            -1,   255,  0,              "DIcon", "setSize",                   NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // when showing picture within windows, called with 2nd/3rd parameters as objects
@@ -767,13 +773,9 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = {
 };
 
 //    gameID,           room,script,lvl,          object-name, method-name,  local-call-signature, index,                workaround
-const SciWorkaroundEntry kStringPutAt_workarounds[] = {
-	{ GID_PHANTASMAGORIA,902, 64918, 0,                 "Str", "callKernel",                 NULL,     0, { WORKAROUND_IGNORE, 0 } }, // When starting a new game from after chapter 1, the game tries to save ego's object in a string
-};
-
-//    gameID,           room,script,lvl,          object-name, method-name,  local-call-signature, index,                workaround
 const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = {
 	{ GID_PHANTASMAGORIA, 45, 64907,  0,   "ScrollableWindow", "addString",                  NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice
+	SCI_WORKAROUNDENTRY_TERMINATOR
 };
 
 SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin) {
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 2f7a9e5..5304fce 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -89,12 +89,13 @@ extern const SciWorkaroundEntry kRandom_workarounds[];
 extern const SciWorkaroundEntry kReadNumber_workarounds[];
 extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[];
 extern const SciWorkaroundEntry kSetCursor_workarounds[];
+extern const SciWorkaroundEntry kArraySetElements_workarounds[];
 extern const SciWorkaroundEntry kSetPort_workarounds[];
 extern const SciWorkaroundEntry kStrAt_workarounds[];
 extern const SciWorkaroundEntry kStrCpy_workarounds[];
 extern const SciWorkaroundEntry kStrLen_workarounds[];
 extern const SciWorkaroundEntry kUnLoad_workarounds[];
-extern const SciWorkaroundEntry kStringPutAt_workarounds[];
+extern const SciWorkaroundEntry kStringNew_workarounds[];
 extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[];
 
 extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciCallOrigin *trackOrigin);
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index bdc7248..89997d3 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -313,8 +313,8 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
 
 	if (textChanged) {
 		editor.text.trim();
-		SciString *string = _segMan->lookupString(textObject);
-		string->fromString(editor.text);
+		SciArray &string = *_segMan->lookupArray(textObject);
+		string.fromString(editor.text);
 	}
 
 	return make_reg(0, textChanged);
diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
index e98f783..c45017e 100644
--- a/engines/sci/graphics/transitions32.cpp
+++ b/engines/sci/graphics/transitions32.cpp
@@ -267,9 +267,9 @@ void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeOb
 				// NOTE: SCI2.1mid engine does no check to verify that an array is
 				// successfully retrieved, and SegMan will cause a fatal error
 				// if we try to use a memory segment that is not an array
-				SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
+				SciArray &table = *_segMan->lookupArray(pFadeArray);
 
-				uint32 rangeCount = table->getSize();
+				uint32 rangeCount = table.size();
 				entry->fadeColorRangesCount = rangeCount;
 
 				// NOTE: SCI engine code always allocates memory even if the range
@@ -278,7 +278,7 @@ void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeOb
 				if (rangeCount > 0) {
 					entry->fadeColorRanges = new uint16[rangeCount];
 					for (size_t i = 0; i < rangeCount; ++i) {
-						entry->fadeColorRanges[i] = table->getValue(i).toUint16();
+						entry->fadeColorRanges[i] = table.int16At(i);
 					}
 				}
 			}


Commit: 88de81a72ebab8c386e1d24ae84096dd4ac16a19
    https://github.com/scummvm/scummvm/commit/88de81a72ebab8c386e1d24ae84096dd4ac16a19
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Patch bad calls to IntArray::newWith in Phant1 and SQ6

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



diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 664e7fa..61af8a9 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -103,6 +103,9 @@ static const char *const selectorNameTable[] = {
 	"modNum",       // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
 	"cycler",       // Space Quest 4 / system selector
 	"setLoop",      // Laura Bow 1 Colonel's Bequest
+#ifdef ENABLE_SCI32
+	"newWith",      // SCI2 array script
+#endif
 	NULL
 };
 
@@ -132,6 +135,10 @@ enum ScriptPatcherSelectors {
 	SELECTOR_modNum,
 	SELECTOR_cycler,
 	SELECTOR_setLoop
+#ifdef ENABLE_SCI32
+	,
+	SELECTOR_newWith
+#endif
 };
 
 // ===========================================================================
@@ -2700,6 +2707,40 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#ifdef ENABLE_SCI32
+// Phantasmagoria & SQ6 try to initialize the first entry of an int16 array
+// using an empty string, which is not valid (it should be a number)
+static const uint16 sci21IntArraySignature[] = {
+	0x38, SIG_SELECTOR16(newWith), // pushi newWith
+	0x7a,                          // push2
+	0x39, 0x04,                    // pushi $4
+	0x72, SIG_ADDTOOFFSET(+2),     // lofsa string ""
+	SIG_MAGICDWORD,
+	0x36,                          // push
+	0x51, 0x0b,                    // class IntArray
+	0x4a, 0x8,                     // send $8
+	SIG_END
+};
+
+static const uint16 sci21IntArrayPatch[] = {
+	PATCH_ADDTOOFFSET(+6),      // push $b9; push2; pushi $4
+	0x76,                       // push0
+	0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes)
+	PATCH_END
+};
+
+#pragma mark -
+#pragma mark Phantasmagoria
+
+//          script, description,                                      signature                        patch
+static const SciScriptPatcherEntry phantasmagoriaSignatures[] = {
+	{  true,   901, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
+	SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#pragma mark -
+#endif
+
 // ===========================================================================
 // Police Quest 1 VGA
 
@@ -4371,6 +4412,22 @@ static const SciScriptPatcherEntry sq5Signatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Space Quest 6
+
+//          script, description,                                      signature                        patch
+static const SciScriptPatcherEntry sq6Signatures[] = {
+	{  true,    15, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
+	{  true,    22, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
+	{  true,   460, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
+	{  true,   510, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
+	SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#pragma mark -
+#endif
+
 // =================================================================================
 
 ScriptPatcher::ScriptPatcher() {
@@ -4835,6 +4892,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
 	case GID_MOTHERGOOSE256:
 		signatureTable = mothergoose256Signatures;
 		break;
+#ifdef ENABLE_SCI32
+	case GID_PHANTASMAGORIA:
+		signatureTable = phantasmagoriaSignatures;
+		break;
+#endif
 	case GID_PQ1:
 		signatureTable = pq1vgaSignatures;
 		break;
@@ -4859,6 +4921,10 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
 	case GID_SQ5:
 		signatureTable = sq5Signatures;
 		break;
+#ifdef ENABLE_SCI32
+	case GID_SQ6:
+		signatureTable = sq6Signatures;
+#endif
 	default:
 		break;
 	}


Commit: 2be2629a3b2b43a0c86cfb7ea26cf979b91251bd
    https://github.com/scummvm/scummvm/commit/2be2629a3b2b43a0c86cfb7ea26cf979b91251bd
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix kFileIOOpen signature

The mode argument is required in SCI32 too; Torin just had a script
bug.

Changed paths:
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kfile.cpp
    engines/sci/engine/workarounds.cpp
    engines/sci/engine/workarounds.h



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 6630f1a..201afb6 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -311,9 +311,9 @@ static const SciKernelMapSubEntry kPalette_subops[] = {
 	SCI_SUBOPENTRY_TERMINATOR
 };
 
+//    version,         subId, function-mapping,                    signature,              workarounds
 static const SciKernelMapSubEntry kFileIO_subops[] = {
-	{ SIG_SCI32,           0, MAP_CALL(FileIOOpen),                "r(i)",                 NULL },
-	{ SIG_SCIALL,          0, MAP_CALL(FileIOOpen),                "ri",                   NULL },
+	{ SIG_SCIALL,          0, MAP_CALL(FileIOOpen),                "ri",                   kFileIOOpen_workarounds },
 	{ SIG_SCIALL,          1, MAP_CALL(FileIOClose),               "i",                    NULL },
 	{ SIG_SCIALL,          2, MAP_CALL(FileIOReadRaw),             "iri",                  NULL },
 	{ SIG_SCIALL,          3, MAP_CALL(FileIOWriteRaw),            "iri",                  NULL },
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 324eaf2..b810383 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -233,9 +233,7 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
 reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 	Common::String name = s->_segMan->getString(argv[0]);
 
-	// SCI32 can call K_FILEIO_OPEN with only one argument. It seems to
-	// just be checking if it exists.
-	int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16();
+	int mode = argv[1].toUint16();
 	bool unwrapFilename = true;
 
 	// SQ4 floppy prepends /\ to the filenames
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 71ffda5..6669a14 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -550,6 +550,12 @@ const SciWorkaroundEntry kGetAngle_workarounds[] = {
 };
 
 //    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
+const SciWorkaroundEntry kFileIOOpen_workarounds[] = {
+	{ GID_TORIN,       61000, 61000,  0,       "roSierraLogo", "init",                      NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // Missing second argument when the game checks for autosave.cat after the Sierra logo
+	SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+//    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
 const SciWorkaroundEntry kFindKey_workarounds[] = {
 	{ GID_ECOQUEST2,     100,   999,  0,            "myList", "contains",                   NULL,     0, { WORKAROUND_FAKE, 0 } }, // When Noah Greene gives Adam the Ecorder, and just before the game gives a demonstration, a null reference to a list is passed - bug #4987
 	{ GID_HOYLE4,        300,   999,  0,             "Piles", "contains",                   NULL,     0, { WORKAROUND_FAKE, 0 } }, // When passing the three cards in Hearts, a null reference to a list is passed - bug #5664
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 5304fce..a272bae 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -70,6 +70,7 @@ extern const SciWorkaroundEntry kDirLoop_workarounds[];
 extern const SciWorkaroundEntry kDisposeScript_workarounds[];
 extern const SciWorkaroundEntry kDoSoundPlay_workarounds[];
 extern const SciWorkaroundEntry kDoSoundFade_workarounds[];
+extern const SciWorkaroundEntry kFileIOOpen_workarounds[];
 extern const SciWorkaroundEntry kFindKey_workarounds[];
 extern const SciWorkaroundEntry kDeleteKey_workarounds[];
 extern const SciWorkaroundEntry kGetAngle_workarounds[];


Commit: 44dd029cb17160316b2015321a0a53f8854b6dd3
    https://github.com/scummvm/scummvm/commit/44dd029cb17160316b2015321a0a53f8854b6dd3
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Implement kSetHotRectangles

Used only by chapter 7 of Phant1.

Changed paths:
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kevent.cpp
    engines/sci/event.cpp
    engines/sci/event.h
    engines/sci/graphics/video32.cpp
    video/coktel_decoder.cpp
    video/coktel_decoder.h



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index bd9d1bb..6cacb5f 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -483,6 +483,7 @@ reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv);
 reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
 reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
 
+reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv);
 reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
 reg_t kListAt(EngineState *s, int argc, reg_t *argv);
 
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 201afb6..b8d8450 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -969,6 +969,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	// SetHotRectangles - used by Phantasmagoria 1, script 64981 (used in the chase scene)
 	//     <lskovlun> The idea, if I understand correctly, is that the engine generates events
 	//     of a special HotRect type continuously when the mouse is on that rectangle
+	{ MAP_CALL(SetHotRectangles), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(r)",             NULL,            NULL },
 
 	// Used by SQ6 to scroll through the inventory via the up/down buttons
 	{ MAP_CALL(MovePlaneItems),     SIG_SINCE_SCI21, SIGFOR_ALL, "oii(i)",            NULL,            NULL },
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index 600dce3..8dcafe0 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -369,6 +369,30 @@ reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) {
 
 	return make_reg(0, visible);
 }
+
+reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv) {
+	if (argc == 1) {
+		g_sci->getEventManager()->setHotRectanglesActive((bool)argv[0].toUint16());
+		return s->r_acc;
+	}
+
+	const int16 numRects = argv[0].toSint16();
+	SciArray &hotRects = *s->_segMan->lookupArray(argv[1]);
+
+	Common::Array<Common::Rect> rects;
+	rects.resize(numRects);
+
+	for (int16 i = 0; i < numRects; ++i) {
+		rects[i].left   = hotRects.int16At(i * 4);
+		rects[i].top    = hotRects.int16At(i * 4 + 1);
+		rects[i].right  = hotRects.int16At(i * 4 + 2) + 1;
+		rects[i].bottom = hotRects.int16At(i * 4 + 3) + 1;
+	}
+
+	g_sci->getEventManager()->setHotRectanglesActive(true);
+	g_sci->getEventManager()->setHotRectangles(rects);
+	return s->r_acc;
+}
 #endif
 
 } // End of namespace Sci
diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index 43db00c..345b380 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -109,8 +109,12 @@ static const MouseEventConversion mouseEventMappings[] = {
 	{   Common::EVENT_MBUTTONUP, SCI_EVENT_MOUSE_RELEASE }
 };
 
-EventManager::EventManager(bool fontIsExtended) : _fontIsExtended(fontIsExtended) {
-}
+EventManager::EventManager(bool fontIsExtended) :
+	_fontIsExtended(fontIsExtended)
+#ifdef ENABLE_SCI32
+	, _hotRectanglesActive(false)
+#endif
+	{}
 
 EventManager::~EventManager() {
 }
@@ -138,8 +142,8 @@ static int altify(int ch) {
 
 SciEvent EventManager::getScummVMEvent() {
 #ifdef ENABLE_SCI32
-	SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
-	SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
+	SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point(), -1 };
+	SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point(), -1 };
 #else
 	SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point() };
 	SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point() };
@@ -169,17 +173,20 @@ SciEvent EventManager::getScummVMEvent() {
 	if (getSciVersion() >= SCI_VERSION_2) {
 		const Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer();
 
+		Common::Point mousePosSci = mousePos;
+		mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight));
+		noEvent.mousePosSci = input.mousePosSci = mousePosSci;
+
 		if (ev.type == Common::EVENT_MOUSEMOVE) {
 			// This will clamp `mousePos` according to the restricted zone,
 			// so any cursor or screen item associated with the mouse position
 			// does not bounce when it hits the edge (or ignore the edge)
 			g_sci->_gfxCursor32->deviceMoved(mousePos);
+			if (_hotRectanglesActive) {
+				checkHotRectangles(mousePosSci);
+			}
 		}
 
-		Common::Point mousePosSci = mousePos;
-		mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight));
-		noEvent.mousePosSci = input.mousePosSci = mousePosSci;
-
 	} else {
 #endif
 		g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x);
@@ -365,7 +372,7 @@ void EventManager::updateScreen() {
 
 SciEvent EventManager::getSciEvent(uint32 mask) {
 #ifdef ENABLE_SCI32
-	SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() };
+	SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point(), -1 };
 #else
 	SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point() };
 #endif
@@ -402,4 +409,43 @@ SciEvent EventManager::getSciEvent(uint32 mask) {
 
 	return event;
 }
+
+#ifdef ENABLE_SCI32
+void EventManager::setHotRectanglesActive(const bool active) {
+	_hotRectanglesActive = active;
+}
+
+void EventManager::setHotRectangles(const Common::Array<Common::Rect> &rects) {
+	_hotRects = rects;
+}
+
+void EventManager::checkHotRectangles(const Common::Point &mousePosition) {
+	int lastActiveRectIndex = _activeRectIndex;
+	_activeRectIndex = -1;
+
+	for (int16 i = 0; i < (int16)_hotRects.size(); ++i) {
+		if (_hotRects[i].contains(mousePosition)) {
+			_activeRectIndex = i;
+			if (i != lastActiveRectIndex) {
+				SciEvent hotRectEvent;
+				hotRectEvent.type = SCI_EVENT_HOT_RECTANGLE;
+				hotRectEvent.hotRectangleIndex = i;
+				_events.push_front(hotRectEvent);
+				break;
+			}
+
+			lastActiveRectIndex = _activeRectIndex;
+		}
+	}
+
+	if (lastActiveRectIndex != _activeRectIndex && lastActiveRectIndex != -1) {
+		_activeRectIndex = -1;
+		SciEvent hotRectEvent;
+		hotRectEvent.type = SCI_EVENT_HOT_RECTANGLE;
+		hotRectEvent.hotRectangleIndex = -1;
+		_events.push_front(hotRectEvent);
+	}
+}
+#endif
+
 } // End of namespace Sci
diff --git a/engines/sci/event.h b/engines/sci/event.h
index 15a94b3..23b59f9 100644
--- a/engines/sci/event.h
+++ b/engines/sci/event.h
@@ -50,6 +50,8 @@ struct SciEvent {
 	 * in script coordinates.
 	 */
 	Common::Point mousePosSci;
+
+	int16 hotRectangleIndex;
 #endif
 };
 
@@ -60,6 +62,9 @@ struct SciEvent {
 #define SCI_EVENT_KEYBOARD        (1 << 2)
 #define SCI_EVENT_DIRECTION       (1 << 6)
 #define SCI_EVENT_SAID            (1 << 7)
+#ifdef ENABLE_SCI32
+#define SCI_EVENT_HOT_RECTANGLE   (1 << 8)
+#endif
 /*Fake values for other events*/
 #define SCI_EVENT_QUIT            (1 << 11)
 #define SCI_EVENT_PEEK            (1 << 15)
@@ -138,6 +143,17 @@ private:
 
 	const bool _fontIsExtended;
 	Common::List<SciEvent> _events;
+#ifdef ENABLE_SCI32
+public:
+	void setHotRectanglesActive(const bool active);
+	void setHotRectangles(const Common::Array<Common::Rect> &rects);
+	void checkHotRectangles(const Common::Point &mousePosition);
+
+private:
+	bool _hotRectanglesActive;
+	Common::Array<Common::Rect> _hotRects;
+	int16 _activeRectIndex;
+#endif
 };
 
 } // End of namespace Sci
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index 506cde7..84c07d3 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -485,13 +485,6 @@ AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) {
 				break;
 			}
 		}
-
-		// TODO: Hot rectangles
-		if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) {
-			warning("Hot rectangles not implemented in VMD player");
-			stopFlag = kEventFlagHotRectangle;
-			break;
-		}
 	}
 
 	return stopFlag;
@@ -655,11 +648,11 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co
 }
 
 VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
-	// Flushing all the keyboard and mouse events out of the event manager to
-	// avoid letting any events queued from before the video started from
-	// accidentally activating an event callback
+	// Flushing all the keyboard and mouse events out of the event manager
+	// keeps events queued from before the start of playback from accidentally
+	// activating a video stop flag
 	for (;;) {
-		const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_QUIT);
+		const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_QUIT);
 		if (event.type == SCI_EVENT_NONE) {
 			break;
 		} else if (event.type == SCI_EVENT_QUIT) {
@@ -667,8 +660,6 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
 		}
 	}
 
-	_decoder->pauseVideo(false);
-
 	if (flags & kEventFlagReverse) {
 		// NOTE: This flag may not work properly since SSCI does not care
 		// if a video has audio, but the VMD decoder does.
@@ -748,6 +739,14 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
 
 		g_sci->_gfxFrameout->addScreenItem(*_screenItem);
 
+		// HACK: When VMD playback is allowed to yield back to the VM, this
+		// causes the VMD decoder to freak out for some reason and video output
+		// starts jittering horribly. This problem does not happen if audio sync
+		// is disabled, but this causes some audible clicking between frames
+		// of audio (and, presumably, will cause some AV sync problems). Still,
+		// that's better than really bad jitter.
+		_decoder->setAudioSync(!(flags & kEventFlagYieldToVM));
+
 		_decoder->start();
 	}
 
@@ -758,9 +757,11 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
 			break;
 		}
 
-		g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
-		g_sci->getEngineState()->_throttleTrigger = true;
-		if (_decoder->needsUpdate()) {
+		// Sleeping any more than 1/60th of a second will make the mouse feel
+		// very sluggish during VMD action sequences because the frame rate of
+		// VMDs is usually only 15fps
+		g_sci->sleep(MIN<uint32>(10, _decoder->getTimeToNextFrame()));
+		while (_decoder->needsUpdate()) {
 			renderFrame();
 		}
 
@@ -802,15 +803,13 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
 			}
 		}
 
-		// TODO: Hot rectangles
-		if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) {
-			warning("Hot rectangles not implemented in VMD player");
+		event = _eventMan->getSciEvent(SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_PEEK);
+		if ((flags & kEventFlagHotRectangle) && event.type == SCI_EVENT_HOT_RECTANGLE) {
 			stopFlag = kEventFlagHotRectangle;
 			break;
 		}
 	}
 
-	_decoder->pauseVideo(true);
 	return stopFlag;
 }
 
@@ -859,7 +858,6 @@ void VMDPlayer::renderFrame() const {
 		g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
 		g_sci->getSciDebugger()->onFrame();
 		g_sci->_gfxFrameout->frameOut(true);
-		g_sci->_gfxFrameout->throttle();
 	}
 }
 
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp
index 278dc2d..4e05499 100644
--- a/video/coktel_decoder.cpp
+++ b/video/coktel_decoder.cpp
@@ -2779,6 +2779,7 @@ void VMDDecoder::setAutoStartSound(bool autoStartSound) {
 AdvancedVMDDecoder::AdvancedVMDDecoder(Audio::Mixer::SoundType soundType) {
 	_decoder = new VMDDecoder(g_system->getMixer(), soundType);
 	_decoder->setAutoStartSound(false);
+	_useAudioSync = true;
 }
 
 AdvancedVMDDecoder::~AdvancedVMDDecoder() {
diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h
index 44de1c7..726d052 100644
--- a/video/coktel_decoder.h
+++ b/video/coktel_decoder.h
@@ -569,6 +569,11 @@ public:
 	void close();
 
 	void setSurfaceMemory(void *mem, uint16 width, uint16 height, uint8 bpp);
+	void setAudioSync(bool sync) { _useAudioSync = sync; }
+
+protected:
+	bool _useAudioSync;
+	bool useAudioSync() const { return _useAudioSync; }
 
 private:
 	class VMDVideoTrack : public FixedRateVideoTrack {


Commit: 02540b8d38571ccd4da31c6fcae2878f923bbc4a
    https://github.com/scummvm/scummvm/commit/02540b8d38571ccd4da31c6fcae2878f923bbc4a
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Add support for kPrintDebug

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



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 6cacb5f..b3677d9 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -630,6 +630,7 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv);
 reg_t kMessageBox(EngineState *s, int argc, reg_t *argv);
 reg_t kGetConfig(EngineState *s, int argc, reg_t *argv);
 reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv);
+reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv);
 reg_t kCelInfo(EngineState *s, int argc, reg_t *argv);
 reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv);
 reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index b8d8450..8d2408a 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -929,7 +929,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	// stub in the original interpreters, but it gets called by the game scripts.
 	// Usually, it gets called with a string (which is the output format) and a
 	// variable number of parameters
-	{ MAP_EMPTY(PrintDebug),        SIG_EVERYWHERE,          "(.*)",                  NULL,            NULL },
+	{ MAP_CALL(PrintDebug),        SIG_SCI32, SIGFOR_ALL,    "r(.*)",                 NULL,            NULL },
 
 	// SetWindowsOption is used to set Windows specific options, like for example the title bar visibility of
 	// the game window in Phantasmagoria 2. We ignore these settings completely.
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index f2a3c6b..9aa03a4 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -459,6 +459,14 @@ reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv) {
 	}
 }
 
+extern Common::String format(const Common::String &source, int argc, const reg_t *argv);
+
+reg_t kPrintDebug(EngineState *s, int argc, reg_t *argv) {
+	const Common::String debugString = s->_segMan->getString(argv[0]);
+	debugC(kDebugLevelGame, "%s", format(debugString, argc - 1, argv + 1).c_str());
+	return s->r_acc;
+}
+
 #endif
 
 // kIconBar is really a subop of kMacPlatform for SCI1.1 Mac
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 8e330f2..6ae858e 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -128,6 +128,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
 	DebugMan.addDebugChannel(kDebugLevelScriptPatcher, "ScriptPatcher", "Notifies when scripts are patched");
 	DebugMan.addDebugChannel(kDebugLevelWorkarounds, "Workarounds", "Notifies when workarounds are triggered");
 	DebugMan.addDebugChannel(kDebugLevelVideo, "Video", "Video (SEQ, VMD, RBT) debugging");
+	DebugMan.addDebugChannel(kDebugLevelGame, "Game", "Debug calls from game scripts");
 	DebugMan.addDebugChannel(kDebugLevelGC, "GC", "Garbage Collector debugging");
 	DebugMan.addDebugChannel(kDebugLevelResMan, "ResMan", "Resource manager debugging");
 	DebugMan.addDebugChannel(kDebugLevelOnStartup, "OnStartup", "Enter debugger at start of game");
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index b336eb8..b46207b 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -125,7 +125,8 @@ enum kDebugLevels {
 	kDebugLevelDebugMode     = 1 << 21,
 	kDebugLevelScriptPatcher = 1 << 22,
 	kDebugLevelWorkarounds   = 1 << 23,
-	kDebugLevelVideo         = 1 << 24
+	kDebugLevelVideo         = 1 << 24,
+	kDebugLevelGame          = 1 << 25
 };
 
 enum SciGameId {


Commit: c7dacf273e0ac39386c3124de9ba04a5af62257d
    https://github.com/scummvm/scummvm/commit/c7dacf273e0ac39386c3124de9ba04a5af62257d
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Clean up and fix some SCI32-only kFileIO operations

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



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index b3677d9..c1f33f5 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -716,7 +716,7 @@ reg_t kFileIOReadByte(EngineState *s, int argc, reg_t *argv);
 reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv);
 reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv);
 reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv);
-reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv);
+reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv);
 reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv);
 #endif
 
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 8d2408a..3275ca0 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -326,13 +326,13 @@ static const SciKernelMapSubEntry kFileIO_subops[] = {
 	{ SIG_SCIALL,         10, MAP_CALL(FileIOExists),              "r",                    NULL },
 	{ SIG_SINCE_SCI11,    11, MAP_CALL(FileIORename),              "rr",                   NULL },
 #ifdef ENABLE_SCI32
-	{ SIG_SCI32,          13, MAP_CALL(FileIOReadByte),            "i",                    NULL },
-	{ SIG_SCI32,          14, MAP_CALL(FileIOWriteByte),           "ii",                   NULL },
-	{ SIG_SCI32,          15, MAP_CALL(FileIOReadWord),            "i",                    NULL },
-	{ SIG_SCI32,          16, MAP_CALL(FileIOWriteWord),           "ii",                   NULL },
-	{ SIG_SCI32,          17, MAP_CALL(FileIOCreateSaveSlot),      "ir",                   NULL },
-	{ SIG_SCI32,          18, MAP_EMPTY(FileIOChangeDirectory),    "r",                    NULL }, // for SQ6, when changing the savegame directory in the save/load dialog
-	{ SIG_SCI32,          19, MAP_CALL(FileIOIsValidDirectory),    "r",                    NULL }, // for Torin / Torin demo
+	{ SIG_SINCE_SCI21MID, 13, MAP_CALL(FileIOReadByte),            "i",                    NULL },
+	{ SIG_SINCE_SCI21MID, 14, MAP_CALL(FileIOWriteByte),           "ii",                   NULL },
+	{ SIG_SINCE_SCI21MID, 15, MAP_CALL(FileIOReadWord),            "i",                    NULL },
+	{ SIG_SINCE_SCI21MID, 16, MAP_CALL(FileIOWriteWord),           "ii",                   NULL },
+	{ SIG_SINCE_SCI21MID, 17, "FileIOCheckFreeSpace", kCheckFreeSpace, "i(r)",             NULL },
+	{ SIG_SINCE_SCI21MID, 18, MAP_CALL(FileIOGetCWD),              "r",                    NULL },
+	{ SIG_SINCE_SCI21MID, 19, MAP_CALL(FileIOIsValidDirectory),    "r",                    NULL },
 #endif
 	SCI_SUBOPENTRY_TERMINATOR
 };
@@ -625,9 +625,9 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ "CelHigh", kCelHigh32,       SIG_SCI32, SIGFOR_ALL,    "iii",                   NULL,            NULL },
 	{ "CelWide", kCelWide32,       SIG_SCI32, SIGFOR_ALL,    "iii",                   NULL,            kCelWide_workarounds },
 #endif
-	{ MAP_CALL(CheckFreeSpace),    SIG_SCI32, SIGFOR_ALL,    "r.*",                   NULL,            NULL },
+	{ MAP_CALL(CheckFreeSpace),    SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r(i)",          NULL,            NULL },
 	{ MAP_CALL(CheckFreeSpace),    SIG_SCI11, SIGFOR_ALL,    "r(i)",                  NULL,            NULL },
-	{ MAP_CALL(CheckFreeSpace),    SIG_EVERYWHERE,           "r",                     NULL,            NULL },
+	{ MAP_CALL(CheckFreeSpace),    SIG_SCI16, SIGFOR_ALL,    "r",                     NULL,            NULL },
 	{ MAP_CALL(CheckSaveGame),     SIG_EVERYWHERE,           ".*",                    NULL,            NULL },
 	{ MAP_CALL(Clone),             SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(CoordPri),          SIG_EVERYWHERE,           "i(i)",                  NULL,            NULL },
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index b810383..4d151de 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -158,33 +158,37 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) {
-	if (argc > 1) {
-		// SCI1.1/SCI32
-		// TODO: don't know if those are right for SCI32 as well
-		// Please note that sierra sci supported both calls either w/ or w/o opcode in SCI1.1
-		switch (argv[1].toUint16()) {
-		case 0: // return saved game size
-			return make_reg(0, 0); // we return 0
-
-		case 1: // return free harddisc space (shifted right somehow)
-			return make_reg(0, 0x7fff); // we return maximum
-
-		case 2: // same as call w/o opcode
-			break;
-			return make_reg(0, 1);
-
-		default:
-			error("kCheckFreeSpace: called with unknown sub-op %d", argv[1].toUint16());
-		}
+	// A file path to test is also passed to this function as a separate
+	// argument, but we do not actually check anything, so it is unused
+
+	enum {
+		kSaveGameSize      = 0,
+		kFreeDiskSpace     = 1,
+		kEnoughSpaceToSave = 2
+	};
+
+	int16 subop;
+	// In SCI2.1mid, the call is moved into kFileIO and the arguments are
+	// flipped
+	if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+		subop = argc > 0 ? argv[0].toSint16() : 2;
+	} else {
+		subop = argc > 1 ? argv[1].toSint16() : 2;
 	}
 
-	Common::String path = s->_segMan->getString(argv[0]);
+	switch (subop) {
+	case kSaveGameSize:
+		return make_reg(0, 0);
 
-	debug(3, "kCheckFreeSpace(%s)", path.c_str());
-	// We simply always pretend that there is enough space. The alternative
-	// would be to write a big test file, which is not nice on systems where
-	// doing so is very slow.
-	return make_reg(0, 1);
+	case kFreeDiskSpace: // in KiB; up to 32MiB maximum
+		return make_reg(0, 0x7fff);
+
+	case kEnoughSpaceToSave:
+		return make_reg(0, 1);
+
+	default:
+		error("kCheckFreeSpace: called with unknown sub-op %d", subop);
+	}
 }
 
 reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
@@ -660,7 +664,7 @@ reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) {
 	FileHandle *f = getFileFromHandle(s, argv[0].toUint16());
 	if (f)
 		f->_out->writeByte(argv[1].toUint16() & 0xff);
-	return s->r_acc; // FIXME: does this really not return anything?
+	return s->r_acc;
 }
 
 reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) {
@@ -674,27 +678,13 @@ reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) {
 	FileHandle *f = getFileFromHandle(s, argv[0].toUint16());
 	if (f)
 		f->_out->writeUint16LE(argv[1].toUint16());
-	return s->r_acc; // FIXME: does this really not return anything?
+	return s->r_acc;
 }
 
-reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) {
-	// Used in Shivers when the user enters his name on the guest book
-	// in the beginning to start the game.
-
-	// Creates a new save slot, and returns if the operation was successful
-
-	// Argument 0 denotes the save slot as a negative integer, 2 means "0"
-	// Argument 1 is a string, with the file name, obtained from kSave(5).
-	// The interpreter checks if it can be written to (by checking for free
-	// disk space and write permissions)
-
-	// We don't really use or need any of this...
-
-	uint16 saveSlot = argv[0].toUint16();
-	const SciArray &fileName = *s->_segMan->lookupArray(argv[1]);
-	warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName.toString().c_str());
-
-	return TRUE_REG;	// slot creation was successful
+reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv) {
+	SciArray &fileName = *s->_segMan->lookupArray(argv[0]);
+	fileName.fromString("C:\\SIERRA\\");
+	return argv[0];
 }
 
 reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv) {


Commit: 64dc37cfe22b5cea0cfa4457dd149bf21787b5f9
    https://github.com/scummvm/scummvm/commit/64dc37cfe22b5cea0cfa4457dd149bf21787b5f9
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Start implementing kSave for SCI32

Changed paths:
    engines/sci/detection.cpp
    engines/sci/engine/file.cpp
    engines/sci/engine/file.h
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kfile.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/state.h
    engines/sci/sci.cpp



diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index ad2b0f3..256f312 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -772,8 +772,7 @@ SaveStateList SciMetaEngine::listSaves(const char *target) const {
 				SaveStateDescriptor descriptor(slotNr, meta.name);
 
 				if (slotNr == 0) {
-					// ScummVM auto-save slot, not used by SCI
-					// SCI does not support auto-saving, but slot 0 is reserved for auto-saving in ScummVM.
+					// ScummVM auto-save slot
 					descriptor.setWriteProtectedFlag(true);
 				} else {
 					descriptor.setWriteProtectedFlag(false);
@@ -795,9 +794,8 @@ SaveStateDescriptor SciMetaEngine::querySaveMetaInfos(const char *target, int sl
 	Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName);
 	SaveStateDescriptor descriptor(slotNr, "");
 
-	// Do not allow save slot 0 (used for auto-saving) to be deleted or
-	// overwritten. SCI does not support auto-saving, but slot 0 is reserved for auto-saving in ScummVM.
 	if (slotNr == 0) {
+		// ScummVM auto-save slot
 		descriptor.setWriteProtectedFlag(true);
 		descriptor.setDeletableFlag(false);
 	} else {
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 8cecd8c..b0b753b 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -330,14 +330,23 @@ void listSavegames(Common::Array<SavegameDesc> &saves) {
 			}
 			delete in;
 
+			const int id = strtol(filename.end() - 3, NULL, 10);
+
+#ifdef ENABLE_SCI32
+			if (id == kNewGameId) {
+				continue;
+			}
+#endif
+
 			SavegameDesc desc;
-			desc.id = strtol(filename.end() - 3, NULL, 10);
+			desc.id = id;
 			desc.date = meta.saveDate;
 			// We need to fix date in here, because we save DDMMYYYY instead of
 			// YYYYMMDD, so sorting wouldn't work
 			desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24);
 			desc.time = meta.saveTime;
 			desc.version = meta.version;
+			desc.gameVersion = meta.gameVersion;
 
 			if (meta.name.lastChar() == '\n')
 				meta.name.deleteLastChar();
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index 982d7b7..7102b29 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -34,12 +34,17 @@ enum {
 	_K_FILE_MODE_CREATE = 2
 };
 
-/* Maximum length of a savegame name (including terminator character). */
-#define SCI_MAX_SAVENAME_LENGTH 0x24
+enum {
+	SCI_MAX_SAVENAME_LENGTH = 36, ///< Maximum length of a savegame name (including terminator character).
+	MAX_SAVEGAME_NR = 20 ///< Maximum number of savegames
+};
 
+#ifdef ENABLE_SCI32
 enum {
-	MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */
+	kAutoSaveId = 0,
+	kNewGameId = 100
 };
+#endif
 
 #define VIRTUALFILE_HANDLE_START 32000
 #define VIRTUALFILE_HANDLE_SCI32SAVE 32100
@@ -53,6 +58,7 @@ struct SavegameDesc {
 	int time;
 	int version;
 	char name[SCI_MAX_SAVENAME_LENGTH];
+	Common::String gameVersion;
 };
 
 class FileHandle {
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index c1f33f5..f37d878 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -483,6 +483,12 @@ reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv);
 reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
 reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
 
+reg_t kSave(EngineState *s, int argc, reg_t *argv);
+reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv);
+reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv);
+reg_t kSaveList32(EngineState *s, int argc, reg_t *argv);
+reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv);
+
 reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv);
 reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
 reg_t kListAt(EngineState *s, int argc, reg_t *argv);
@@ -615,8 +621,6 @@ reg_t kMorphOn(EngineState *s, int argc, reg_t *argv);
 reg_t kText(EngineState *s, int argc, reg_t *argv);
 reg_t kTextSize32(EngineState *s, int argc, reg_t *argv);
 reg_t kTextWidth(EngineState *s, int argc, reg_t *argv);
-reg_t kSave(EngineState *s, int argc, reg_t *argv);
-reg_t kAutoSave(EngineState *s, int argc, reg_t *argv);
 reg_t kList(EngineState *s, int argc, reg_t *argv);
 reg_t kCD(EngineState *s, int argc, reg_t *argv);
 reg_t kCheckCD(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 3275ca0..d234edb 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -351,15 +351,19 @@ static const SciKernelMapSubEntry kPalCycle_subops[] = {
 
 //    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 },
-	{ SIG_SCI32,           2, MAP_CALL(GetSaveDir),                "(r*)",                 NULL },
-	{ SIG_SCI32,           3, MAP_CALL(CheckSaveGame),             ".*",                   NULL },
+	{ SIG_SCI32,           0, MAP_CALL(SaveSave32),                "rir[r0]",              NULL },
+	{ SIG_SCI32,           1, MAP_CALL(SaveRestore32),             "ri[r0]",               NULL },
+	// System script 64994 in several SCI2.1mid games (KQ7 2.00b, Phant1,
+	// PQ:SWAT, SQ6, Torin) calls GetSaveDir with an extra unused argument, and
+	// it is easier to just handle it here than to bother with creating
+	// workarounds
+	{ SIG_SCI32,           2, MAP_CALL(GetSaveDir),                "(r)",                  NULL },
+	{ SIG_SCI32,           3, MAP_CALL(SaveCheck32),               "ri[r0]",               NULL },
 	// Subop 4 hasn't been encountered yet
-	{ SIG_SCI32,           5, MAP_CALL(GetSaveFiles),              "rrr",                  NULL },
+	{ SIG_SCI32,           5, MAP_CALL(SaveList32),                "rrr",                  NULL },
 	{ SIG_SCI32,           6, MAP_CALL(MakeSaveCatName),           "rr",                   NULL },
 	{ SIG_SCI32,           7, MAP_CALL(MakeSaveFileName),          "rri",                  NULL },
-	{ SIG_SCI32,           8, MAP_CALL(AutoSave),                  "[o0]",                 NULL },
+	{ SIG_SCI32,           8, MAP_EMPTY(GameIsRestarting),         ".*",                   NULL },
 	SCI_SUBOPENTRY_TERMINATOR
 };
 
@@ -628,7 +632,10 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(CheckFreeSpace),    SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r(i)",          NULL,            NULL },
 	{ MAP_CALL(CheckFreeSpace),    SIG_SCI11, SIGFOR_ALL,    "r(i)",                  NULL,            NULL },
 	{ MAP_CALL(CheckFreeSpace),    SIG_SCI16, SIGFOR_ALL,    "r",                     NULL,            NULL },
-	{ MAP_CALL(CheckSaveGame),     SIG_EVERYWHERE,           ".*",                    NULL,            NULL },
+#ifdef ENABLE_SCI32
+	{ "CheckSaveGame", kSaveCheck32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",      NULL,            NULL },
+#endif
+	{ MAP_CALL(CheckSaveGame),     SIG_SCI16, SIGFOR_ALL,    ".*",                    NULL,            NULL },
 	{ MAP_CALL(Clone),             SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(CoordPri),          SIG_EVERYWHERE,           "i(i)",                  NULL,            NULL },
 	{ MAP_CALL(CosDiv),            SIG_EVERYWHERE,           "ii",                    NULL,            NULL },
@@ -678,8 +685,13 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(GetMenu),           SIG_EVERYWHERE,           "i.",                    NULL,            NULL },
 	{ MAP_CALL(GetMessage),        SIG_EVERYWHERE,           "iiir",                  NULL,            NULL },
 	{ MAP_CALL(GetPort),           SIG_EVERYWHERE,           "",                      NULL,            NULL },
-	{ MAP_CALL(GetSaveDir),        SIG_SCI32, SIGFOR_ALL,    "(r*)",                  NULL,            NULL },
-	{ MAP_CALL(GetSaveDir),        SIG_EVERYWHERE,           "",                      NULL,            NULL },
+#ifdef ENABLE_SCI32
+	{ MAP_CALL(GetSaveDir),        SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "",              NULL,            NULL },
+#endif
+	{ MAP_CALL(GetSaveDir),        SIG_SCI16, SIGFOR_ALL,    "",                      NULL,            NULL },
+#ifdef ENABLE_SCI32
+	{ "GetSaveFiles", kSaveList32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rrr",           NULL,            NULL },
+#endif
 	{ MAP_CALL(GetSaveFiles),      SIG_EVERYWHERE,           "rrr",                   NULL,            NULL },
 	{ MAP_CALL(GetTime),           SIG_EVERYWHERE,           "(i)",                   NULL,            NULL },
 	{ MAP_CALL(GlobalToLocal),     SIG_SCI16, SIGFOR_ALL,    "o",                     NULL,            NULL },
@@ -741,9 +753,15 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(ResCheck),          SIG_EVERYWHERE,           "ii(iiii)",              NULL,            NULL },
 	{ MAP_CALL(RespondsTo),        SIG_EVERYWHERE,           ".i",                    NULL,            NULL },
 	{ MAP_CALL(RestartGame),       SIG_EVERYWHERE,           "",                      NULL,            NULL },
+#ifdef ENABLE_SCI32
+	{ "RestoreGame", kSaveRestore32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",      NULL,            NULL },
+#endif
 	{ MAP_CALL(RestoreGame),       SIG_EVERYWHERE,           "[r0]i[r0]",             NULL,            NULL },
 	{ MAP_CALL(Said),              SIG_EVERYWHERE,           "[r0]",                  NULL,            NULL },
-	{ MAP_CALL(SaveGame),          SIG_EVERYWHERE,           "[r0]i[r0](r0)",         NULL,            NULL },
+#ifdef ENABLE_SCI32
+	{ "SaveGame", kSaveSave32,     SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rir[r0]",       NULL,            NULL },
+#endif
+	{ MAP_CALL(SaveGame),          SIG_SCI16, SIGFOR_ALL,    "[r0]i[r0](r0)",         NULL,            NULL },
 	{ MAP_CALL(ScriptID),          SIG_EVERYWHERE,           "[io](i)",               NULL,            NULL },
 	{ MAP_CALL(SetCursor),         SIG_SCI11, SIGFOR_ALL,    "i(i)(i)(i)(iiiiii)",    NULL,            NULL },
 	{ MAP_CALL(SetCursor),         SIG_SCI16, SIGFOR_ALL,    "i(i)(i)(i)(i)",         NULL,            kSetCursor_workarounds },
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 4d151de..00138ae 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -257,17 +257,22 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 
 	// Torin's autosave system checks for the presence of autosave.cat
 	// by opening it. Since we don't use .cat files, we instead check
-	// for autosave.000 or autosave.001.
+	// for the autosave game.
 	//
-	// The same logic is being followed for torinsg.cat - this shows
-	// the "Open..." button when continuing a game.
+	// Similar logic is needed for torinsg.cat - this shows the "Open..." button
+	// when continuing a game if it exists.
 	//
-	// This has the added benefit of not detecting an SSCI autosave.cat
-	// accompanying SSCI autosave files that we wouldn't be able to load.
+	// TODO: Other games with autosave built in should be included here
 	if (g_sci->getGameId() == GID_TORIN && (name == "autosave.cat" || name == "torinsg.cat")) {
 		Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
-		const Common::String pattern = (name == "autosave.cat") ? g_sci->wrapFilename("autosave.###") : g_sci->getSavegamePattern();
-		bool exists = !saveFileMan->listSavefiles(pattern).empty();
+
+		bool exists;
+		if (name == "autosave.cat") {
+			exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty();
+		} else {
+			exists = !saveFileMan->listSavefiles(g_sci->getSavegamePattern()).empty();
+		}
+
 		if (exists) {
 			// Dummy handle. Torin only checks if this is SIGNAL_REG,
 			// and calls kFileIOClose on it.
@@ -297,12 +302,10 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 	// because they didn't want to modify the save/load code to add the extra
 	// information.
 	// Each slot in the book then has two strings, the save description and a
-	// description of the current spot that the player is at. Currently, the
-	// spot strings are always empty (probably related to the unimplemented
-	// kString subop 14, which gets called right before this call).
+	// description of the current spot that the player is at.
 	// For now, we don't allow the creation of these files, which means that
-	// all the spot descriptions next to each slot description will be empty
-	// (they are empty anyway). Until a viable solution is found to handle these
+	// all the spot descriptions next to each slot description will be empty.
+	// Until a viable solution is found to handle these
 	// extra files and until the spot description strings are initialized
 	// correctly, we resort to virtual files in order to make the load screen
 	// useable. Without this code it is unusable, as the extra information is
@@ -325,7 +328,8 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 
 			Common::Array<SavegameDesc> saves;
 			listSavegames(saves);
-			int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START);
+			int savegameNr = findSavegame(saves, slotNumber);
+			assert(savegameNr >= 0);
 
 			int size = strlen(saves[savegameNr].name) + 2;
 			char *buf = (char *)malloc(size);
@@ -709,6 +713,13 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) {
 #endif
 
 reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
+	// slot 0 is the ScummVM auto-save slot, which is not used by us, but is
+	// still reserved
+	enum {
+		SAVEGAMESLOT_FIRST = 1,
+		SAVEGAMESLOT_LAST = 99
+	};
+
 	Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : "";
 	int16 virtualId = argv[1].toSint16();
 	int16 savegameId = -1;
@@ -986,10 +997,6 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
-	Common::String game_id = s->_segMan->getString(argv[0]);
-
-	debug(3, "kGetSaveFiles(%s)", game_id.c_str());
-
 	// Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really
 	//  mean new slot instead of overwriting the old one
 	s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START;
@@ -998,6 +1005,10 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
 	listSavegames(saves);
 	uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR);
 
+	Common::String game_id = s->_segMan->getString(argv[0]);
+
+	debug(3, "kGetSaveFiles(%s)", game_id.c_str());
+
 	reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves);
 
 	if (!slot) {
@@ -1024,6 +1035,129 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
 }
 
 #ifdef ENABLE_SCI32
+reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv) {
+	const Common::String gameName = s->_segMan->getString(argv[0]);
+	int16 saveNo = argv[1].toSint16();
+	const Common::String saveDescription = s->_segMan->getString(argv[2]);
+	const Common::String gameVersion = argv[3].isNull() ? "" : s->_segMan->getString(argv[3]);
+
+	// Auto-save system used by Torin and LSL7
+	if (gameName == "Autosave" || gameName == "Autosv") {
+		if (saveNo == 0) {
+			// Autosave slot 0 is the autosave
+		} else {
+			// Autosave slot 1 is a "new game" save
+			saveNo = kNewGameId;
+		}
+	}
+
+	assert(gameName == "Autosave" || gameName == "Autosv" || saveNo > 0);
+
+	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+	const Common::String filename = g_sci->getSavegameName(saveNo);
+	Common::OutSaveFile *saveStream = saveFileMan->openForSaving(filename);
+
+	if (saveStream == nullptr) {
+		warning("Error opening savegame \"%s\" for writing", filename.c_str());
+		return NULL_REG;
+	}
+
+	if (!gamestate_save(s, saveStream, saveDescription, gameVersion)) {
+		warning("Saving the game failed");
+		saveStream->finalize();
+		delete saveStream;
+		return NULL_REG;
+	}
+
+	saveStream->finalize();
+	if (saveStream->err()) {
+		warning("Writing the savegame failed");
+		delete saveStream;
+		return NULL_REG;
+	}
+
+	delete saveStream;
+	return TRUE_REG;
+}
+
+reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv) {
+	const Common::String gameName = s->_segMan->getString(argv[0]);
+	int16 saveNo = argv[1].toSint16();
+	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+
+	if (gameName == "Autosave" || gameName == "Autosv") {
+		if (saveNo == 0) {
+			// Autosave slot 0 is the autosave
+		} else {
+			// Autosave slot 1 is a "new game" save
+			saveNo = kNewGameId;
+		}
+	}
+
+	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+	const Common::String filename = g_sci->getSavegameName(saveNo);
+	Common::SeekableReadStream *saveStream = saveFileMan->openForLoading(filename);
+
+	if (saveStream == nullptr) {
+		warning("Savegame #%d not found", saveNo);
+		return NULL_REG;
+	}
+
+	gamestate_restore(s, saveStream);
+	delete saveStream;
+
+	gamestate_afterRestoreFixUp(s, saveNo);
+	return TRUE_REG;
+}
+
+reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv) {
+	const Common::String gameName = s->_segMan->getString(argv[0]);
+	int16 saveNo = argv[1].toSint16();
+	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+
+	Common::Array<SavegameDesc> saves;
+	listSavegames(saves);
+
+	const int16 saveIndex = findSavegame(saves, saveNo);
+	if (saveIndex == -1) {
+		return NULL_REG;
+	}
+
+	const SavegameDesc &save = saves[saveIndex];
+
+	if (save.version < MINIMUM_SAVEGAME_VERSION ||
+		save.version > CURRENT_SAVEGAME_VERSION ||
+		save.gameVersion != gameVersion) {
+
+		return NULL_REG;
+	}
+
+	return TRUE_REG;
+}
+
+reg_t kSaveList32(EngineState *s, int argc, reg_t *argv) {
+	// argv[0] is gameName, used in SSCI as the name of the save game catalogue
+	// but unused here since ScummVM does not support multiple catalogues
+	SciArray &descriptions = *s->_segMan->lookupArray(argv[1]);
+	SciArray &saveIds = *s->_segMan->lookupArray(argv[2]);
+
+	Common::Array<SavegameDesc> saves;
+	listSavegames(saves);
+
+	// Normally SSCI limits to 20 games per directory, but ScummVM allows more
+	// than that
+	descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1);
+	saveIds.resize(saves.size());
+
+	for (uint i = 0; i < saves.size(); ++i) {
+		const SavegameDesc &save = saves[i];
+		char *target = &descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * i);
+		Common::strlcpy(target, save.name, SCI_MAX_SAVENAME_LENGTH);
+		saveIds.int16At(i) = save.id;
+	}
+
+	return make_reg(0, saves.size());
+}
 
 reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
 	// Normally, this creates the name of the save catalogue/directory to save into.
@@ -1054,17 +1188,6 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
 	return argv[0];
 }
 
-reg_t kAutoSave(EngineState *s, int argc, reg_t *argv) {
-	// TODO
-	// This is a timer callback, with 1 parameter: the timer object
-	// (e.g. "timers").
-	// It's used for auto-saving (i.e. save every X minutes, by checking
-	// the elapsed time from the timer object)
-
-	// This function has to return something other than 0 to proceed
-	return TRUE_REG;
-}
-
 #endif
 
 } // End of namespace Sci
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 9def79c..5a73526 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -1009,10 +1009,11 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin
 	meta.gameObjectOffset = g_sci->getGameObject().getOffset();
 
 	// Checking here again
-	if (s->executionStackBase) {
-		warning("Cannot save from below kernel function");
-		return false;
-	}
+// TODO: This breaks Torin autosave, is there actually any reason for it?
+//	if (s->executionStackBase) {
+//		warning("Cannot save from below kernel function");
+//		return false;
+//	}
 
 	Common::Serializer ser(0, fh);
 	sync_SavegameMetadata(ser, meta);
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index f1ec5d3..36ae784 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -57,10 +57,6 @@ enum AbortGameState {
 	kAbortQuitGame = 3
 };
 
-// slot 0 is the ScummVM auto-save slot, which is not used by us, but is still reserved
-#define SAVEGAMESLOT_FIRST 1
-#define SAVEGAMESLOT_LAST 99
-
 // We assume that scripts give us savegameId 0->99 for creating a new save slot
 //  and savegameId 100->199 for existing save slots. Refer to kfile.cpp
 enum {
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 6ae858e..eb314a9 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -569,6 +569,7 @@ void SciEngine::patchGameSaveRestore() {
 	case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
 	case GID_PHANTASMAGORIA: // has custom save/load code
 	case GID_SHIVERS: // has custom save/load code
+	case GID_PQSWAT: // has custom save/load code
 		return;
 	default:
 		break;


Commit: 90ee2d799ddaa51d75cdd55840301af4c833aaf0
    https://github.com/scummvm/scummvm/commit/90ee2d799ddaa51d75cdd55840301af4c833aaf0
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Split savegame descriptor fill code from savegame list code

The descriptor is used to check the validity of single savegames
in SCI32.

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



diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index b0b753b..74cd812 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -311,52 +311,57 @@ static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r)
 	return (l.time > r.time);
 }
 
-// Create a sorted array containing all found savedgames
-void listSavegames(Common::Array<SavegameDesc> &saves) {
+bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc) {
 	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+	Common::SeekableReadStream *in;
+	if ((in = saveFileMan->openForLoading(filename)) == nullptr) {
+		return false;
+	}
 
-	// Load all saves
-	Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
+	SavegameMetadata meta;
+	if (!get_savegame_metadata(in, &meta) || meta.name.empty()) {
+		// invalid
+		delete in;
+		return false;
+	}
+	delete in;
 
-	for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
-		Common::String filename = *iter;
-		Common::SeekableReadStream *in;
-		if ((in = saveFileMan->openForLoading(filename))) {
-			SavegameMetadata meta;
-			if (!get_savegame_metadata(in, &meta) || meta.name.empty()) {
-				// invalid
-				delete in;
-				continue;
-			}
-			delete in;
-
-			const int id = strtol(filename.end() - 3, NULL, 10);
+	const int id = strtol(filename.end() - 3, NULL, 10);
+	desc->id = id;
+	desc->date = meta.saveDate;
+	// We need to fix date in here, because we save DDMMYYYY instead of
+	// YYYYMMDD, so sorting wouldn't work
+	desc->date = ((desc->date & 0xFFFF) << 16) | ((desc->date & 0xFF0000) >> 8) | ((desc->date & 0xFF000000) >> 24);
+	desc->time = meta.saveTime;
+	desc->version = meta.version;
+	desc->gameVersion = meta.gameVersion;
 
-#ifdef ENABLE_SCI32
-			if (id == kNewGameId) {
-				continue;
-			}
-#endif
+	if (meta.name.lastChar() == '\n')
+		meta.name.deleteLastChar();
 
-			SavegameDesc desc;
-			desc.id = id;
-			desc.date = meta.saveDate;
-			// We need to fix date in here, because we save DDMMYYYY instead of
-			// YYYYMMDD, so sorting wouldn't work
-			desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24);
-			desc.time = meta.saveTime;
-			desc.version = meta.version;
-			desc.gameVersion = meta.gameVersion;
+	Common::strlcpy(desc->name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH);
 
-			if (meta.name.lastChar() == '\n')
-				meta.name.deleteLastChar();
+	return desc;
+}
 
-			Common::strlcpy(desc.name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH);
+// Create an array containing all found savedgames, sorted by creation date
+void listSavegames(Common::Array<SavegameDesc> &saves) {
+	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+	Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
 
-			debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id);
+	for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
+		const Common::String &filename = *iter;
 
-			saves.push_back(desc);
+#ifdef ENABLE_SCI32
+		const int id = strtol(filename.end() - 3, NULL, 10);
+		if (id == kNewGameId) {
+			continue;
 		}
+#endif
+
+		SavegameDesc desc;
+		fillSavegameDesc(filename, &desc);
+		saves.push_back(desc);
 	}
 
 	// Sort the list by creation date of the saves


Commit: 57c62c59f95701c99878ffed3c938f0e592c1fbb
    https://github.com/scummvm/scummvm/commit/57c62c59f95701c99878ffed3c938f0e592c1fbb
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Add constants documentation

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



diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index 7102b29..88d483b 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -41,8 +41,8 @@ enum {
 
 #ifdef ENABLE_SCI32
 enum {
-	kAutoSaveId = 0,
-	kNewGameId = 100
+	kAutoSaveId = 0, ///< The save game slot number for autosaves
+	kNewGameId = 999 ///< The save game slot number for a "new game" save
 };
 #endif
 


Commit: a22bfb0db22487537254dcb2e83dc02dc484d1fa
    https://github.com/scummvm/scummvm/commit/a22bfb0db22487537254dcb2e83dc02dc484d1fa
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix "new game" and auto-save functionality

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



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index f37d878..e1ec5c0 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -488,6 +488,7 @@ reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv);
 reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv);
 reg_t kSaveList32(EngineState *s, int argc, reg_t *argv);
 reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv);
+reg_t kSaveMakeFileName32(EngineState *s, int argc, reg_t *argv);
 
 reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv);
 reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index d234edb..7244262 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -362,7 +362,7 @@ static const SciKernelMapSubEntry kSave_subops[] = {
 	// Subop 4 hasn't been encountered yet
 	{ SIG_SCI32,           5, MAP_CALL(SaveList32),                "rrr",                  NULL },
 	{ SIG_SCI32,           6, MAP_CALL(MakeSaveCatName),           "rr",                   NULL },
-	{ SIG_SCI32,           7, MAP_CALL(MakeSaveFileName),          "rri",                  NULL },
+	{ SIG_SCI32,           7, MAP_CALL(SaveMakeFileName32),        "rri",                  NULL },
 	{ SIG_SCI32,           8, MAP_EMPTY(GameIsRestarting),         ".*",                   NULL },
 	SCI_SUBOPENTRY_TERMINATOR
 };
@@ -686,7 +686,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(GetMessage),        SIG_EVERYWHERE,           "iiir",                  NULL,            NULL },
 	{ MAP_CALL(GetPort),           SIG_EVERYWHERE,           "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ MAP_CALL(GetSaveDir),        SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "",              NULL,            NULL },
+	{ MAP_CALL(GetSaveDir),        SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(r)",           NULL,            NULL },
 #endif
 	{ MAP_CALL(GetSaveDir),        SIG_SCI16, SIGFOR_ALL,    "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
@@ -871,7 +871,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(ObjectIntersect),   SIG_EVERYWHERE,           "oo",                    NULL,            NULL },
 	{ MAP_CALL(EditText),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(MakeSaveCatName),   SIG_EVERYWHERE,           "rr",                    NULL,            NULL },
-	{ MAP_CALL(MakeSaveFileName),  SIG_EVERYWHERE,           "rri",                   NULL,            NULL },
+	{ "MakeSaveFileName", kSaveMakeFileName32, SIG_UNTIL_SCI21MID, SIGFOR_ALL, "rri", NULL,            NULL },
 	{ MAP_CALL(SetScroll),         SIG_EVERYWHERE,           "oiiii(i)(i)",           NULL,            NULL },
 	{ MAP_CALL(PalCycle),          SIG_EVERYWHERE,           "(.*)",                  kPalCycle_subops, NULL },
 
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 00138ae..797cd5d 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -51,6 +51,7 @@ extern FileHandle *getFileFromHandle(EngineState *s, uint handle);
 extern int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle);
 extern void listSavegames(Common::Array<SavegameDesc> &saves);
 extern int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId);
+extern bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc);
 
 /**
  * Writes the cwd to the supplied address and returns the address in acc.
@@ -270,7 +271,10 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 		if (name == "autosave.cat") {
 			exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty();
 		} else {
-			exists = !saveFileMan->listSavefiles(g_sci->getSavegamePattern()).empty();
+			// There will always be one save game in Torin, the "new game" game,
+			// which should be ignored when deciding if there are any save games
+			// to open
+			exists = saveFileMan->listSavefiles(g_sci->getSavegamePattern()).size() > 1;
 		}
 
 		if (exists) {
@@ -1049,9 +1053,36 @@ reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv) {
 			// Autosave slot 1 is a "new game" save
 			saveNo = kNewGameId;
 		}
-	}
+	} else if (saveNo == 0) {
+		// SSCI save games normally start from save number 0, but this is
+		// reserved for the autosave game in ScummVM. So, any time a game tries
+		// to save to slot 0 (and it isn't an autosave), it should instead go to
+		// the next highest free ID
+		Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+		Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
+		Common::sort(saveNames.begin(), saveNames.end());
+		if (saveNames.size()) {
+			int lastId = 0;
+			for (int i = 0; i < (int)saveNames.size(); ++i) {
+				const int id = strtol(saveNames[i].end() - 3, NULL, 10);
+				if (id == 0) {
+					continue;
+				}
+
+				if (id != lastId + 1) {
+					saveNo = lastId + 1;
+					break;
+				}
 
-	assert(gameName == "Autosave" || gameName == "Autosv" || saveNo > 0);
+				++lastId;
+			}
+		}
+
+		// There was no gap, so this save goes to a brand new slot
+		if (saveNo == 0) {
+			saveNo = saveNames.size() + 1;
+		}
+	}
 
 	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
 	const Common::String filename = g_sci->getSavegameName(saveNo);
@@ -1118,12 +1149,14 @@ reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv) {
 	Common::Array<SavegameDesc> saves;
 	listSavegames(saves);
 
-	const int16 saveIndex = findSavegame(saves, saveNo);
-	if (saveIndex == -1) {
-		return NULL_REG;
+	if ((gameName == "Autosave" || gameName == "Autosv") && saveNo == 1) {
+		saveNo = kNewGameId;
 	}
 
-	const SavegameDesc &save = saves[saveIndex];
+	SavegameDesc save;
+	if (!fillSavegameDesc(g_sci->getSavegameName(saveNo), &save)) {
+		return NULL_REG;
+	}
 
 	if (save.version < MINIMUM_SAVEGAME_VERSION ||
 		save.version > CURRENT_SAVEGAME_VERSION ||
@@ -1167,24 +1200,11 @@ reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
 	return argv[0];
 }
 
-reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
-	// Creates a savegame name from a slot number. Used when deleting saved games.
-	// Param 0: the output buffer (same as in kMakeSaveCatName)
-	// Param 1: a string with game parameters, ignored
-	// Param 2: the selected slot
-
-	SciArray *resultString = s->_segMan->lookupArray(argv[0]);
-	uint16 virtualId = argv[2].toUint16();
-	if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
-		error("kMakeSaveFileName: invalid savegame ID specified");
-	uint saveSlot = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
-
-	Common::Array<SavegameDesc> saves;
-	listSavegames(saves);
-
-	Common::String filename = g_sci->getSavegameName(saveSlot);
-	resultString->fromString(filename);
-
+reg_t kSaveMakeFileName32(EngineState *s, int argc, reg_t *argv) {
+	SciArray &outFileName = *s->_segMan->lookupArray(argv[0]);
+	// argv[1] is the game name, which is not used by ScummVM
+	int16 saveNo = argv[2].toSint16();
+	outFileName.fromString(g_sci->getSavegameName(saveNo));
 	return argv[0];
 }
 


Commit: 10f450917cc484242893322aeb966cec67d6fd68
    https://github.com/scummvm/scummvm/commit/10f450917cc484242893322aeb966cec67d6fd68
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Implement kSave for standard-save SCI32 games

Games with custom save code (KQ7, MGDX, PQ:SWAT, Shivers) are not
fully supported yet.

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



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index e1ec5c0..05e15da 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -484,11 +484,12 @@ reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
 reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
 
 reg_t kSave(EngineState *s, int argc, reg_t *argv);
-reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv);
-reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv);
-reg_t kSaveList32(EngineState *s, int argc, reg_t *argv);
-reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv);
-reg_t kSaveMakeFileName32(EngineState *s, int argc, reg_t *argv);
+reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv);
+reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv);
+reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv);
+reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv);
+reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
+reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
 
 reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv);
 reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
@@ -591,8 +592,6 @@ reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv);
 reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv);
 
 reg_t kEditText(EngineState *s, int argc, reg_t *argv);
-reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv);
-reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
 reg_t kSetScroll(EngineState *s, int argc, reg_t *argv);
 
 reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 7244262..c74b3ae 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -351,18 +351,18 @@ static const SciKernelMapSubEntry kPalCycle_subops[] = {
 
 //    version,         subId, function-mapping,                    signature,              workarounds
 static const SciKernelMapSubEntry kSave_subops[] = {
-	{ SIG_SCI32,           0, MAP_CALL(SaveSave32),                "rir[r0]",              NULL },
-	{ SIG_SCI32,           1, MAP_CALL(SaveRestore32),             "ri[r0]",               NULL },
+	{ SIG_SCI32,           0, MAP_CALL(SaveGame32),                "rir[r0]",              NULL },
+	{ SIG_SCI32,           1, MAP_CALL(RestoreGame32),             "ri[r0]",               NULL },
 	// System script 64994 in several SCI2.1mid games (KQ7 2.00b, Phant1,
 	// PQ:SWAT, SQ6, Torin) calls GetSaveDir with an extra unused argument, and
 	// it is easier to just handle it here than to bother with creating
 	// workarounds
 	{ SIG_SCI32,           2, MAP_CALL(GetSaveDir),                "(r)",                  NULL },
-	{ SIG_SCI32,           3, MAP_CALL(SaveCheck32),               "ri[r0]",               NULL },
+	{ SIG_SCI32,           3, MAP_CALL(CheckSaveGame32),           "ri[r0]",               NULL },
 	// Subop 4 hasn't been encountered yet
-	{ SIG_SCI32,           5, MAP_CALL(SaveList32),                "rrr",                  NULL },
+	{ SIG_SCI32,           5, MAP_CALL(GetSaveFiles32),            "rrr",                  NULL },
 	{ SIG_SCI32,           6, MAP_CALL(MakeSaveCatName),           "rr",                   NULL },
-	{ SIG_SCI32,           7, MAP_CALL(SaveMakeFileName32),        "rri",                  NULL },
+	{ SIG_SCI32,           7, MAP_CALL(MakeSaveFileName),          "rri",                  NULL },
 	{ SIG_SCI32,           8, MAP_EMPTY(GameIsRestarting),         ".*",                   NULL },
 	SCI_SUBOPENTRY_TERMINATOR
 };
@@ -633,7 +633,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(CheckFreeSpace),    SIG_SCI11, SIGFOR_ALL,    "r(i)",                  NULL,            NULL },
 	{ MAP_CALL(CheckFreeSpace),    SIG_SCI16, SIGFOR_ALL,    "r",                     NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "CheckSaveGame", kSaveCheck32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",      NULL,            NULL },
+	{ "CheckSaveGame", kCheckSaveGame32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",  NULL,            NULL },
 #endif
 	{ MAP_CALL(CheckSaveGame),     SIG_SCI16, SIGFOR_ALL,    ".*",                    NULL,            NULL },
 	{ MAP_CALL(Clone),             SIG_EVERYWHERE,           "o",                     NULL,            NULL },
@@ -690,7 +690,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 #endif
 	{ MAP_CALL(GetSaveDir),        SIG_SCI16, SIGFOR_ALL,    "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "GetSaveFiles", kSaveList32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rrr",           NULL,            NULL },
+	{ "GetSaveFiles", kGetSaveFiles32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rrr",       NULL,            NULL },
 #endif
 	{ MAP_CALL(GetSaveFiles),      SIG_EVERYWHERE,           "rrr",                   NULL,            NULL },
 	{ MAP_CALL(GetTime),           SIG_EVERYWHERE,           "(i)",                   NULL,            NULL },
@@ -754,12 +754,12 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(RespondsTo),        SIG_EVERYWHERE,           ".i",                    NULL,            NULL },
 	{ MAP_CALL(RestartGame),       SIG_EVERYWHERE,           "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "RestoreGame", kSaveRestore32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",      NULL,            NULL },
+	{ "RestoreGame", kRestoreGame32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",      NULL,            NULL },
 #endif
 	{ MAP_CALL(RestoreGame),       SIG_EVERYWHERE,           "[r0]i[r0]",             NULL,            NULL },
 	{ MAP_CALL(Said),              SIG_EVERYWHERE,           "[r0]",                  NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "SaveGame", kSaveSave32,     SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rir[r0]",       NULL,            NULL },
+	{ "SaveGame", kSaveGame32,     SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rir[r0]",       NULL,            NULL },
 #endif
 	{ MAP_CALL(SaveGame),          SIG_SCI16, SIGFOR_ALL,    "[r0]i[r0](r0)",         NULL,            NULL },
 	{ MAP_CALL(ScriptID),          SIG_EVERYWHERE,           "[io](i)",               NULL,            NULL },
@@ -870,8 +870,8 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(UpdateScreenItem),  SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(ObjectIntersect),   SIG_EVERYWHERE,           "oo",                    NULL,            NULL },
 	{ MAP_CALL(EditText),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },
-	{ MAP_CALL(MakeSaveCatName),   SIG_EVERYWHERE,           "rr",                    NULL,            NULL },
-	{ "MakeSaveFileName", kSaveMakeFileName32, SIG_UNTIL_SCI21MID, SIGFOR_ALL, "rri", NULL,            NULL },
+	{ MAP_CALL(MakeSaveCatName),   SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rr",            NULL,            NULL },
+	{ MAP_CALL(MakeSaveFileName),  SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rri",           NULL,            NULL },
 	{ MAP_CALL(SetScroll),         SIG_EVERYWHERE,           "oiiii(i)(i)",           NULL,            NULL },
 	{ MAP_CALL(PalCycle),          SIG_EVERYWHERE,           "(.*)",                  kPalCycle_subops, NULL },
 
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 797cd5d..41aa16b 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -256,6 +256,12 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 		return SIGNAL_REG;
 	}
 
+#ifdef ENABLE_SCI32
+	// See kMakeSaveCatName
+	if (name == "fake.cat") {
+		return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+	}
+
 	// Torin's autosave system checks for the presence of autosave.cat
 	// by opening it. Since we don't use .cat files, we instead check
 	// for the autosave game.
@@ -285,6 +291,7 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 			return SIGNAL_REG;
 		}
 	}
+#endif
 
 	if (name.empty()) {
 		// Happens many times during KQ1 (e.g. when typing something)
@@ -1039,7 +1046,7 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
 }
 
 #ifdef ENABLE_SCI32
-reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv) {
+reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 	const Common::String gameName = s->_segMan->getString(argv[0]);
 	int16 saveNo = argv[1].toSint16();
 	const Common::String saveDescription = s->_segMan->getString(argv[2]);
@@ -1111,7 +1118,7 @@ reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv) {
 	return TRUE_REG;
 }
 
-reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv) {
+reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
 	const Common::String gameName = s->_segMan->getString(argv[0]);
 	int16 saveNo = argv[1].toSint16();
 	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
@@ -1141,7 +1148,7 @@ reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv) {
 	return TRUE_REG;
 }
 
-reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv) {
+reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv) {
 	const Common::String gameName = s->_segMan->getString(argv[0]);
 	int16 saveNo = argv[1].toSint16();
 	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
@@ -1168,7 +1175,7 @@ reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv) {
 	return TRUE_REG;
 }
 
-reg_t kSaveList32(EngineState *s, int argc, reg_t *argv) {
+reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
 	// argv[0] is gameName, used in SSCI as the name of the save game catalogue
 	// but unused here since ScummVM does not support multiple catalogues
 	SciArray &descriptions = *s->_segMan->lookupArray(argv[1]);
@@ -1179,8 +1186,8 @@ reg_t kSaveList32(EngineState *s, int argc, reg_t *argv) {
 
 	// Normally SSCI limits to 20 games per directory, but ScummVM allows more
 	// than that
-	descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1);
-	saveIds.resize(saves.size());
+	descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1, true);
+	saveIds.resize(saves.size(), true);
 
 	for (uint i = 0; i < saves.size(); ++i) {
 		const SavegameDesc &save = saves[i];
@@ -1189,18 +1196,22 @@ reg_t kSaveList32(EngineState *s, int argc, reg_t *argv) {
 		saveIds.int16At(i) = save.id;
 	}
 
+	descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0';
+
 	return make_reg(0, saves.size());
 }
 
 reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
-	// Normally, this creates the name of the save catalogue/directory to save into.
-	// First parameter is the string to save the result into. Second is a string
-	// with game parameters. We don't have a use for this at all, as we have our own
-	// savegame directory management, thus we always return an empty string.
+	// ScummVM does not use SCI catalogues for save games, but game scripts try
+	// to write out catalogues manually after a save game is deleted, so we need
+	// to be able to identify and ignore these IO operations by always giving
+	// back a fixed catalogue name and then ignoring it in kFileIO
+	SciArray &outCatName = *s->_segMan->lookupArray(argv[0]);
+	outCatName.fromString("fake.cat");
 	return argv[0];
 }
 
-reg_t kSaveMakeFileName32(EngineState *s, int argc, reg_t *argv) {
+reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
 	SciArray &outFileName = *s->_segMan->lookupArray(argv[0]);
 	// argv[1] is the game name, which is not used by ScummVM
 	int16 saveNo = argv[2].toSint16();


Commit: a96dc1a7b879994ab5111ed223e9831ea81054b9
    https://github.com/scummvm/scummvm/commit/a96dc1a7b879994ab5111ed223e9831ea81054b9
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Correct return values for kFileIO operations

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



diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 41aa16b..7e5af79 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -238,6 +238,8 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
 reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 	Common::String name = s->_segMan->getString(argv[0]);
 
+	assert(name != "");
+
 	int mode = argv[1].toUint16();
 	bool unwrapFilename = true;
 
@@ -380,7 +382,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
 
 	if (handle >= VIRTUALFILE_HANDLE_START) {
 		// it's a virtual handle? ignore it
-		return SIGNAL_REG;
+		return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;
 	}
 
 	FileHandle *f = getFileFromHandle(s, handle);
@@ -388,7 +390,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
 		f->close();
 		if (getSciVersion() <= SCI_VERSION_0_LATE)
 			return s->r_acc;	// SCI0 semantics: no value returned
-		return SIGNAL_REG;
+		return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;
 	}
 
 	if (getSciVersion() <= SCI_VERSION_0_LATE)
@@ -420,18 +422,37 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) {
 reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) {
 	uint16 handle = argv[0].toUint16();
 	uint16 size = argv[2].toUint16();
+
+#ifdef ENABLE_SCI32
+	if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
+		return make_reg(0, size);
+	}
+#endif
+
 	char *buf = new char[size];
+	uint bytesWritten = 0;
 	bool success = false;
 	s->_segMan->memcpy((byte *)buf, argv[1], size);
 	debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size);
 
 	FileHandle *f = getFileFromHandle(s, handle);
 	if (f) {
-		f->_out->write(buf, size);
-		success = true;
+		bytesWritten = f->_out->write(buf, size);
+		success = !f->_out->err();
 	}
 
 	delete[] buf;
+
+#ifdef ENABLE_SCI32
+	if (getSciVersion() >= SCI_VERSION_2) {
+		if (!success) {
+			return make_reg(0, -1);
+		}
+
+		return make_reg(0, bytesWritten);
+	}
+#endif
+
 	if (success)
 		return NULL_REG;
 	return make_reg(0, 6); // DOS - invalid handle
@@ -461,6 +482,7 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
 		int savedir_nr = saves[slotNum].id;
 		name = g_sci->getSavegameName(savedir_nr);
 		result = saveFileMan->removeSavefile(name);
+#ifdef ENABLE_SCI32
 	} else if (getSciVersion() >= SCI_VERSION_2) {
 		// The file name may be already wrapped, so check both cases
 		result = saveFileMan->removeSavefile(name);
@@ -468,12 +490,20 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
 			const Common::String wrappedName = g_sci->wrapFilename(name);
 			result = saveFileMan->removeSavefile(wrappedName);
 		}
+#endif
 	} else {
 		const Common::String wrappedName = g_sci->wrapFilename(name);
 		result = saveFileMan->removeSavefile(wrappedName);
 	}
 
 	debugC(kDebugLevelFile, "kFileIO(unlink): %s", name.c_str());
+
+#ifdef ENABLE_SCI32
+	if (getSciVersion() >= SCI_VERSION_2) {
+		return make_reg(0, result);
+	}
+#endif
+
 	if (result)
 		return NULL_REG;
 	return make_reg(0, 2); // DOS - file not found error code


Commit: b5d0fffb8b111c5d8154a37ba9a0d7b18866c7dd
    https://github.com/scummvm/scummvm/commit/b5d0fffb8b111c5d8154a37ba9a0d7b18866c7dd
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Replace magic numbers for globals with named constants

Changed paths:
    engines/sci/engine/kscripts.cpp
    engines/sci/engine/state.cpp
    engines/sci/engine/vm.cpp
    engines/sci/engine/vm.h
    engines/sci/graphics/animate.cpp
    engines/sci/graphics/frameout.cpp
    engines/sci/sci.cpp



diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index 6fd130b..749af67 100644
--- a/engines/sci/engine/kscripts.cpp
+++ b/engines/sci/engine/kscripts.cpp
@@ -238,8 +238,8 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) {
 	// initialized to 0, whereas it's 6 in other versions. Thus, we assign it
 	// to 6 here, fixing the speed of the introduction. Refer to bug #3102071.
 	if (g_sci->getGameId() == GID_PQ2 && script == 200 &&
-		s->variables[VAR_GLOBAL][3].isNull()) {
-		s->variables[VAR_GLOBAL][3] = make_reg(0, 6);
+		s->variables[VAR_GLOBAL][kSpeed].isNull()) {
+		s->variables[VAR_GLOBAL][kSpeed] = make_reg(0, 6);
 	}
 
 	return make_reg(scriptSeg, address);
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index d253d31..5d4eba7 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -159,11 +159,11 @@ void EngineState::initGlobals() {
 }
 
 uint16 EngineState::currentRoomNumber() const {
-	return variables[VAR_GLOBAL][13].toUint16();
+	return variables[VAR_GLOBAL][kNewRoomNo].toUint16();
 }
 
 void EngineState::setRoomNumber(uint16 roomNumber) {
-	variables[VAR_GLOBAL][13] = make_reg(0, roomNumber);
+	variables[VAR_GLOBAL][kNewRoomNo] = make_reg(0, roomNumber);
 }
 
 void EngineState::shrinkStackToBase() {
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 72e340d..7c7f75c 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -179,7 +179,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
 		// stopGroop object, which points to ego, to the new ego object. If this is not
 		// done, ego's movement will not be updated properly, so the result is
 		// unpredictable (for example in LSL5, Patti spins around instead of walking).
-		if (index == 0 && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) {	// global 0 is ego
+		if (index == kEgo && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) {
 			reg_t stopGroopPos = s->_segMan->findObjectByName("stopGroop");
 			if (!stopGroopPos.isNull()) {	// does the game have a stopGroop object?
 				// Find the "client" member variable of the stopGroop object, and update it
@@ -200,9 +200,9 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
 
 		s->variables[type][index] = value;
 
-		if (type == VAR_GLOBAL && index == 90) {
+		if (type == VAR_GLOBAL && index == kMessageType) {
 			// The game is trying to change its speech/subtitle settings
-			if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][4] == TRUE_REG) {
+			if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kQuit] == TRUE_REG) {
 				// ScummVM audio options haven't been applied yet, so apply them.
 				// We also force the ScummVM audio options when loading a game from
 				// the launcher.
diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h
index c41060d..e127706 100644
--- a/engines/sci/engine/vm.h
+++ b/engines/sci/engine/vm.h
@@ -136,6 +136,21 @@ enum {
 	VAR_PARAM = 3
 };
 
+enum GlobalVar {
+	kEgo            = 0,
+	kGame           = 1,
+	kCurrentRoom    = 2,
+	kSpeed          = 3,  // SCI16
+	kDefaultPlane   = 3,  // SCI32
+	kQuit           = 4,
+	kPlanes         = 10, // SCI32
+	kCurrentRoomNo  = 11,
+	kPreviousRoomNo = 12,
+	kNewRoomNo      = 13,
+	kFastCast       = 84, // SCI16
+	kMessageType    = 90
+};
+
 /** Number of kernel calls in between gcs; should be < 50000 */
 enum {
 	GC_INTERVAL = 0x8000
diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp
index 9827839..e593484 100644
--- a/engines/sci/graphics/animate.cpp
+++ b/engines/sci/graphics/animate.cpp
@@ -145,7 +145,7 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) {
 		if (_fastCastEnabled) {
 			// Check if the game has a fastCast object set
 			//  if we don't abort kAnimate processing, at least in kq5 there will be animation cels drawn into speech boxes.
-			if (!_s->variables[VAR_GLOBAL][84].isNull()) {
+			if (!_s->variables[VAR_GLOBAL][kFastCast].isNull()) {
 				// This normally points to an object called "fastCast",
 				// but for example in Eco Quest 1 it may also point to an object called "EventHandler" (see bug #5170)
 				// Original SCI only checked, if this global was not 0.
@@ -338,7 +338,7 @@ void GfxAnimate::applyGlobalScaling(AnimateList::iterator entry, GfxView *view)
 	int16 maxScale = readSelectorValue(_s->_segMan, entry->object, SELECTOR(maxScale));
 	int16 celHeight = view->getHeight(entry->loopNo, entry->celNo);
 	int16 maxCelHeight = (maxScale * celHeight) >> 7;
-	reg_t globalVar2 = _s->variables[VAR_GLOBAL][2]; // current room object
+	reg_t globalVar2 = _s->variables[VAR_GLOBAL][kCurrentRoom]; // current room object
 	int16 vanishingY = readSelectorValue(_s->_segMan, globalVar2, SELECTOR(vanishingY));
 
 	int16 fixedPortY = _ports->getPort()->rect.bottom - vanishingY;
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 7d38d01..7410bda 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -141,7 +141,7 @@ void GfxFrameout::syncWithScripts(bool addElements) {
 		return;
 
 	// Get planes list object
-	reg_t planesListObject = engineState->variables[VAR_GLOBAL][10];
+	reg_t planesListObject = engineState->variables[VAR_GLOBAL][kPlanes];
 	reg_t planesListElements = readSelector(segMan, planesListObject, SELECTOR(elements));
 
 	List *planesList = segMan->lookupList(planesListElements);
@@ -565,7 +565,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *show
 	Palette sourcePalette(_palette->getNextPalette());
 	alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
 
-	int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
+	int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][kPreviousRoomNo].toSint16();
 
 	Common::Rect rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight);
 	_showList.add(rect);
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index eb314a9..bdfb6cc 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -1088,9 +1088,9 @@ void SciEngine::syncIngameAudioOptions() {
 
 		if (useGlobal90) {
 			if (subtitlesOn && !speechOn) {
-				_gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 1);	// subtitles
+				_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 1);	// subtitles
 			} else if (!subtitlesOn && speechOn) {
-				_gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 2);	// speech
+				_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 2);	// speech
 			} else if (subtitlesOn && speechOn) {
 				// Is it a game that supports simultaneous speech and subtitles?
 				switch (_gameId) {
@@ -1111,11 +1111,11 @@ void SciEngine::syncIngameAudioOptions() {
 				// Phantasmagoria does not support simultaneous speech + subtitles
 				// Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles
 #endif // ENABLE_SCI32
-					_gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 3);	// speech + subtitles
+					_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 3);	// speech + subtitles
 					break;
 				default:
 					// Game does not support speech and subtitles, set it to speech
-					_gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 2);	// speech
+					_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 2);	// speech
 				}
 			}
 		}
@@ -1126,7 +1126,7 @@ void SciEngine::updateScummVMAudioOptions() {
 	// Update ScummVM's speech/subtitles settings for SCI1.1 CD games,
 	// depending on the in-game settings
 	if (isCD() && getSciVersion() == SCI_VERSION_1_1) {
-		uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][90].getOffset();
+		uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kMessageType].getOffset();
 
 		switch (ingameSetting) {
 		case 1:


Commit: 1eb6d90eec4aa2dab3532e9630c81b6b3dbddafb
    https://github.com/scummvm/scummvm/commit/1eb6d90eec4aa2dab3532e9630c81b6b3dbddafb
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Implement ScummVM save/load

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



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index c74b3ae..e11326f 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -351,8 +351,8 @@ static const SciKernelMapSubEntry kPalCycle_subops[] = {
 
 //    version,         subId, function-mapping,                    signature,              workarounds
 static const SciKernelMapSubEntry kSave_subops[] = {
-	{ SIG_SCI32,           0, MAP_CALL(SaveGame32),                "rir[r0]",              NULL },
-	{ SIG_SCI32,           1, MAP_CALL(RestoreGame32),             "ri[r0]",               NULL },
+	{ SIG_SCI32,           0, MAP_CALL(SaveGame32),                "[r0]i[r0][r0]",        NULL },
+	{ SIG_SCI32,           1, MAP_CALL(RestoreGame32),             "[r0]i[r0]",            NULL },
 	// System script 64994 in several SCI2.1mid games (KQ7 2.00b, Phant1,
 	// PQ:SWAT, SQ6, Torin) calls GetSaveDir with an extra unused argument, and
 	// it is easier to just handle it here than to bother with creating
@@ -754,12 +754,12 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(RespondsTo),        SIG_EVERYWHERE,           ".i",                    NULL,            NULL },
 	{ MAP_CALL(RestartGame),       SIG_EVERYWHERE,           "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "RestoreGame", kRestoreGame32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",      NULL,            NULL },
+	{ "RestoreGame", kRestoreGame32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0]",   NULL,            NULL },
 #endif
 	{ MAP_CALL(RestoreGame),       SIG_EVERYWHERE,           "[r0]i[r0]",             NULL,            NULL },
 	{ MAP_CALL(Said),              SIG_EVERYWHERE,           "[r0]",                  NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "SaveGame", kSaveGame32,     SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rir[r0]",       NULL,            NULL },
+	{ "SaveGame", kSaveGame32,     SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0][r0]", NULL,            NULL },
 #endif
 	{ MAP_CALL(SaveGame),          SIG_SCI16, SIGFOR_ALL,    "[r0]i[r0](r0)",         NULL,            NULL },
 	{ MAP_CALL(ScriptID),          SIG_EVERYWHERE,           "[io](i)",               NULL,            NULL },
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 7e5af79..50560be 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -1077,10 +1077,32 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
 
 #ifdef ENABLE_SCI32
 reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
-	const Common::String gameName = s->_segMan->getString(argv[0]);
-	int16 saveNo = argv[1].toSint16();
-	const Common::String saveDescription = s->_segMan->getString(argv[2]);
-	const Common::String gameVersion = argv[3].isNull() ? "" : s->_segMan->getString(argv[3]);
+	Common::String gameName = "";
+	int16 saveNo;
+	Common::String saveDescription;
+	Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]);
+
+	if (argv[0].isNull()) {
+		// ScummVM call, from a patched Game::save
+		g_sci->_soundCmd->pauseAll(true);
+		GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
+		saveNo = dialog.runModalWithCurrentTarget();
+		g_sci->_soundCmd->pauseAll(false);
+
+		if (saveNo < 0) {
+			return NULL_REG;
+		}
+
+		saveDescription = dialog.getResultString();
+		if (saveDescription.empty()) {
+			saveDescription = dialog.createDefaultSaveDescription(saveNo);
+		}
+	} else {
+		// Native script call
+		gameName = s->_segMan->getString(argv[0]);
+		saveNo = argv[1].toSint16();
+		saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+	}
 
 	// Auto-save system used by Torin and LSL7
 	if (gameName == "Autosave" || gameName == "Autosv") {
@@ -1149,10 +1171,23 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
-	const Common::String gameName = s->_segMan->getString(argv[0]);
+	Common::String gameName = "";
 	int16 saveNo = argv[1].toSint16();
 	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
 
+	if (argv[0].isNull() && saveNo == -1) {
+		// ScummVM call, either from lancher or a patched Game::restore
+		g_sci->_soundCmd->pauseAll(true);
+		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
+		saveNo = dialog.runModalWithCurrentTarget();
+		g_sci->_soundCmd->pauseAll(false);
+		if (saveNo < 0) {
+			return s->r_acc;
+		}
+	} else {
+		gameName = s->_segMan->getString(argv[0]);
+	}
+
 	if (gameName == "Autosave" || gameName == "Autosv") {
 		if (saveNo == 0) {
 			// Autosave slot 0 is the autosave
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index bdfb6cc..f161569 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -496,61 +496,86 @@ static byte patchGameRestoreSave[] = {
 };
 
 // SCI2 version: Same as above, but the second parameter to callk is a word
-static byte patchGameRestoreSaveSci2[] = {
-	0x39, 0x03,        // pushi 03
-	0x76,              // push0
-	0x38, 0xff, 0xff,  // pushi -1
-	0x76,              // push0
-	0x43, 0xff, 0x06, 0x00, // callk kRestoreGame/kSaveGame (will get changed afterwards)
-	0x48,              // ret
+// and third parameter is a string reference
+static byte patchGameRestoreSci2[] = {
+	0x39, 0x03,             // pushi 03
+	0x76,                   // push0          (game name)
+	0x38, 0xff, 0xff,       // pushi -1       (save number)
+	0x89, 0x1b,             // lsg global[27] (game version)
+	0x43, 0xff, 0x06, 0x00, // callk kRestoreGame (0xFF will be overwritten by patcher)
+	0x48,                   // ret
 };
 
-// SCI21 version: Same as above, but the second parameter to callk is a word
-static byte patchGameRestoreSaveSci21[] = {
-	0x39, 0x04,        // pushi 04
-	0x76,              // push0	// 0: save, 1: restore (will get changed afterwards)
-	0x76,              // push0
-	0x38, 0xff, 0xff,  // pushi -1
-	0x76,              // push0
-	0x43, 0xff, 0x08, 0x00, // callk kSave (will get changed afterwards)
-	0x48,              // ret
+static byte patchGameSaveSci2[] = {
+	0x39, 0x04,             // pushi 04
+	0x76,                   // push0          (game name)
+	0x38, 0xff, 0xff,       // pushi -1       (save number)
+	0x76,                   // push0          (save description)
+	0x89, 0x1b,             // lsg global[27] (game version)
+	0x43, 0xff, 0x08, 0x00, // callk kSaveGame (0xFF will be overwritten by patcher)
+	0x48,                   // ret
+};
+
+// SCI2.1mid version: Same as above, but with an extra subop parameter
+static byte patchGameRestoreSci21[] = {
+	0x39, 0x04,             // pushi 04
+	0x78,                   // push1          (subop)
+	0x76,                   // push0          (game name)
+	0x38, 0xff, 0xff,       // pushi -1       (save number)
+	0x89, 0x1b,             // lsg global[27] (game version)
+	0x43, 0xff, 0x08, 0x00, // callk kSave (0xFF will be overwritten by patcher)
+	0x48,                   // ret
+};
+
+static byte patchGameSaveSci21[] = {
+	0x39, 0x05,             // pushi 05
+	0x76,                   // push0          (subop)
+	0x76,                   // push0          (game name)
+	0x38, 0xff, 0xff,       // pushi -1       (save number)
+	0x76,                   // push0          (save description)
+	0x89, 0x1b,             // lsg global[27] (game version)
+	0x43, 0xff, 0x0a, 0x00, // callk kSave (0xFF will be overwritten by patcher)
+	0x48,                   // ret
 };
 
 static void patchGameSaveRestoreCode(SegManager *segMan, reg_t methodAddress, byte id) {
 	Script *script = segMan->getScript(methodAddress.getSegment());
 	byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
 
-	if (getSciVersion() <= SCI_VERSION_1_1) {
-		memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave));
-	} else {	// SCI2+
-		memcpy(patchPtr, patchGameRestoreSaveSci2, sizeof(patchGameRestoreSaveSci2));
-
-		if (g_sci->isBE()) {
-			// LE -> BE
-			patchPtr[9] = 0x00;
-			patchPtr[10] = 0x06;
-		}
-	}
-
+	memcpy(patchPtr, patchGameRestoreSave, sizeof(patchGameRestoreSave));
 	patchPtr[8] = id;
 }
 
-static void patchGameSaveRestoreCodeSci21(SegManager *segMan, reg_t methodAddress, byte id, bool doRestore) {
+#ifdef ENABLE_SCI32
+static void patchGameSaveRestoreCodeSci2(SegManager *segMan, reg_t methodAddress, byte id, bool doRestore) {
 	Script *script = segMan->getScript(methodAddress.getSegment());
 	byte *patchPtr = const_cast<byte *>(script->getBuf(methodAddress.getOffset()));
-	memcpy(patchPtr, patchGameRestoreSaveSci21, sizeof(patchGameRestoreSaveSci21));
+	int kcallOffset;
 
-	if (doRestore)
-		patchPtr[2] = 0x78;	// push1
+	if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+		if (doRestore) {
+			memcpy(patchPtr, patchGameRestoreSci2, sizeof(patchGameRestoreSci2));
+			kcallOffset = 9;
+		} else {
+			memcpy(patchPtr, patchGameSaveSci2, sizeof(patchGameSaveSci2));
+			kcallOffset = 10;
+		}
+	} else {
+		if (doRestore) {
+			memcpy(patchPtr, patchGameRestoreSci21, sizeof(patchGameRestoreSci21));
+			kcallOffset = 10;
+		} else {
+			memcpy(patchPtr, patchGameSaveSci21, sizeof(patchGameSaveSci21));
+			kcallOffset = 11;
+		}
+	}
 
+	patchPtr[kcallOffset] = id;
 	if (g_sci->isBE()) {
-		// LE -> BE
-		patchPtr[10] = 0x00;
-		patchPtr[11] = 0x08;
+		SWAP(patchPtr[kcallOffset + 1], patchPtr[kcallOffset + 2]);
 	}
-
-	patchPtr[9] = id;
 }
+#endif
 
 void SciEngine::patchGameSaveRestore() {
 	SegManager *segMan = _gamestate->_segMan;
@@ -595,17 +620,21 @@ void SciEngine::patchGameSaveRestore() {
 		uint16 selectorId = gameSuperObject->getFuncSelector(methodNr);
 		Common::String methodName = _kernel->getSelectorName(selectorId);
 		if (methodName == "restore") {
-			if (kernelIdSave != kernelIdRestore)
+#ifdef ENABLE_SCI32
+			if (getSciVersion() >= SCI_VERSION_2) {
+				patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore, true);
+			} else
+#endif
 				patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore);
-			else
-				patchGameSaveRestoreCodeSci21(segMan, gameSuperObject->getFunction(methodNr), kernelIdRestore, true);
 		}
 		else if (methodName == "save") {
 			if (_gameId != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
-				if (kernelIdSave != kernelIdRestore)
+#ifdef ENABLE_SCI32
+				if (getSciVersion() >= SCI_VERSION_2) {
+					patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
+				} else
+#endif
 					patchGameSaveRestoreCode(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave);
-				else
-					patchGameSaveRestoreCodeSci21(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
 			}
 		}
 	}
@@ -629,10 +658,12 @@ void SciEngine::patchGameSaveRestore() {
 		Common::String methodName = _kernel->getSelectorName(selectorId);
 		if (methodName == "save") {
 			if (_gameId != GID_FAIRYTALES) {	// Fairy Tales saves automatically without a dialog
-				if (kernelIdSave != kernelIdRestore)
+#ifdef ENABLE_SCI32
+				if (getSciVersion() >= SCI_VERSION_2) {
+					patchGameSaveRestoreCodeSci2(segMan, gameSuperObject->getFunction(methodNr), kernelIdSave, false);
+				} else
+#endif
 					patchGameSaveRestoreCode(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave);
-				else
-					patchGameSaveRestoreCodeSci21(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave, false);
 			}
 			break;
 		}


Commit: c8516ff9ebc7480c2303b4ab2c151b46444e1911
    https://github.com/scummvm/scummvm/commit/c8516ff9ebc7480c2303b4ab2c151b46444e1911
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Add handling for save catalogues

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



diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 50560be..cfb829b 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -229,6 +229,45 @@ reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) {
 
 // ---- FileIO operations -----------------------------------------------------
 
+#ifdef ENABLE_SCI32
+static bool isSaveCatalogue(const Common::String &name) {
+	return name == "autosave.cat" || name.hasSuffix("sg.cat");
+}
+
+// SCI32 save game scripts check for, and write directly to, the save game
+// catalogue. Since ScummVM does not use these catalogues, when looking for a
+// catalogue, we instead check for save games within ScummVM that are logically
+// equivalent to the behaviour of SSCI.
+static bool saveCatalogueExists(const Common::String &name) {
+	bool exists = false;
+	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+
+	// There will always be one save game in some games, the "new game"
+	// game, which should be ignored when deciding if there are any save
+	// games available
+	uint numPermanentSaves;
+	switch (g_sci->getGameId()) {
+	case GID_TORIN:
+	case GID_LSL7:
+	case GID_LIGHTHOUSE:
+		numPermanentSaves = 1;
+		break;
+	default:
+		numPermanentSaves = 0;
+		break;
+	}
+
+	// Torin uses autosave.cat; LSL7 uses autosvsg.cat
+	if (name == "autosave.cat" || name == "autosvsg.cat") {
+		exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty();
+	} else {
+		exists = saveFileMan->listSavefiles(g_sci->getSavegamePattern()).size() > numPermanentSaves;
+	}
+
+	return exists;
+}
+#endif
+
 reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
 	if (!s)
 		return make_reg(0, getSciVersion());
@@ -264,30 +303,11 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 		return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
 	}
 
-	// Torin's autosave system checks for the presence of autosave.cat
-	// by opening it. Since we don't use .cat files, we instead check
-	// for the autosave game.
-	//
-	// Similar logic is needed for torinsg.cat - this shows the "Open..." button
-	// when continuing a game if it exists.
-	//
-	// TODO: Other games with autosave built in should be included here
-	if (g_sci->getGameId() == GID_TORIN && (name == "autosave.cat" || name == "torinsg.cat")) {
-		Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
-
-		bool exists;
-		if (name == "autosave.cat") {
-			exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty();
-		} else {
-			// There will always be one save game in Torin, the "new game" game,
-			// which should be ignored when deciding if there are any save games
-			// to open
-			exists = saveFileMan->listSavefiles(g_sci->getSavegamePattern()).size() > 1;
-		}
-
+	if (isSaveCatalogue(name)) {
+		const bool exists = saveCatalogueExists(name);
 		if (exists) {
-			// Dummy handle. Torin only checks if this is SIGNAL_REG,
-			// and calls kFileIOClose on it.
+			// Dummy handle is used to represent the catalogue and ignore any
+			// direct game script writes
 			return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
 		} else {
 			return SIGNAL_REG;
@@ -614,6 +634,12 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
 			return NULL_REG;
 	}
 
+#ifdef ENABLE_SCI32
+	if (isSaveCatalogue(name)) {
+		return saveCatalogueExists(name) ? TRUE_REG : NULL_REG;
+	}
+#endif
+
 	// TODO: It may apparently be worth caching the existence of
 	// phantsg.dir, and possibly even keeping it open persistently
 
@@ -776,13 +802,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
 		return NULL_REG;
 	}
 
-	// Torin has two sets of saves: autosave.### and torinsg.###, both with
-	// their own slots and .cat file.
-	// The autosave system uses autosave.000 and autosave.001.
-	// It also checks the presence of autosave.cat to determine if it should
-	// show the chapter selection menu on startup. (See kFileIOOpen.)
-	bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave";
-
 	if (argv[0].isNull()) {
 		// Direct call, from a patched Game::save
 		if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull()))
@@ -801,14 +820,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
 		g_sci->_soundCmd->pauseAll(false); // unpause music (we can't have it paused during save)
 		if (savegameId < 0)
 			return NULL_REG;
-
-	} else if (torinAutosave) {
-		if (argv[2].isNull())
-			error("kSaveGame: called with description being NULL");
-		game_description = s->_segMan->getString(argv[2]);
-		savegameId = virtualId;
-
-		debug(3, "kSaveGame(%s,%d,%s,%s) [Torin autosave]", game_id.c_str(), virtualId, game_description.c_str(), version.c_str());
 	} else {
 		// Real call from script
 		if (argv[2].isNull())
@@ -884,10 +895,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
 	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
 	Common::OutSaveFile *out;
 
-	if (torinAutosave) {
-		filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId));
-	}
-
 	out = saveFileMan->openForSaving(filename);
 	if (!out) {
 		warning("Error opening savegame \"%s\" for writing", filename.c_str());
@@ -916,10 +923,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
 
 	debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId);
 
-
-	// See comment in kSaveGame
-	bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave";
-
 	if (argv[0].isNull()) {
 		// Direct call, either from launcher or from a patched Game::restore
 		if (savegameId == -1) {
@@ -935,7 +938,7 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
 			pausedMusic = true;
 		}
 		// don't adjust ID of the saved game, it's already correct
-	} else if (!torinAutosave) {
+	} else {
 		if (g_sci->getGameId() == GID_JONES) {
 			// Jones has one save slot only
 			savegameId = 0;
@@ -952,9 +955,8 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
 	s->r_acc = NULL_REG; // signals success
 
 	Common::Array<SavegameDesc> saves;
-	if (!torinAutosave)
-		listSavegames(saves);
-	if (!torinAutosave && findSavegame(saves, savegameId) == -1) {
+	listSavegames(saves);
+	if (findSavegame(saves, savegameId) == -1) {
 		s->r_acc = TRUE_REG;
 		warning("Savegame ID %d not found", savegameId);
 	} else {
@@ -962,10 +964,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
 		Common::String filename = g_sci->getSavegameName(savegameId);
 		Common::SeekableReadStream *in;
 
-		if (torinAutosave) {
-			filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId));
-		}
-
 		in = saveFileMan->openForLoading(filename);
 		if (in) {
 			// found a savegame file


Commit: b3cfe699f6b3cd9590a4f3b1e5ec2ef656b86dbb
    https://github.com/scummvm/scummvm/commit/b3cfe699f6b3cd9590a4f3b1e5ec2ef656b86dbb
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix read overflow when there are no save games

SCI system scripts will always try to read once from the game IDs
array even if the number of games is zero.

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



diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index cfb829b..b6fbd45 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -1250,7 +1250,7 @@ reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
 	// Normally SSCI limits to 20 games per directory, but ScummVM allows more
 	// than that
 	descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1, true);
-	saveIds.resize(saves.size(), true);
+	saveIds.resize(saves.size() + 1, true);
 
 	for (uint i = 0; i < saves.size(); ++i) {
 		const SavegameDesc &save = saves[i];
@@ -1260,6 +1260,7 @@ reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
 	}
 
 	descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0';
+	saveIds.int16At(saves.size()) = 0;
 
 	return make_reg(0, saves.size());
 }


Commit: 0e53c8e9f3d54347b27e4628fcc1c15331f749d4
    https://github.com/scummvm/scummvm/commit/0e53c8e9f3d54347b27e4628fcc1c15331f749d4
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Improve MGDX support

Audio directory switching is temporarily disabled because it
causes use-after-free in the resource manager.

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



diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 6669a14..6297d48 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -325,8 +325,8 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
 	{ GID_MOTHERGOOSE256, -1,   992,  0,             "AIPath", "init",                            NULL,     0, { WORKAROUND_FAKE,   0 } }, // Happens in the demo and full version. In the demo, it happens when walking two screens from mother goose's house to the north. In the full version, it happens in rooms 7 and 23 - bug #5269
 	{ GID_MOTHERGOOSE256, 90,    90,  0,        "introScript", "changeState",                     NULL,    65, { WORKAROUND_FAKE,   0 } }, // SCI1(CD): At the very end, after the game is completed and restarted - bug #5626
 	{ GID_MOTHERGOOSE256, 94,    94,  0,            "sunrise", "changeState",                     NULL,   367, { WORKAROUND_FAKE,   0 } }, // At the very end, after the game is completed - bug #5294
-	{ GID_MOTHERGOOSEHIRES,-1,64950,  1,            "Feature", "handleEvent",                     NULL,     0, { WORKAROUND_FAKE,   0 } }, // right when clicking on a child at the start and probably also later
-	{ GID_MOTHERGOOSEHIRES,-1,64950,  1,               "View", "handleEvent",                     NULL,     0, { WORKAROUND_FAKE,   0 } }, // see above
+	{ GID_MOTHERGOOSEHIRES,-1,64950, -1,            "Feature", "handleEvent",                     NULL,     0, { WORKAROUND_FAKE,   0 } }, // right when clicking on a child at the start and probably also later
+	{ GID_MOTHERGOOSEHIRES,-1,64950, -1,               "View", "handleEvent",                     NULL,     0, { WORKAROUND_FAKE,   0 } }, // see above
 	{ GID_PEPPER,         -1,   894,  0,            "Package", "doVerb",                          NULL,     3, { WORKAROUND_FAKE,   0 } }, // using the hand on the book in the inventory - bug #5154
 	{ GID_PEPPER,        150,   928,  0,           "Narrator", "startText",                       NULL,     0, { WORKAROUND_FAKE,   0 } }, // happens during the non-interactive demo of Pepper
 	{ GID_PQ4,            -1,    25,  0,         "iconToggle", "select",                          NULL,     1, { WORKAROUND_FAKE,   0 } }, // when toggling the icon bar to auto-hide or not
@@ -711,7 +711,7 @@ const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = {
 //    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
 const SciWorkaroundEntry kSetCursor_workarounds[] = {
 	{ GID_KQ5,            -1,   768,  0,           "KQCursor", "init",                      NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters
-	{ GID_MOTHERGOOSEHIRES,0,     0, -1,                 "MG", "setCursor",                 NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number
+	{ GID_MOTHERGOOSEHIRES,-1,    0, -1,                 "MG", "setCursor",                 NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number
 	SCI_WORKAROUNDENTRY_TERMINATOR
 };
 
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index cbc4a02..4b3a13c 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -965,6 +965,10 @@ bool ResourceManager::addAudioSources() {
 }
 
 void ResourceManager::changeAudioDirectory(Common::String path) {
+	// TODO: This implementation is broken.
+	return;
+
+#if 0
 	// Remove all of the audio map resource sources, as well as the audio resource sources
 	for (Common::List<ResourceSource *>::iterator it = _sources.begin(); it != _sources.end();) {
 		ResourceSource *source = *it;
@@ -1007,6 +1011,7 @@ void ResourceManager::changeAudioDirectory(Common::String path) {
 
 	// Rescan the newly added resources
 	scanNewSources();
+#endif
 }
 
 } // End of namespace Sci


Commit: 4072714143a3b532ac22c73022b931e17af4d5d9
    https://github.com/scummvm/scummvm/commit/4072714143a3b532ac22c73022b931e17af4d5d9
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Increase allowed number of save games in native save/load

This is applied only to games where the standard save/load
dialogue is used; games like KQ7 and Phantasmagoria, which use a
non-standard UI for save games, are still limited to the number of
saves originally supported.

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



diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 61af8a9..9412998 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -141,6 +141,59 @@ enum ScriptPatcherSelectors {
 #endif
 };
 
+#ifdef ENABLE_SCI32
+// Save game script hardcodes the maximum number of save games to 20, but
+// this is an artificial constraint that does not apply to ScummVM
+static const uint16 sci2NumSavesSignature1[] = {
+	SIG_MAGICDWORD,
+	0x8b, 0x02,                    // lsl local[2]
+	0x35, 0x14,                    // ldi 20
+	0x22,                          // lt?
+	SIG_END
+};
+
+static const uint16 sci2NumSavesPatch1[] = {
+	PATCH_ADDTOOFFSET(+2),         // lsl local[2]
+	0x35, 0x63,                    // ldi 99
+	PATCH_END
+};
+
+static const uint16 sci2NumSavesSignature2[] = {
+	SIG_MAGICDWORD,
+	0x8b, 0x02,                    // lsl local[2]
+	0x35, 0x14,                    // ldi 20
+	0x1a,                          // eq?
+	SIG_END
+};
+
+static const uint16 sci2NumSavesPatch2[] = {
+	PATCH_ADDTOOFFSET(+2),         // lsl local[2]
+	0x35, 0x63,                    // ldi 99
+	PATCH_END
+};
+
+// Phantasmagoria & SQ6 try to initialize the first entry of an int16 array
+// using an empty string, which is not valid (it should be a number)
+static const uint16 sci21IntArraySignature[] = {
+	0x38, SIG_SELECTOR16(newWith), // pushi newWith
+	0x7a,                          // push2
+	0x39, 0x04,                    // pushi $4
+	0x72, SIG_ADDTOOFFSET(+2),     // lofsa string ""
+	SIG_MAGICDWORD,
+	0x36,                          // push
+	0x51, 0x0b,                    // class IntArray
+	0x4a, 0x8,                     // send $8
+	SIG_END
+};
+
+static const uint16 sci21IntArrayPatch[] = {
+	PATCH_ADDTOOFFSET(+6),      // push $b9; push2; pushi $4
+	0x76,                       // push0
+	0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes)
+	PATCH_END
+};
+#endif
+
 // ===========================================================================
 // Conquests of Camelot
 // At the bazaar in Jerusalem, it's possible to see a girl taking a shower.
@@ -621,6 +674,10 @@ static const SciScriptPatcherEntry freddypharkasSignatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Gabriel Knight 1
+
 // ===========================================================================
 // daySixBeignet::changeState (4) is called when the cop goes out and sets cycles to 220.
 //  this is not enough time to get to the door, so we patch that to 23 seconds
@@ -925,9 +982,23 @@ static const SciScriptPatcherEntry gk1Signatures[] = {
 	{  true,   230, "day 6 police beignet timer issue",            1, gk1SignatureDay6PoliceBeignet,    gk1PatchDay6PoliceBeignet },
 	{  true,   230, "day 6 police sleep timer issue",              1, gk1SignatureDay6PoliceSleep,      gk1PatchDay6PoliceSleep },
 	{  true,   808, "day 10 gabriel dress up infinite turning",    1, gk1SignatureDay10GabrielDressUp,  gk1PatchDay10GabrielDressUp },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#pragma mark -
+#pragma mark Gabriel Knight 2
+
+//          script, description,                                      signature                         patch
+static const SciScriptPatcherEntry gk2Signatures[] = {
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#endif
+
 // ===========================================================================
 // at least during harpy scene export 29 of script 0 is called in kq5cd and
 //  has an issue for those calls, where temp 3 won't get inititialized, but
@@ -2086,6 +2157,19 @@ static const SciScriptPatcherEntry larry6Signatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Leisure Suit Larry 6 Hires
+
+//          script, description,                                      signature                         patch
+static const SciScriptPatcherEntry larry6HiresSignatures[] = {
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
 // ===========================================================================
 // Laura Bow 1 - Colonel's Bequest
 //
@@ -2708,27 +2792,6 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = {
 };
 
 #ifdef ENABLE_SCI32
-// Phantasmagoria & SQ6 try to initialize the first entry of an int16 array
-// using an empty string, which is not valid (it should be a number)
-static const uint16 sci21IntArraySignature[] = {
-	0x38, SIG_SELECTOR16(newWith), // pushi newWith
-	0x7a,                          // push2
-	0x39, 0x04,                    // pushi $4
-	0x72, SIG_ADDTOOFFSET(+2),     // lofsa string ""
-	SIG_MAGICDWORD,
-	0x36,                          // push
-	0x51, 0x0b,                    // class IntArray
-	0x4a, 0x8,                     // send $8
-	SIG_END
-};
-
-static const uint16 sci21IntArrayPatch[] = {
-	PATCH_ADDTOOFFSET(+6),      // push $b9; push2; pushi $4
-	0x76,                       // push0
-	0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes)
-	PATCH_END
-};
-
 #pragma mark -
 #pragma mark Phantasmagoria
 
@@ -2738,7 +2801,6 @@ static const SciScriptPatcherEntry phantasmagoriaSignatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
-#pragma mark -
 #endif
 
 // ===========================================================================
@@ -2882,6 +2944,19 @@ static const SciScriptPatcherEntry pq1vgaSignatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Police Quest 4
+
+//          script, description,                                      signature                         patch
+static const SciScriptPatcherEntry pq4Signatures[] = {
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
 // ===========================================================================
 //  At the healer's house there is a bird's nest up on the tree.
 //   The player can throw rocks at it until it falls to the ground.
@@ -3849,6 +3924,19 @@ static const SciScriptPatcherEntry qfg3Signatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#ifdef ENABLE_SCI32
+#pragma mark -
+#pragma mark Quest for Glory 4
+
+//          script, description,                                      signature                         patch
+static const SciScriptPatcherEntry qfg4Signatures[] = {
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	SCI_SIGNATUREENTRY_TERMINATOR
+};
+
+#endif
+
 // ===========================================================================
 //  script 298 of sq4/floppy has an issue. object "nest" uses another property
 //   which isn't included in property count. We return 0 in that case, the game
@@ -4422,10 +4510,21 @@ static const SciScriptPatcherEntry sq6Signatures[] = {
 	{  true,    22, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
 	{  true,   460, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
 	{  true,   510, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,          sci2NumSavesPatch1 },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,          sci2NumSavesPatch2 },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
 #pragma mark -
+#pragma mark Torin's Passage
+
+//          script, description,                                      signature                         patch
+static const SciScriptPatcherEntry torinSignatures[] = {
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
+	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	SCI_SIGNATUREENTRY_TERMINATOR
+};
+
 #endif
 
 // =================================================================================
@@ -4859,18 +4958,25 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
 	case GID_FREDDYPHARKAS:
 		signatureTable = freddypharkasSignatures;
 		break;
+#ifdef ENABLE_SCI32
 	case GID_GK1:
 		signatureTable = gk1Signatures;
 		break;
+	case GID_GK2:
+		signatureTable = gk2Signatures;
+		break;
+#endif
 	case GID_KQ5:
 		signatureTable = kq5Signatures;
 		break;
 	case GID_KQ6:
 		signatureTable = kq6Signatures;
 		break;
+#ifdef ENABLE_SCI32
 	case GID_KQ7:
 		signatureTable = kq7Signatures;
 		break;
+#endif
 	case GID_LAURABOW:
 		signatureTable = laurabow1Signatures;
 		break;
@@ -4889,6 +4995,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
 	case GID_LSL6:
 		signatureTable = larry6Signatures;
 		break;
+#ifdef ENABLE_SCI32
+	case GID_LSL6HIRES:
+		signatureTable = larry6HiresSignatures;
+		break;
+#endif
 	case GID_MOTHERGOOSE256:
 		signatureTable = mothergoose256Signatures;
 		break;
@@ -4900,6 +5011,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
 	case GID_PQ1:
 		signatureTable = pq1vgaSignatures;
 		break;
+#ifdef ENABLE_SCI32
+	case GID_PQ4:
+		signatureTable = pq4Signatures;
+		break;
+#endif
 	case GID_QFG1:
 		signatureTable = qfg1egaSignatures;
 		break;
@@ -4912,6 +5028,11 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
 	case GID_QFG3:
 		signatureTable = qfg3Signatures;
 		break;
+#ifdef ENABLE_SCI32
+	case GID_QFG4:
+		signatureTable = qfg4Signatures;
+		break;
+#endif
 	case GID_SQ1:
 		signatureTable = sq1vgaSignatures;
 		break;
@@ -4924,6 +5045,10 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3
 #ifdef ENABLE_SCI32
 	case GID_SQ6:
 		signatureTable = sq6Signatures;
+		break;
+	case GID_TORIN:
+		signatureTable = torinSignatures;
+		break;
 #endif
 	default:
 		break;


Commit: 25c874b9cd370cab26ebef6e8e30786e39823c21
    https://github.com/scummvm/scummvm/commit/25c874b9cd370cab26ebef6e8e30786e39823c21
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Add more debugger support for dumping SCI32 arrays

Changed paths:
    engines/sci/console.cpp



diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 6d04b82..32db2e9 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -2802,10 +2802,49 @@ bool Console::cmdViewReference(int argc, const char **argv) {
 			switch (_engine->_gamestate->_segMan->getSegmentType(reg.getSegment())) {
 #ifdef ENABLE_SCI32
 				case SEG_TYPE_ARRAY: {
-					debugPrintf("SCI32 array:\n");
-					// TODO: Different prints for different types
 					const SciArray *array = _engine->_gamestate->_segMan->lookupArray(reg);
-					hexDumpReg((reg_t *)array->getRawData(), array->size(), 4, 0, true);
+					const char *arrayType;
+					switch (array->getType()) {
+						case kArrayTypeID:
+							arrayType = "reg_t";
+							break;
+						case kArrayTypeByte:
+							arrayType = "byte";
+							break;
+						case kArrayTypeInt16:
+							arrayType = "int16";
+							break;
+						case kArrayTypeString:
+							arrayType = "string";
+							break;
+						default:
+							arrayType = "invalid";
+							break;
+					}
+					debugPrintf("SCI32 %s array (%u entries):\n", arrayType, array->size());
+					switch (array->getType()) {
+					case kArrayTypeID:
+						hexDumpReg((reg_t *)array->getRawData(), array->size(), 4, 0, true);
+						break;
+					case kArrayTypeByte:
+					case kArrayTypeString: {
+						Common::hexdump((byte *)array->getRawData(), array->size(), 16, 0);
+						break;
+					}
+					case kArrayTypeInt16: {
+						const int16 *data = (const int16 *)array->getRawData();
+						for (int i = 0; i < array->size(); ++i) {
+							debugN("% 6d ", *data++);
+							if ((i % 8) == 0) {
+								debugN("\n");
+							}
+						}
+						break;
+					}
+					default:
+						break;
+					}
+
 					break;
 				}
 				case SEG_TYPE_BITMAP: {


Commit: 2629269212b9a5946e11cadf6abead7856b5fe58
    https://github.com/scummvm/scummvm/commit/2629269212b9a5946e11cadf6abead7856b5fe58
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Emulate Shivers 1 game score metadata

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



diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 74cd812..efeae63a 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -335,6 +335,7 @@ bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc) {
 	desc->time = meta.saveTime;
 	desc->version = meta.version;
 	desc->gameVersion = meta.gameVersion;
+	desc->score = meta.score;
 
 	if (meta.name.lastChar() == '\n')
 		meta.name.deleteLastChar();
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index 88d483b..27250d3 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -59,6 +59,10 @@ struct SavegameDesc {
 	int version;
 	char name[SCI_MAX_SAVENAME_LENGTH];
 	Common::String gameVersion;
+#ifdef ENABLE_SCI32
+	// Used by Shivers 1
+	uint32 score;
+#endif
 };
 
 class FileHandle {
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index b6fbd45..33ddd1f 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -328,50 +328,42 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 	}
 
 #ifdef ENABLE_SCI32
-	// Shivers is trying to store savegame descriptions and current spots in
-	// separate .SG files, which are hardcoded in the scripts.
-	// Essentially, there is a normal save file, created by the executable
-	// and an extra hardcoded save file, created by the game scripts, probably
-	// because they didn't want to modify the save/load code to add the extra
-	// information.
-	// Each slot in the book then has two strings, the save description and a
-	// description of the current spot that the player is at.
-	// For now, we don't allow the creation of these files, which means that
-	// all the spot descriptions next to each slot description will be empty.
-	// Until a viable solution is found to handle these
-	// extra files and until the spot description strings are initialized
-	// correctly, we resort to virtual files in order to make the load screen
-	// useable. Without this code it is unusable, as the extra information is
-	// always saved to 0.SG for some reason, but on restore the correct file is
-	// used. Perhaps the virtual ID is not taken into account when saving.
-	//
-	// Future TODO: maintain spot descriptions and show them too, ideally without
-	// having to return to this logic of extra hardcoded files.
+	// Shivers stores the name and score of save games in separate %d.SG files,
+	// which are used by the save/load screen
 	if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) {
 		if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
-			// Game scripts are trying to create a file with the save
-			// description, stop them here
+			// Suppress creation of the SG file, since it is not necessary
 			debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
 			return SIGNAL_REG;
 		} else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
 			// Create a virtual file containing the save game description
 			// and slot number, as the game scripts expect.
-			int slotNumber;
-			sscanf(name.c_str(), "%d.SG", &slotNumber);
-
-			Common::Array<SavegameDesc> saves;
-			listSavegames(saves);
-			int savegameNr = findSavegame(saves, slotNumber);
-			assert(savegameNr >= 0);
+			int saveNo;
+			sscanf(name.c_str(), "%d.SG", &saveNo);
+
+			SavegameDesc save;
+			fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
+			Common::String score;
+			const uint16 lowScore = save.score & 0xFFFF;
+			const uint16 highScore = save.score >> 16;
+
+			if (!highScore) {
+				score = Common::String::format("%u", lowScore);
+			} else {
+				score = Common::String::format("%u%03u", highScore, lowScore);
+			}
 
-			int size = strlen(saves[savegameNr].name) + 2;
-			char *buf = (char *)malloc(size);
-			strcpy(buf, saves[savegameNr].name);
-			buf[size - 1] = 0; // Spot description (empty)
+			const uint nameLength = strlen(save.name);
+			const uint size = nameLength + /* \r\n */ 2 + score.size();
+			char *buffer = (char *)malloc(size);
+			memcpy(buffer, save.name, nameLength);
+			buffer[nameLength] = '\r';
+			buffer[nameLength + 1] = '\n';
+			memcpy(buffer + nameLength + 2, score.c_str(), score.size());
 
-			uint handle = findFreeFileHandle(s);
+			const uint handle = findFreeFileHandle(s);
 
-			s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buf, size, DisposeAfterUse::YES);
+			s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES);
 			s->_fileHandles[handle]._out = nullptr;
 			s->_fileHandles[handle]._name = "";
 
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 5a73526..74493fc 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -330,6 +330,17 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj)
 		}
 		s.syncAsUint32LE(obj.playTime);
 	}
+
+	if (s.getVersion() >= 38) {
+		if (s.isSaving()) {
+			obj.score = g_sci->getEngineState()->variables[VAR_GLOBAL][kScore].toUint16();
+			if (g_sci->getGameId() == GID_SHIVERS) {
+				obj.score |= g_sci->getEngineState()->variables[VAR_GLOBAL][kShivers1Score].toUint16() << 16;
+			}
+		}
+
+		s.syncAsUint32LE(obj.score);
+	}
 }
 
 void EngineState::saveLoadWithSerializer(Common::Serializer &s) {
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 5995de7..01f92ed 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,7 +37,7 @@ struct EngineState;
  *
  * Version - new/changed feature
  * =============================
- *      38 - SCI32 cursor, accurate SCI32 arrays/strings
+ *      38 - SCI32 cursor, accurate SCI32 arrays/strings, score metadata
  *      37 - Segment entry data changed to pointers
  *      36 - SCI32 bitmap segment
  *      35 - SCI32 remap
@@ -77,6 +77,10 @@ struct SavegameMetadata {
 	uint32 playTime;
 	uint16 gameObjectOffset;
 	uint16 script0Size;
+#ifdef ENABLE_SCI32
+	// Used by Shivers 1
+	uint32 score;
+#endif
 };
 
 /**
diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h
index e127706..3143355 100644
--- a/engines/sci/engine/vm.h
+++ b/engines/sci/engine/vm.h
@@ -147,8 +147,10 @@ enum GlobalVar {
 	kCurrentRoomNo  = 11,
 	kPreviousRoomNo = 12,
 	kNewRoomNo      = 13,
+	kScore          = 15,
 	kFastCast       = 84, // SCI16
-	kMessageType    = 90
+	kMessageType    = 90,
+	kShivers1Score  = 349
 };
 
 /** Number of kernel calls in between gcs; should be < 50000 */


Commit: fba85684845f83cab3b51e8fc22b11d59ed9cd28
    https://github.com/scummvm/scummvm/commit/fba85684845f83cab3b51e8fc22b11d59ed9cd28
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix multiple bugs in kSave

1. Shift save numbers up/down for game scripts that rely on save
   game numbers starting from 0 to work correctly
2. Add fake file operations to support KQ7 save games
3. Hide autosave games from native save/load list to match SSCI.

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



diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index efeae63a..be3fb33 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -355,7 +355,7 @@ void listSavegames(Common::Array<SavegameDesc> &saves) {
 
 #ifdef ENABLE_SCI32
 		const int id = strtol(filename.end() - 3, NULL, 10);
-		if (id == kNewGameId) {
+		if (id == kNewGameId || id == kAutoSaveId) {
 			continue;
 		}
 #endif
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index 27250d3..b960058 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -41,8 +41,13 @@ enum {
 
 #ifdef ENABLE_SCI32
 enum {
-	kAutoSaveId = 0, ///< The save game slot number for autosaves
-	kNewGameId = 999 ///< The save game slot number for a "new game" save
+	kAutoSaveId = 0,  ///< The save game slot number for autosaves
+	kNewGameId = 999, ///< The save game slot number for a "new game" save
+
+	// SCI engine expects game IDs to start at 0, but slot 0 in ScummVM is
+	// reserved for autosave, so non-autosave games get their IDs shifted up
+	// when saving or restoring, and shifted down when enumerating save games
+	kSaveIdShift = 1
 };
 #endif
 
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 33ddd1f..0980f3c 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -277,7 +277,11 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
 reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 	Common::String name = s->_segMan->getString(argv[0]);
 
-	assert(name != "");
+	if (name.empty()) {
+		// Happens many times during KQ1 (e.g. when typing something)
+		debugC(kDebugLevelFile, "Attempted to open a file with an empty filename");
+		return SIGNAL_REG;
+	}
 
 	int mode = argv[1].toUint16();
 	bool unwrapFilename = true;
@@ -298,36 +302,12 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 	}
 
 #ifdef ENABLE_SCI32
-	// See kMakeSaveCatName
-	if (name == "fake.cat") {
-		return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
-	}
-
-	if (isSaveCatalogue(name)) {
-		const bool exists = saveCatalogueExists(name);
-		if (exists) {
-			// Dummy handle is used to represent the catalogue and ignore any
-			// direct game script writes
-			return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
-		} else {
-			return SIGNAL_REG;
-		}
-	}
-#endif
-
-	if (name.empty()) {
-		// Happens many times during KQ1 (e.g. when typing something)
-		debugC(kDebugLevelFile, "Attempted to open a file with an empty filename");
-		return SIGNAL_REG;
-	}
-	debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode);
-
-	if (name.hasPrefix("sciAudio\\")) {
-		// fan-made sciAudio extension, don't create those files and instead return a virtual handle
-		return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO);
+	// GK1, GK2, KQ7, LSL6hires, Phant1, PQ4, PQ:SWAT, and SQ6 read in
+	// their game version from the VERSION file
+	if (name.compareToIgnoreCase("version") == 0) {
+		unwrapFilename = false;
 	}
 
-#ifdef ENABLE_SCI32
 	// Shivers stores the name and score of save games in separate %d.SG files,
 	// which are used by the save/load screen
 	if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) {
@@ -340,6 +320,7 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 			// and slot number, as the game scripts expect.
 			int saveNo;
 			sscanf(name.c_str(), "%d.SG", &saveNo);
+			saveNo += kSaveIdShift;
 
 			SavegameDesc save;
 			fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
@@ -370,8 +351,73 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 			return make_reg(0, handle);
 		}
 	}
+
+	if (g_sci->getGameId() == GID_KQ7) {
+		// KQ7 creates a temp.tmp file to perform an atomic rewrite of the
+		// catalogue, but since we do not create catalogues for most SCI32
+		// games, ignore the write
+		if (name == "temp.tmp") {
+			return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+		}
+
+		// KQ7 tries to read out game information from catalogues directly
+		// instead of using the standard kSaveGetFiles function
+		if (name == "kq7cdsg.cat") {
+			if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
+				// Suppress creation of the catalogue file, since it is not necessary
+				debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
+				return SIGNAL_REG;
+			} else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+				Common::Array<SavegameDesc> saves;
+				listSavegames(saves);
+
+				const uint recordSize = sizeof(int16) + SCI_MAX_SAVENAME_LENGTH;
+				const uint numSaves = MIN<uint>(saves.size(), 10);
+				const uint size = numSaves * recordSize + /* terminator */ 2;
+				byte *const buffer = (byte *)malloc(size);
+
+				byte *out = buffer;
+				for (uint i = 0; i < numSaves; ++i) {
+					WRITE_UINT16(out, saves[i].id);
+					Common::strlcpy((char *)out + sizeof(int16), saves[i].name, SCI_MAX_SAVENAME_LENGTH);
+					out += recordSize;
+				}
+				WRITE_UINT16(out, 0xFFFF);
+
+				const uint handle = findFreeFileHandle(s);
+				s->_fileHandles[handle]._in = new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES);
+				s->_fileHandles[handle]._out = nullptr;
+				s->_fileHandles[handle]._name = "";
+
+				return make_reg(0, handle);
+			}
+		}
+	}
+
+	// See kMakeSaveCatName
+	if (name == "fake.cat") {
+		return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+	}
+
+	if (isSaveCatalogue(name)) {
+		const bool exists = saveCatalogueExists(name);
+		if (exists) {
+			// Dummy handle is used to represent the catalogue and ignore any
+			// direct game script writes
+			return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+		} else {
+			return SIGNAL_REG;
+		}
+	}
 #endif
 
+	debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode);
+
+	if (name.hasPrefix("sciAudio\\")) {
+		// fan-made sciAudio extension, don't create those files and instead return a virtual handle
+		return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO);
+	}
+
 	// QFG import rooms get a virtual filelisting instead of an actual one
 	if (g_sci->inQfGImportRoom()) {
 		// We need to find out what the user actually selected, "savedHeroes" is
@@ -1066,13 +1112,15 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
 }
 
 #ifdef ENABLE_SCI32
+
 reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
+	const bool isScummVMSave = argv[0].isNull();
 	Common::String gameName = "";
 	int16 saveNo;
 	Common::String saveDescription;
 	Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]);
 
-	if (argv[0].isNull()) {
+	if (isScummVMSave) {
 		// ScummVM call, from a patched Game::save
 		g_sci->_soundCmd->pauseAll(true);
 		GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
@@ -1080,6 +1128,7 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 		g_sci->_soundCmd->pauseAll(false);
 
 		if (saveNo < 0) {
+			// User cancelled save
 			return NULL_REG;
 		}
 
@@ -1094,6 +1143,8 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 		saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
 	}
 
+	debugC(kDebugLevelFile, "Game name %s save %d desc %s ver %s", gameName.c_str(), saveNo, saveDescription.c_str(), gameVersion.c_str());
+
 	// Auto-save system used by Torin and LSL7
 	if (gameName == "Autosave" || gameName == "Autosv") {
 		if (saveNo == 0) {
@@ -1102,35 +1153,10 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 			// Autosave slot 1 is a "new game" save
 			saveNo = kNewGameId;
 		}
-	} else if (saveNo == 0) {
-		// SSCI save games normally start from save number 0, but this is
-		// reserved for the autosave game in ScummVM. So, any time a game tries
-		// to save to slot 0 (and it isn't an autosave), it should instead go to
-		// the next highest free ID
-		Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
-		Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
-		Common::sort(saveNames.begin(), saveNames.end());
-		if (saveNames.size()) {
-			int lastId = 0;
-			for (int i = 0; i < (int)saveNames.size(); ++i) {
-				const int id = strtol(saveNames[i].end() - 3, NULL, 10);
-				if (id == 0) {
-					continue;
-				}
-
-				if (id != lastId + 1) {
-					saveNo = lastId + 1;
-					break;
-				}
-
-				++lastId;
-			}
-		}
-
-		// There was no gap, so this save goes to a brand new slot
-		if (saveNo == 0) {
-			saveNo = saveNames.size() + 1;
-		}
+	} else if (!isScummVMSave) {
+		// ScummVM save screen will give a pre-corrected save number, but native
+		// save-load will not
+		saveNo += kSaveIdShift;
 	}
 
 	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
@@ -1161,17 +1187,20 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
+	const bool isScummVMRestore = argv[0].isNull();
 	Common::String gameName = "";
 	int16 saveNo = argv[1].toSint16();
 	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
 
-	if (argv[0].isNull() && saveNo == -1) {
+	if (isScummVMRestore && saveNo == -1) {
 		// ScummVM call, either from lancher or a patched Game::restore
 		g_sci->_soundCmd->pauseAll(true);
 		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
 		saveNo = dialog.runModalWithCurrentTarget();
 		g_sci->_soundCmd->pauseAll(false);
+
 		if (saveNo < 0) {
+			// User cancelled restore
 			return s->r_acc;
 		}
 	} else {
@@ -1185,6 +1214,10 @@ reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
 			// Autosave slot 1 is a "new game" save
 			saveNo = kNewGameId;
 		}
+	} else if (!isScummVMRestore) {
+		// ScummVM save screen will give a pre-corrected save number, but native
+		// save-load will not
+		saveNo += kSaveIdShift;
 	}
 
 	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
@@ -1211,8 +1244,12 @@ reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv) {
 	Common::Array<SavegameDesc> saves;
 	listSavegames(saves);
 
-	if ((gameName == "Autosave" || gameName == "Autosv") && saveNo == 1) {
-		saveNo = kNewGameId;
+	if (gameName == "Autosave" || gameName == "Autosv") {
+		if (saveNo == 1) {
+			saveNo = kNewGameId;
+		}
+	} else {
+		saveNo += kSaveIdShift;
 	}
 
 	SavegameDesc save;
@@ -1240,7 +1277,7 @@ reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
 	listSavegames(saves);
 
 	// Normally SSCI limits to 20 games per directory, but ScummVM allows more
-	// than that
+	// than that with games that use the standard save-load dialogue
 	descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1, true);
 	saveIds.resize(saves.size() + 1, true);
 
@@ -1248,7 +1285,7 @@ reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
 		const SavegameDesc &save = saves[i];
 		char *target = &descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * i);
 		Common::strlcpy(target, save.name, SCI_MAX_SAVENAME_LENGTH);
-		saveIds.int16At(i) = save.id;
+		saveIds.int16At(i) = save.id - kSaveIdShift;
 	}
 
 	descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0';
@@ -1270,7 +1307,7 @@ reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
 reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
 	SciArray &outFileName = *s->_segMan->lookupArray(argv[0]);
 	// argv[1] is the game name, which is not used by ScummVM
-	int16 saveNo = argv[2].toSint16();
+	const int16 saveNo = argv[2].toSint16();
 	outFileName.fromString(g_sci->getSavegameName(saveNo));
 	return argv[0];
 }
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index f161569..0752d8f 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -590,11 +590,12 @@ void SciEngine::patchGameSaveRestore() {
 	case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all
 	case GID_HOYLE2: // gets confused, see hoyle1
 	case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required
+	case GID_KQ7: // has custom save/load code
 	case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
 	case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
 	case GID_PHANTASMAGORIA: // has custom save/load code
-	case GID_SHIVERS: // has custom save/load code
 	case GID_PQSWAT: // has custom save/load code
+	case GID_SHIVERS: // has custom save/load code
 		return;
 	default:
 		break;


Commit: aa720ca9cd035912a422ece64111d80346b4f477
    https://github.com/scummvm/scummvm/commit/aa720ca9cd035912a422ece64111d80346b4f477
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix deleting save games in KQ7

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



diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 0980f3c..596ff5e 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -378,7 +378,7 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 
 				byte *out = buffer;
 				for (uint i = 0; i < numSaves; ++i) {
-					WRITE_UINT16(out, saves[i].id);
+					WRITE_UINT16(out, saves[i].id - kSaveIdShift);
 					Common::strlcpy((char *)out + sizeof(int16), saves[i].name, SCI_MAX_SAVENAME_LENGTH);
 					out += recordSize;
 				}
@@ -542,6 +542,13 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
 		result = saveFileMan->removeSavefile(name);
 #ifdef ENABLE_SCI32
 	} else if (getSciVersion() >= SCI_VERSION_2) {
+		// Special case for KQ7, basically identical to the SQ4 case above,
+		// where the game hardcodes its save game names
+		if (name.hasPrefix("kq7cdsg.")) {
+			int saveNo = atoi(name.c_str() + name.size() - 3);
+			name = g_sci->getSavegameName(saveNo + kSaveIdShift);
+		}
+
 		// The file name may be already wrapped, so check both cases
 		result = saveFileMan->removeSavefile(name);
 		if (!result) {


Commit: 0c9d423497073f351e670431fe7680edcebfd67a
    https://github.com/scummvm/scummvm/commit/0c9d423497073f351e670431fe7680edcebfd67a
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Emulate MGDX ego view metadata

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



diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index be3fb33..717efcb 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -335,7 +335,11 @@ bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc) {
 	desc->time = meta.saveTime;
 	desc->version = meta.version;
 	desc->gameVersion = meta.gameVersion;
-	desc->score = meta.score;
+	if (g_sci->getGameId() == GID_SHIVERS) {
+		desc->score = meta.score;
+	} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+		desc->avatarId = meta.avatarId;
+	}
 
 	if (meta.name.lastChar() == '\n')
 		meta.name.deleteLastChar();
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index b960058..7d2461f 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -65,8 +65,12 @@ struct SavegameDesc {
 	char name[SCI_MAX_SAVENAME_LENGTH];
 	Common::String gameVersion;
 #ifdef ENABLE_SCI32
-	// Used by Shivers 1
-	uint32 score;
+	union {
+		// Used by Shivers 1
+		uint32 score;
+		// Used by MGDX
+		uint8 avatarId;
+	};
 #endif
 };
 
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 596ff5e..e983922 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -308,9 +308,9 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 		unwrapFilename = false;
 	}
 
-	// Shivers stores the name and score of save games in separate %d.SG files,
-	// which are used by the save/load screen
 	if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) {
+		// Shivers stores the name and score of save games in separate %d.SG
+		// files, which are used by the save/load screen
 		if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
 			// Suppress creation of the SG file, since it is not necessary
 			debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
@@ -350,9 +350,41 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 
 			return make_reg(0, handle);
 		}
-	}
+	} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES && name.hasSuffix(".DTA")) {
+		// MGDX stores the name and avatar ID in separate %d.DTA files, which
+		// are used by the save/load screen
+		if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
+			// Suppress creation of the DTA file, since it is not necessary
+			debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
+			return SIGNAL_REG;
+		} else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+			// Create a virtual file containing the save game description
+			// and slot number, as the game scripts expect.
+			int saveNo;
+			sscanf(name.c_str(), "%d.DTA", &saveNo);
+			saveNo += kSaveIdShift;
+
+			SavegameDesc save;
+			fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
+
+			const Common::String avatarId = Common::String::format("%02d", save.avatarId);
+			const uint nameLength = strlen(save.name);
+			const uint size = nameLength + /* \r\n */ 2 + avatarId.size() + 1;
+			char *buffer = (char *)malloc(size);
+			memcpy(buffer, save.name, nameLength);
+			buffer[nameLength] = '\r';
+			buffer[nameLength + 1] = '\n';
+			memcpy(buffer + nameLength + 2, avatarId.c_str(), avatarId.size() + 1);
+
+			const uint handle = findFreeFileHandle(s);
 
-	if (g_sci->getGameId() == GID_KQ7) {
+			s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES);
+			s->_fileHandles[handle]._out = nullptr;
+			s->_fileHandles[handle]._name = "";
+
+			return make_reg(0, handle);
+		}
+	} else if (g_sci->getGameId() == GID_KQ7) {
 		// KQ7 creates a temp.tmp file to perform an atomic rewrite of the
 		// catalogue, but since we do not create catalogues for most SCI32
 		// games, ignore the write
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 74493fc..aee11ef 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -331,11 +331,16 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj)
 		s.syncAsUint32LE(obj.playTime);
 	}
 
+	// Some games require additional metadata to display the load screen
+	// correctly
 	if (s.getVersion() >= 38) {
 		if (s.isSaving()) {
-			obj.score = g_sci->getEngineState()->variables[VAR_GLOBAL][kScore].toUint16();
+			const reg_t *globals = g_sci->getEngineState()->variables[VAR_GLOBAL];
 			if (g_sci->getGameId() == GID_SHIVERS) {
-				obj.score |= g_sci->getEngineState()->variables[VAR_GLOBAL][kShivers1Score].toUint16() << 16;
+				obj.score = globals[kScore].toUint16();
+				obj.score |= globals[kShivers1Score].toUint16() << 16;
+			} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+				obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kEgo], SELECTOR(view));
 			}
 		}
 
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 01f92ed..627fc64 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -78,8 +78,12 @@ struct SavegameMetadata {
 	uint16 gameObjectOffset;
 	uint16 script0Size;
 #ifdef ENABLE_SCI32
-	// Used by Shivers 1
-	uint32 score;
+	union {
+		// Used by Shivers 1
+		uint32 score;
+		// Used by MGDX
+		uint8 avatarId;
+	};
 #endif
 };
 
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 0752d8f..ce6e264 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -593,6 +593,7 @@ void SciEngine::patchGameSaveRestore() {
 	case GID_KQ7: // has custom save/load code
 	case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
 	case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
+	case GID_MOTHERGOOSEHIRES: // has custom save/load code
 	case GID_PHANTASMAGORIA: // has custom save/load code
 	case GID_PQSWAT: // has custom save/load code
 	case GID_SHIVERS: // has custom save/load code


Commit: c644f817e35db81ab6e9ec4c324e64fe04b8e740
    https://github.com/scummvm/scummvm/commit/c644f817e35db81ab6e9ec4c324e64fe04b8e740
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Add mark for KQ7 in script patches

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



diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 9412998..1bab106 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -1696,6 +1696,9 @@ static const SciScriptPatcherEntry kq6Signatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#pragma mark -
+#pragma mark King's Quest 7
+
 // ===========================================================================
 
 // King's Quest 7 has really weird subtitles. It seems as if the subtitles were


Commit: 7bae02e558dbc7cb21b00d5eef0797af31c6dfea
    https://github.com/scummvm/scummvm/commit/7bae02e558dbc7cb21b00d5eef0797af31c6dfea
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Put brackets around rest parameters

The kernel signature * means "one or more", not "zero or more" as
might be expected from the otherwise regex-like syntax.

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



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index e11326f..2bf71bb 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -363,7 +363,7 @@ static const SciKernelMapSubEntry kSave_subops[] = {
 	{ SIG_SCI32,           5, MAP_CALL(GetSaveFiles32),            "rrr",                  NULL },
 	{ SIG_SCI32,           6, MAP_CALL(MakeSaveCatName),           "rr",                   NULL },
 	{ SIG_SCI32,           7, MAP_CALL(MakeSaveFileName),          "rri",                  NULL },
-	{ SIG_SCI32,           8, MAP_EMPTY(GameIsRestarting),         ".*",                   NULL },
+	{ SIG_SCI32,           8, MAP_EMPTY(GameIsRestarting),         "(.*)",                 NULL },
 	SCI_SUBOPENTRY_TERMINATOR
 };
 
@@ -509,7 +509,7 @@ static const SciKernelMapSubEntry kArray_subops[] = {
 	{ SIG_SCI32,           0, MAP_CALL(ArrayNew),                  "ii",                   NULL },
 	{ SIG_SCI32,           1, MAP_CALL(ArrayGetSize),              "r",                    NULL },
 	{ SIG_SCI32,           2, MAP_CALL(ArrayGetElement),           "ri",                   NULL },
-	{ SIG_SCI32,           3, MAP_CALL(ArraySetElements),          "ri.*",                 kArraySetElements_workarounds },
+	{ SIG_SCI32,           3, MAP_CALL(ArraySetElements),          "ri(.*)",               kArraySetElements_workarounds },
 	{ SIG_SCI32,           4, MAP_CALL(ArrayFree),                 "r",                    NULL },
 	{ SIG_SCI32,           5, MAP_CALL(ArrayFill),                 "riii",                 NULL },
 	{ SIG_SCI32,           6, MAP_CALL(ArrayCopy),                 "ririi",                NULL },
@@ -537,8 +537,8 @@ static const SciKernelMapSubEntry kString_subops[] = {
 	{ SIG_UNTIL_SCI21MID,  8, MAP_CALL(ArrayDuplicate),            "r",                    NULL },
 	{ SIG_UNTIL_SCI21MID,  9, MAP_CALL(StringGetData),             "[0or]",                NULL },
 	{ SIG_UNTIL_SCI21MID, 10, MAP_CALL(StringLength),              "r",                    NULL },
-	{ SIG_UNTIL_SCI21MID, 11, MAP_CALL(StringFormat),              "r.*",                  NULL },
-	{ SIG_UNTIL_SCI21MID, 12, MAP_CALL(StringFormatAt),            "r[ro].*",              NULL },
+	{ SIG_UNTIL_SCI21MID, 11, MAP_CALL(StringFormat),              "r(.*)",                NULL },
+	{ SIG_UNTIL_SCI21MID, 12, MAP_CALL(StringFormatAt),            "r[ro](.*)",            NULL },
 	{ SIG_UNTIL_SCI21MID, 13, MAP_CALL(StringToInteger),           "r",                    NULL },
 	{ SIG_UNTIL_SCI21MID, 14, MAP_CALL(StringTrim),                "ri(i)",                NULL },
 	{ SIG_UNTIL_SCI21MID, 15, MAP_CALL(StringToUpperCase),         "r",                    NULL },
@@ -547,8 +547,8 @@ static const SciKernelMapSubEntry kString_subops[] = {
 	{ SIG_UNTIL_SCI21MID, 18, MAP_CALL(StringReplaceSubstringEx),  "rrrr",                 NULL },
 
 	{ SIG_SINCE_SCI21LATE, 8, MAP_CALL(StringLength),              "r",                    NULL },
-	{ SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringFormat),              "r.*",                  NULL },
-	{ SIG_SINCE_SCI21LATE,10, MAP_CALL(StringFormatAt),            "rr.*",                 NULL },
+	{ SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringFormat),              "r(.*)",                NULL },
+	{ SIG_SINCE_SCI21LATE,10, MAP_CALL(StringFormatAt),            "rr(.*)",               NULL },
 	{ SIG_SINCE_SCI21LATE,11, MAP_CALL(StringToInteger),           "r",                    NULL },
 	{ SIG_SINCE_SCI21LATE,12, MAP_CALL(StringTrim),                "ri(i)",                NULL },
 	{ SIG_SINCE_SCI21LATE,13, MAP_CALL(StringToUpperCase),         "r",                    NULL },


Commit: 3e75f42d85d82cc77d2607def29f4b63208844c1
    https://github.com/scummvm/scummvm/commit/3e75f42d85d82cc77d2607def29f4b63208844c1
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Rename "until" to "thru" in kernel call signatures

Kernel calls with these signatures are inclusive of the engine
version given in the name; this change makes it clearer that
this is the case.

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



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 2bf71bb..99bcc8f 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -67,8 +67,8 @@ struct SciKernelMapSubEntry {
 #define SIG_SINCE_SCI11      SCI_VERSION_1_1, SCI_VERSION_NONE
 #define SIG_SCI2             SCI_VERSION_2, SCI_VERSION_2
 #define SIG_SCI21EARLY       SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY
-#define SIG_UNTIL_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY
-#define SIG_UNTIL_SCI21MID   SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE
+#define SIG_THRU_SCI21EARLY  SCI_VERSION_2, SCI_VERSION_2_1_EARLY
+#define SIG_THRU_SCI21MID    SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE
 #define SIG_SINCE_SCI21      SCI_VERSION_2_1_EARLY, SCI_VERSION_3
 #define SIG_SINCE_SCI21MID   SCI_VERSION_2_1_MIDDLE, SCI_VERSION_3
 #define SIG_SINCE_SCI21LATE  SCI_VERSION_2_1_LATE, SCI_VERSION_3
@@ -525,26 +525,26 @@ static const SciKernelMapSubEntry kString_subops[] = {
 	// every single copy of script 64918 in SCI2 through 2.1mid calls StringNew
 	// with a second type argument which is unused (new strings are always type
 	// 3)
-	{ SIG_UNTIL_SCI21MID,  0, MAP_CALL(StringNew),                 "i(i)",                 NULL },
-	{ SIG_UNTIL_SCI21MID,  1, MAP_CALL(ArrayGetSize),              "r",                    NULL },
-	{ SIG_UNTIL_SCI21MID,  2, MAP_CALL(StringGetChar),             "ri",                   NULL },
-	{ SIG_UNTIL_SCI21MID,  3, MAP_CALL(ArraySetElements),          "rii*",                 kArraySetElements_workarounds },
-	{ SIG_UNTIL_SCI21MID,  4, MAP_CALL(StringFree),                "[0r]",                 NULL },
-	{ SIG_UNTIL_SCI21MID,  5, MAP_CALL(ArrayFill),                 "rii",                  NULL },
-	{ SIG_UNTIL_SCI21MID,  6, MAP_CALL(ArrayCopy),                 "ririi",                NULL },
+	{ SIG_THRU_SCI21MID,   0, MAP_CALL(StringNew),                 "i(i)",                 NULL },
+	{ SIG_THRU_SCI21MID,   1, MAP_CALL(ArrayGetSize),              "r",                    NULL },
+	{ SIG_THRU_SCI21MID,   2, MAP_CALL(StringGetChar),             "ri",                   NULL },
+	{ SIG_THRU_SCI21MID,   3, MAP_CALL(ArraySetElements),          "rii*",                 kArraySetElements_workarounds },
+	{ SIG_THRU_SCI21MID,   4, MAP_CALL(StringFree),                "[0r]",                 NULL },
+	{ SIG_THRU_SCI21MID,   5, MAP_CALL(ArrayFill),                 "rii",                  NULL },
+	{ SIG_THRU_SCI21MID,   6, MAP_CALL(ArrayCopy),                 "ririi",                NULL },
 	{ SIG_SCI32,           7, MAP_CALL(StringCompare),             "rr(i)",                NULL },
 
-	{ SIG_UNTIL_SCI21MID,  8, MAP_CALL(ArrayDuplicate),            "r",                    NULL },
-	{ SIG_UNTIL_SCI21MID,  9, MAP_CALL(StringGetData),             "[0or]",                NULL },
-	{ SIG_UNTIL_SCI21MID, 10, MAP_CALL(StringLength),              "r",                    NULL },
-	{ SIG_UNTIL_SCI21MID, 11, MAP_CALL(StringFormat),              "r(.*)",                NULL },
-	{ SIG_UNTIL_SCI21MID, 12, MAP_CALL(StringFormatAt),            "r[ro](.*)",            NULL },
-	{ SIG_UNTIL_SCI21MID, 13, MAP_CALL(StringToInteger),           "r",                    NULL },
-	{ SIG_UNTIL_SCI21MID, 14, MAP_CALL(StringTrim),                "ri(i)",                NULL },
-	{ SIG_UNTIL_SCI21MID, 15, MAP_CALL(StringToUpperCase),         "r",                    NULL },
-	{ SIG_UNTIL_SCI21MID, 16, MAP_CALL(StringToLowerCase),         "r",                    NULL },
-	{ SIG_UNTIL_SCI21MID, 17, MAP_CALL(StringReplaceSubstring),    "rrrr",                 NULL },
-	{ SIG_UNTIL_SCI21MID, 18, MAP_CALL(StringReplaceSubstringEx),  "rrrr",                 NULL },
+	{ SIG_THRU_SCI21MID,   8, MAP_CALL(ArrayDuplicate),            "r",                    NULL },
+	{ SIG_THRU_SCI21MID,   9, MAP_CALL(StringGetData),             "[0or]",                NULL },
+	{ SIG_THRU_SCI21MID,  10, MAP_CALL(StringLength),              "r",                    NULL },
+	{ SIG_THRU_SCI21MID,  11, MAP_CALL(StringFormat),              "r(.*)",                NULL },
+	{ SIG_THRU_SCI21MID,  12, MAP_CALL(StringFormatAt),            "r[ro](.*)",            NULL },
+	{ SIG_THRU_SCI21MID,  13, MAP_CALL(StringToInteger),           "r",                    NULL },
+	{ SIG_THRU_SCI21MID,  14, MAP_CALL(StringTrim),                "ri(i)",                NULL },
+	{ SIG_THRU_SCI21MID,  15, MAP_CALL(StringToUpperCase),         "r",                    NULL },
+	{ SIG_THRU_SCI21MID,  16, MAP_CALL(StringToLowerCase),         "r",                    NULL },
+	{ SIG_THRU_SCI21MID,  17, MAP_CALL(StringReplaceSubstring),    "rrrr",                 NULL },
+	{ SIG_THRU_SCI21MID,  18, MAP_CALL(StringReplaceSubstringEx),  "rrrr",                 NULL },
 
 	{ SIG_SINCE_SCI21LATE, 8, MAP_CALL(StringLength),              "r",                    NULL },
 	{ SIG_SINCE_SCI21LATE, 9, MAP_CALL(StringFormat),              "r(.*)",                NULL },
@@ -629,11 +629,11 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ "CelHigh", kCelHigh32,       SIG_SCI32, SIGFOR_ALL,    "iii",                   NULL,            NULL },
 	{ "CelWide", kCelWide32,       SIG_SCI32, SIGFOR_ALL,    "iii",                   NULL,            kCelWide_workarounds },
 #endif
-	{ MAP_CALL(CheckFreeSpace),    SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r(i)",          NULL,            NULL },
+	{ MAP_CALL(CheckFreeSpace),    SIG_THRU_SCI21EARLY, SIGFOR_ALL, "r(i)",           NULL,            NULL },
 	{ MAP_CALL(CheckFreeSpace),    SIG_SCI11, SIGFOR_ALL,    "r(i)",                  NULL,            NULL },
 	{ MAP_CALL(CheckFreeSpace),    SIG_SCI16, SIGFOR_ALL,    "r",                     NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "CheckSaveGame", kCheckSaveGame32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]",  NULL,            NULL },
+	{ "CheckSaveGame", kCheckSaveGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri[r0]",   NULL,            NULL },
 #endif
 	{ MAP_CALL(CheckSaveGame),     SIG_SCI16, SIGFOR_ALL,    ".*",                    NULL,            NULL },
 	{ MAP_CALL(Clone),             SIG_EVERYWHERE,           "o",                     NULL,            NULL },
@@ -686,11 +686,11 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(GetMessage),        SIG_EVERYWHERE,           "iiir",                  NULL,            NULL },
 	{ MAP_CALL(GetPort),           SIG_EVERYWHERE,           "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ MAP_CALL(GetSaveDir),        SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(r)",           NULL,            NULL },
+	{ MAP_CALL(GetSaveDir),        SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(r)",            NULL,            NULL },
 #endif
 	{ MAP_CALL(GetSaveDir),        SIG_SCI16, SIGFOR_ALL,    "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "GetSaveFiles", kGetSaveFiles32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rrr",       NULL,            NULL },
+	{ "GetSaveFiles", kGetSaveFiles32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rrr",        NULL,            NULL },
 #endif
 	{ MAP_CALL(GetSaveFiles),      SIG_EVERYWHERE,           "rrr",                   NULL,            NULL },
 	{ MAP_CALL(GetTime),           SIG_EVERYWHERE,           "(i)",                   NULL,            NULL },
@@ -754,12 +754,12 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(RespondsTo),        SIG_EVERYWHERE,           ".i",                    NULL,            NULL },
 	{ MAP_CALL(RestartGame),       SIG_EVERYWHERE,           "",                      NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "RestoreGame", kRestoreGame32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0]",   NULL,            NULL },
+	{ "RestoreGame", kRestoreGame32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0]",    NULL,            NULL },
 #endif
 	{ MAP_CALL(RestoreGame),       SIG_EVERYWHERE,           "[r0]i[r0]",             NULL,            NULL },
 	{ MAP_CALL(Said),              SIG_EVERYWHERE,           "[r0]",                  NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ "SaveGame", kSaveGame32,     SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0][r0]", NULL,            NULL },
+	{ "SaveGame", kSaveGame32,     SIG_THRU_SCI21EARLY, SIGFOR_ALL, "[r0]i[r0][r0]",  NULL,            NULL },
 #endif
 	{ MAP_CALL(SaveGame),          SIG_SCI16, SIGFOR_ALL,    "[r0]i[r0](r0)",         NULL,            NULL },
 	{ MAP_CALL(ScriptID),          SIG_EVERYWHERE,           "[io](i)",               NULL,            NULL },
@@ -835,9 +835,9 @@ static SciKernelMapEntry s_kernelMap[] = {
 #ifdef ENABLE_SCI32
 	// SCI2 Kernel Functions
 	// TODO: whoever knows his way through those calls, fix the signatures.
-	{ "TextSize", kTextSize32,     SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)",     NULL,            NULL },
-	{ MAP_DUMMY(TextColors),       SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)",          NULL,            NULL },
-	{ MAP_DUMMY(TextFonts),        SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)",          NULL,            NULL },
+	{ "TextSize", kTextSize32,     SIG_THRU_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)",      NULL,            NULL },
+	{ MAP_DUMMY(TextColors),       SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)",           NULL,            NULL },
+	{ MAP_DUMMY(TextFonts),        SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)",           NULL,            NULL },
 
 	{ MAP_CALL(AddPlane),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(AddScreenItem),     SIG_EVERYWHERE,           "o",                     NULL,            NULL },
@@ -870,8 +870,8 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(UpdateScreenItem),  SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(ObjectIntersect),   SIG_EVERYWHERE,           "oo",                    NULL,            NULL },
 	{ MAP_CALL(EditText),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },
-	{ MAP_CALL(MakeSaveCatName),   SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rr",            NULL,            NULL },
-	{ MAP_CALL(MakeSaveFileName),  SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rri",           NULL,            NULL },
+	{ MAP_CALL(MakeSaveCatName),   SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rr",             NULL,            NULL },
+	{ MAP_CALL(MakeSaveFileName),  SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rri",            NULL,            NULL },
 	{ MAP_CALL(SetScroll),         SIG_EVERYWHERE,           "oiiii(i)(i)",           NULL,            NULL },
 	{ MAP_CALL(PalCycle),          SIG_EVERYWHERE,           "(.*)",                  kPalCycle_subops, NULL },
 
@@ -909,9 +909,9 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_DUMMY(MarkMemory),       SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 	{ MAP_DUMMY(GetHighItemPri),   SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 	{ MAP_DUMMY(ShowStylePercent), SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
-	{ MAP_DUMMY(InvertRect),       SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)",          NULL,            NULL },
+	{ MAP_DUMMY(InvertRect),       SIG_THRU_SCI21EARLY, SIGFOR_ALL, "(.*)",           NULL,            NULL },
 	{ MAP_DUMMY(InputText),        SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
-	{ MAP_CALL(TextWidth),         SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri",            NULL,            NULL },
+	{ MAP_CALL(TextWidth),         SIG_THRU_SCI21EARLY, SIGFOR_ALL, "ri",             NULL,            NULL },
 	{ MAP_DUMMY(PointSize),        SIG_EVERYWHERE,           "(.*)",                  NULL,            NULL },
 
 	// SCI2.1 Kernel Functions


Commit: 1847b0f7059328e195d9fca2372ab2b8a12aad06
    https://github.com/scummvm/scummvm/commit/1847b0f7059328e195d9fca2372ab2b8a12aad06
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix warnings and incompatible save games when built without SCI32

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



diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 717efcb..d46d68d 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -335,11 +335,14 @@ bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc) {
 	desc->time = meta.saveTime;
 	desc->version = meta.version;
 	desc->gameVersion = meta.gameVersion;
+#ifdef ENABLE_SCI32
 	if (g_sci->getGameId() == GID_SHIVERS) {
-		desc->score = meta.score;
+		desc->lowScore = meta.lowScore;
+		desc->highScore = meta.highScore;
 	} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
 		desc->avatarId = meta.avatarId;
 	}
+#endif
 
 	if (meta.name.lastChar() == '\n')
 		meta.name.deleteLastChar();
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index 7d2461f..0154338 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -65,12 +65,11 @@ struct SavegameDesc {
 	char name[SCI_MAX_SAVENAME_LENGTH];
 	Common::String gameVersion;
 #ifdef ENABLE_SCI32
-	union {
-		// Used by Shivers 1
-		uint32 score;
-		// Used by MGDX
-		uint8 avatarId;
-	};
+	// Used by Shivers 1
+	uint16 lowScore;
+	uint16 highScore;
+	// Used by MGDX
+	uint8 avatarId;
 #endif
 };
 
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index e983922..2d2f885 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -324,14 +324,12 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
 
 			SavegameDesc save;
 			fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
-			Common::String score;
-			const uint16 lowScore = save.score & 0xFFFF;
-			const uint16 highScore = save.score >> 16;
 
-			if (!highScore) {
-				score = Common::String::format("%u", lowScore);
+			Common::String score;
+			if (!save.highScore) {
+				score = Common::String::format("%u", save.lowScore);
 			} else {
-				score = Common::String::format("%u%03u", highScore, lowScore);
+				score = Common::String::format("%u%03u", save.highScore, save.lowScore);
 			}
 
 			const uint nameLength = strlen(save.name);
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index aee11ef..d50df94 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -331,20 +331,26 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj)
 		s.syncAsUint32LE(obj.playTime);
 	}
 
-	// Some games require additional metadata to display the load screen
+	// Some games require additional metadata to display their restore screens
 	// correctly
 	if (s.getVersion() >= 38) {
 		if (s.isSaving()) {
 			const reg_t *globals = g_sci->getEngineState()->variables[VAR_GLOBAL];
 			if (g_sci->getGameId() == GID_SHIVERS) {
-				obj.score = globals[kScore].toUint16();
-				obj.score |= globals[kShivers1Score].toUint16() << 16;
+				obj.lowScore = globals[kScore].toUint16();
+				obj.highScore = globals[kShivers1Score].toUint16();
+				obj.avatarId = 0;
 			} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+				obj.lowScore = obj.highScore = 0;
 				obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kEgo], SELECTOR(view));
+			} else {
+				obj.lowScore = obj.highScore = obj.avatarId = 0;
 			}
 		}
 
-		s.syncAsUint32LE(obj.score);
+		s.syncAsUint16LE(obj.lowScore);
+		s.syncAsUint16LE(obj.highScore);
+		s.syncAsByte(obj.avatarId);
 	}
 }
 
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 627fc64..8c54f3b 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,7 +37,7 @@ struct EngineState;
  *
  * Version - new/changed feature
  * =============================
- *      38 - SCI32 cursor, accurate SCI32 arrays/strings, score metadata
+ *      38 - SCI32 cursor, accurate SCI32 arrays/strings, score metadata, avatar metadata
  *      37 - Segment entry data changed to pointers
  *      36 - SCI32 bitmap segment
  *      35 - SCI32 remap
@@ -77,14 +77,13 @@ struct SavegameMetadata {
 	uint32 playTime;
 	uint16 gameObjectOffset;
 	uint16 script0Size;
-#ifdef ENABLE_SCI32
-	union {
-		// Used by Shivers 1
-		uint32 score;
-		// Used by MGDX
-		uint8 avatarId;
-	};
-#endif
+
+	// Used by Shivers 1
+	uint16 lowScore;
+	uint16 highScore;
+
+	// Used by MGDX
+	uint8 avatarId;
 };
 
 /**
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 1bab106..9ef0b9e 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -1696,6 +1696,7 @@ static const SciScriptPatcherEntry kq6Signatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#ifdef ENABLE_SCI32
 #pragma mark -
 #pragma mark King's Quest 7
 
@@ -1860,6 +1861,8 @@ static const SciScriptPatcherEntry kq7Signatures[] = {
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
+#endif
+
 // ===========================================================================
 // Script 210 in the German version of Longbow handles the case where Robin
 // hands out the scroll to Marion and then types his name using the hand code.
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index ce6e264..4767b8a 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -495,6 +495,7 @@ static byte patchGameRestoreSave[] = {
 	0x48,              // ret
 };
 
+#ifdef ENABLE_SCI32
 // SCI2 version: Same as above, but the second parameter to callk is a word
 // and third parameter is a string reference
 static byte patchGameRestoreSci2[] = {
@@ -537,6 +538,7 @@ static byte patchGameSaveSci21[] = {
 	0x43, 0xff, 0x0a, 0x00, // callk kSave (0xFF will be overwritten by patcher)
 	0x48,                   // ret
 };
+#endif
 
 static void patchGameSaveRestoreCode(SegManager *segMan, reg_t methodAddress, byte id) {
 	Script *script = segMan->getScript(methodAddress.getSegment());


Commit: 4d73736d1f9409304beea57ddd39b5546a60bc2c
    https://github.com/scummvm/scummvm/commit/4d73736d1f9409304beea57ddd39b5546a60bc2c
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix compilation warnings

Changed paths:
    engines/sci/console.cpp
    engines/sci/engine/kstring.cpp



diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 32db2e9..97f219b 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -2824,11 +2824,11 @@ bool Console::cmdViewReference(int argc, const char **argv) {
 					debugPrintf("SCI32 %s array (%u entries):\n", arrayType, array->size());
 					switch (array->getType()) {
 					case kArrayTypeID:
-						hexDumpReg((reg_t *)array->getRawData(), array->size(), 4, 0, true);
+						hexDumpReg((const reg_t *)array->getRawData(), array->size(), 4, 0, true);
 						break;
 					case kArrayTypeByte:
 					case kArrayTypeString: {
-						Common::hexdump((byte *)array->getRawData(), array->size(), 16, 0);
+						Common::hexdump((const byte *)array->getRawData(), array->size(), 16, 0);
 						break;
 					}
 					case kArrayTypeInt16: {
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index 4d96ddc..6ec6134 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -791,7 +791,7 @@ namespace {
 			return Common::String::format("%s", format);
 		}
 	}
-};
+}
 
 Common::String format(const Common::String &source, int argc, const reg_t *argv) {
 	Common::String out;


Commit: 2c61569c62b450aa9aba904680d1de4183b5539b
    https://github.com/scummvm/scummvm/commit/2c61569c62b450aa9aba904680d1de4183b5539b
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Disable "change directory" button in native save/load dialogue

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



diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 9ef0b9e..280e26a 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -142,6 +142,27 @@ enum ScriptPatcherSelectors {
 };
 
 #ifdef ENABLE_SCI32
+// It is not possible to change the directory for ScummVM save games, so disable
+// the "change directory" button in the standard save dialogue
+static const uint16 sci2ChangeDirSignature[] = {
+	0x72, SIG_ADDTOOFFSET(+2), // lofsa changeDirI
+	0x4a, SIG_UINT16(0x04),    // send 4
+	SIG_MAGICDWORD,
+	0x36,                      // push
+	0x35, 0xF7,                // ldi $f7
+	0x12,                      // and
+	0x36,                      // push
+	SIG_END
+};
+
+static const uint16 sci2ChangeDirPatch[] = {
+	PATCH_ADDTOOFFSET(+3),    // lofsa changeDirI
+	PATCH_ADDTOOFFSET(+3),    // send 4
+	PATCH_ADDTOOFFSET(+1),    // push
+	0x35, 0x00,               // ldi 0
+	PATCH_END
+};
+
 // Save game script hardcodes the maximum number of save games to 20, but
 // this is an artificial constraint that does not apply to ScummVM
 static const uint16 sci2NumSavesSignature1[] = {
@@ -984,6 +1005,7 @@ static const SciScriptPatcherEntry gk1Signatures[] = {
 	{  true,   808, "day 10 gabriel dress up infinite turning",    1, gk1SignatureDay10GabrielDressUp,  gk1PatchDay10GabrielDressUp },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
@@ -994,6 +1016,7 @@ static const SciScriptPatcherEntry gk1Signatures[] = {
 static const SciScriptPatcherEntry gk2Signatures[] = {
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
@@ -2171,6 +2194,7 @@ static const SciScriptPatcherEntry larry6Signatures[] = {
 static const SciScriptPatcherEntry larry6HiresSignatures[] = {
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
@@ -2958,6 +2982,7 @@ static const SciScriptPatcherEntry pq1vgaSignatures[] = {
 static const SciScriptPatcherEntry pq4Signatures[] = {
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
@@ -3938,6 +3963,7 @@ static const SciScriptPatcherEntry qfg3Signatures[] = {
 static const SciScriptPatcherEntry qfg4Signatures[] = {
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
+	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 
@@ -4518,6 +4544,7 @@ static const SciScriptPatcherEntry sq6Signatures[] = {
 	{  true,   510, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,          sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,          sci2NumSavesPatch2 },
+	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 


Commit: cb66bfead0bbd0ff2ba3eebc8202f1bedc7777b3
    https://github.com/scummvm/scummvm/commit/cb66bfead0bbd0ff2ba3eebc8202f1bedc7777b3
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix CelObj scaling in games with hi-res scripts

Changed paths:
    engines/sci/graphics/celobj32.cpp



diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 9019517..27d20de 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -221,17 +221,17 @@ struct SCALER_Scale {
 		} else {
 			if (FLIP) {
 				const int lastIndex = celObj._width - 1;
-				for (int16 x = 0; x < targetRect.width(); ++x) {
-					_valuesX[targetRect.left + x] = lastIndex - table->valuesX[x];
+				for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+					_valuesX[x] = lastIndex - table->valuesX[x - scaledPosition.x];
 				}
 			} else {
-				for (int16 x = 0; x < targetRect.width(); ++x) {
-					_valuesX[targetRect.left + x] = table->valuesX[x];
+				for (int16 x = targetRect.left; x < targetRect.right; ++x) {
+					_valuesX[x] = table->valuesX[x - scaledPosition.x];
 				}
 			}
 
-			for (int16 y = 0; y < targetRect.height(); ++y) {
-				_valuesY[targetRect.top + y] = table->valuesY[y];
+			for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
+				_valuesY[y] = table->valuesY[y - scaledPosition.y];
 			}
 		}
 	}


Commit: 7011ee8f507da0598b46e19004169e5d60d7d5a6
    https://github.com/scummvm/scummvm/commit/7011ee8f507da0598b46e19004169e5d60d7d5a6
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix Torin inventory crash caused by wrong array type

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



diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 280e26a..60ade45 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -4551,8 +4551,30 @@ static const SciScriptPatcherEntry sq6Signatures[] = {
 #pragma mark -
 #pragma mark Torin's Passage
 
+// Torin initializes the inventory with an int16 array, which happens to work
+// in SSCI because object references are int16s, but in ScummVM object
+// references are reg_ts, so this array needs to be created as an IDArray
+// instead
+static const uint16 torinInventItemSlotsSignature[] = {
+	0x38, SIG_UINT16(0x8d), // pushi $8d (new)
+	0x78,                   // push1
+	SIG_MAGICDWORD,
+	0x67, 0x2e,             // pTos $2e (invSlotsTot)
+	0x51, 0x0b,             // class IntArray
+	SIG_END
+};
+
+static const uint16 torinInventItemSlotsPatch[] = {
+	PATCH_ADDTOOFFSET(+3),  // pushi $8d (new)
+	PATCH_ADDTOOFFSET(+1),  // push1
+	PATCH_ADDTOOFFSET(+2),  // pTos $2e (invSlotsTot)
+	0x51, 0x0c,             // class IDArray
+	PATCH_END
+};
+
 //          script, description,                                      signature                         patch
 static const SciScriptPatcherEntry torinSignatures[] = {
+	{  true, 64895, "fix inventory array type",                    1, torinInventItemSlotsSignature,    torinInventItemSlotsPatch },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },
 	SCI_SIGNATUREENTRY_TERMINATOR


Commit: 2a27f27bb4f4ad979559e77d547cd6c8c2504f11
    https://github.com/scummvm/scummvm/commit/2a27f27bb4f4ad979559e77d547cd6c8c2504f11
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Clean up scaling flags

Changed paths:
    engines/sci/graphics/screen_item32.cpp
    engines/sci/graphics/screen_item32.h
    engines/sci/graphics/video32.cpp
    engines/sci/video/robot_decoder.cpp



diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
index f4ed269..0956ef7 100644
--- a/engines/sci/graphics/screen_item32.cpp
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -263,16 +263,13 @@ void ScreenItem::calcRects(const Plane &plane) {
 	}
 
 	Ratio scaleX, scaleY;
-
-	if (_scale.signal & kScaleSignalDoScaling32) {
-		if (_scale.signal & kScaleSignalUseVanishingPoint) {
-			int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
-			scaleX = Ratio(num, 128);
-			scaleY = Ratio(num, 128);
-		} else {
-			scaleX = Ratio(_scale.x, 128);
-			scaleY = Ratio(_scale.y, 128);
-		}
+	if (_scale.signal == kScaleSignalManual) {
+		scaleX = Ratio(_scale.x, 128);
+		scaleY = Ratio(_scale.y, 128);
+	} else if (_scale.signal == kScaleSignalVanishingPoint) {
+		int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+		scaleX = Ratio(num, 128);
+		scaleY = Ratio(num, 128);
 	}
 
 	if (scaleX.getNumerator() && scaleY.getNumerator()) {
@@ -593,15 +590,13 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {
 	const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
 
 	Ratio scaleX, scaleY;
-	if (_scale.signal & kScaleSignalDoScaling32) {
-		if (_scale.signal & kScaleSignalUseVanishingPoint) {
-			int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
-			scaleX = Ratio(num, 128);
-			scaleY = Ratio(num, 128);
-		} else {
-			scaleX = Ratio(_scale.x, 128);
-			scaleY = Ratio(_scale.y, 128);
-		}
+	if (_scale.signal == kScaleSignalManual) {
+		scaleX = Ratio(_scale.x, 128);
+		scaleY = Ratio(_scale.y, 128);
+	} else if (_scale.signal == kScaleSignalVanishingPoint) {
+		int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+		scaleX = Ratio(num, 128);
+		scaleY = Ratio(num, 128);
 	}
 
 	if (scaleX.getNumerator() == 0 || scaleY.getNumerator() == 0) {
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
index 4221c0e..009b608 100644
--- a/engines/sci/graphics/screen_item32.h
+++ b/engines/sci/graphics/screen_item32.h
@@ -30,13 +30,9 @@
 namespace Sci {
 
 enum ScaleSignals32 {
-	kScaleSignalNone                    = 0,
-	// TODO: rename to 'manual'
-	kScaleSignalDoScaling32				= 1, // enables scaling when drawing that cel (involves scaleX and scaleY)
-	kScaleSignalUseVanishingPoint       = 2,
-	// TODO: Is this actually a thing? I have not seen it and
-	// the original engine masks &3 where it uses scale signals.
-	kScaleSignalDisableGlobalScaling32	= 4
+	kScaleSignalNone           = 0,
+	kScaleSignalManual         = 1,
+	kScaleSignalVanishingPoint = 2
 };
 
 struct ScaleInfo {
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index 84c07d3..62bc12e 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -689,12 +689,12 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
 		if (_doublePixels) {
 			vmdScaleInfo.x = 256;
 			vmdScaleInfo.y = 256;
-			vmdScaleInfo.signal = kScaleSignalDoScaling32;
+			vmdScaleInfo.signal = kScaleSignalManual;
 			vmdRect.right += vmdRect.width();
 			vmdRect.bottom += vmdRect.height();
 		} else if (_stretchVertical) {
 			vmdScaleInfo.y = 256;
-			vmdScaleInfo.signal = kScaleSignalDoScaling32;
+			vmdScaleInfo.signal = kScaleSignalManual;
 			vmdRect.bottom += vmdRect.height();
 		}
 
diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp
index 1757088..0426dd0 100644
--- a/engines/sci/video/robot_decoder.cpp
+++ b/engines/sci/video/robot_decoder.cpp
@@ -445,7 +445,7 @@ void RobotDecoder::initVideo(const int16 x, const int16 y, const int16 scale, co
 	if (scale != 128) {
 		_scaleInfo.x = scale;
 		_scaleInfo.y = scale;
-		_scaleInfo.signal = kScaleSignalDoScaling32;
+		_scaleInfo.signal = kScaleSignalManual;
 	}
 
 	_plane = g_sci->_gfxFrameout->getPlanes().findByObject(plane);
@@ -1391,7 +1391,7 @@ void RobotDecoder::doVersion5(const bool shouldSubmitAudio) {
 
 // TODO: Version 6 robot?
 //		int scaleXRemainder;
-		if (_scaleInfo.signal == kScaleSignalDoScaling32) {
+		if (_scaleInfo.signal == kScaleSignalManual) {
 			position.x = (position.x * _scaleInfo.x) / 128;
 // TODO: Version 6 robot?
 //			scaleXRemainder = (position.x * _scaleInfo.x) % 128;


Commit: 9a64c0587bbbb68d80130149ec03a9c592bc5e2a
    https://github.com/scummvm/scummvm/commit/9a64c0587bbbb68d80130149ec03a9c592bc5e2a
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Always build scaler tables to the maximum possible size

This fixes rendering errors in Torin caused by the scaler table
being cut off early when cels larger than the dimensions of the
screen are scaled.

Changed paths:
    engines/sci/graphics/celobj32.cpp
    engines/sci/graphics/celobj32.h



diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 27d20de..03e2114 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -35,9 +35,6 @@ namespace Sci {
 CelScaler *CelObj::_scaler = nullptr;
 
 void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
-	const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
-	const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
-
 	for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) {
 		if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) {
 			_activeIndex = i;
@@ -50,14 +47,12 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
 	CelScalerTable &table = _scaleTables[i];
 
 	if (table.scaleX != scaleX) {
-		assert(screenWidth <= ARRAYSIZE(table.valuesX));
-		buildLookupTable(table.valuesX, scaleX, screenWidth);
+		buildLookupTable(table.valuesX, scaleX, kCelScalerTableSize);
 		table.scaleX = scaleX;
 	}
 
 	if (table.scaleY != scaleY) {
-		assert(screenHeight <= ARRAYSIZE(table.valuesY));
-		buildLookupTable(table.valuesY, scaleY, screenHeight);
+		buildLookupTable(table.valuesY, scaleY, kCelScalerTableSize);
 		table.scaleY = scaleY;
 	}
 }
@@ -164,8 +159,8 @@ struct SCALER_Scale {
 	const byte *_row;
 	READER _reader;
 	int16 _x;
-	static int16 _valuesX[4096];
-	static int16 _valuesY[4096];
+	static int16 _valuesX[kCelScalerTableSize];
+	static int16 _valuesY[kCelScalerTableSize];
 
 	SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) :
 	_row(nullptr),
@@ -249,9 +244,9 @@ struct SCALER_Scale {
 };
 
 template<bool FLIP, typename READER>
-int16 SCALER_Scale<FLIP, READER>::_valuesX[4096];
+int16 SCALER_Scale<FLIP, READER>::_valuesX[kCelScalerTableSize];
 template<bool FLIP, typename READER>
-int16 SCALER_Scale<FLIP, READER>::_valuesY[4096];
+int16 SCALER_Scale<FLIP, READER>::_valuesY[kCelScalerTableSize];
 
 #pragma mark -
 #pragma mark CelObj - Resource readers
@@ -283,7 +278,7 @@ public:
 struct READER_Compressed {
 private:
 	const byte *const _resource;
-	byte _buffer[4096];
+	byte _buffer[kCelScalerTableSize];
 	uint32 _controlOffset;
 	uint32 _dataOffset;
 	uint32 _uncompressedDataOffset;
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
index 21e86d0..c30529f 100644
--- a/engines/sci/graphics/celobj32.h
+++ b/engines/sci/graphics/celobj32.h
@@ -141,13 +141,20 @@ typedef Common::Array<CelCacheEntry> CelCache;
 #pragma mark -
 #pragma mark CelScaler
 
+enum {
+	/**
+	 * The maximum size of a row/column of scaled pixel data.
+	 */
+	kCelScalerTableSize = 4096
+};
+
 struct CelScalerTable {
 	/**
 	 * A lookup table of indexes that should be used to find
 	 * the correct column to read from the source bitmap
 	 * when drawing a scaled version of the source bitmap.
 	 */
-	int valuesX[4096];
+	int valuesX[kCelScalerTableSize];
 
 	/**
 	 * The ratio used to generate the x-values.
@@ -159,7 +166,7 @@ struct CelScalerTable {
 	 * the correct row to read from a source bitmap when
 	 * drawing a scaled version of the source bitmap.
 	 */
-	int valuesY[4096];
+	int valuesY[kCelScalerTableSize];
 
 	/**
 	 * The ratio used to generate the y-values.


Commit: 3208f063b50f50ba643b94846d096f1d2b298d23
    https://github.com/scummvm/scummvm/commit/3208f063b50f50ba643b94846d096f1d2b298d23
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Extra bounds checking in CelObj renderer

Changed paths:
    engines/sci/graphics/celobj32.cpp



diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 03e2114..5d2d5dd 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -154,6 +154,7 @@ struct SCALER_NoScale {
 template<bool FLIP, typename READER>
 struct SCALER_Scale {
 #ifndef NDEBUG
+	int16 _minX;
 	int16 _maxX;
 #endif
 	const byte *_row;
@@ -165,6 +166,7 @@ struct SCALER_Scale {
 	SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) :
 	_row(nullptr),
 #ifndef NDEBUG
+	_minX(targetRect.left),
 	_maxX(targetRect.right - 1),
 #endif
 	// The maximum width of the scaled object may not be as
@@ -234,11 +236,11 @@ struct SCALER_Scale {
 	inline void setTarget(const int16 x, const int16 y) {
 		_row = _reader.getRow(_valuesY[y]);
 		_x = x;
-		assert(_x >= 0 && _x <= _maxX);
+		assert(_x >= _minX && _x <= _maxX);
 	}
 
 	inline byte read() {
-		assert(_x >= 0 && _x <= _maxX);
+		assert(_x >= _minX && _x <= _maxX);
 		return _row[_valuesX[_x++]];
 	}
 };


Commit: 9d2397e1e9c1be301ddd6e40365e7cf2a1e2f5f5
    https://github.com/scummvm/scummvm/commit/9d2397e1e9c1be301ddd6e40365e7cf2a1e2f5f5
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Implement kBitmapCreateFromView and kBitmapGetInfo

Used by Torin room 40300 to perform pathfinding by bitmap.

Changed paths:
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kgraphics32.cpp
    engines/sci/engine/segment.h



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 99bcc8f..5e14e43 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -395,8 +395,8 @@ static const SciKernelMapSubEntry kBitmap_subops[] = {
 	{ SIG_SINCE_SCI21MID,  9, MAP_CALL(BitmapCreateFromView),      "iii(i)(i)(i)([r0])",   NULL },
 	{ SIG_SINCE_SCI21MID, 10, MAP_CALL(BitmapCopyPixels),          "rr",                   NULL },
 	{ SIG_SINCE_SCI21MID, 11, MAP_CALL(BitmapClone),               "r",                    NULL },
-	{ SIG_SINCE_SCI21LATE, 12, MAP_CALL(BitmapGetInfo),            "r(i)(i)",              NULL },
-	{ SIG_SINCE_SCI21LATE, 13, MAP_CALL(BitmapScale),              "r...ii",               NULL },
+	{ SIG_SINCE_SCI21MID, 12, MAP_CALL(BitmapGetInfo),             "r(i)(i)",              NULL },
+	{ SIG_SINCE_SCI21LATE,13, MAP_CALL(BitmapScale),               "r...ii",               NULL },
 	{ SIG_SCI3,           14, MAP_CALL(BitmapCreateFromUnknown),   "......",               NULL },
 	{ SIG_SCI3,           15, MAP_EMPTY(Bitmap),                   "(.*)",                 NULL },
 	{ SIG_SCI3,           16, MAP_EMPTY(Bitmap),                   "(.*)",                 NULL },
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 3c7a3c2..cceb710 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -766,9 +766,30 @@ reg_t kBitmapSetDisplace(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv) {
-	// viewId, loopNo, celNo, skipColor, backColor, useRemap, source overlay bitmap
+	CelObjView view(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16());
+	const uint8 skipColor = argc > 3 && argv[3].toSint16() != -1 ? argv[3].toSint16() : view._transparentColor;
+	const uint8 backColor = argc > 4 && argv[4].toSint16() != -1 ? argv[4].toSint16() : view._transparentColor;
+	const bool useRemap = argc > 5 ? (bool)argv[5].toSint16() : false;
 
-	return kStub(s, argc + 1, argv - 1);
+	reg_t bitmapId;
+	SciBitmap &bitmap = *s->_segMan->allocateBitmap(&bitmapId, view._width, view._height, skipColor, 0, 0, view._scaledWidth, view._scaledHeight, 0, useRemap, true);
+	Buffer &buffer = bitmap.getBuffer();
+
+	const Common::Rect viewRect(view._width, view._height);
+	buffer.fillRect(viewRect, backColor);
+	view.draw(buffer, viewRect, Common::Point(0, 0), view._mirrorX);
+
+	if (argc > 6 && !argv[6].isNull()) {
+		reg_t clutHandle = argv[6];
+		if (s->_segMan->isObject(clutHandle)) {
+			clutHandle = readSelector(s->_segMan, clutHandle, SELECTOR(data));
+		}
+
+		SciArray &clut = *s->_segMan->lookupArray(clutHandle);
+		bitmap.applyRemap(clut);
+	}
+
+	return bitmapId;
 }
 
 reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv) {
@@ -784,12 +805,24 @@ reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv) {
-	// bitmap
+	SciBitmap &bitmap = *s->_segMan->lookupBitmap(argv[0]);
 
-	// argc 1 = get width
-	// argc 2 = pixel at row 0 col n
-	// argc 3 = pixel at row n col n
-	return kStub(s, argc + 1, argv - 1);
+	if (argc == 1) {
+		return make_reg(0, bitmap.getWidth());
+	}
+
+	int32 offset;
+	if (argc == 2) {
+		offset = argv[1].toUint16();
+	} else {
+		const int16 x = argv[1].toSint16();
+		const int16 y = argv[2].toSint16();
+		offset = y * bitmap.getWidth() + x;
+	}
+
+	assert(offset >= 0 && offset < bitmap.getWidth() * bitmap.getHeight());
+	const uint8 color = bitmap.getPixels()[offset];
+	return make_reg(0, color);
 }
 
 reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv) {
diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h
index 39376e3..fd9b3f7 100644
--- a/engines/sci/engine/segment.h
+++ b/engines/sci/engine/segment.h
@@ -1120,6 +1120,15 @@ public:
 	}
 
 	virtual void saveLoadWithSerializer(Common::Serializer &ser);
+
+	void applyRemap(SciArray &clut) {
+		const int length = getWidth() * getHeight();
+		uint8 *pixel = getPixels();
+		for (int i = 0; i < length; ++i) {
+			uint8 color = clut.int16At(*pixel);
+			*pixel++ = color;
+		}
+	}
 };
 
 struct BitmapTable : public SegmentObjTable<SciBitmap> {


Commit: a8009fb0a9e56491d8ddf8cba344e0cf4174d5ff
    https://github.com/scummvm/scummvm/commit/a8009fb0a9e56491d8ddf8cba344e0cf4174d5ff
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix crashes in line drawing code

Fixes Torin room 43000.

The algorithm in Graphics::drawThickLine2 for drawing thick lines
is not completely accurate and so there are still some single-pixel
rendering bugs, but these do not impact the game itself and can
be fixed separately.

Changed paths:
    engines/sci/graphics/paint32.cpp



diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp
index 509df7c..958820d 100644
--- a/engines/sci/graphics/paint32.cpp
+++ b/engines/sci/graphics/paint32.cpp
@@ -38,7 +38,6 @@ reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &st
 
 	Common::Rect gameRect;
 	reg_t bitmapId = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect);
-	SciBitmap &bitmap = *_segMan->lookupBitmap(bitmapId);
 
 	CelInfo32 celInfo;
 	celInfo.type = kCelTypeMem;
@@ -48,7 +47,7 @@ reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &st
 	// `kUpdateLine` can get the originally used color
 	celInfo.color = color;
 
-	ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, Common::Rect(startPoint.x, startPoint.y, startPoint.x + bitmap.getWidth(), startPoint.y + bitmap.getHeight()));
+	ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, gameRect);
 	screenItem->_priority = priority;
 	screenItem->_fixedPriority = true;
 
@@ -89,9 +88,8 @@ void GfxPaint32::plotter(int x, int y, int color, void *data) {
 	LineProperties &properties = *static_cast<LineProperties *>(data);
 	byte *pixels = properties.bitmap->getPixels();
 
-	const uint32 index = properties.bitmap->getWidth() * y + x;
-
-	if (index < properties.bitmap->getDataSize()) {
+	if (x >= 0 && x < properties.bitmap->getWidth() && y >= 0 && y < properties.bitmap->getHeight()) {
+		const uint32 index = properties.bitmap->getWidth() * y + x;
 		if (properties.solid) {
 			pixels[index] = (uint8)color;
 			return;
@@ -112,8 +110,6 @@ void GfxPaint32::plotter(int x, int y, int color, void *data) {
 		if (properties.patternIndex == ARRAYSIZE(properties.pattern)) {
 			properties.patternIndex = 0;
 		}
-	} else {
-		warning("GfxPaint32::plotter: Attempted to write out of bounds (%u >= %u)", index, properties.bitmap->getDataSize());
 	}
 }
 
@@ -124,13 +120,18 @@ reg_t GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::
 	thickness = ((MAX<uint8>(1, thickness) - 1) | 1);
 	const uint8 halfThickness = thickness >> 1;
 
-	outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness;
-	outRect.top = (startPoint.y < endPoint.y ? startPoint.y : endPoint.y) - halfThickness;
-	outRect.right = (startPoint.x > endPoint.x ? startPoint.x : endPoint.x) + halfThickness + 1;
-	outRect.bottom = (startPoint.y > endPoint.y ? startPoint.y : endPoint.y) + halfThickness + 1;
+	const uint16 scriptWidth  = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+	const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+	outRect.left   = MIN<int16>(startPoint.x, endPoint.x);
+	outRect.top    = MIN<int16>(startPoint.y, endPoint.y);
+	outRect.right  = MAX<int16>(startPoint.x, endPoint.x) + 1;
+	outRect.bottom = MAX<int16>(startPoint.y, endPoint.y) + 1;
+	outRect.grow(halfThickness);
+	outRect.clip(Common::Rect(scriptWidth, scriptHeight));
 
 	reg_t bitmapId;
-	SciBitmap &bitmap = *_segMan->allocateBitmap(&bitmapId, outRect.width(), outRect.height(), skipColor, 0, 0, g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, 0, false, true);
+	SciBitmap &bitmap = *_segMan->allocateBitmap(&bitmapId, outRect.width(), outRect.height(), skipColor, 0, 0, scriptWidth, scriptHeight, 0, false, true);
 
 	byte *pixels = bitmap.getPixels();
 	memset(pixels, skipColor, bitmap.getWidth() * bitmap.getHeight());
@@ -152,12 +153,10 @@ reg_t GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::
 		break;
 	}
 
-	const Common::Rect drawRect(
-		startPoint.x - outRect.left,
-		startPoint.y - outRect.top,
-		endPoint.x - outRect.left,
-		endPoint.y - outRect.top
-	);
+	const int16 x1 = startPoint.x - outRect.left;
+	const int16 y1 = startPoint.y - outRect.top;
+	const int16 x2 =   endPoint.x - outRect.left;
+	const int16 y2 =   endPoint.y - outRect.top;
 
 	if (!properties.solid) {
 		for (int i = 0; i < ARRAYSIZE(properties.pattern); ++i) {
@@ -166,14 +165,14 @@ reg_t GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::
 		}
 
 		properties.patternIndex = 0;
-		properties.horizontal = ABS(drawRect.right - drawRect.left) > ABS(drawRect.bottom - drawRect.top);
-		properties.lastAddress = properties.horizontal ? drawRect.left : drawRect.top;
+		properties.horizontal = ABS(x2 - x1) > ABS(y2 - y1);
+		properties.lastAddress = properties.horizontal ? x1 : y1;
 	}
 
 	if (thickness <= 1) {
-		Graphics::drawLine(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, color, plotter, &properties);
+		Graphics::drawLine(x1, y1, x2, y2, color, plotter, &properties);
 	} else {
-		Graphics::drawThickLine2(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, thickness, color, plotter, &properties);
+		Graphics::drawThickLine2(x1, y1, x2, y2, thickness, color, plotter, &properties);
 	}
 
 	return bitmapId;


Commit: 3f6cfff225f6b23310e25e3ff44423ef2e49ad8c
    https://github.com/scummvm/scummvm/commit/3f6cfff225f6b23310e25e3ff44423ef2e49ad8c
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix SQ6 crash in Holocabana control panel

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



diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 60ade45..830ab53 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -4536,10 +4536,30 @@ static const SciScriptPatcherEntry sq5Signatures[] = {
 #pragma mark -
 #pragma mark Space Quest 6
 
+// When pressing number buttons on the Holocabana controls, View objects are
+// put in an int16 array. This happens to work in SSCI, but ScummVM requires a
+// proper IDArray because reg_t is larger than 16 bits.
+static const uint16 sq6HoloIntArraySignature[] = {
+	0x38, SIG_SELECTOR16(new), // pushi new
+	0x76,                      // push0
+	0x51, 0x0b,                // class IntArray
+	SIG_MAGICDWORD,
+	0x4a, SIG_UINT16(0x04),    // send 4
+	0xa3, 0x06,                // sal local[6]
+	SIG_END
+};
+
+static const uint16 sq6HoloIntArrayPatch[] = {
+	PATCH_ADDTOOFFSET(+4),      // pushi new; push0
+	0x51, 0x0c,                 // class IDArray
+	PATCH_END
+};
+
 //          script, description,                                      signature                        patch
 static const SciScriptPatcherEntry sq6Signatures[] = {
 	{  true,    15, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
 	{  true,    22, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
+	{  true,   400, "invalid array type",                          1, sq6HoloIntArraySignature,        sq6HoloIntArrayPatch },
 	{  true,   460, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
 	{  true,   510, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,          sci2NumSavesPatch1 },


Commit: 5877542d7eca653ffaa31f2666bc32ffca241e61
    https://github.com/scummvm/scummvm/commit/5877542d7eca653ffaa31f2666bc32ffca241e61
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Ease debugging of wrong array types

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



diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h
index fd9b3f7..2af4780 100644
--- a/engines/sci/engine/segment.h
+++ b/engines/sci/engine/segment.h
@@ -664,7 +664,9 @@ public:
 			const reg_t *source = values;
 			int16 *target = (int16 *)_data + index;
 			while (count--) {
-				assert(source->isNumber());
+				if (!source->isNumber()) {
+					error("Non-number %04x:%04x sent to int16 array", PRINT_REG(*source));
+				}
 				*target++ = source->toSint16();
 				++source;
 			}
@@ -683,7 +685,9 @@ public:
 			const reg_t *source = values;
 			byte *target = (byte *)_data + index;
 			while (count--) {
-				assert(source->isNumber());
+				if (!source->isNumber()) {
+					error("Non-number %04x:%04x sent to byte or string array", PRINT_REG(*source));
+				}
 				*target++ = source->getOffset();
 				++source;
 			}


Commit: 07e1dc3ca5c51130d44fb1f54ceb399ee9039957
    https://github.com/scummvm/scummvm/commit/07e1dc3ca5c51130d44fb1f54ceb399ee9039957
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix SQ6 crash using the special button in Stooge Fighter 3

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



diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 6297d48..85a7966 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -379,6 +379,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
 	{ GID_SQ6,            -1,     0,  0,                "SQ6", "init",                            NULL,     2, { WORKAROUND_FAKE,   0 } }, // Demo and full version: called when the game starts (demo: room 0, full: room 100)
 	{ GID_SQ6,            -1, 64950, -1,            "Feature", "handleEvent",                     NULL,     0, { WORKAROUND_FAKE,   0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places
 	{ GID_SQ6,            -1, 64964,  0,              "DPath", "init",                            NULL,     1, { WORKAROUND_FAKE,   0 } }, // during the game
+	{ GID_SQ6,           210,   210,  0,       "buttonSecret", "doVerb",                          NULL,     0, { WORKAROUND_FAKE,   0 } }, // after winning the first round of stooge fighter 3
 	{ GID_TORIN,          -1, 64017,  0,             "oFlags", "clear",                           NULL,     0, { WORKAROUND_FAKE,   0 } }, // entering Torin's home in the French version
 	{ GID_TORIN,       10000, 64029,  0,          "oMessager", "nextMsg",                         NULL,     3, { WORKAROUND_FAKE,   0 } }, // start of chapter one
 	{ GID_TORIN,       20100, 64964,  0,              "DPath", "init",                            NULL,     1, { WORKAROUND_FAKE,   0 } }, // going down the cliff at the first screen of chapter 2 (washing area)


Commit: 2035e21667b746c346e9f95658109904a86597d3
    https://github.com/scummvm/scummvm/commit/2035e21667b746c346e9f95658109904a86597d3
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Add workaround for kNumCels

This workaround may be able to be vastly simplified in the future
since, so far, simply returning the number of cels in loop 0 is
enough to make all the scripts with this bug work as expected.

Changed paths:
    engines/sci/engine/workarounds.cpp
    engines/sci/engine/workarounds.h
    engines/sci/graphics/celobj32.cpp



diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 85a7966..8012b5b 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -683,6 +683,19 @@ const SciWorkaroundEntry kNewWindow_workarounds[] = {
 };
 
 //    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
+const SciWorkaroundEntry kNumCels_workarounds[] = {
+	{ GID_GK1,           808, 64998, -1,          "sDJEnters", "changeState",               NULL,    -1, { WORKAROUND_FAKE, 6 } }, //
+	{ GID_GK2,           470, 64998, -1,        "pLookieLoos", "lastCel",                   NULL,    -1, { WORKAROUND_FAKE, 50 } }, // random background movement in the crime scene in Munich city centre
+	{ GID_GK2,           470, 64998, -1,          "pNewsCrew", "lastCel",                   NULL,    -1, { WORKAROUND_FAKE, 18 } }, // random background movement in the crime scene in Munich city centre
+	{ GID_GK2,           470, 64998, -1,        "pLeberGroup", "lastCel",                   NULL,    -1, { WORKAROUND_FAKE, 22 } }, // random background movement in the crime scene in Munich city centre
+	{ GID_SQ6,           270, 64998, -1,           "offWorld", "lastCel",                   NULL,    -1, { WORKAROUND_FAKE, 3 } }, // when exiting the kidnapping room
+	{ GID_SQ6,           320, 64998, -1,          "wandererB", "lastCel",                   NULL,    -1, { WORKAROUND_FAKE, 8 } }, // random background movement on Polysorbate LX street 3
+	{ GID_SQ6,           340, 64998, -1,          "wandererB", "lastCel",                   NULL,    -1, { WORKAROUND_FAKE, 8 } }, // random background movement on Polysorbate LX street 1
+	{ GID_SQ6,           530, 64998, -1,           "monitors", "lastCel",                   NULL,    -1, { WORKAROUND_FAKE, 5 } }, // random background movement during cutscene
+	SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+//    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
 const SciWorkaroundEntry kPalVarySetPercent_workarounds[] = {
 	{ GID_GK1,           370,   370,  0,        "graceComeOut", "changeState",              NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // there's an extra parameter in GK1, when changing chapters. This extra parameter seems to be a bug or just unimplemented functionality, as there's no visible change from the original in the chapter change room
 	SCI_WORKAROUNDENTRY_TERMINATOR
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index a272bae..d1e985d 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -85,6 +85,7 @@ extern const SciWorkaroundEntry kIsObject_workarounds[];
 extern const SciWorkaroundEntry kMemory_workarounds[];
 extern const SciWorkaroundEntry kMoveCursor_workarounds[];
 extern const SciWorkaroundEntry kNewWindow_workarounds[];
+extern const SciWorkaroundEntry kNumCels_workarounds[];
 extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[];
 extern const SciWorkaroundEntry kRandom_workarounds[];
 extern const SciWorkaroundEntry kReadNumber_workarounds[];
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 5d2d5dd..e514924 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -28,6 +28,7 @@
 #include "sci/graphics/palette32.h"
 #include "sci/graphics/remap32.h"
 #include "sci/graphics/text32.h"
+#include "sci/engine/workarounds.h"
 
 namespace Sci {
 #pragma mark CelScaler
@@ -819,8 +820,18 @@ int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) {
 	// explicitly trap the bad condition here and report it so that any other
 	// game scripts relying on this broken behavior can be fixed as well
 	if (loopNo == loopCount) {
-		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
-		error("[CelObjView::getNumCels]: loop number is equal to loop count in method %s::%s (room %d, script %d, localCall %x)", origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+		SciCallOrigin origin;
+		SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kNumCels_workarounds, &origin);
+		switch (solution.type) {
+		case WORKAROUND_NONE:
+			error("[CelObjView::getNumCels]: loop number is equal to loop count in method %s::%s (room %d, script %d, localCall %x)", origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+		case WORKAROUND_FAKE:
+			return (int16)solution.value;
+		case WORKAROUND_IGNORE:
+			return 0;
+		case WORKAROUND_STILLCALL:
+			break;
+		}
 	}
 
 	if (loopNo > loopCount || loopNo < 0) {


Commit: 015a62976c2da36f22b215a22811ae8fdb81c805
    https://github.com/scummvm/scummvm/commit/015a62976c2da36f22b215a22811ae8fdb81c805
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix whitespace

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



diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index 830ab53..6101147 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -4564,7 +4564,7 @@ static const SciScriptPatcherEntry sq6Signatures[] = {
 	{  true,   510, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,          sci2NumSavesPatch1 },
 	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,          sci2NumSavesPatch2 },
-	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch },
+	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,          sci2ChangeDirPatch },
 	SCI_SIGNATUREENTRY_TERMINATOR
 };
 


Commit: cf07e0cb7c091e8f265e852d8a90a074f3f2c5af
    https://github.com/scummvm/scummvm/commit/cf07e0cb7c091e8f265e852d8a90a074f3f2c5af
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Ensure break to debugger works during transitions

Changed paths:
    engines/sci/graphics/transitions32.cpp



diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
index c45017e..bffbd54 100644
--- a/engines/sci/graphics/transitions32.cpp
+++ b/engines/sci/graphics/transitions32.cpp
@@ -20,6 +20,7 @@
  *
  */
 
+#include "sci/console.h"
 #include "sci/engine/segment.h"
 #include "sci/engine/seg_manager.h"
 #include "sci/engine/state.h"
@@ -118,6 +119,7 @@ void GfxTransitions32::processShowStyles() {
 
 		if (doFrameOut) {
 			g_sci->_gfxFrameout->frameOut(true);
+			g_sci->getSciDebugger()->onFrame();
 			throttle();
 		}
 	} while(continueProcessing && doFrameOut);


Commit: 43f908d68b708d01b2e0c401abec8e2c87df9f2a
    https://github.com/scummvm/scummvm/commit/43f908d68b708d01b2e0c401abec8e2c87df9f2a
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Break into debugger after frameout, not before

Changed paths:
    engines/sci/graphics/video32.cpp



diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index 62bc12e..1db6664 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -121,8 +121,8 @@ void SEQPlayer::renderFrame() const {
 	}
 
 	g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
-	g_sci->getSciDebugger()->onFrame();
 	g_sci->_gfxFrameout->frameOut(true);
+	g_sci->getSciDebugger()->onFrame();
 }
 
 #pragma mark -
@@ -409,8 +409,8 @@ void AVIPlayer::renderFrame() const {
 		}
 
 		g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
-		g_sci->getSciDebugger()->onFrame();
 		g_sci->_gfxFrameout->frameOut(true);
+		g_sci->getSciDebugger()->onFrame();
 	} else {
 		assert(surface->format.bytesPerPixel == 4);
 
@@ -856,8 +856,8 @@ void VMDPlayer::renderFrame() const {
 		}
 	} else {
 		g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
-		g_sci->getSciDebugger()->onFrame();
 		g_sci->_gfxFrameout->frameOut(true);
+		g_sci->getSciDebugger()->onFrame();
 	}
 }
 


Commit: bde709070850d8d2d027d70a5078f2777deffdac
    https://github.com/scummvm/scummvm/commit/bde709070850d8d2d027d70a5078f2777deffdac
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix kArray signature

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



diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 5e14e43..5874fe5 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -841,7 +841,7 @@ static SciKernelMapEntry s_kernelMap[] = {
 
 	{ MAP_CALL(AddPlane),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(AddScreenItem),     SIG_EVERYWHERE,           "o",                     NULL,            NULL },
-	{ MAP_CALL(Array),             SIG_EVERYWHERE,           "(.*)",                  kArray_subops,   NULL },
+	{ MAP_CALL(Array),             SIG_EVERYWHERE,           "i(.*)",                 kArray_subops,   NULL },
 	{ MAP_CALL(CreateTextBitmap),  SIG_EVERYWHERE,           "i(.*)",                 NULL,            NULL },
 	{ MAP_CALL(DeletePlane),       SIG_EVERYWHERE,           "o",                     NULL,            NULL },
 	{ MAP_CALL(DeleteScreenItem),  SIG_EVERYWHERE,           "o",                     NULL,            NULL },


Commit: 8cb994b80125fbb02a1c2d35781e96e194a56ed1
    https://github.com/scummvm/scummvm/commit/8cb994b80125fbb02a1c2d35781e96e194a56ed1
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Increase resource manager LRU cache size

This fixes high CPU utilisation playing Stooge Fighter 3 in SQ6.

Changed paths:
    engines/sci/resource.cpp



diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index fb93c3e..8826b06 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -948,7 +948,7 @@ void ResourceManager::init() {
 	// cache, leading to constant decompression of picture resources
 	// and making the renderer very slow.
 	if (getSciVersion() >= SCI_VERSION_2) {
-		_maxMemoryLRU = 2048 * 1024; // 2MiB
+		_maxMemoryLRU = 4096 * 1024; // 4MiB
 	}
 
 	switch (_viewType) {


Commit: 6290f1e5fcc241441230912c4762828500988dab
    https://github.com/scummvm/scummvm/commit/6290f1e5fcc241441230912c4762828500988dab
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Add prefix to global variable constants

Changed paths:
    engines/sci/engine/kscripts.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/state.cpp
    engines/sci/engine/vm.cpp
    engines/sci/engine/vm.h
    engines/sci/graphics/animate.cpp
    engines/sci/graphics/frameout.cpp
    engines/sci/sci.cpp



diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index 749af67..0e29ccf 100644
--- a/engines/sci/engine/kscripts.cpp
+++ b/engines/sci/engine/kscripts.cpp
@@ -238,8 +238,8 @@ reg_t kScriptID(EngineState *s, int argc, reg_t *argv) {
 	// initialized to 0, whereas it's 6 in other versions. Thus, we assign it
 	// to 6 here, fixing the speed of the introduction. Refer to bug #3102071.
 	if (g_sci->getGameId() == GID_PQ2 && script == 200 &&
-		s->variables[VAR_GLOBAL][kSpeed].isNull()) {
-		s->variables[VAR_GLOBAL][kSpeed] = make_reg(0, 6);
+		s->variables[VAR_GLOBAL][kGlobalVarSpeed].isNull()) {
+		s->variables[VAR_GLOBAL][kGlobalVarSpeed] = make_reg(0, 6);
 	}
 
 	return make_reg(scriptSeg, address);
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index d50df94..36c35e4 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -337,12 +337,12 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj)
 		if (s.isSaving()) {
 			const reg_t *globals = g_sci->getEngineState()->variables[VAR_GLOBAL];
 			if (g_sci->getGameId() == GID_SHIVERS) {
-				obj.lowScore = globals[kScore].toUint16();
-				obj.highScore = globals[kShivers1Score].toUint16();
+				obj.lowScore = globals[kGlobalVarScore].toUint16();
+				obj.highScore = globals[kGlobalVarShivers1Score].toUint16();
 				obj.avatarId = 0;
 			} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
 				obj.lowScore = obj.highScore = 0;
-				obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kEgo], SELECTOR(view));
+				obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kGlobalVarEgo], SELECTOR(view));
 			} else {
 				obj.lowScore = obj.highScore = obj.avatarId = 0;
 			}
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index 5d4eba7..cec402d 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -159,11 +159,11 @@ void EngineState::initGlobals() {
 }
 
 uint16 EngineState::currentRoomNumber() const {
-	return variables[VAR_GLOBAL][kNewRoomNo].toUint16();
+	return variables[VAR_GLOBAL][kGlobalVarNewRoomNo].toUint16();
 }
 
 void EngineState::setRoomNumber(uint16 roomNumber) {
-	variables[VAR_GLOBAL][kNewRoomNo] = make_reg(0, roomNumber);
+	variables[VAR_GLOBAL][kGlobalVarNewRoomNo] = make_reg(0, roomNumber);
 }
 
 void EngineState::shrinkStackToBase() {
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 7c7f75c..c0334f5 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -179,7 +179,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
 		// stopGroop object, which points to ego, to the new ego object. If this is not
 		// done, ego's movement will not be updated properly, so the result is
 		// unpredictable (for example in LSL5, Patti spins around instead of walking).
-		if (index == kEgo && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) {
+		if (index == kGlobalVarEgo && type == VAR_GLOBAL && getSciVersion() > SCI_VERSION_0_EARLY) {
 			reg_t stopGroopPos = s->_segMan->findObjectByName("stopGroop");
 			if (!stopGroopPos.isNull()) {	// does the game have a stopGroop object?
 				// Find the "client" member variable of the stopGroop object, and update it
@@ -200,9 +200,9 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {
 
 		s->variables[type][index] = value;
 
-		if (type == VAR_GLOBAL && index == kMessageType) {
+		if (type == VAR_GLOBAL && index == kGlobalVarMessageType) {
 			// The game is trying to change its speech/subtitle settings
-			if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kQuit] == TRUE_REG) {
+			if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) {
 				// ScummVM audio options haven't been applied yet, so apply them.
 				// We also force the ScummVM audio options when loading a game from
 				// the launcher.
diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h
index 3143355..a4ac16e 100644
--- a/engines/sci/engine/vm.h
+++ b/engines/sci/engine/vm.h
@@ -137,20 +137,18 @@ enum {
 };
 
 enum GlobalVar {
-	kEgo            = 0,
-	kGame           = 1,
-	kCurrentRoom    = 2,
-	kSpeed          = 3,  // SCI16
-	kDefaultPlane   = 3,  // SCI32
-	kQuit           = 4,
-	kPlanes         = 10, // SCI32
-	kCurrentRoomNo  = 11,
-	kPreviousRoomNo = 12,
-	kNewRoomNo      = 13,
-	kScore          = 15,
-	kFastCast       = 84, // SCI16
-	kMessageType    = 90,
-	kShivers1Score  = 349
+	kGlobalVarEgo            = 0,
+	kGlobalVarCurrentRoom    = 2,
+	kGlobalVarSpeed          = 3,  // SCI16
+	kGlobalVarQuit           = 4,
+	kGlobalVarPlanes         = 10, // SCI32
+	kGlobalVarCurrentRoomNo  = 11,
+	kGlobalVarPreviousRoomNo = 12,
+	kGlobalVarNewRoomNo      = 13,
+	kGlobalVarScore          = 15,
+	kGlobalVarFastCast       = 84, // SCI16
+	kGlobalVarMessageType    = 90,
+	kGlobalVarShivers1Score  = 349
 };
 
 /** Number of kernel calls in between gcs; should be < 50000 */
diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp
index e593484..8d92cb9 100644
--- a/engines/sci/graphics/animate.cpp
+++ b/engines/sci/graphics/animate.cpp
@@ -145,7 +145,7 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) {
 		if (_fastCastEnabled) {
 			// Check if the game has a fastCast object set
 			//  if we don't abort kAnimate processing, at least in kq5 there will be animation cels drawn into speech boxes.
-			if (!_s->variables[VAR_GLOBAL][kFastCast].isNull()) {
+			if (!_s->variables[VAR_GLOBAL][kGlobalVarFastCast].isNull()) {
 				// This normally points to an object called "fastCast",
 				// but for example in Eco Quest 1 it may also point to an object called "EventHandler" (see bug #5170)
 				// Original SCI only checked, if this global was not 0.
@@ -338,7 +338,7 @@ void GfxAnimate::applyGlobalScaling(AnimateList::iterator entry, GfxView *view)
 	int16 maxScale = readSelectorValue(_s->_segMan, entry->object, SELECTOR(maxScale));
 	int16 celHeight = view->getHeight(entry->loopNo, entry->celNo);
 	int16 maxCelHeight = (maxScale * celHeight) >> 7;
-	reg_t globalVar2 = _s->variables[VAR_GLOBAL][kCurrentRoom]; // current room object
+	reg_t globalVar2 = _s->variables[VAR_GLOBAL][kGlobalVarCurrentRoom]; // current room object
 	int16 vanishingY = readSelectorValue(_s->_segMan, globalVar2, SELECTOR(vanishingY));
 
 	int16 fixedPortY = _ports->getPort()->rect.bottom - vanishingY;
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 7410bda..c5c06c6 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -141,7 +141,7 @@ void GfxFrameout::syncWithScripts(bool addElements) {
 		return;
 
 	// Get planes list object
-	reg_t planesListObject = engineState->variables[VAR_GLOBAL][kPlanes];
+	reg_t planesListObject = engineState->variables[VAR_GLOBAL][kGlobalVarPlanes];
 	reg_t planesListElements = readSelector(segMan, planesListObject, SELECTOR(elements));
 
 	List *planesList = segMan->lookupList(planesListElements);
@@ -565,7 +565,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *show
 	Palette sourcePalette(_palette->getNextPalette());
 	alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
 
-	int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][kPreviousRoomNo].toSint16();
+	int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][kGlobalVarPreviousRoomNo].toSint16();
 
 	Common::Rect rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight);
 	_showList.add(rect);
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 4767b8a..168e3c4 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -1123,9 +1123,9 @@ void SciEngine::syncIngameAudioOptions() {
 
 		if (useGlobal90) {
 			if (subtitlesOn && !speechOn) {
-				_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 1);	// subtitles
+				_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 1);	// subtitles
 			} else if (!subtitlesOn && speechOn) {
-				_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 2);	// speech
+				_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2);	// speech
 			} else if (subtitlesOn && speechOn) {
 				// Is it a game that supports simultaneous speech and subtitles?
 				switch (_gameId) {
@@ -1146,11 +1146,11 @@ void SciEngine::syncIngameAudioOptions() {
 				// Phantasmagoria does not support simultaneous speech + subtitles
 				// Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles
 #endif // ENABLE_SCI32
-					_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 3);	// speech + subtitles
+					_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 3);	// speech + subtitles
 					break;
 				default:
 					// Game does not support speech and subtitles, set it to speech
-					_gamestate->variables[VAR_GLOBAL][kMessageType] = make_reg(0, 2);	// speech
+					_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2);	// speech
 				}
 			}
 		}
@@ -1161,7 +1161,7 @@ void SciEngine::updateScummVMAudioOptions() {
 	// Update ScummVM's speech/subtitles settings for SCI1.1 CD games,
 	// depending on the in-game settings
 	if (isCD() && getSciVersion() == SCI_VERSION_1_1) {
-		uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kMessageType].getOffset();
+		uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType].getOffset();
 
 		switch (ingameSetting) {
 		case 1:


Commit: 8fd19f84c800d42dfb81563116c78bcce2df5b3a
    https://github.com/scummvm/scummvm/commit/8fd19f84c800d42dfb81563116c78bcce2df5b3a
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI: Deduplicate call origin formatting

Changed paths:
    engines/sci/engine/selector.cpp
    engines/sci/engine/state.h
    engines/sci/engine/vm.cpp
    engines/sci/engine/vm_types.cpp
    engines/sci/graphics/celobj32.cpp
    engines/sci/graphics/paint16.cpp



diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 7912655..3ae9022 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -227,12 +227,12 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t
 
 	if ((selectorId < 0) || (selectorId > (int)g_sci->getKernel()->getSelectorNamesSize())) {
 		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
-		error("Attempt to write to invalid selector %d. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", selectorId, PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+		error("Attempt to write to invalid selector %d. Address %04x:%04x, %s", selectorId, PRINT_REG(object), origin.toString().c_str());
 	}
 
 	if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) {
 		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
-		error("Selector '%s' of object could not be written to. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+		error("Selector '%s' of object could not be written to. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str());
 	} else {
 		*address.getPointer(segMan) = value;
 #ifdef ENABLE_SCI32
@@ -255,11 +255,11 @@ void invokeSelector(EngineState *s, reg_t object, int selectorId,
 
 	if (slc_type == kSelectorNone) {
 		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
-		error("invokeSelector: Selector '%s' could not be invoked. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+		error("invokeSelector: Selector '%s' could not be invoked. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str());
 	}
 	if (slc_type == kSelectorVariable) {
 		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
-		error("invokeSelector: Attempting to invoke variable selector %s. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+		error("invokeSelector: Attempting to invoke variable selector %s. Address %04x:%04x, %s", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object), origin.toString().c_str());
 	}
 
 	for (i = 0; i < argc; i++)
@@ -288,7 +288,7 @@ SelectorType lookupSelector(SegManager *segMan, reg_t obj_location, Selector sel
 
 	if (!obj) {
 		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
-		error("lookupSelector: Attempt to send to non-object or invalid script. Address %04x:%04x, method %s::%s (room %d, script %d, localCall %x)", PRINT_REG(obj_location), origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+		error("lookupSelector: Attempt to send to non-object or invalid script. Address %04x:%04x, %s", PRINT_REG(obj_location), origin.toString().c_str());
 	}
 
 	index = obj->locateVarSelector(segMan, selectorId);
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index 36ae784..5297a17 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -104,6 +104,10 @@ struct SciCallOrigin {
 	Common::String methodName; //< The name of the method being called
 	int localCallOffset; //< The byte offset of a local script subroutine called by the origin method. -1 if not in a local subroutine.
 	int roomNr; //< The room that was loaded at the time of the call
+
+	Common::String toString() const {
+		return Common::String::format("method %s::%s (room %d, script %d, localCall %x)", objectName.c_str(), methodName.c_str(), roomNr, scriptNr, localCallOffset);
+	}
 };
 
 struct EngineState : public Common::Serializable {
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index c0334f5..9d8c5f7 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -129,16 +129,12 @@ static reg_t read_var(EngineState *s, int type, int index) {
 				if (solution.type == WORKAROUND_NONE) {
 #ifdef RELEASE_BUILD
 					// If we are running an official ScummVM release -> fake 0 in unknown cases
-					warning("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)",
-					index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(),
-					originReply.scriptNr, originReply.localCallOffset);
+					warning("Uninitialized read for temp %d from %s", index, originReply.toString().c_str());
 
 					s->variables[type][index] = NULL_REG;
 					break;
 #else
-					error("Uninitialized read for temp %d from method %s::%s (room %d, script %d, localCall %x)",
-					index, originReply.objectName.c_str(), originReply.methodName.c_str(), s->currentRoomNumber(),
-					originReply.scriptNr, originReply.localCallOffset);
+					error("Uninitialized read for temp %d from %s", index, originReply.toString().c_str());
 #endif
 				}
 				assert(solution.type == WORKAROUND_FAKE);
@@ -367,10 +363,7 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
 		case WORKAROUND_NONE: {
 			Common::String signatureDetailsStr;
 			kernel->signatureDebug(signatureDetailsStr, kernelCall.signature, argc, argv);
-			error("\n%s[VM] k%s[%x]: signature mismatch in method %s::%s (room %d, script %d, localCall 0x%x)",
-				signatureDetailsStr.c_str(),
-				kernelCall.name, kernelCallNr, originReply.objectName.c_str(), originReply.methodName.c_str(),
-				s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
+			error("\n%s[VM] k%s[%x]: signature mismatch in %s", signatureDetailsStr.c_str(), kernelCall.name, kernelCallNr, originReply.toString().c_str());
 			break;
 			}
 		case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone
@@ -438,15 +431,13 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
 				int callNameLen = strlen(kernelCall.name);
 				if (strncmp(kernelCall.name, kernelSubCall.name, callNameLen) == 0) {
 					const char *subCallName = kernelSubCall.name + callNameLen;
-					error("\n%s[VM] k%s(%s): signature mismatch in method %s::%s (room %d, script %d, localCall %x)",
-						signatureDetailsStr.c_str(),
-						kernelCall.name, subCallName, originReply.objectName.c_str(), originReply.methodName.c_str(),
-						s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
+					error("\n%s[VM] k%s(%s): signature mismatch in %s",
+						signatureDetailsStr.c_str(), kernelCall.name, subCallName,
+						originReply.toString().c_str());
 				}
-				error("\n%s[VM] k%s: signature mismatch in method %s::%s (room %d, script %d, localCall %x)",
-					signatureDetailsStr.c_str(),
-					kernelSubCall.name, originReply.objectName.c_str(), originReply.methodName.c_str(),
-					s->currentRoomNumber(), originReply.scriptNr, originReply.localCallOffset);
+				error("\n%s[VM] k%s: signature mismatch in %s",
+					signatureDetailsStr.c_str(), kernelSubCall.name,
+					originReply.toString().c_str());
 				break;
 			}
 			case WORKAROUND_IGNORE: // don't do kernel call, leave acc alone
diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp
index 1076863..b2e250a 100644
--- a/engines/sci/engine/vm_types.cpp
+++ b/engines/sci/engine/vm_types.cpp
@@ -69,10 +69,7 @@ reg_t reg_t::lookForWorkaround(const reg_t right, const char *operation) const {
 	SciCallOrigin originReply;
 	SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply);
 	if (solution.type == WORKAROUND_NONE)
-		error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from method %s::%s (room %d, script %d, localCall %x)",
-		operation, PRINT_REG(*this), PRINT_REG(right), originReply.objectName.c_str(),
-		originReply.methodName.c_str(), g_sci->getEngineState()->currentRoomNumber(), originReply.scriptNr,
-		originReply.localCallOffset);
+		error("Invalid arithmetic operation (%s - params: %04x:%04x and %04x:%04x) from %s", operation, PRINT_REG(*this), PRINT_REG(right), originReply.toString().c_str());
 	assert(solution.type == WORKAROUND_FAKE);
 	return make_reg(0, solution.value);
 }
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index e514924..5aa6ea3 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -824,7 +824,7 @@ int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) {
 		SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kNumCels_workarounds, &origin);
 		switch (solution.type) {
 		case WORKAROUND_NONE:
-			error("[CelObjView::getNumCels]: loop number is equal to loop count in method %s::%s (room %d, script %d, localCall %x)", origin.objectName.c_str(), origin.methodName.c_str(), origin.roomNr, origin.scriptNr, origin.localCallOffset);
+			error("[CelObjView::getNumCels]: loop number is equal to loop count in %s", origin.toString().c_str());
 		case WORKAROUND_FAKE:
 			return (int16)solution.value;
 		case WORKAROUND_IGNORE:
diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp
index 53449fb..aac922d 100644
--- a/engines/sci/graphics/paint16.cpp
+++ b/engines/sci/graphics/paint16.cpp
@@ -544,9 +544,7 @@ reg_t GfxPaint16::kernelDisplay(const char *text, uint16 languageSplitter, int a
 			SciCallOrigin originReply;
 			SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, kDisplay_workarounds, &originReply);
 			if (solution.type == WORKAROUND_NONE)
-				error("Unknown kDisplay argument (%04x:%04x) from method %s::%s (script %d, localCall %x)",
-						PRINT_REG(displayArg), originReply.objectName.c_str(), originReply.methodName.c_str(),
-						originReply.scriptNr, originReply.localCallOffset);
+				error("Unknown kDisplay argument (%04x:%04x) from %s", PRINT_REG(displayArg), originReply.toString().c_str());
 			assert(solution.type == WORKAROUND_IGNORE);
 			break;
 		}


Commit: 7ad9418583a6a5a15356d1ee6920d2cfa56f7d4d
    https://github.com/scummvm/scummvm/commit/7ad9418583a6a5a15356d1ee6920d2cfa56f7d4d
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix off-by-one error in palette fades

Changed paths:
    engines/sci/graphics/palette32.cpp



diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index db81669..135badb 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -810,21 +810,20 @@ void GfxPalette32::applyCycles() {
 #pragma mark -
 #pragma mark Fading
 
-// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call
-// setFade with numColorsToFade set to 256, but other parts of the engine like
-// processShowStyleNone use 255 instead of 256. It is not clear if this is because
-// the last palette entry is intentionally left unmodified, or if this is a bug
-// in the engine. It certainly seems confused because all other places that accept
-// color ranges typically receive values in the range of 0–255.
-void GfxPalette32::setFade(uint16 percent, uint8 fromColor, uint16 numColorsToFade) {
-	if (fromColor > numColorsToFade) {
+void GfxPalette32::setFade(uint16 percent, uint8 fromColor, uint16 toColor) {
+	if (fromColor > toColor) {
 		return;
 	}
 
-	assert(numColorsToFade <= ARRAYSIZE(_fadeTable));
+	// Some game scripts (like SQ6 Sierra logo and main menu) incorrectly call
+	// setFade with toColor set to 256
+	if (toColor > 255) {
+		toColor = 255;
+	}
 
-	for (int i = fromColor; i < numColorsToFade; i++)
+	for (int i = fromColor; i <= toColor; i++) {
 		_fadeTable[i] = percent;
+	}
 }
 
 void GfxPalette32::fadeOff() {


Commit: e412eaffbc47e5087401dc57912863bd59dcbf48
    https://github.com/scummvm/scummvm/commit/e412eaffbc47e5087401dc57912863bd59dcbf48
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Clean up and document GfxPalette32

Changed paths:
    engines/sci/engine/kgraphics32.cpp
    engines/sci/graphics/celobj32.cpp
    engines/sci/graphics/palette32.cpp
    engines/sci/graphics/palette32.h



diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index cceb710..49215b2 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -961,17 +961,17 @@ reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) {
-	uint16 fromColor = argv[0].toUint16();
-	uint16 toColor = argv[1].toUint16();
-	uint16 percent = argv[2].toUint16();
+	const uint16 fromColor = argv[0].toUint16();
+	const uint16 toColor = argv[1].toUint16();
+	const uint16 percent = argv[2].toUint16();
 	g_sci->_gfxPalette32->setFade(percent, fromColor, toColor);
 	return s->r_acc;
 }
 
 reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) {
-	GuiResourceId paletteId = argv[0].toUint16();
-	int time = argc > 1 ? argv[1].toSint16() * 60 : 0;
-	int16 percent = argc > 2 ? argv[2].toSint16() : 100;
+	const GuiResourceId paletteId = argv[0].toUint16();
+	const int32 time = argc > 1 ? argv[1].toSint16() * 60 : 0;
+	const int16 percent = argc > 2 ? argv[2].toSint16() : 100;
 	int16 fromColor;
 	int16 toColor;
 
@@ -987,9 +987,9 @@ reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv) {
-	int time = argc > 0 ? argv[0].toSint16() * 60 : 0;
-	int16 percent = argc > 1 ? argv[1].toSint16() : 0;
-	g_sci->_gfxPalette32->setVaryPercent(percent, time, -1, -1);
+	const int32 time = argc > 0 ? argv[0].toSint16() * 60 : 0;
+	const int16 percent = argc > 1 ? argv[1].toSint16() : 0;
+	g_sci->_gfxPalette32->setVaryPercent(percent, time);
 	return s->r_acc;
 }
 
@@ -1003,31 +1003,31 @@ reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv) {
 }
 
 reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) {
-	GuiResourceId paletteId = argv[0].toUint16();
+	const GuiResourceId paletteId = argv[0].toUint16();
 	g_sci->_gfxPalette32->kernelPalVaryMergeTarget(paletteId);
 	return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
 }
 
 reg_t kPalVarySetTime(EngineState *s, int argc, reg_t *argv) {
-	int time = argv[0].toSint16() * 60;
+	const int32 time = argv[0].toSint16() * 60;
 	g_sci->_gfxPalette32->setVaryTime(time);
 	return s->r_acc;
 }
 
 reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv) {
-	GuiResourceId paletteId = argv[0].toUint16();
+	const GuiResourceId paletteId = argv[0].toUint16();
 	g_sci->_gfxPalette32->kernelPalVarySetTarget(paletteId);
 	return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
 }
 
 reg_t kPalVarySetStart(EngineState *s, int argc, reg_t *argv) {
-	GuiResourceId paletteId = argv[0].toUint16();
+	const GuiResourceId paletteId = argv[0].toUint16();
 	g_sci->_gfxPalette32->kernelPalVarySetStart(paletteId);
 	return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
 }
 
 reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv) {
-	GuiResourceId paletteId = argv[0].toUint16();
+	const GuiResourceId paletteId = argv[0].toUint16();
 	g_sci->_gfxPalette32->kernelPalVaryMergeStart(paletteId);
 	return make_reg(0, g_sci->_gfxPalette32->getVaryPercent());
 }
@@ -1043,7 +1043,6 @@ reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv) {
 	const uint16 toColor = argv[1].toUint16();
 	const int16 direction = argv[2].toSint16();
 	const uint16 delay = argc > 3 ? argv[3].toUint16() : 0;
-
 	g_sci->_gfxPalette32->setCycle(fromColor, toColor, direction, delay);
 	return s->r_acc;
 }
@@ -1051,7 +1050,6 @@ reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv) {
 reg_t kPalCycleDoCycle(EngineState *s, int argc, reg_t *argv) {
 	const uint16 fromColor = argv[0].toUint16();
 	const int16 speed = argc > 1 ? argv[1].toSint16() : 1;
-
 	g_sci->_gfxPalette32->doCycle(fromColor, speed);
 	return s->r_acc;
 }
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 5aa6ea3..7514052 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -568,7 +568,7 @@ uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
 
 void CelObj::submitPalette() const {
 	if (_hunkPaletteOffset) {
-		HunkPalette palette(getResPointer() + _hunkPaletteOffset);
+		const HunkPalette palette(getResPointer() + _hunkPaletteOffset);
 		g_sci->_gfxPalette32->submit(palette);
 	}
 }
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 135badb..6b719f8 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -52,7 +52,7 @@ HunkPalette::HunkPalette(byte *rawPalette) :
 	}
 }
 
-void HunkPalette::setVersion(const uint32 version) {
+void HunkPalette::setVersion(const uint32 version) const {
 	if (_numPalettes != _data[10]) {
 		error("Invalid HunkPalette");
 	}
@@ -93,9 +93,9 @@ const Palette HunkPalette::toPalette() const {
 
 	if (_numPalettes) {
 		const EntryHeader header = getEntryHeader();
-		byte *data = getPalPointer() + kEntryHeaderSize;
+		const byte *data = getPalPointer() + kEntryHeaderSize;
 
-		int16 end = header.startColor + header.numColors;
+		const int16 end = header.startColor + header.numColors;
 		assert(end <= 256);
 
 		if (header.sharedUsed) {
@@ -118,20 +118,19 @@ const Palette HunkPalette::toPalette() const {
 	return outPalette;
 }
 
-
 #pragma mark -
 #pragma mark GfxPalette32
 
 GfxPalette32::GfxPalette32(ResourceManager *resMan)
 	: _resMan(resMan),
+
 	// Palette versioning
 	_version(1),
 	_needsUpdate(false),
 	_currentPalette(),
 	_sourcePalette(),
 	_nextPalette(),
-	// Clut
-	_clutTable(nullptr),
+
 	// Palette varying
 	_varyStartPalette(nullptr),
 	_varyTargetPalette(nullptr),
@@ -142,6 +141,7 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan)
 	_varyDirection(0),
 	_varyTargetPercent(0),
 	_varyNumTimesPaused(0),
+
 	// Palette cycling
 	_cyclers(),
 	_cycleMap() {
@@ -161,44 +161,6 @@ GfxPalette32::~GfxPalette32() {
 	cycleAllOff();
 }
 
-inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
-	// The last color is always white, so it is not copied.
-	// (Some palettes try to set the last color, which causes
-	// churning in the palettes when they are merged)
-	for (int i = 0, len = ARRAYSIZE(to->colors) - 1; i < len; ++i) {
-		if (from->colors[i].used) {
-			to->colors[i] = from->colors[i];
-		}
-	}
-}
-
-void GfxPalette32::submit(const Palette &palette) {
-	const Palette oldSourcePalette(_sourcePalette);
-	mergePaletteInternal(&_sourcePalette, &palette);
-
-	if (!_needsUpdate && _sourcePalette != oldSourcePalette) {
-		++_version;
-		_needsUpdate = true;
-	}
-}
-
-void GfxPalette32::submit(HunkPalette &hunkPalette) {
-	if (hunkPalette.getVersion() == _version) {
-		return;
-	}
-
-	const Palette oldSourcePalette(_sourcePalette);
-	const Palette palette = hunkPalette.toPalette();
-	mergePaletteInternal(&_sourcePalette, &palette);
-
-	if (!_needsUpdate && oldSourcePalette != _sourcePalette) {
-		++_version;
-		_needsUpdate = true;
-	}
-
-	hunkPalette.setVersion(_version);
-}
-
 bool GfxPalette32::loadPalette(const GuiResourceId resourceId) {
 	Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
 
@@ -206,7 +168,7 @@ bool GfxPalette32::loadPalette(const GuiResourceId resourceId) {
 		return false;
 	}
 
-	HunkPalette palette(palResource->data);
+	const HunkPalette palette(palResource->data);
 	submit(palette);
 	return true;
 }
@@ -241,6 +203,33 @@ int16 GfxPalette32::matchColor(const uint8 r, const uint8 g, const uint8 b) {
 	return bestIndex;
 }
 
+void GfxPalette32::submit(const Palette &palette) {
+	const Palette oldSourcePalette(_sourcePalette);
+	mergePalette(_sourcePalette, palette);
+
+	if (!_needsUpdate && _sourcePalette != oldSourcePalette) {
+		++_version;
+		_needsUpdate = true;
+	}
+}
+
+void GfxPalette32::submit(const HunkPalette &hunkPalette) {
+	if (hunkPalette.getVersion() == _version) {
+		return;
+	}
+
+	const Palette oldSourcePalette(_sourcePalette);
+	const Palette palette = hunkPalette.toPalette();
+	mergePalette(_sourcePalette, palette);
+
+	if (!_needsUpdate && oldSourcePalette != _sourcePalette) {
+		++_version;
+		_needsUpdate = true;
+	}
+
+	hunkPalette.setVersion(_version);
+}
+
 bool GfxPalette32::updateForFrame() {
 	applyAll();
 	_needsUpdate = false;
@@ -266,10 +255,10 @@ void GfxPalette32::updateHardware(const bool updateScreen) {
 		_currentPalette.colors[i] = _nextPalette.colors[i];
 
 		// NOTE: If the brightness option in the user configuration file is set,
-		// SCI engine adjusts palette brightnesses here by mapping RGB values to values
-		// in some hard-coded brightness tables. There is no reason on modern hardware
-		// to implement this, unless it is discovered that some game uses a non-standard
-		// brightness setting by default
+		// SCI engine adjusts palette brightnesses here by mapping RGB values to
+		// values in some hard-coded brightness tables. There is no reason on
+		// modern hardware to implement this, unless it is discovered that some
+		// game uses a non-standard brightness setting by default
 
 		// All color entries MUST be copied, not just "used" entries, otherwise
 		// uninitialised memory from bpal makes its way into the system palette.
@@ -299,6 +288,29 @@ void GfxPalette32::updateHardware(const bool updateScreen) {
 	}
 }
 
+Palette GfxPalette32::getPaletteFromResource(const GuiResourceId resourceId) const {
+	Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
+
+	if (!palResource) {
+		error("Could not load vary palette %d", resourceId);
+	}
+
+	const HunkPalette rawPalette(palResource->data);
+	return rawPalette.toPalette();
+}
+
+void GfxPalette32::mergePalette(Palette &to, const Palette &from) {
+	// The last color is always white in SCI, so it is not copied. (Some
+	// palettes, particularly in KQ7, try to set the last color, which causes
+	// unnecessary palette updates since the last color is forced by SSCI to a
+	// specific value)
+	for (int i = 0; i < ARRAYSIZE(to.colors) - 1; ++i) {
+		if (from.colors[i].used) {
+			to.colors[i] = from.colors[i];
+		}
+	}
+}
+
 void GfxPalette32::applyAll() {
 	applyVary();
 	applyCycles();
@@ -306,81 +318,43 @@ void GfxPalette32::applyAll() {
 }
 
 #pragma mark -
-#pragma mark Colour look-up
-
-#ifdef ENABLE_SCI3_GAMES
-bool GfxPalette32::loadClut(uint16 clutId) {
-	// loadClut() will load a color lookup table from a clu file and set
-	// the palette found in the file. This is to be used with Phantasmagoria 2.
-
-	unloadClut();
-
-	Common::String filename = Common::String::format("%d.clu", clutId);
-	Common::File clut;
-
-	if (!clut.open(filename) || clut.size() != 0x10000 + 236 * 3)
-		return false;
-
-	// Read in the lookup table
-	// It maps each RGB565 color to a palette index
-	_clutTable = new byte[0x10000];
-	clut.read(_clutTable, 0x10000);
+#pragma mark Varying
 
-	Palette pal;
-	memset(&pal, 0, sizeof(Palette));
+void GfxPalette32::setVary(const Palette &target, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor) {
+	setTarget(target);
+	setVaryTime(percent, ticks);
 
-	// Setup 1:1 mapping
-	for (int i = 0; i < 256; i++) {
-		pal.mapping[i] = i;
+	if (fromColor > -1) {
+		_varyFromColor = fromColor;
 	}
-
-	// Now load in the palette
-	for (int i = 1; i <= 236; i++) {
-		pal.colors[i].used = 1;
-		pal.colors[i].r = clut.readByte();
-		pal.colors[i].g = clut.readByte();
-		pal.colors[i].b = clut.readByte();
+	if (toColor > -1) {
+		assert(toColor < 256);
+		_varyToColor = toColor;
 	}
-
-	set(&pal, true);
-	setOnScreen();
-	return true;
 }
 
-byte GfxPalette32::matchClutColor(uint16 color) {
-	// Match a color in RGB565 format to a palette index based on the loaded CLUT
-	assert(_clutTable);
-	return _clutTable[color];
-}
+void GfxPalette32::setVaryPercent(const int16 percent, const int32 ticks) {
+	if (_varyTargetPalette != nullptr) {
+		setVaryTime(percent, ticks);
+	}
 
-void GfxPalette32::unloadClut() {
-	// This will only unload the actual table, but not reset any palette
-	delete[] _clutTable;
-	_clutTable = nullptr;
+	// NOTE: SSCI had two additional parameters for this function to change the
+	// `_varyFromColor`, but they were always hardcoded to be ignored
 }
-#endif
 
-#pragma mark -
-#pragma mark Varying
-
-inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId resourceId) const {
-	Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false);
-
-	if (!palResource) {
-		error("Could not load vary palette %d", resourceId);
+void GfxPalette32::setVaryTime(const int32 time) {
+	if (_varyTargetPalette != nullptr) {
+		setVaryTime(_varyTargetPercent, time);
 	}
-
-	HunkPalette rawPalette(palResource->data);
-	return rawPalette.toPalette();
 }
 
-inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int time) {
+void GfxPalette32::setVaryTime(const int16 percent, const int32 ticks) {
 	_varyLastTick = g_sci->getTickCount();
-	if (!time || _varyPercent == percent) {
+	if (!ticks || _varyPercent == percent) {
 		_varyDirection = 0;
 		_varyTargetPercent = _varyPercent = percent;
 	} else {
-		_varyTime = time / (percent - _varyPercent);
+		_varyTime = ticks / (percent - _varyPercent);
 		_varyTargetPercent = percent;
 
 		if (_varyTime > 0) {
@@ -395,72 +369,6 @@ inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int tim
 	}
 }
 
-void GfxPalette32::kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor) {
-	Palette palette = getPaletteFromResourceInternal(paletteId);
-	setVary(&palette, percent, time, fromColor, toColor);
-}
-
-void GfxPalette32::kernelPalVaryMergeTarget(GuiResourceId paletteId) {
-	Palette palette = getPaletteFromResourceInternal(paletteId);
-	mergeTarget(&palette);
-}
-
-void GfxPalette32::kernelPalVarySetTarget(GuiResourceId paletteId) {
-	Palette palette = getPaletteFromResourceInternal(paletteId);
-	setTarget(&palette);
-}
-
-void GfxPalette32::kernelPalVarySetStart(GuiResourceId paletteId) {
-	Palette palette = getPaletteFromResourceInternal(paletteId);
-	setStart(&palette);
-}
-
-void GfxPalette32::kernelPalVaryMergeStart(GuiResourceId paletteId) {
-	Palette palette = getPaletteFromResourceInternal(paletteId);
-	mergeStart(&palette);
-}
-
-void GfxPalette32::kernelPalVaryPause(bool pause) {
-	if (pause) {
-		varyPause();
-	} else {
-		varyOn();
-	}
-}
-
-void GfxPalette32::setVary(const Palette *const target, const int16 percent, const int time, const int16 fromColor, const int16 toColor) {
-	setTarget(target);
-	setVaryTimeInternal(percent, time);
-
-	if (fromColor > -1) {
-		_varyFromColor = fromColor;
-	}
-	if (toColor > -1) {
-		assert(toColor < 256);
-		_varyToColor = toColor;
-	}
-}
-
-void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate) {
-	if (_varyTargetPalette != nullptr) {
-		setVaryTimeInternal(percent, time);
-	}
-
-	// This looks like a mistake in the actual SCI engine (both SQ6 and Lighthouse);
-	// the values are always hardcoded to -1 in kPalVary, so this code can never
-	// actually be executed
-	if (fromColor > -1) {
-		_varyFromColor = fromColor;
-	}
-	if (fromColorAlternate > -1) {
-		_varyFromColor = fromColorAlternate;
-	}
-}
-
-int16 GfxPalette32::getVaryPercent() const {
-	return ABS(_varyPercent);
-}
-
 void GfxPalette32::varyOff() {
 	_varyNumTimesPaused = 0;
 	_varyPercent = _varyTargetPercent = 0;
@@ -479,14 +387,6 @@ void GfxPalette32::varyOff() {
 	}
 }
 
-void GfxPalette32::mergeTarget(const Palette *const palette) {
-	if (_varyTargetPalette != nullptr) {
-		mergePaletteInternal(_varyTargetPalette, palette);
-	} else {
-		_varyTargetPalette = new Palette(*palette);
-	}
-}
-
 void GfxPalette32::varyPause() {
 	_varyDirection = 0;
 	++_varyNumTimesPaused;
@@ -497,51 +397,44 @@ void GfxPalette32::varyOn() {
 		--_varyNumTimesPaused;
 	}
 
-	if (_varyTargetPalette != nullptr && _varyNumTimesPaused == 0 && _varyPercent != _varyTargetPercent) {
-		if (_varyTime == 0) {
-			_varyPercent = _varyTargetPercent;
-		} else if (_varyTargetPercent < _varyPercent) {
-			_varyDirection = -1;
+	if (_varyTargetPalette != nullptr && _varyNumTimesPaused == 0) {
+		if (_varyPercent != _varyTargetPercent && _varyTime != 0) {
+			_varyDirection = (_varyTargetPercent - _varyPercent > 0) ? 1 : -1;
 		} else {
-			_varyDirection = 1;
+			_varyPercent = _varyTargetPercent;
 		}
 	}
 }
 
-void GfxPalette32::setVaryTime(const int time) {
-	if (_varyTargetPalette == nullptr) {
-		return;
-	}
-
-	setVaryTimeInternal(_varyTargetPercent, time);
+void GfxPalette32::setTarget(const Palette &palette) {
+	delete _varyTargetPalette;
+	_varyTargetPalette = new Palette(palette);
 }
 
-void GfxPalette32::setTarget(const Palette *const palette) {
-	if (_varyTargetPalette != nullptr) {
-		delete _varyTargetPalette;
-	}
-
-	_varyTargetPalette = new Palette(*palette);
+void GfxPalette32::setStart(const Palette &palette) {
+	delete _varyStartPalette;
+	_varyStartPalette = new Palette(palette);
 }
 
-void GfxPalette32::setStart(const Palette *const palette) {
+void GfxPalette32::mergeStart(const Palette &palette) {
 	if (_varyStartPalette != nullptr) {
-		delete _varyStartPalette;
+		mergePalette(*_varyStartPalette, palette);
+	} else {
+		_varyStartPalette = new Palette(palette);
 	}
-
-	_varyStartPalette = new Palette(*palette);
 }
 
-void GfxPalette32::mergeStart(const Palette *const palette) {
-	if (_varyStartPalette != nullptr) {
-		mergePaletteInternal(_varyStartPalette, palette);
+void GfxPalette32::mergeTarget(const Palette &palette) {
+	if (_varyTargetPalette != nullptr) {
+		mergePalette(*_varyTargetPalette, palette);
 	} else {
-		_varyStartPalette = new Palette(*palette);
+		_varyTargetPalette = new Palette(palette);
 	}
 }
 
 void GfxPalette32::applyVary() {
-	while (g_sci->getTickCount() - _varyLastTick > (uint32)_varyTime && _varyDirection != 0) {
+	const uint32 now = g_sci->getTickCount();
+	while ((int32)(now - _varyLastTick) > _varyTime && _varyDirection != 0) {
 		_varyLastTick += _varyTime;
 
 		if (_varyPercent == _varyTargetPercent) {
@@ -552,7 +445,7 @@ void GfxPalette32::applyVary() {
 	}
 
 	if (_varyPercent == 0 || _varyTargetPalette == nullptr) {
-		for (int i = 0, len = ARRAYSIZE(_nextPalette.colors); i < len; ++i) {
+		for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
 			if (_varyStartPalette != nullptr && i >= _varyFromColor && i <= _varyToColor) {
 				_nextPalette.colors[i] = _varyStartPalette->colors[i];
 			} else {
@@ -560,7 +453,7 @@ void GfxPalette32::applyVary() {
 			}
 		}
 	} else {
-		for (int i = 0, len = ARRAYSIZE(_nextPalette.colors); i < len; ++i) {
+		for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
 			if (i >= _varyFromColor && i <= _varyToColor) {
 				Color targetColor = _varyTargetPalette->colors[i];
 				Color sourceColor;
@@ -591,82 +484,67 @@ void GfxPalette32::applyVary() {
 	}
 }
 
-#pragma mark -
-#pragma mark Cycling
-
-inline void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) {
-	bool *mapEntry = _cycleMap + fromColor;
-	const bool *lastEntry = _cycleMap + numColorsToClear;
-	while (mapEntry < lastEntry) {
-		*mapEntry++ = false;
-	}
+void GfxPalette32::kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor) {
+	const Palette palette = getPaletteFromResource(paletteId);
+	setVary(palette, percent, ticks, fromColor, toColor);
 }
 
-inline void GfxPalette32::setCycleMap(const uint16 fromColor, const uint16 numColorsToSet) {
-	bool *mapEntry = _cycleMap + fromColor;
-	const bool *lastEntry = _cycleMap + numColorsToSet;
-	while (mapEntry < lastEntry) {
-		if (*mapEntry != false) {
-			error("Cycles intersect");
-		}
-		*mapEntry++ = true;
-	}
+void GfxPalette32::kernelPalVaryMergeTarget(const GuiResourceId paletteId) {
+	const Palette palette = getPaletteFromResource(paletteId);
+	mergeTarget(palette);
 }
 
-inline PalCycler *GfxPalette32::getCycler(const uint16 fromColor) {
-	const int numCyclers = ARRAYSIZE(_cyclers);
-
-	for (int cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
-		PalCycler *cycler = _cyclers[cyclerIndex];
-		if (cycler != nullptr && cycler->fromColor == fromColor) {
-			return cycler;
-		}
-	}
+void GfxPalette32::kernelPalVarySetTarget(const GuiResourceId paletteId) {
+	const Palette palette = getPaletteFromResource(paletteId);
+	setTarget(palette);
+}
 
-	return nullptr;
+void GfxPalette32::kernelPalVarySetStart(const GuiResourceId paletteId) {
+	const Palette palette = getPaletteFromResource(paletteId);
+	setStart(palette);
 }
 
-inline void doCycleInternal(PalCycler *cycler, const int16 speed) {
-	int16 currentCycle = cycler->currentCycle;
-	const uint16 numColorsToCycle = cycler->numColorsToCycle;
+void GfxPalette32::kernelPalVaryMergeStart(const GuiResourceId paletteId) {
+	const Palette palette = getPaletteFromResource(paletteId);
+	mergeStart(palette);
+}
 
-	if (cycler->direction == 0) {
-		currentCycle = (currentCycle - (speed % numColorsToCycle)) + numColorsToCycle;
+void GfxPalette32::kernelPalVaryPause(const bool pause) {
+	if (pause) {
+		varyPause();
 	} else {
-		currentCycle = currentCycle + speed;
+		varyOn();
 	}
-
-	cycler->currentCycle = (uint8) (currentCycle % numColorsToCycle);
 }
 
+#pragma mark -
+#pragma mark Cycling
+
 void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay) {
 	assert(fromColor < toColor);
 
-	int cyclerIndex;
-	const int numCyclers = ARRAYSIZE(_cyclers);
-
 	PalCycler *cycler = getCycler(fromColor);
 
 	if (cycler != nullptr) {
 		clearCycleMap(fromColor, cycler->numColorsToCycle);
 	} else {
-		for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
-			if (_cyclers[cyclerIndex] == nullptr) {
-				cycler = new PalCycler;
-				_cyclers[cyclerIndex] = cycler;
+		for (int i = 0; i < kNumCyclers; ++i) {
+			if (_cyclers[i] == nullptr) {
+				_cyclers[i] = cycler = new PalCycler;
 				break;
 			}
 		}
 	}
 
-	// SCI engine overrides the first oldest cycler that it finds where
-	// “oldest” is determined by the difference between the tick and now
+	// If there are no free cycler slots, SCI engine overrides the first oldest
+	// cycler that it finds, where "oldest" is determined by the difference
+	// between the tick and now
 	if (cycler == nullptr) {
 		const uint32 now = g_sci->getTickCount();
 		uint32 minUpdateDelta = 0xFFFFFFFF;
 
-		for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) {
-			PalCycler *candidate = _cyclers[cyclerIndex];
+		for (int i = 0; i < kNumCyclers; ++i) {
+			PalCycler *const candidate = _cyclers[i];
 
 			const uint32 updateDelta = now - candidate->lastUpdateTick;
 			if (updateDelta < minUpdateDelta) {
@@ -678,11 +556,11 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in
 		clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
 	}
 
-	const uint16 numColorsToCycle = 1 + ((uint8) toColor) - fromColor;
-	cycler->fromColor = (uint8) fromColor;
-	cycler->numColorsToCycle = (uint8) numColorsToCycle;
-	cycler->currentCycle = (uint8) fromColor;
-	cycler->direction = direction < 0 ? PalCycleBackward : PalCycleForward;
+	const uint16 numColorsToCycle = toColor - fromColor + 1;
+	cycler->fromColor = fromColor;
+	cycler->numColorsToCycle = numColorsToCycle;
+	cycler->currentCycle = fromColor;
+	cycler->direction = direction < 0 ? kPalCycleBackward : kPalCycleForward;
 	cycler->delay = delay;
 	cycler->lastUpdateTick = g_sci->getTickCount();
 	cycler->numTimesPaused = 0;
@@ -691,30 +569,30 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in
 }
 
 void GfxPalette32::doCycle(const uint8 fromColor, const int16 speed) {
-	PalCycler *cycler = getCycler(fromColor);
+	PalCycler *const cycler = getCycler(fromColor);
 	if (cycler != nullptr) {
 		cycler->lastUpdateTick = g_sci->getTickCount();
-		doCycleInternal(cycler, speed);
+		updateCycler(*cycler, speed);
 	}
 }
 
 void GfxPalette32::cycleOn(const uint8 fromColor) {
-	PalCycler *cycler = getCycler(fromColor);
+	PalCycler *const cycler = getCycler(fromColor);
 	if (cycler != nullptr && cycler->numTimesPaused > 0) {
 		--cycler->numTimesPaused;
 	}
 }
 
 void GfxPalette32::cyclePause(const uint8 fromColor) {
-	PalCycler *cycler = getCycler(fromColor);
+	PalCycler *const cycler = getCycler(fromColor);
 	if (cycler != nullptr) {
 		++cycler->numTimesPaused;
 	}
 }
 
 void GfxPalette32::cycleAllOn() {
-	for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
-		PalCycler *cycler = _cyclers[i];
+	for (int i = 0; i < kNumCyclers; ++i) {
+		PalCycler *const cycler = _cyclers[i];
 		if (cycler != nullptr && cycler->numTimesPaused > 0) {
 			--cycler->numTimesPaused;
 		}
@@ -724,8 +602,8 @@ void GfxPalette32::cycleAllOn() {
 void GfxPalette32::cycleAllPause() {
 	// NOTE: The original engine did not check for null pointers in the
 	// palette cyclers pointer array.
-	for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
-		PalCycler *cycler = _cyclers[i];
+	for (int i = 0; i < kNumCyclers; ++i) {
+		PalCycler *const cycler = _cyclers[i];
 		if (cycler != nullptr) {
 			// This seems odd, because currentCycle is 0..numColorsPerCycle,
 			// but fromColor is 0..255. When applyAllCycles runs, the values
@@ -736,8 +614,8 @@ void GfxPalette32::cycleAllPause() {
 
 	applyAllCycles();
 
-	for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
-		PalCycler *cycler = _cyclers[i];
+	for (int i = 0; i < kNumCyclers; ++i) {
+		PalCycler *const cycler = _cyclers[i];
 		if (cycler != nullptr) {
 			++cycler->numTimesPaused;
 		}
@@ -745,8 +623,8 @@ void GfxPalette32::cycleAllPause() {
 }
 
 void GfxPalette32::cycleOff(const uint8 fromColor) {
-	for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
-		PalCycler *cycler = _cyclers[i];
+	for (int i = 0; i < kNumCyclers; ++i) {
+		PalCycler *const cycler = _cyclers[i];
 		if (cycler != nullptr && cycler->fromColor == fromColor) {
 			clearCycleMap(fromColor, cycler->numColorsToCycle);
 			delete cycler;
@@ -757,8 +635,8 @@ void GfxPalette32::cycleOff(const uint8 fromColor) {
 }
 
 void GfxPalette32::cycleAllOff() {
-	for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
-		PalCycler *cycler = _cyclers[i];
+	for (int i = 0; i < kNumCyclers; ++i) {
+		PalCycler *const cycler = _cyclers[i];
 		if (cycler != nullptr) {
 			clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
 			delete cycler;
@@ -767,16 +645,57 @@ void GfxPalette32::cycleAllOff() {
 	}
 }
 
+void GfxPalette32::updateCycler(PalCycler &cycler, const int16 speed) {
+	int16 currentCycle = cycler.currentCycle;
+	const uint16 numColorsToCycle = cycler.numColorsToCycle;
+
+	if (cycler.direction == kPalCycleBackward) {
+		currentCycle = (currentCycle - (speed % numColorsToCycle)) + numColorsToCycle;
+	} else {
+		currentCycle = currentCycle + speed;
+	}
+
+	cycler.currentCycle = currentCycle % numColorsToCycle;
+}
+
+void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) {
+	bool *mapEntry = _cycleMap + fromColor;
+	const bool *const lastEntry = _cycleMap + numColorsToClear;
+	while (mapEntry < lastEntry) {
+		*mapEntry++ = false;
+	}
+}
+
+void GfxPalette32::setCycleMap(const uint16 fromColor, const uint16 numColorsToSet) {
+	bool *mapEntry = _cycleMap + fromColor;
+	const bool *const lastEntry = _cycleMap + numColorsToSet;
+	while (mapEntry < lastEntry) {
+		if (*mapEntry != false) {
+			error("Cycles intersect");
+		}
+		*mapEntry++ = true;
+	}
+}
+
+PalCycler *GfxPalette32::getCycler(const uint16 fromColor) {
+	for (int cyclerIndex = 0; cyclerIndex < kNumCyclers; ++cyclerIndex) {
+		PalCycler *cycler = _cyclers[cyclerIndex];
+		if (cycler != nullptr && cycler->fromColor == fromColor) {
+			return cycler;
+		}
+	}
+
+	return nullptr;
+}
+
 void GfxPalette32::applyAllCycles() {
 	Color paletteCopy[256];
 	memcpy(paletteCopy, _nextPalette.colors, sizeof(Color) * 256);
 
-	for (int cyclerIndex = 0, numCyclers = ARRAYSIZE(_cyclers); cyclerIndex < numCyclers; ++cyclerIndex) {
-		PalCycler *cycler = _cyclers[cyclerIndex];
+	for (int i = 0; i < kNumCyclers; ++i) {
+		PalCycler *const cycler = _cyclers[i];
 		if (cycler != nullptr) {
-			cycler->currentCycle = (uint8) ((((int) cycler->currentCycle) + 1) % cycler->numColorsToCycle);
-			// Disassembly was not fully evaluated to verify this is exactly the same
-			// as the code from applyCycles, but it appeared to be at a glance
+			cycler->currentCycle = (((int) cycler->currentCycle) + 1) % cycler->numColorsToCycle;
 			for (int j = 0; j < cycler->numColorsToCycle; j++) {
 				_nextPalette.colors[cycler->fromColor + j] = paletteCopy[cycler->fromColor + (cycler->currentCycle + j) % cycler->numColorsToCycle];
 			}
@@ -788,15 +707,16 @@ void GfxPalette32::applyCycles() {
 	Color paletteCopy[256];
 	memcpy(paletteCopy, _nextPalette.colors, sizeof(Color) * 256);
 
-	for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) {
-		PalCycler *cycler = _cyclers[i];
+	const uint32 now = g_sci->getTickCount();
+	for (int i = 0; i < kNumCyclers; ++i) {
+		PalCycler *const cycler = _cyclers[i];
 		if (cycler == nullptr) {
 			continue;
 		}
 
 		if (cycler->delay != 0 && cycler->numTimesPaused == 0) {
-			while ((cycler->delay + cycler->lastUpdateTick) < g_sci->getTickCount()) {
-				doCycleInternal(cycler, 1);
+			while ((cycler->delay + cycler->lastUpdateTick) < now) {
+				updateCycler(*cycler, 1);
 				cycler->lastUpdateTick += cycler->delay;
 			}
 		}
@@ -810,7 +730,7 @@ void GfxPalette32::applyCycles() {
 #pragma mark -
 #pragma mark Fading
 
-void GfxPalette32::setFade(uint16 percent, uint8 fromColor, uint16 toColor) {
+void GfxPalette32::setFade(const uint16 percent, const uint8 fromColor, uint16 toColor) {
 	if (fromColor > toColor) {
 		return;
 	}
@@ -827,13 +747,14 @@ void GfxPalette32::setFade(uint16 percent, uint8 fromColor, uint16 toColor) {
 }
 
 void GfxPalette32::fadeOff() {
-	setFade(100, 0, 256);
+	setFade(100, 0, 255);
 }
 
 void GfxPalette32::applyFade() {
 	for (int i = 0; i < ARRAYSIZE(_fadeTable); ++i) {
-		if (_fadeTable[i] == 100)
+		if (_fadeTable[i] == 100) {
 			continue;
+		}
 
 		Color &color = _nextPalette.colors[i];
 
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index dc21580..81e9bbb 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -24,30 +24,33 @@
 #define SCI_GRAPHICS_PALETTE32_H
 
 #include "sci/graphics/palette.h"
-
 namespace Sci {
 
+#pragma mark HunkPalette
+
 /**
- * HunkPalette represents a raw palette resource
- * read from disk.
+ * HunkPalette represents a raw palette resource read from disk. The data
+ * structure of a HunkPalette allows palettes to be smaller than 256 colors. It
+ * also allows multiple palettes to be stored in one HunkPalette, though in
+ * SCI32 games there seems to only ever be one palette per HunkPalette.
  */
 class HunkPalette {
 public:
 	HunkPalette(byte *rawPalette);
 
 	/**
-	 * Gets the version of the palette.
+	 * Gets the version of the palette. Used to avoid resubmitting a HunkPalette
+	 * which has already been submitted for the next frame.
 	 */
 	uint32 getVersion() const { return _version; }
 
 	/**
 	 * Sets the version of the palette.
 	 */
-	void setVersion(const uint32 version);
+	void setVersion(const uint32 version) const;
 
 	/**
-	 * Converts the hunk palette to a standard
-	 * palette.
+	 * Converts the hunk palette to a standard Palette.
 	 */
 	const Palette toPalette() const;
 
@@ -64,15 +67,14 @@ private:
 		kEntryHeaderSize = 22,
 
 		/**
-		 * The offset of the hunk palette version
-		 * within the palette entry header.
+		 * The offset of the hunk palette version within the palette entry
+		 * header.
 		 */
 		kEntryVersionOffset = 18
 	};
 
 	/**
-	 * The header for a palette inside the
-	 * HunkPalette.
+	 * The header for a palette inside the HunkPalette.
 	 */
 	struct EntryHeader {
 		/**
@@ -81,8 +83,7 @@ private:
 		uint8 startColor;
 
 		/**
-		 * The number of palette colors in this
-		 * entry.
+		 * The number of palette colors in this entry.
 		 */
 		uint16 numColors;
 
@@ -92,8 +93,7 @@ private:
 		bool used;
 
 		/**
-		 * Whether or not all palette entries
-		 * share the same `used` value in
+		 * Whether or not all palette entries share the same `used` value in
 		 * `defaultFlag`.
 		 */
 		bool sharedUsed;
@@ -105,14 +105,14 @@ private:
 	};
 
 	/**
-	 * The version number from the last time this
-	 * palette was submitted to GfxPalette32.
+	 * The version number from the last time this palette was submitted to
+	 * GfxPalette32.
 	 */
-	uint32 _version;
+	mutable uint32 _version;
 
 	/**
-	 * The number of palettes stored in the hunk
-	 * palette. In SCI32 games this is always 1.
+	 * The number of palettes stored in the hunk palette. In SCI32 games this is
+	 * always 1.
 	 */
 	uint8 _numPalettes;
 
@@ -122,41 +122,44 @@ private:
 	byte *_data;
 
 	/**
-	 * Returns a struct that describes the palette
-	 * held by this HunkPalette. The entry header
-	 * is reconstructed on every call from the raw
-	 * palette data.
+	 * Returns a struct that describes the palette held by this HunkPalette. The
+	 * entry header is reconstructed on every call from the raw palette data.
 	 */
 	const EntryHeader getEntryHeader() const;
 
 	/**
-	 * Returns a pointer to the palette data within
-	 * the hunk palette.
+	 * Returns a pointer to the palette data within the hunk palette.
 	 */
 	byte *getPalPointer() const {
 		return _data + kHunkPaletteHeaderSize + (2 * _numPalettes);
 	}
 };
 
+#pragma mark -
+#pragma mark PalCycler
+
 enum PalCyclerDirection {
-	PalCycleBackward = 0,
-	PalCycleForward = 1
+	kPalCycleBackward = 0,
+	kPalCycleForward  = 1
 };
 
+/**
+ * PalCycler represents a range of palette entries that are rotated on a timer.
+ */
 struct PalCycler {
 	/**
-	 * The color index of the palette cycler. This value is effectively used as the ID for the
-	 * cycler.
+	 * The color index of this palette cycler. This value is used as the unique
+	 * key for this PalCycler object.
 	 */
 	uint8 fromColor;
 
 	/**
-	 * The number of palette slots which are cycled by the palette cycler.
+	 * The number of palette slots which are to be cycled by this cycler.
 	 */
 	uint16 numColorsToCycle;
 
 	/**
-	 * The position of the cursor in its cycle.
+	 * The current position of the first palette entry.
 	 */
 	uint8 currentCycle;
 
@@ -166,15 +169,15 @@ struct PalCycler {
 	PalCyclerDirection direction;
 
 	/**
-	 * The cycle tick at the last time the cycler’s currentCycle was updated.
-	 * 795 days of game time ought to be enough for everyone? :)
+	 * The last tick the cycler cycled.
 	 */
 	uint32 lastUpdateTick;
 
 	/**
-	 * The amount of time in ticks each cycle should take to complete. In other words,
-	 * the higher the delay, the slower the cycle animation. If delay is 0, the cycler
-	 * does not automatically cycle and needs to be pumped manually with DoCycle.
+	 * The amount of time in ticks each cycle should take to complete. In other
+	 * words, the higher the delay, the slower the cycle animation. If delay is
+	 * 0, the cycler does not automatically cycle and needs to be cycled
+	 * manually by calling `doCycle`.
 	 */
 	int16 delay;
 
@@ -184,17 +187,72 @@ struct PalCycler {
 	uint16 numTimesPaused;
 };
 
+#pragma mark -
+#pragma mark GfxPalette32
+
 class GfxPalette32 {
 public:
 	GfxPalette32(ResourceManager *resMan);
 	~GfxPalette32();
 
+	void saveLoadWithSerializer(Common::Serializer &s);
+
+	/**
+	 * Gets the palette that will be use for the next frame.
+	 */
+	inline const Palette &getNextPalette() const { return _nextPalette; };
+
+	/**
+	 * Gets the palette that is used for the current frame.
+	 */
+	inline const Palette &getCurrentPalette() const { return _currentPalette; };
+
+	/**
+	 * Loads a palette into GfxPalette32 with the given resource ID.
+	 */
+	bool loadPalette(const GuiResourceId resourceId);
+
+	/**
+	 * Finds the nearest color in the current palette matching the given RGB
+	 * value.
+	 */
+	int16 matchColor(const uint8 r, const uint8 g, const uint8 b);
+
+	/**
+	 * Submits a palette to display. Entries marked as "used" in the submitted
+	 * palette are merged into `_sourcePalette`.
+	 */
+	void submit(const Palette &palette);
+	void submit(const HunkPalette &palette);
+
+	/**
+	 * Applies all fades, cycles, remaps, and varies for the current frame to
+	 * `nextPalette`.
+	 */
+	bool updateForFrame();
+
+	/**
+	 * Copies all palette entries from `sourcePalette` to `nextPalette` and
+	 * applies remaps. Unlike `updateForFrame`, this call does not apply fades,
+	 * cycles, or varies.
+	 */
+	void updateFFrame();
+
+	/**
+	 * Copies all entries from `nextPalette` to `currentPalette` and updates the
+	 * backend's raw palette.
+	 *
+	 * @param updateScreen If true, this call will also tell the backend to draw
+	 * to the screen.
+	 */
+	void updateHardware(const bool updateScreen = true);
+
 private:
 	ResourceManager *_resMan;
 
 	/**
-	 * The palette revision version. Increments once per game
-	 * loop that changes the source palette.
+	 * The palette revision version. Increments once per game loop that changes
+	 * the source palette.
 	 */
 	uint32 _version;
 
@@ -209,90 +267,140 @@ private:
 	Palette _currentPalette;
 
 	/**
-	 * The unmodified source palette loaded by kPalette. Additional
-	 * palette entries may be mixed into the source palette by
-	 * CelObj objects, which contain their own palettes.
+	 * The unmodified source palette loaded by kPalette. Additional palette
+	 * entries may be mixed into the source palette by CelObj objects, which
+	 * contain their own palettes.
 	 */
 	Palette _sourcePalette;
 
 	/**
 	 * The palette to be used when the hardware is next updated.
-	 * On update, _nextPalette is transferred to _currentPalette.
+	 * On update, `_nextPalette` is transferred to `_currentPalette`.
 	 */
 	Palette _nextPalette;
 
-	bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
-	Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
+	/**
+	 * Creates and returns a new Palette object with data from the given
+	 * resource ID.
+	 */
+	Palette getPaletteFromResource(const GuiResourceId paletteId) const;
+
+	/**
+	 * Merges used colors in the `from` palette into the `to` palette.
+	 */
+	void mergePalette(Palette &to, const Palette &from);
+
+	/**
+	 * Applies all varies, cycles, and fades to `_nextPalette`.
+	 */
+	void applyAll();
 
+#pragma mark -
+#pragma mark Varying
 public:
-	void saveLoadWithSerializer(Common::Serializer &s);
-	inline const Palette &getNextPalette() const { return _nextPalette; };
-	inline const Palette &getCurrentPalette() const { return _currentPalette; };
+	/**
+	 * Blends the `target` palette into the current palette over `time` ticks.
+	 *
+	 * @param target The target palette.
+	 * @param percent The amount that the target palette should be blended into
+	 *                the source palette by the end of the vary.
+	 * @param ticks The number of ticks that it should take for the blend to be
+	 *              completed.
+	 * @param fromColor The first palette entry that should be blended.
+	 * @param toColor The last palette entry that should be blended.
+	 */
+	void setVary(const Palette &target, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor);
 
 	/**
-	 * Loads a palette into GfxPalette32 with the given resource
-	 * ID.
+	 * Gets the current vary blend amount.
 	 */
-	bool loadPalette(const GuiResourceId resourceId);
+	inline int16 getVaryPercent() const { return ABS(_varyPercent); }
 
 	/**
-	 * Finds the nearest color in the current palette matching the
-	 * given RGB value.
+	 * Changes the percentage of the current vary to `percent`, to be completed
+	 * over `time` ticks, if there is a currently active vary target palette.
 	 */
-	int16 matchColor(const uint8 r, const uint8 g, const uint8 b);
+	void setVaryPercent(const int16 percent, const int32 time);
 
 	/**
-	 * Submits a palette to display. Entries marked as “used” in the
-	 * submitted palette are merged into the existing entries of
-	 * _sourcePalette.
+	 * Changes the amount of time, in ticks, an in-progress palette vary should
+	 * take to finish.
 	 */
-	void submit(const Palette &palette);
-	void submit(HunkPalette &palette);
+	void setVaryTime(const int32 ticks);
 
-	bool updateForFrame();
-	void updateFFrame();
-	void updateHardware(const bool updateScreen = true);
-	void applyAll();
+	/**
+	 * Changes the vary percent and time to perform the vary.
+	 */
+	void setVaryTime(const int16 percent, const int32 ticks);
 
-#pragma mark -
-#pragma mark Color look-up
-private:
 	/**
-	 * An optional lookup table used to remap RGB565 colors to a palette
-	 * index. Used by Phantasmagoria 2 in 8-bit color environments.
+	 * Removes the active palette vary.
 	 */
-	byte *_clutTable;
+	void varyOff();
 
-public:
-	bool loadClut(uint16 clutId);
-	byte matchClutColor(uint16 color);
-	void unloadClut();
+	/**
+	 * Pauses any active palette vary.
+	 */
+	void varyPause();
+
+	/**
+	 * Unpauses any paused palette vary.
+	 */
+	void varyOn();
+
+	/**
+	 * Sets the target palette for the blend.
+	 */
+	void setTarget(const Palette &palette);
+
+	/**
+	 * Sets the start palette for the blend.
+	 */
+	void setStart(const Palette &palette);
+
+	/**
+	 * Merges a new start palette into the existing start palette.
+	 */
+	void mergeStart(const Palette &palette);
+
+	/**
+	 * Merges a new target palette into the existing target palette.
+	 */
+	void mergeTarget(const Palette &palette);
+
+	/**
+	 * Applies any active palette vary to `_nextPalette`.
+	 */
+	void applyVary();
+
+	void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int32 ticks, const int16 fromColor, const int16 toColor);
+	void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
+	void kernelPalVarySetTarget(const GuiResourceId paletteId);
+	void kernelPalVarySetStart(const GuiResourceId paletteId);
+	void kernelPalVaryMergeStart(const GuiResourceId paletteId);
+	void kernelPalVaryPause(const bool pause);
 
-#pragma mark -
-#pragma mark Varying
 private:
 	/**
-	 * An optional palette used to describe the source colors used
-	 * in a palette vary operation. If this palette is not specified,
-	 * sourcePalette is used instead.
+	 * An optional palette used to provide source colors for a palette vary
+	 * operation. If this palette is not specified, `_sourcePalette` is used
+	 * instead.
 	 */
 	Palette *_varyStartPalette;
 
 	/**
-	 * An optional palette used to describe the target colors used
-	 * in a palette vary operation.
+	 * An optional palette used to provide target colors for a palette vary
+	 * operation.
 	 */
 	Palette *_varyTargetPalette;
 
 	/**
-	 * The minimum palette index that has been varied from the
-	 * source palette. 0–255
+	 * The minimum palette index that has been varied from the source palette.
 	 */
 	uint8 _varyFromColor;
 
 	/**
-	 * The maximum palette index that is has been varied from the
-	 * source palette. 0-255
+	 * The maximum palette index that has been varied from the source palette.
 	 */
 	uint8 _varyToColor;
 
@@ -302,10 +410,10 @@ private:
 	uint32 _varyLastTick;
 
 	/**
-	 * The amount of time to elapse, in ticks, between each cycle
-	 * of a palette vary animation.
+	 * The amount of time that should elapse, in ticks, between each cycle of a
+	 * palette vary animation.
 	 */
-	int _varyTime;
+	int32 _varyTime;
 
 	/**
 	 * The direction of change: -1, 0, or 1.
@@ -313,97 +421,158 @@ private:
 	int16 _varyDirection;
 
 	/**
-	 * The amount, in percent, that the vary color is currently
-	 * blended into the source color.
+	 * The amount, in percent, that the vary color is currently blended into the
+	 * source color.
 	 */
 	int16 _varyPercent;
 
 	/**
-	 * The target amount that a vary color will be blended into
-	 * the source color.
+	 * The target amount that a vary color will be blended into the source
+	 * color.
 	 */
 	int16 _varyTargetPercent;
 
 	/**
-	 * The number of time palette varying has been paused.
+	 * The number of times palette varying has been paused.
 	 */
 	uint16 _varyNumTimesPaused;
 
-public:
-	void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
-	void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
-	void kernelPalVarySetTarget(const GuiResourceId paletteId);
-	void kernelPalVarySetStart(const GuiResourceId paletteId);
-	void kernelPalVaryMergeStart(const GuiResourceId paletteId);
-	void kernelPalVaryPause(bool pause);
-
-	void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
-	void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
-	int16 getVaryPercent() const;
-	void varyOff();
-	void mergeTarget(const Palette *const palette);
-	void varyPause();
-	void varyOn();
-	void setVaryTime(const int time);
-	void setTarget(const Palette *const palette);
-	void setStart(const Palette *const palette);
-	void mergeStart(const Palette *const palette);
-	void setVaryTimeInternal(const int16 percent, const int time);
-	void applyVary();
-
 #pragma mark -
 #pragma mark Cycling
-private:
-	// SQ6 defines 10 cyclers
-	PalCycler *_cyclers[10];
+public:
+	inline const bool *getCycleMap() const { return _cycleMap; }
 
 	/**
-	 * The cycle map is used to detect overlapping cyclers.
-	 * According to SCI engine code, when two cyclers overlap,
-	 * a fatal error has occurred and the engine will display
-	 * an error and then exit.
+	 * Cycle palette entries between `fromColor` and `toColor`, inclusive.
+	 * Palette cyclers may not overlap. `fromColor` is used in other methods as
+	 * the key for looking up a cycler.
 	 *
-	 * The cycle map is also by the color remapping system to
-	 * avoid attempting to remap to palette entries that are
-	 * cycling (so won't be the expected color once the cycler
-	 * runs again).
+	 * @param fromColor The first color in the cycle.
+	 * @param toColor The last color in the cycle.
+	 * @param delay The number of ticks that should elapse between cycles.
+	 * @param direction A negative `direction` will cycle backwards instead of
+	 *                  forwards. The numeric value of this argument is ignored;
+	 *                  only its sign is used to determine direction.
 	 */
-	bool _cycleMap[256];
-	inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
-	inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
-	inline PalCycler *getCycler(uint16 fromColor);
-
-public:
 	void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
+
+	/**
+	 * Performs a round of palette cycling.
+	 *
+	 * @param fromColor The color key for the cycler.
+	 * @param speed The number of entries that should be cycled this round.
+	 */
 	void doCycle(const uint8 fromColor, const int16 speed);
+
+	/**
+	 * Unpauses the cycler starting at `fromColor`.
+	 */
 	void cycleOn(const uint8 fromColor);
+
+	/**
+	 * Pauses the cycler starting at `fromColor`.
+	 */
 	void cyclePause(const uint8 fromColor);
+
+	/**
+	 * Unpauses all cyclers.
+	 */
 	void cycleAllOn();
+
+	/**
+	 * Pauses all cyclers.
+	 */
 	void cycleAllPause();
+
+	/**
+	 * Removes the cycler starting at `fromColor`.
+	 */
 	void cycleOff(const uint8 fromColor);
+
+	/**
+	 * Removes all cyclers.
+	 */
 	void cycleAllOff();
+
+private:
+	enum {
+		kNumCyclers = 10
+	};
+
+	PalCycler *_cyclers[kNumCyclers];
+
+	/**
+	 * Updates the `currentCycle` of the given `cycler` by `speed` entries.
+	 */
+	void updateCycler(PalCycler &cycler, const int16 speed);
+
+	/**
+	 * The cycle map is used to detect overlapping cyclers, and to avoid
+	 * remapping to palette entries that are being cycled.
+	 *
+	 * According to SCI engine code, when two cyclers overlap, a fatal error has
+	 * occurred and the engine will display an error and then exit.
+	 *
+	 * The color remapping system avoids attempts to remap to palette entries
+	 * that are cycling because they won't be the expected color once the cycler
+	 * updates the palette entries.
+	 */
+	bool _cycleMap[256];
+
+	/**
+	 * Marks `numColorsToClear` colors starting at `fromColor` in the cycle
+	 * map as inactive.
+	 */
+	void clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear);
+
+	/**
+	 * Marks `numColorsToClear` colors starting at `fromColor` in the cycle
+	 * map as active.
+	 */
+	void setCycleMap(const uint16 fromColor, const uint16 numColorsToClear);
+
+	/**
+	 * Gets the cycler object that starts at the given `fromColor`, or NULL if
+	 * there is no cycler for that color.
+	 */
+	PalCycler *getCycler(const uint16 fromColor);
+
+	/**
+	 * Advances all cyclers by one step, regardless of whether or not it is time
+	 * to perform another cycle.
+	 */
 	void applyAllCycles();
+
+	/**
+	 * Advances, by one step, only the cyclers whose time has come to cycle.
+	 */
 	void applyCycles();
-	inline const bool *getCycleMap() const { return _cycleMap; }
 
 #pragma mark -
 #pragma mark Fading
-private:
+public:
 	/**
-	 * The fade table records the expected intensity level of each pixel
-	 * in the palette that will be displayed on the next frame.
+	 * Sets the intensity level for a range of palette entries. An intensity of
+	 * zero indicates total darkness. Intensity may also be set above 100
+	 * percent to increase the intensity of a palette entry.
 	 */
-	uint16 _fadeTable[256];
+	void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor);
 
-public:
 	/**
-	 * Sets the intensity level for a range of palette
-	 * entries. An intensity of zero indicates total
-	 * darkness. Intensity may be set to over 100 percent.
+	 * Resets the intensity of all palette entries to 100%.
 	 */
-	void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor);
 	void fadeOff();
+
+	/**
+	 * Applies intensity values to the palette entries in `_nextPalette`.
+	 */
 	void applyFade();
+
+private:
+	/**
+	 * The intensity levels of each palette entry, in percent. Defaults to 100.
+	 */
+	uint16 _fadeTable[256];
 };
 
 } // End of namespace Sci


Commit: b7f93623c420718e5e781c08149a57a2163318c2
    https://github.com/scummvm/scummvm/commit/b7f93623c420718e5e781c08149a57a2163318c2
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-09-29T19:39:16-05:00

Commit Message:
SCI32: Fix broken palette cycling in SCI2/2.1early games

This fixes the incorrect appearance of the Sierra logo in PQ4 when
the main menu appears in hi-res mode.

The behaviour of kPalCycle(SetCycle) changed in between 2.1early
and 2.1mid to fix an off-by-one error that prevented the last
palette entry in a cycle range from being used. Some earlier games,
like PQ4CD in hi-res mode, relied on this behaviour, and would
render incorrectly if the last palette entry in a range was used.

Changed paths:
    engines/sci/graphics/palette32.cpp



diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 6b719f8..ec3d912 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -556,7 +556,10 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in
 		clearCycleMap(cycler->fromColor, cycler->numColorsToCycle);
 	}
 
-	const uint16 numColorsToCycle = toColor - fromColor + 1;
+	uint16 numColorsToCycle = toColor - fromColor;
+	if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) {
+		numColorsToCycle += 1;
+	}
 	cycler->fromColor = fromColor;
 	cycler->numColorsToCycle = numColorsToCycle;
 	cycler->currentCycle = fromColor;





More information about the Scummvm-git-logs mailing list