[Scummvm-cvs-logs] scummvm master -> 1632bd50c890c9e50139c57b52d8e1bfc8f61c61

csnover csnover at users.noreply.github.com
Sun Jun 26 19:55:26 CEST 2016


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

Summary:
6ec206ccee SCI32: Fix race condition when freeing audio resources
c5914eb80d SCI32: Fix hang when playing auto-play audio channels
cfda8b9ecd SCI32: Fix broken Remap implementation
6feec9ed72 SCI32: Add more workarounds for SCI2.1early games
26e7d3d9e9 SCI32: Remove no-longer-relevant comment
3ffde88b18 SCI32: Fix typo
5e6f08d9fe SCI32: Enable aspect ratio correction on QFG4CD
60849a16cb SCI32: Change Color::operator!= to all operator== directly
1632bd50c8 SCI32: Fix benchmarking for QFG4


Commit: 6ec206ccee670254f591c93a7421e16ad3124a66
    https://github.com/scummvm/scummvm/commit/6ec206ccee670254f591c93a7421e16ad3124a66
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T10:53:45-05:00

Commit Message:
SCI32: Fix race condition when freeing audio resources

It's not possible to call any ResourceManager methods from any
thread other than the main thread because it is not thread-safe.

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



diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
index 7958a6f..1714d3d 100644
--- a/engines/sci/sound/audio32.cpp
+++ b/engines/sci/sound/audio32.cpp
@@ -107,6 +107,7 @@ Audio32::Audio32(ResourceManager *resMan) :
 	_mutex(),
 
 	_numActiveChannels(0),
+	_inAudioThread(false),
 
 	_maxAllowedSampleRate(44100),
 	_maxAllowedBitDepth(16),
@@ -236,10 +237,18 @@ int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream
 int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) {
 	Common::StackLock lock(_mutex);
 
+	// ResourceManager is not thread-safe so we need to
+	// avoid calling into it from the audio thread, but at
+	// the same time we need to be able to clear out any
+	// finished channels on a regular basis
+	_inAudioThread = true;
+
 	// The system mixer should not try to get data when
 	// Audio32 is paused
 	assert(_pausedAtTick == 0 && _numActiveChannels > 0);
 
+	freeUnusedChannels();
+
 	// The caller of `readBuffer` is a rate converter,
 	// which reuses (without clearing) an intermediate
 	// buffer, so we need to zero the intermediate buffer
@@ -354,7 +363,7 @@ int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) {
 		}
 	}
 
-	freeUnusedChannels();
+	_inAudioThread = false;
 
 	return maxSamplesWritten;
 }
@@ -440,6 +449,10 @@ void Audio32::freeUnusedChannels() {
 			}
 		}
 	}
+
+	if (!_inAudioThread) {
+		unlockResources();
+	}
 }
 
 void Audio32::freeChannel(const int16 channelIndex) {
@@ -450,7 +463,17 @@ void Audio32::freeChannel(const int16 channelIndex) {
 	// 4. Clear monitored memory buffer, if one existed
 	Common::StackLock lock(_mutex);
 	AudioChannel &channel = getChannel(channelIndex);
-	_resMan->unlockResource(channel.resource);
+
+	// We cannot unlock resources from the audio thread
+	// because ResourceManager is not thread-safe; instead,
+	// we just record that the resource needs unlocking and
+	// unlock it whenever we are on the main thread again
+	if (_inAudioThread) {
+		_resourcesToUnlock.push_back(channel.resource);
+	} else {
+		_resMan->unlockResource(channel.resource);
+	}
+
 	channel.resource = nullptr;
 	delete channel.stream;
 	channel.stream = nullptr;
@@ -464,6 +487,16 @@ void Audio32::freeChannel(const int16 channelIndex) {
 	}
 }
 
+void Audio32::unlockResources() {
+	Common::StackLock lock(_mutex);
+	assert(!_inAudioThread);
+
+	for (UnlockList::const_iterator it = _resourcesToUnlock.begin(); it != _resourcesToUnlock.end(); ++it) {
+		_resMan->unlockResource(*it);
+	}
+	_resourcesToUnlock.clear();
+}
+
 #pragma mark -
 #pragma mark Script compatibility
 
@@ -497,6 +530,8 @@ void Audio32::setNumOutputChannels(int16 numChannels) {
 uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) {
 	Common::StackLock lock(_mutex);
 
+	freeUnusedChannels();
+
 	if (channelIndex != kNoExistingChannel) {
 		AudioChannel &channel = getChannel(channelIndex);
 
diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h
index 82893fb..416b81d 100644
--- a/engines/sci/sound/audio32.h
+++ b/engines/sci/sound/audio32.h
@@ -225,6 +225,8 @@ public:
 	int16 findChannelById(const ResourceId resourceId, const reg_t soundNode = NULL_REG) const;
 
 private:
+	typedef Common::Array<Resource *> UnlockList;
+
 	/**
 	 * The audio channels.
 	 */
@@ -238,6 +240,23 @@ private:
 	uint8 _numActiveChannels;
 
 	/**
+	 * Whether or not we are in the audio thread.
+	 *
+	 * This flag is used instead of passing a parameter to
+	 * `freeUnusedChannels` because a parameter would
+	 * require forwarding through the public method `stop`,
+	 * and there is not currently any reason for this
+	 * implementation detail to be exposed.
+	 */
+	bool _inAudioThread;
+
+	/**
+	 * The list of resources from freed channels that need
+	 * to be unlocked from the main thread.
+	 */
+	UnlockList _resourcesToUnlock;
+
+	/**
 	 * Gets the audio channel at the given index.
 	 */
 	inline AudioChannel &getChannel(const int16 channelIndex) {
@@ -266,6 +285,12 @@ private:
 	 */
 	void freeChannel(const int16 channelIndex);
 
+	/**
+	 * Unlocks all resources that were freed by the audio
+	 * thread.
+	 */
+	void unlockResources();
+
 #pragma mark -
 #pragma mark Script compatibility
 public:


Commit: c5914eb80db79233766b2b6070ee92c637a18069
    https://github.com/scummvm/scummvm/commit/c5914eb80db79233766b2b6070ee92c637a18069
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T10:56:36-05:00

Commit Message:
SCI32: Fix hang when playing auto-play audio channels

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



diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
index 1714d3d..20595b0 100644
--- a/engines/sci/sound/audio32.cpp
+++ b/engines/sci/sound/audio32.cpp
@@ -596,14 +596,11 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool
 	AudioChannel &channel = getChannel(channelIndex);
 	channel.id = resourceId;
 	channel.resource = resource;
-	// resourceStream, stream, converter, duration and startedAtTick will be initialized below
-	channel.pausedAtTick = 0;
 	channel.loop = loop;
-	channel.lastFadeTick = 0;
-	channel.fadeStepsRemaining = 0;
-	// fadeVolume, fadeSpeed and stopChannelOnFade will be initialized once they are actually used
 	channel.robot = false;
 	channel.vmd = false;
+	channel.lastFadeTick = 0;
+	channel.fadeStepsRemaining = 0;
 	channel.soundNode = soundNode;
 	channel.volume = volume < 0 || volume > kMaxVolume ? (int)kMaxVolume : volume;
 	// TODO: SCI3 introduces stereo audio
@@ -648,9 +645,7 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool
 	channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000);
 
 	const uint32 now = g_sci->getTickCount();
-	if (!autoPlay) {
-		channel.pausedAtTick = now;
-	}
+	channel.pausedAtTick = autoPlay ? 0 : now;
 	channel.startedAtTick = now;
 
 	if (_numActiveChannels == 1) {


Commit: cfda8b9ecd8a6e3c003abe533ea2e2981d8ba984
    https://github.com/scummvm/scummvm/commit/cfda8b9ecd8a6e3c003abe533ea2e2981d8ba984
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T12:42:58-05:00

Commit Message:
SCI32: Fix broken Remap implementation

Remap would crash SCI2.1early games with 19 remap slots, and
did not actually work in most cases in SCI2.1mid+ games.

The SCI16 implementation was moved to its own separate file but
was otherwise touched as little as possible, so may still have
similar problems to the SCI32 code.

1. Split SCI16 and SCI32 code into separate files
2. Use -32 prefixes for SCI32 code and no prefix for SCI16 code,
   where possible, to match other existing code
3. Avoid accidental corruption of values from the VM that may be
   valid when signed or larger than 8 bits
4. Added documentation
5. Add missing remap CelObj calls
6. Inline where possible in performance-critical code paths
7. Fix bad `matchColor` function, and move it from GfxPalette to
   GfxRemap32 since it is only used by GfxRemap32
8. Fix bad capitalisation in getCycleMap
9. Remove unnecessary initialisation of SingleRemaps
10. Update architecture to more closely mirror how SSCI worked
11. Clarify the purpose of each type of remap type (and
    associated variable names)
12. Split large `apply` function into smaller units
13. Fix buffer overrun when loading a SCI2.1early game with remap
14. Remove use of `#define` constants
15. Warn instead of crashing with an error on invalid input (to
    match SSCI more closely)
16. Change the collision avoidance mechanism between the RemapType
    enum and remap kernel functions
17. Add save/load function

Changed paths:
  A engines/sci/graphics/remap32.cpp
  A engines/sci/graphics/remap32.h
    engines/sci/engine/kernel.h
    engines/sci/engine/kernel_tables.h
    engines/sci/engine/kgraphics.cpp
    engines/sci/engine/kgraphics32.cpp
    engines/sci/engine/savegame.cpp
    engines/sci/engine/savegame.h
    engines/sci/graphics/celobj32.cpp
    engines/sci/graphics/frameout.cpp
    engines/sci/graphics/palette32.cpp
    engines/sci/graphics/palette32.h
    engines/sci/graphics/plane32.cpp
    engines/sci/graphics/remap.cpp
    engines/sci/graphics/remap.h
    engines/sci/graphics/view.h
    engines/sci/module.mk
    engines/sci/sci.cpp



diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index b269e97..1202982 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -412,7 +412,7 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv);
 reg_t kTextColors(EngineState *s, int argc, reg_t *argv);
 reg_t kTextFonts(EngineState *s, int argc, reg_t *argv);
 reg_t kShow(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);
 reg_t kDummy(EngineState *s, int argc, reg_t *argv);
 reg_t kEmpty(EngineState *s, int argc, reg_t *argv);
 reg_t kStub(EngineState *s, int argc, reg_t *argv);
@@ -482,13 +482,13 @@ reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv);
 
 reg_t kMulDiv(EngineState *s, int argc, reg_t *argv);
 
-reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapOff(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv);
-reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv);
+reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv);
 
 reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);
 reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 19bee35..dacaafe 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -414,12 +414,12 @@ static const SciKernelMapSubEntry kList_subops[] = {
 
 //    version,         subId, function-mapping,                    signature,              workarounds
 static const SciKernelMapSubEntry kRemapColors_subops[] = {
-	{ SIG_SCI32,           0, MAP_CALL(RemapOff),                 "(i)",                  NULL },
-	{ SIG_SCI32,           1, MAP_CALL(RemapByRange),             "iiii(i)",              NULL },
-	{ SIG_SCI32,           2, MAP_CALL(RemapByPercent),           "ii(i)",                NULL },
-	{ SIG_SCI32,           3, MAP_CALL(RemapToGray),              "ii(i)",                NULL },
-	{ SIG_SCI32,           4, MAP_CALL(RemapToPercentGray),       "iii(i)",               NULL },
-	{ SIG_SCI32,           5, MAP_CALL(RemapSetNoMatchRange),     "ii",                   NULL },
+	{ SIG_SCI32,           0, MAP_CALL(RemapColorsOff),            "(i)",                  NULL },
+	{ SIG_SCI32,           1, MAP_CALL(RemapColorsByRange),        "iiii(i)",              NULL },
+	{ SIG_SCI32,           2, MAP_CALL(RemapColorsByPercent),      "ii(i)",                NULL },
+	{ SIG_SCI32,           3, MAP_CALL(RemapColorsToGray),         "ii(i)",                NULL },
+	{ SIG_SCI32,           4, MAP_CALL(RemapColorsToPercentGray),  "iii(i)",               NULL },
+	{ SIG_SCI32,           5, MAP_CALL(RemapColorsBlockRange),     "ii",                   NULL },
 	SCI_SUBOPENTRY_TERMINATOR
 };
 
@@ -634,9 +634,9 @@ static SciKernelMapEntry s_kernelMap[] = {
 	{ MAP_CALL(PriCoord),          SIG_EVERYWHERE,           "i",                     NULL,            NULL },
 	{ MAP_CALL(Random),            SIG_EVERYWHERE,           "i(i)(i)",               NULL,            NULL },
 	{ MAP_CALL(ReadNumber),        SIG_EVERYWHERE,           "r",                     NULL,            kReadNumber_workarounds },
-	{ "RemapColors", kRemapColors16,       SIG_SCI11, SIGFOR_ALL,    "i(i)(i)(i)(i)", NULL,            NULL },
+	{ MAP_CALL(RemapColors),       SIG_SCI11, SIGFOR_ALL,    "i(i)(i)(i)(i)",         NULL,            NULL },
 #ifdef ENABLE_SCI32
-	{ MAP_CALL(RemapColors),       SIG_SCI32, SIGFOR_ALL,    "i(i)(i)(i)(i)(i)",      kRemapColors_subops, NULL },
+	{ "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL,  "i(i)(i)(i)(i)(i)",      kRemapColors_subops, NULL },
 #endif
 	{ MAP_CALL(ResCheck),          SIG_EVERYWHERE,           "ii(iiii)",              NULL,            NULL },
 	{ MAP_CALL(RespondsTo),        SIG_EVERYWHERE,           ".i",                    NULL,            NULL },
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index 73236b9..cae5a09 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -1258,7 +1258,7 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) {
 }
 
 // Early variant of the SCI32 kRemapColors kernel function, used in the demo of QFG4
-reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv) {
+reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
 	uint16 operation = argv[0].toUint16();
 
 	switch (operation) {
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 9270c81..019a069 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -54,6 +54,7 @@
 #include "sci/graphics/frameout.h"
 #include "sci/graphics/paint32.h"
 #include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
 #include "sci/graphics/text32.h"
 #endif
 
@@ -925,57 +926,69 @@ reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) {
 	return s->r_acc;
 }
 
-reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {
+reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) {
 	if (!s)
 		return make_reg(0, getSciVersion());
 	error("not supposed to call this");
 }
 
-reg_t kRemapOff(EngineState *s, int argc, reg_t *argv) {
-	byte color = (argc >= 1) ? argv[0].toUint16() : 0;
-	g_sci->_gfxRemap32->remapOff(color);
+reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv) {
+	if (argc == 0) {
+		g_sci->_gfxRemap32->remapAllOff();
+	} else {
+		const uint8 color = argv[0].toUint16();
+		g_sci->_gfxRemap32->remapOff(color);
+	}
 	return s->r_acc;
 }
 
-reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv) {
-	byte color = argv[0].toUint16();
-	byte from = argv[1].toUint16();
-	byte to = argv[2].toUint16();
-	byte base = argv[3].toUint16();
-	// The last parameter, depth, is unused
-	g_sci->_gfxRemap32->setRemappingRange(color, from, to, base);
+reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv) {
+	const uint8 color = argv[0].toUint16();
+	const int16 from = argv[1].toSint16();
+	const int16 to = argv[2].toSint16();
+	const int16 base = argv[3].toSint16();
+	// NOTE: There is an optional last parameter after `base`
+	// which was only used by the priority map debugger, which
+	// does not exist in release versions of SSCI
+	g_sci->_gfxRemap32->remapByRange(color, from, to, base);
 	return s->r_acc;
 }
 
-reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv) {
-	byte color = argv[0].toUint16();
-	byte percent = argv[1].toUint16();
-	// The last parameter, depth, is unused
-	g_sci->_gfxRemap32->setRemappingPercent(color, percent);
+reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv) {
+	const uint8 color = argv[0].toUint16();
+	const int16 percent = argv[1].toSint16();
+	// NOTE: There is an optional last parameter after `percent`
+	// which was only used by the priority map debugger, which
+	// does not exist in release versions of SSCI
+	g_sci->_gfxRemap32->remapByPercent(color, percent);
 	return s->r_acc;
 }
 
-reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv) {
-	byte color = argv[0].toUint16();
-	byte gray = argv[1].toUint16();
-	// The last parameter, depth, is unused
-	g_sci->_gfxRemap32->setRemappingToGray(color, gray);
+reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv) {
+	const uint8 color = argv[0].toUint16();
+	const int16 gray = argv[1].toSint16();
+	// NOTE: There is an optional last parameter after `gray`
+	// which was only used by the priority map debugger, which
+	// does not exist in release versions of SSCI
+	g_sci->_gfxRemap32->remapToGray(color, gray);
 	return s->r_acc;
 }
 
-reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv) {
-	byte color = argv[0].toUint16();
-	byte gray = argv[1].toUint16();
-	byte percent = argv[2].toUint16();
-	// The last parameter, depth, is unused
-	g_sci->_gfxRemap32->setRemappingToPercentGray(color, gray, percent);
+reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv) {
+	const uint8 color = argv[0].toUint16();
+	const int16 gray = argv[1].toSint16();
+	const int16 percent = argv[2].toSint16();
+	// NOTE: There is an optional last parameter after `percent`
+	// which was only used by the priority map debugger, which
+	// does not exist in release versions of SSCI
+	g_sci->_gfxRemap32->remapToPercentGray(color, gray, percent);
 	return s->r_acc;
 }
 
-reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv) {
-	byte from = argv[0].toUint16();
-	byte count = argv[1].toUint16();
-	g_sci->_gfxRemap32->setNoMatchRange(from, count);
+reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv) {
+	const uint8 from = argv[0].toUint16();
+	const uint8 count = argv[1].toUint16();
+	g_sci->_gfxRemap32->blockRange(from, count);
 	return s->r_acc;
 }
 
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 302f046..0972aec 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -48,8 +48,9 @@
 #include "sci/sound/music.h"
 
 #ifdef ENABLE_SCI32
-#include "sci/graphics/palette32.h"
 #include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
 #endif
 
 namespace Sci {
@@ -807,6 +808,33 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {
 		}
 	}
 }
+
+void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) {
+	if (s.getVersion() < 35) {
+		return;
+	}
+
+	s.syncAsByte(_numActiveRemaps);
+	s.syncAsByte(_blockedRangeStart);
+	s.syncAsSint16LE(_blockedRangeCount);
+
+	for (uint i = 0; i < _remaps.size(); ++i) {
+		SingleRemap &singleRemap = _remaps[i];
+		s.syncAsByte(singleRemap._type);
+		if (s.isLoading() && singleRemap._type != kRemapNone) {
+			singleRemap.reset();
+		}
+		s.syncAsByte(singleRemap._from);
+		s.syncAsByte(singleRemap._to);
+		s.syncAsByte(singleRemap._delta);
+		s.syncAsByte(singleRemap._percent);
+		s.syncAsByte(singleRemap._gray);
+	}
+
+	if (s.isLoading()) {
+		_needsUpdate = true;
+	}
+}
 #endif
 
 void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) {
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index 459e992..43909ac 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,6 +37,7 @@ struct EngineState;
  *
  * Version - new/changed feature
  * =============================
+ *      35 - SCI32 remap
  *      34 - SCI32 palettes, and store play time in ticks
  *      33 - new overridePriority flag in MusicEntry
  *      32 - new playBed flag in MusicEntry
@@ -59,7 +60,7 @@ struct EngineState;
  */
 
 enum {
-	CURRENT_SAVEGAME_VERSION = 34,
+	CURRENT_SAVEGAME_VERSION = 35,
 	MINIMUM_SAVEGAME_VERSION = 14
 };
 
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 77d333a..af5ee3a 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -26,13 +26,12 @@
 #include "sci/graphics/celobj32.h"
 #include "sci/graphics/frameout.h"
 #include "sci/graphics/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
 #include "sci/graphics/text32.h"
-#include "sci/graphics/view.h"
 
 namespace Sci {
 #pragma mark CelScaler
+
 CelScaler *CelObj::_scaler = nullptr;
 
 void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
@@ -323,6 +322,10 @@ public:
 #pragma mark -
 #pragma mark CelObj - Remappers
 
+/**
+ * Pixel mapper for a CelObj with transparent pixels and no
+ * remapping data.
+ */
 struct MAPPER_NoMD {
 	inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
 		if (pixel != skipColor) {
@@ -330,25 +333,49 @@ struct MAPPER_NoMD {
 		}
 	}
 };
+
+/**
+ * Pixel mapper for a CelObj with no transparent pixels and
+ * no remapping data.
+ */
 struct MAPPER_NoMDNoSkip {
 	inline void draw(byte *target, const byte pixel, const uint8) const {
 		*target = pixel;
 	}
 };
 
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping enabled.
+ */
 struct MAPPER_Map {
 	inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
 		if (pixel != skipColor) {
+			// NOTE: For some reason, SSCI never checks if the source
+			// pixel is *above* the range of remaps.
 			if (pixel < g_sci->_gfxRemap32->getStartColor()) {
 				*target = pixel;
-			} else {
-				if (g_sci->_gfxRemap32->remapEnabled(pixel))
-					*target = g_sci->_gfxRemap32->remapColor(pixel, *target);
+			} else if (g_sci->_gfxRemap32->remapEnabled(pixel)) {
+				*target = g_sci->_gfxRemap32->remapColor(pixel, *target);
 			}
 		}
 	}
 };
 
+/**
+ * Pixel mapper for a CelObj with transparent pixels,
+ * remapping data, and remapping disabled.
+ */
+struct MAPPER_NoMap {
+	inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {
+		// NOTE: For some reason, SSCI never checks if the source
+		// pixel is *above* the range of remaps.
+		if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) {
+			*target = pixel;
+		}
+	}
+};
+
 void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
 	const Common::Point &scaledPosition = screenItem._scaledPosition;
 	const Ratio &scaleX = screenItem._ratioX;
@@ -523,6 +550,7 @@ void CelObj::submitPalette() const {
 
 #pragma mark -
 #pragma mark CelObj - Caching
+
 int CelObj::_nextCacheId = 1;
 CelCache *CelObj::_cache = nullptr;
 
@@ -624,33 +652,35 @@ void dummyFill(Buffer &target, const Common::Rect &targetRect) {
 }
 
 void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("drawHzFlip");
-	dummyFill(target, targetRect);
+	render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
 }
 
 void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("drawNoFlip");
-	dummyFill(target, targetRect);
+	render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
 }
 
 void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("drawUncompNoFlip");
-	dummyFill(target, targetRect);
+	render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
 }
 
 void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("drawUncompHzFlip");
-	dummyFill(target, targetRect);
+	render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
 }
 
 void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("scaleDraw");
-	dummyFill(target, targetRect);
+	if (_drawMirrored) {
+		render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	} else {
+		render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	}
 }
 
 void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	debug("scaleDrawUncomp");
-	dummyFill(target, targetRect);
+	if (_drawMirrored) {
+		render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	} else {
+		render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	}
 }
 
 void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
@@ -670,17 +700,19 @@ void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect,
 }
 
 void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	if (_drawMirrored)
+	if (_drawMirrored) {
 		render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
-	else
+	} else {
 		render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	}
 }
 
 void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	if (_drawMirrored)
+	if (_drawMirrored) {
 		render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
-	else
+	} else {
 		render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	}
 }
 
 void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
@@ -715,14 +747,16 @@ void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &sca
 }
 
 void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
-	if (_drawMirrored)
+	if (_drawMirrored) {
 		render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
-	else
+	} else {
 		render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
+	}
 }
 
 #pragma mark -
 #pragma mark CelObjView
+
 CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
 	_info.type = kCelTypeView;
 	_info.resourceId = viewId;
@@ -840,8 +874,12 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
 bool CelObjView::analyzeUncompressedForRemap() const {
 	byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
 	for (int i = 0; i < _width * _height; ++i) {
-		byte pixel = pixels[i];
-		if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+		const byte pixel = pixels[i];
+		if (
+			pixel >= g_sci->_gfxRemap32->getStartColor() &&
+			pixel <= g_sci->_gfxRemap32->getEndColor() &&
+			pixel != _transparentColor
+		) {
 			return true;
 		}
 	}
@@ -853,8 +891,12 @@ bool CelObjView::analyzeForRemap() const {
 	for (int y = 0; y < _height; y++) {
 		const byte *curRow = reader.getRow(y);
 		for (int x = 0; x < _width; x++) {
-			byte pixel = curRow[x];
-			if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) {
+			const byte pixel = curRow[x];
+			if (
+				pixel >= g_sci->_gfxRemap32->getStartColor() &&
+				pixel <= g_sci->_gfxRemap32->getEndColor() &&
+				pixel != _transparentColor
+			) {
 				return true;
 			}
 		}
@@ -881,6 +923,7 @@ byte *CelObjView::getResPointer() const {
 
 #pragma mark -
 #pragma mark CelObjPic
+
 CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
 	_info.type = kCelTypePic;
 	_info.resourceId = picId;
@@ -1002,6 +1045,7 @@ byte *CelObjPic::getResPointer() const {
 
 #pragma mark -
 #pragma mark CelObjMem
+
 CelObjMem::CelObjMem(const reg_t bitmapObject) {
 	_info.type = kCelTypeMem;
 	_info.bitmap = bitmapObject;
@@ -1031,6 +1075,7 @@ byte *CelObjMem::getResPointer() const {
 
 #pragma mark -
 #pragma mark CelObjColor
+
 CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
 	_info.type = kCelTypeColor;
 	_info.color = color;
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index ccc56c0..cbd6146 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -42,15 +42,13 @@
 #include "sci/graphics/coordadjuster.h"
 #include "sci/graphics/compare.h"
 #include "sci/graphics/font.h"
-#include "sci/graphics/view.h"
 #include "sci/graphics/screen.h"
 #include "sci/graphics/paint32.h"
 #include "sci/graphics/palette32.h"
-#include "sci/graphics/picture.h"
-#include "sci/graphics/remap.h"
-#include "sci/graphics/text32.h"
 #include "sci/graphics/plane32.h"
+#include "sci/graphics/remap32.h"
 #include "sci/graphics/screen_item32.h"
+#include "sci/graphics/text32.h"
 #include "sci/graphics/frameout.h"
 #include "sci/video/robot_decoder.h"
 
@@ -862,7 +860,7 @@ void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showLi
 }
 
 void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
-	Palette sourcePalette(*_palette->getNextPalette());
+	Palette sourcePalette(_palette->getNextPalette());
 	alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
 
 	int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
@@ -904,7 +902,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
 		drawScreenItemList(screenItemLists[i]);
 	}
 
-	Palette nextPalette(*_palette->getNextPalette());
+	Palette nextPalette(_palette->getNextPalette());
 
 	if (prevRoom < 1000) {
 		for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 6844011..0840e82 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -28,7 +28,7 @@
 #include "sci/event.h"
 #include "sci/resource.h"
 #include "sci/graphics/palette32.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
 #include "sci/graphics/screen.h"
 
 namespace Sci {
@@ -78,10 +78,6 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
 	}
 }
 
-const Palette *GfxPalette32::getNextPalette() const {
-	return &_nextPalette;
-}
-
 void GfxPalette32::submit(Palette &palette) {
 	// TODO: The resource manager in SCI32 retains raw data of palettes from
 	// the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and
@@ -178,49 +174,6 @@ void GfxPalette32::set(Palette *newPalette, bool force, bool forceRealMerge) {
 	submit(*newPalette);
 }
 
-// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *)
-// and is used by Remap
-// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to
-// match using an algorithm from SCI16 engine right now. This needs to be
-// corrected in the future so either nothing calls
-// GfxPalette::matchColor(int, int, int), or it is fixed to match the other
-// SCI32 algorithms.
-int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) {
-	int16 bestIndex = -1;
-	int bestDifference = 0xFFFFF;
-	int difference = defaultDifference;
-
-	// SQ6 DOS really does check only the first 236 entries
-	for (int i = 0, channelDifference; i < 236; ++i) {
-		if (matchTable[i] == 0) {
-			continue;
-		}
-
-		difference = _sysPalette.colors[i].r - r;
-		difference *= difference;
-		if (bestDifference <= difference) {
-			continue;
-		}
-		channelDifference = _sysPalette.colors[i].g - g;
-		difference += channelDifference * channelDifference;
-		if (bestDifference <= difference) {
-			continue;
-		}
-		channelDifference = _sysPalette.colors[i].b - b;
-		difference += channelDifference * channelDifference;
-		if (bestDifference <= difference) {
-			continue;
-		}
-		bestDifference = difference;
-		bestIndex = i;
-	}
-
-	// NOTE: This value is only valid if the last index to
-	// perform a difference calculation was the best index
-	lastCalculatedDifference = difference;
-	return bestIndex;
-}
-
 bool GfxPalette32::updateForFrame() {
 	applyAll();
 	_versionUpdated = false;
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index a545077..70237c3 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -113,12 +113,12 @@ private:
 
 public:
 	virtual void saveLoadWithSerializer(Common::Serializer &s) override;
-	const Palette *getNextPalette() const;
+	inline const Palette &getNextPalette() const { return _nextPalette; };
+	inline const Palette &getCurrentPalette() const { return _sysPalette; };
 
 	bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
 	int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
 	void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
-	int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
 
 	/**
 	 * Submits a palette to display. Entries marked as “used” in the
@@ -240,6 +240,11 @@ private:
 	 * 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 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).
 	 */
 	bool _cycleMap[256];
 	inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
@@ -257,7 +262,7 @@ public:
 	void cycleAllOff();
 	void applyAllCycles();
 	void applyCycles();
-	const bool *getCyclemap() { return _cycleMap; }
+	inline const bool *getCycleMap() const { return _cycleMap; }
 
 #pragma mark -
 #pragma mark Fading
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
index 470986f..3f0b035 100644
--- a/engines/sci/graphics/plane32.cpp
+++ b/engines/sci/graphics/plane32.cpp
@@ -27,7 +27,7 @@
 #include "sci/graphics/frameout.h"
 #include "sci/graphics/lists32.h"
 #include "sci/graphics/plane32.h"
-#include "sci/graphics/remap.h"
+#include "sci/graphics/remap32.h"
 #include "sci/graphics/screen.h"
 #include "sci/graphics/screen_item32.h"
 
diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp
index ff49e52..2abf03e 100644
--- a/engines/sci/graphics/remap.cpp
+++ b/engines/sci/graphics/remap.cpp
@@ -21,31 +21,23 @@
  */
 
 #include "sci/sci.h"
-#include "sci/resource.h"
 #include "sci/graphics/palette.h"
-#include "sci/graphics/palette32.h"
 #include "sci/graphics/remap.h"
 #include "sci/graphics/screen.h"
 
 namespace Sci {
 
-#pragma mark -
-#pragma mark SCI16 remapping (QFG4 demo)
-
 GfxRemap::GfxRemap(GfxPalette *palette)
 	: _palette(palette) {
 	_remapOn = false;
 	resetRemapping();
 }
 
-GfxRemap::~GfxRemap() {
-}
-
 byte GfxRemap::remapColor(byte remappedColor, byte screenColor) {
 	assert(_remapOn);
-	if (_remappingType[remappedColor] == kRemappingByRange)
+	if (_remappingType[remappedColor] == kRemapByRange)
 		return _remappingByRange[screenColor];
-	else if (_remappingType[remappedColor] == kRemappingByPercent)
+	else if (_remappingType[remappedColor] == kRemapByPercent)
 		return _remappingByPercent[screenColor];
 	else
 		error("remapColor(): Color %d isn't remapped", remappedColor);
@@ -58,7 +50,7 @@ void GfxRemap::resetRemapping() {
 	_remappingPercentToSet = 0;
 
 	for (int i = 0; i < 256; i++) {
-		_remappingType[i] = kRemappingNone;
+		_remappingType[i] = kRemapNone;
 		_remappingByPercent[i] = i;
 		_remappingByRange[i] = i;
 	}
@@ -80,7 +72,7 @@ void GfxRemap::setRemappingPercent(byte color, byte percent) {
 		_remappingByPercent[i] = _palette->kernelFindColor(r, g, b);
 	}
 
-	_remappingType[color] = kRemappingByPercent;
+	_remappingType[color] = kRemapByPercent;
 }
 
 void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
@@ -90,7 +82,7 @@ void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {
 		_remappingByRange[i] = i + base;
 	}
 
-	_remappingType[color] = kRemappingByRange;
+	_remappingType[color] = kRemapByRange;
 }
 
 void GfxRemap::updateRemapping() {
@@ -104,277 +96,4 @@ void GfxRemap::updateRemapping() {
 		}
 	}
 }
-
-#pragma mark -
-#pragma mark SCI32 remapping
-
-#ifdef ENABLE_SCI32
-
-GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) {
-	for (int i = 0; i < REMAP_COLOR_COUNT; i++)
-		_remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
-	_noMapStart = _noMapCount = 0;
-	_update = false;
-	_remapCount = 0;
-
-	// The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle.
-	// All versions of KQ7 are using the older remap range semantics.
-	_remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) ? 244 : 254;
-}
-
-void GfxRemap32::remapOff(byte color) {
-	if (!color) {
-		for (int i = 0; i < REMAP_COLOR_COUNT; i++)
-			_remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
-
-		_remapCount = 0;
-	} else {
-		assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
-		const byte index = _remapEndColor - color;
-		_remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone);
-		_remapCount--;
-	}
-
-	_update = true;
-}
-
-void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) {
-	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
-	_remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange);
-	initColorArrays(_remapEndColor - color);
-	_remapCount++;
-	_update = true;
-}
-
-void GfxRemap32::setRemappingPercent(byte color, byte percent) {
-	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
-	_remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent);
-	initColorArrays(_remapEndColor - color);
-	_remapCount++;
-	_update = true;
-}
-
-void GfxRemap32::setRemappingToGray(byte color, byte gray) {
-	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
-	_remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray);
-	initColorArrays(_remapEndColor - color);
-	_remapCount++;
-	_update = true;
-}
-
-void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) {
-	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
-	_remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray);
-	initColorArrays(_remapEndColor - color);
-	_remapCount++;
-	_update = true;
-}
-
-void GfxRemap32::setNoMatchRange(byte from, byte count) {
-	_noMapStart = from;
-	_noMapCount = count;
-}
-
-bool GfxRemap32::remapEnabled(byte color) const {
-	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
-	const byte index = _remapEndColor - color;
-	return (_remaps[index].type != kRemappingNone);
-}
-
-byte GfxRemap32::remapColor(byte color, byte target) {
-	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT);
-	const byte index = _remapEndColor - color;
-	if (_remaps[index].type != kRemappingNone)
-		return _remaps[index].remap[target];
-	else
-		return target;
-}
-
-void GfxRemap32::initColorArrays(byte index) {
-	Palette *curPalette = &_palette->_sysPalette;
-	RemapParams *curRemap = &_remaps[index];
-
-	memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
-	memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color));
-}
-
-bool GfxRemap32::updateRemap(byte index, bool palChanged) {
-	int result;
-	RemapParams *curRemap = &_remaps[index];
-	const Palette *curPalette = &_palette->_sysPalette;
-	const Palette *nextPalette = _palette->getNextPalette();
-	bool changed = false;
-
-	if (!_update && !palChanged)
-		return false;
-
-	Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false);
-
-	switch (curRemap->type) {
-	case kRemappingNone:
-		return false;
-	case kRemappingByRange:
-		for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)  {
-			if (curRemap->from <= i && i <= curRemap->to)
-				result = i + curRemap->base;
-			else
-				result = i;
-
-			if (curRemap->remap[i] != result) {
-				changed = true;
-				curRemap->remap[i] = result;
-			}
-
-			curRemap->colorChanged[i] = true;
-		}
-		return changed;
-	case kRemappingByPercent:
-		for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
-			// NOTE: This method uses nextPalette instead of curPalette
-			Color color = nextPalette->colors[i];
-
-			if (curRemap->curColor[i] != color) {
-				curRemap->colorChanged[i] = true;
-				curRemap->curColor[i] = color;
-			}
-
-			if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i])  {
-				byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255);
-				byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255);
-				byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255);
-				byte used = curRemap->targetColor[i].used;
-
-				Color newColor = { used, red, green, blue };
-				if (curRemap->targetColor[i] != newColor)  {
-					_targetChanged[i] = true;
-					curRemap->targetColor[i] = newColor;
-				}
-			}
-		}
-		
-		changed = applyRemap(index);
-		Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
-		curRemap->oldPercent = curRemap->percent;
-		return changed;
-	case kRemappingToGray:
-		for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
-			Color color = curPalette->colors[i];
-
-			if (curRemap->curColor[i] != color) {
-				curRemap->colorChanged[i] = true;
-				curRemap->curColor[i] = color;
-			}
-
-			if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i])  {
-				byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
-				byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
-				byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
-				byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
-				byte used = curRemap->targetColor[i].used;
-
-				Color newColor = { used, red, green, blue };
-				if (curRemap->targetColor[i] != newColor)  {
-					_targetChanged[i] = true;
-					curRemap->targetColor[i] = newColor;
-				}
-			}
-		}
-
-		changed = applyRemap(index);
-		Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
-		curRemap->oldGray = curRemap->gray;
-		return changed;
-	case kRemappingToPercentGray:
-		for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) {
-			Color color = curPalette->colors[i];
-
-			if (curRemap->curColor[i] != color) {
-				curRemap->colorChanged[i] = true;
-				curRemap->curColor[i] = color;
-			}
-
-			if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i])  {
-				byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8;
-				lumosity = lumosity * curRemap->percent / 100;
-				byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255);
-				byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255);
-				byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255);
-				byte used = curRemap->targetColor[i].used;
-
-				Color newColor = { used, red, green, blue };
-				if (curRemap->targetColor[i] != newColor)  {
-					_targetChanged[i] = true;
-					curRemap->targetColor[i] = newColor;
-				}
-			}
-		}
-
-		changed = applyRemap(index);
-		Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false);
-		curRemap->oldPercent = curRemap->percent;
-		curRemap->oldGray = curRemap->gray;
-		return changed;
-	default:
-		return false;
-	}
-}
-
-static int colorDistance(Color a, Color b) {
-	int rDiff = (a.r - b.r) * (a.r - b.r);
-	int gDiff = (a.g - b.g) * (a.g - b.g);
-	int bDiff = (a.b - b.b) * (a.b - b.b);
-	return rDiff + gDiff + bDiff;
-}
-
-bool GfxRemap32::applyRemap(byte index) {
-	RemapParams *curRemap = &_remaps[index];
-	const bool *cycleMap = _palette->getCyclemap();
-	bool unmappedColors[NON_REMAPPED_COLOR_COUNT];
-	bool changed = false;
-
-	Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false);
-	if (_noMapCount)
-		Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true);
-
-	for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)  {
-		if (cycleMap[i])
-			unmappedColors[i] = true;
-	}
-
-	for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++)  {
-		Color targetColor = curRemap->targetColor[i];
-		bool colorChanged = curRemap->colorChanged[curRemap->remap[i]];
-
-		if (!_targetChanged[i] && !colorChanged)
-			continue;
-
-		if (_targetChanged[i] && colorChanged)
-			if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i])
-				continue;
-
-		int diff = 0;
-		int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors);
-		if (result != -1 && curRemap->remap[i] != result)  {
-			changed = true;
-			curRemap->remap[i] = result;
-			curRemap->distance[i] = diff;
-		}
-	}
-
-	return changed;
-}
-
-bool GfxRemap32::remapAllTables(bool palChanged) {
-	bool changed = false;
-
-	for (int i = 0; i < REMAP_COLOR_COUNT; i++) {
-		changed |= updateRemap(i, palChanged);
-	}
-
-	_update = false;
-	return changed;
-}
-
-#endif
-
 } // End of namespace Sci
diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h
index d012568..98177f6 100644
--- a/engines/sci/graphics/remap.h
+++ b/engines/sci/graphics/remap.h
@@ -24,42 +24,36 @@
 #define SCI_GRAPHICS_REMAP_H
 
 #include "common/array.h"
-#include "sci/graphics/helpers.h"
+#include "common/serializer.h"
 
 namespace Sci {
 
 class GfxScreen;
 
-enum ColorRemappingType {
-	kRemappingNone = 0,
-	kRemappingByRange = 1,
-	kRemappingByPercent = 2,
-	kRemappingToGray = 3,
-	kRemappingToPercentGray = 4
-};
-
-#define REMAP_COLOR_COUNT 9
-#define NON_REMAPPED_COLOR_COUNT 236
-
 /**
- * Remap class, handles color remapping
+ * This class handles color remapping for the QFG4 demo.
  */
 class GfxRemap {
+private:
+	enum ColorRemappingType {
+		kRemapNone = 0,
+		kRemapByRange = 1,
+		kRemapByPercent = 2
+	};
+
 public:
 	GfxRemap(GfxPalette *_palette);
-	~GfxRemap();
 
 	void resetRemapping();
 	void setRemappingPercent(byte color, byte percent);
 	void setRemappingRange(byte color, byte from, byte to, byte base);
 	bool isRemapped(byte color) const {
-		return _remapOn && (_remappingType[color] != kRemappingNone);
+		return _remapOn && (_remappingType[color] != kRemapNone);
 	}
 	byte remapColor(byte remappedColor, byte screenColor);
 	void updateRemapping();
 
 private:
-	GfxScreen *_screen;
 	GfxPalette *_palette;
 
 	bool _remapOn;
@@ -68,87 +62,6 @@ private:
 	byte _remappingByRange[256];
 	uint16 _remappingPercentToSet;
 };
-
-#ifdef ENABLE_SCI32
-
-struct RemapParams {
-	byte from;
-	byte to;
-	byte base;
-	byte gray;
-	byte oldGray;
-	byte percent;
-	byte oldPercent;
-	ColorRemappingType type;
-	Color curColor[256];
-	Color targetColor[256];
-	byte distance[256];
-	byte remap[256];
-	bool colorChanged[256];
-
-	RemapParams() {
-		from = to = base = gray = oldGray = percent = oldPercent = 0;
-		type = kRemappingNone;
-
-		// curColor and targetColor are initialized in GfxRemap32::initColorArrays
-		memset(curColor, 0, 256 * sizeof(Color));
-		memset(targetColor, 0, 256 * sizeof(Color));
-		memset(distance, 0, 256);
-		for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
-			remap[i] = i;
-		Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
-	}
-
-	RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) {
-		from = from_;
-		to = to_;
-		base = base_;
-		gray = oldGray = gray_;
-		percent = oldPercent = percent_;
-		type = type_;
-
-		// curColor and targetColor are initialized in GfxRemap32::initColorArrays
-		memset(curColor, 0, 256 * sizeof(Color));
-		memset(targetColor, 0, 256 * sizeof(Color));
-		memset(distance, 0, 256);
-		for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)
-			remap[i] = i;
-		Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true);
-	}
-};
-
-class GfxRemap32 {
-public:
-	GfxRemap32(GfxPalette32 *palette);
-	~GfxRemap32() {}
-
-	void remapOff(byte color);
-	void setRemappingRange(byte color, byte from, byte to, byte base);
-	void setRemappingPercent(byte color, byte percent);
-	void setRemappingToGray(byte color, byte gray);
-	void setRemappingToPercentGray(byte color, byte gray, byte percent);
-	void setNoMatchRange(byte from, byte count);
-	bool remapEnabled(byte color) const;
-	byte remapColor(byte color, byte target);
-	bool remapAllTables(bool palChanged);
-	int getRemapCount() const { return _remapCount; }
-	int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; }
-	int getEndColor() const { return _remapEndColor; }
-private:
-	GfxPalette32 *_palette;
-	RemapParams _remaps[REMAP_COLOR_COUNT];
-	bool _update;
-	byte _noMapStart, _noMapCount;
-	bool _targetChanged[NON_REMAPPED_COLOR_COUNT];
-	byte _remapEndColor;
-	int _remapCount;
-
-	void initColorArrays(byte index);
-	bool applyRemap(byte index);
-	bool updateRemap(byte index, bool palChanged);
-};
-#endif
-
 } // End of namespace Sci
 
 #endif
diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp
new file mode 100644
index 0000000..d5a2362
--- /dev/null
+++ b/engines/sci/graphics/remap32.cpp
@@ -0,0 +1,468 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sci.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
+
+namespace Sci {
+
+#pragma mark SingleRemap
+
+void SingleRemap::reset() {
+	_lastPercent = 100;
+	_lastGray = 0;
+
+	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+	const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+	for (uint i = 0; i < remapStartColor; ++i) {
+		const Color &color = currentPalette.colors[i];
+		_remapColors[i] = i;
+		_originalColors[i] = color;
+		_originalColorsChanged[i] = true;
+		_idealColors[i] = color;
+		_idealColorsChanged[i] = false;
+		_matchDistances[i] = 0;
+	}
+}
+
+bool SingleRemap::update() {
+	switch (_type) {
+	case kRemapNone:
+		break;
+	case kRemapByRange:
+		return updateRange();
+	case kRemapByPercent:
+		return updateBrightness();
+	case kRemapToGray:
+		return updateSaturation();
+	case kRemapToPercentGray:
+		return updateSaturationAndBrightness();
+	default:
+		error("Illegal remap type %d", _type);
+	}
+
+	return false;
+}
+
+bool SingleRemap::updateRange() {
+	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+	bool updated = false;
+
+	for (uint i = 0; i < remapStartColor; ++i) {
+		uint8 targetColor;
+		if (_from <= i && i <= _to) {
+			targetColor = i + _delta;
+		} else {
+			targetColor = i;
+		}
+
+		if (_remapColors[i] != targetColor) {
+			updated = true;
+			_remapColors[i] = targetColor;
+		}
+
+		_originalColorsChanged[i] = true;
+	}
+
+	return updated;
+}
+
+bool SingleRemap::updateBrightness() {
+	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+	const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+	for (uint i = 1; i < remapStartColor; ++i) {
+		Color color(nextPalette.colors[i]);
+
+		if (_originalColors[i] != color) {
+			_originalColorsChanged[i] = true;
+			_originalColors[i] = color;
+		}
+
+		if (_percent != _lastPercent || _originalColorsChanged[i]) {
+			// NOTE: SSCI checked if percent was over 100 and only
+			// then clipped values, but we always unconditionally
+			// ensure the result is in the correct range
+			color.r = MIN(255, (uint16)color.r * _percent / 100);
+			color.g = MIN(255, (uint16)color.g * _percent / 100);
+			color.b = MIN(255, (uint16)color.b * _percent / 100);
+
+			if (_idealColors[i] != color) {
+				_idealColorsChanged[i] = true;
+				_idealColors[i] = color;
+			}
+		}
+	}
+
+	const bool updated = apply();
+	Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+	Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+	_lastPercent = _percent;
+	return updated;
+}
+
+bool SingleRemap::updateSaturation() {
+	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+	const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+	for (uint i = 1; i < remapStartColor; ++i) {
+		Color color(currentPalette.colors[i]);
+		if (_originalColors[i] != color) {
+			_originalColorsChanged[i] = true;
+			_originalColors[i] = color;
+		}
+
+		if (_gray != _lastGray || _originalColorsChanged[i]) {
+			const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+			color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100));
+			color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100));
+			color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100));
+
+			if (_idealColors[i] != color) {
+				_idealColorsChanged[i] = true;
+				_idealColors[i] = color;
+			}
+		}
+	}
+
+	const bool updated = apply();
+	Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+	Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+	_lastGray = _gray;
+	return updated;
+}
+
+bool SingleRemap::updateSaturationAndBrightness() {
+	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor();
+	const Palette &currentPalette = g_sci->_gfxPalette32->getCurrentPalette();
+	for (uint i = 1; i < remapStartColor; i++) {
+		Color color(currentPalette.colors[i]);
+		if (_originalColors[i] != color) {
+			_originalColorsChanged[i] = true;
+			_originalColors[i] = color;
+		}
+
+		if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) {
+			const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100;
+
+			color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100);
+			color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100);
+			color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100);
+
+			if (_idealColors[i] != color) {
+				_idealColorsChanged[i] = true;
+				_idealColors[i] = color;
+			}
+		}
+	}
+
+	const bool updated = apply();
+	Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false);
+	Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false);
+	_lastPercent = _percent;
+	_lastGray = _gray;
+	return updated;
+}
+
+bool SingleRemap::apply() {
+	const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32;
+	const uint8 remapStartColor = gfxRemap32->getStartColor();
+
+	// Blocked colors are not allowed to be used as target
+	// colors for the remap
+	bool blockedColors[236];
+	Common::fill(blockedColors, blockedColors + remapStartColor, false);
+
+	const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap();
+
+	const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount();
+	if (blockedRangeCount) {
+		const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart();
+		Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true);
+	}
+
+	for (uint i = 0; i < remapStartColor; ++i) {
+		if (paletteCycleMap[i]) {
+			blockedColors[i] = true;
+		}
+	}
+
+	// NOTE: SSCI did a loop over colors here to create a
+	// new array of updated, unblocked colors, but then
+	// never used it
+
+	bool updated = false;
+	for (uint i = 1; i < remapStartColor; ++i) {
+		int distance;
+
+		if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) {
+			continue;
+		}
+
+		if (
+			_idealColorsChanged[i] &&
+			_originalColorsChanged[_remapColors[i]] &&
+			_matchDistances[i] < 100 &&
+			colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i]
+		) {
+			continue;
+		}
+
+		const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors);
+
+		if (bestColor != -1 && _remapColors[i] != bestColor) {
+			updated = true;
+			_remapColors[i] = bestColor;
+			_matchDistances[i] = distance;
+		}
+	}
+
+	return updated;
+}
+
+int SingleRemap::colorDistance(const Color &a, const Color &b) const {
+	int channelDistance = a.r - b.r;
+	int distance = channelDistance * channelDistance;
+	channelDistance = a.g - b.g;
+	distance += channelDistance * channelDistance;
+	channelDistance = a.b - b.b;
+	distance += channelDistance * channelDistance;
+	return distance;
+}
+
+int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const {
+	int16 bestIndex = -1;
+	int bestDistance = 0xFFFFF;
+	int distance = minimumDistance;
+	const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette();
+
+	for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) {
+		if (blockedIndexes[i]) {
+			continue;
+		}
+
+		distance = nextPalette.colors[i].r - color.r;
+		distance *= distance;
+		if (bestDistance <= distance) {
+			continue;
+		}
+		channelDistance = nextPalette.colors[i].g - color.g;
+		distance += channelDistance * channelDistance;
+		if (bestDistance <= distance) {
+			continue;
+		}
+		channelDistance = nextPalette.colors[i].b - color.b;
+		distance += channelDistance * channelDistance;
+		if (bestDistance <= distance) {
+			continue;
+		}
+		bestDistance = distance;
+		bestIndex = i;
+	}
+
+	// This value is only valid if the last index to
+	// perform a distance calculation was the best index
+	outDistance = distance;
+	return bestIndex;
+}
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+GfxRemap32::GfxRemap32() :
+	_needsUpdate(false),
+	_blockedRangeStart(0),
+	_blockedRangeCount(0),
+	_remapStartColor(236),
+	_numActiveRemaps(0) {
+	// The `_remapStartColor` seems to always be 236 in SSCI,
+	// but if it is ever changed then the various C-style
+	// member arrays hard-coded to 236 need to be changed to
+	// match the highest possible value of `_remapStartColor`
+	assert(_remapStartColor == 236);
+
+	if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) {
+		_remaps.resize(9);
+	} else {
+		_remaps.resize(19);
+	}
+
+	_remapEndColor = _remapStartColor + _remaps.size() - 1;
+}
+
+void GfxRemap32::remapOff(const uint8 color) {
+	if (color == 0) {
+		remapAllOff();
+		return;
+	}
+
+	// NOTE: SSCI simply ignored invalid input values, but
+	// we at least give a warning so games can be investigated
+	// for script bugs
+	if (color < _remapStartColor || color > _remapEndColor) {
+		warning("GfxRemap32::remapOff: %d out of remap range", color);
+		return;
+	}
+
+	const uint8 index = _remapEndColor - color;
+	SingleRemap &singleRemap = _remaps[index];
+	singleRemap._type = kRemapNone;
+	--_numActiveRemaps;
+	_needsUpdate = true;
+}
+
+void GfxRemap32::remapAllOff() {
+	for (uint i = 0, len = _remaps.size(); i < len; ++i) {
+		_remaps[i]._type = kRemapNone;
+	}
+
+	_numActiveRemaps = 0;
+	_needsUpdate = true;
+}
+
+void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) {
+	// NOTE: SSCI simply ignored invalid input values, but
+	// we at least give a warning so games can be investigated
+	// for script bugs
+	if (color < _remapStartColor || color > _remapEndColor) {
+		warning("GfxRemap32::remapByRange: %d out of remap range", color);
+		return;
+	}
+
+	if (from < 0) {
+		warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from);
+		return;
+	}
+
+	if (to >= _remapStartColor) {
+		warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to);
+		return;
+	}
+
+	const uint8 index = _remapEndColor - color;
+	SingleRemap &singleRemap = _remaps[index];
+
+	if (singleRemap._type == kRemapNone) {
+		++_numActiveRemaps;
+		singleRemap.reset();
+	}
+
+	singleRemap._from = from;
+	singleRemap._to = to;
+	singleRemap._delta = delta;
+	singleRemap._type = kRemapByRange;
+	_needsUpdate = true;
+}
+
+void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) {
+	// NOTE: SSCI simply ignored invalid input values, but
+	// we at least give a warning so games can be investigated
+	// for script bugs
+	if (color < _remapStartColor || color > _remapEndColor) {
+		warning("GfxRemap32::remapByPercent: %d out of remap range", color);
+		return;
+	}
+
+	const uint8 index = _remapEndColor - color;
+	SingleRemap &singleRemap = _remaps[index];
+
+	if (singleRemap._type == kRemapNone) {
+		++_numActiveRemaps;
+		singleRemap.reset();
+	}
+
+	singleRemap._percent = percent;
+	singleRemap._type = kRemapByPercent;
+	_needsUpdate = true;
+}
+
+void GfxRemap32::remapToGray(const uint8 color, const int8 gray) {
+	// NOTE: SSCI simply ignored invalid input values, but
+	// we at least give a warning so games can be investigated
+	// for script bugs
+	if (color < _remapStartColor || color > _remapEndColor) {
+		warning("GfxRemap32::remapToGray: %d out of remap range", color);
+		return;
+	}
+
+	if (gray < 0 || gray > 100) {
+		error("RemapToGray percent out of range; gray = %d", gray);
+	}
+
+	const uint8 index = _remapEndColor - color;
+	SingleRemap &singleRemap = _remaps[index];
+
+	if (singleRemap._type == kRemapNone) {
+		++_numActiveRemaps;
+		singleRemap.reset();
+	}
+
+	singleRemap._gray = gray;
+	singleRemap._type = kRemapToGray;
+	_needsUpdate = true;
+}
+
+void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) {
+	// NOTE: SSCI simply ignored invalid input values, but
+	// we at least give a warning so games can be investigated
+	// for script bugs
+	if (color < _remapStartColor || color > _remapEndColor) {
+		warning("GfxRemap32::remapToPercentGray: %d out of remap range", color);
+		return;
+	}
+
+	const uint8 index = _remapEndColor - color;
+	SingleRemap &singleRemap = _remaps[index];
+
+	if (singleRemap._type == kRemapNone) {
+		++_numActiveRemaps;
+		singleRemap.reset();
+	}
+
+	singleRemap._percent = percent;
+	singleRemap._gray = gray;
+	singleRemap._type = kRemapToPercentGray;
+	_needsUpdate = true;
+}
+
+void GfxRemap32::blockRange(const uint8 from, const int16 count) {
+	_blockedRangeStart = from;
+	_blockedRangeCount = count;
+}
+
+bool GfxRemap32::remapAllTables(const bool paletteUpdated) {
+	if (!_needsUpdate && !paletteUpdated) {
+		return false;
+	}
+
+	bool updated = false;
+
+	for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) {
+		if (it->_type != kRemapNone) {
+			updated |= it->update();
+		}
+	}
+
+	_needsUpdate = false;
+	return updated;
+}
+} // End of namespace Sci
diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h
new file mode 100644
index 0000000..5f629d7
--- /dev/null
+++ b/engines/sci/graphics/remap32.h
@@ -0,0 +1,400 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_REMAP32_H
+#define SCI_GRAPHICS_REMAP32_H
+
+#include "common/algorithm.h"
+#include "common/array.h"
+#include "common/scummsys.h"
+#include "sci/graphics/helpers.h"
+
+namespace Sci {
+class GfxPalette32;
+
+enum RemapType {
+	kRemapNone = 0,
+	kRemapByRange = 1,
+	kRemapByPercent = 2,
+	kRemapToGray = 3,
+	kRemapToPercentGray = 4
+};
+
+#pragma mark -
+#pragma mark SingleRemap
+
+/**
+ * SingleRemap objects each manage one remapping operation.
+ */
+class SingleRemap {
+public:
+	SingleRemap() : _type(kRemapNone) {}
+
+	/**
+	 * The type of remap.
+	 */
+	RemapType _type;
+
+	/**
+	 * The first color that should be shifted by a range
+	 * remap.
+	 */
+	uint8 _from;
+
+	/**
+	 * The last color that should be shifted a range remap.
+	 */
+	uint8 _to;
+
+	/**
+	 * The direction and amount that the colors should be
+	 * shifted in a range remap.
+	 */
+	int16 _delta;
+
+	/**
+	 * The difference in brightness that should be
+	 * applied by a brightness (percent) remap.
+	 *
+	 * This value may be be greater than 100, in
+	 * which case the color will be oversaturated.
+	 */
+	int16 _percent;
+
+	/**
+	 * The amount of desaturation that should be
+	 * applied by a saturation (gray) remap, where
+	 * 0 is full saturation and 100 is full
+	 * desaturation.
+	 */
+	uint8 _gray;
+
+	/**
+	 * The final array used by CelObj renderers to composite
+	 * remapped pixels to the screen buffer.
+	 *
+	 * Here is how it works:
+	 *
+	 * The source bitmap being rendered will have pixels
+	 * within the remap range (236-245 or 236-254), and the
+	 * target buffer will have colors in the non-remapped
+	 * range (0-235).
+	 *
+	 * To arrive at the correct color, first the source
+	 * pixel is used to look up the correct SingleRemap for
+	 * that pixel. Then, the final composited color is
+	 * looked up in this array using the target's pixel
+	 * color. In other words,
+	 * `target = _remaps[remapEndColor - source].remapColors[target]`.
+	 */
+	uint8 _remapColors[236];
+
+	/**
+	 * Resets this SingleRemap's color information to
+	 * default values.
+	 */
+	void reset();
+
+	/**
+	 * Recalculates and reapplies remap colors to the
+	 * `_remapColors` array.
+	 */
+	bool update();
+
+private:
+	/**
+	 * The previous brightness value. Used to
+	 * determine whether or not targetColors needs
+	 * to be updated.
+	 */
+	int16 _lastPercent;
+
+	/**
+	 * The previous saturation value. Used to
+	 * determine whether or not targetColors needs
+	 * to be updated.
+	 */
+	uint8 _lastGray;
+
+	/**
+	 * The colors from the current GfxPalette32 palette
+	 * before this SingleRemap is applied.
+	 */
+	Color _originalColors[236];
+
+	/**
+	 * Map of colors that changed in `_originalColors`
+	 * when this SingleRemap was updated. This map is
+	 * transient and gets reset to `false` after the
+	 * SingleRemap finishes updating.
+	 */
+	bool _originalColorsChanged[236];
+
+	/**
+	 * The ideal target RGB color values for each generated
+	 * remap color.
+	 */
+	Color _idealColors[236];
+
+	/**
+	 * Map of colors that changed in `_idealColors` when
+	 * this SingleRemap was updated. This map is transient
+	 * and gets reset to `false` after the SingleRemap
+	 * finishes applying.
+	 */
+	bool _idealColorsChanged[236];
+
+	/**
+	 * When applying a SingleRemap, finding an appropriate
+	 * color in the palette is the responsibility of a
+	 * distance function. Once a match is found, the
+	 * distance of that match is stored here so that the
+	 * next time the SingleRemap is applied, it can check
+	 * the distance from the previous application and avoid
+	 * triggering an expensive redraw of the entire screen
+	 * if the new palette value only changed slightly.
+	 */
+	int _matchDistances[236];
+
+	/**
+	 * Computes the final target values for a range remap
+	 * and applies them directly to the `_remaps` map.
+	 *
+	 * @note Was ByRange in SSCI.
+	 */
+	bool updateRange();
+
+	/**
+	 * Computes the intermediate target values for a
+	 * brightness remap and applies them indirectly via
+	 * the `apply` method.
+	 *
+	 * @note Was ByPercent in SSCI.
+	 */
+	bool updateBrightness();
+
+	/**
+	 * Computes the intermediate target values for a
+	 * saturation remap and applies them indirectly via
+	 * the `apply` method.
+	 *
+	 * @note Was ToGray in SSCI.
+	 */
+	bool updateSaturation();
+
+	/**
+	 * Computes the intermediate target values for a
+	 * saturation + brightness bitmap and applies them
+	 * indirectly via the `apply` method.
+	 *
+	 * @note Was ToPercentGray in SSCI.
+	 */
+	bool updateSaturationAndBrightness();
+
+	/**
+	 * Computes and applies the final values to the
+	 * `_remaps` map.
+	 *
+	 * @note In SSCI, a boolean array of changed values
+	 * was passed into this method, but this was done by
+	 * creating arrays on the stack in the caller. Instead
+	 * of doing this, we simply add another member property
+	 * `_idealColorsChanged` and use that instead.
+	 */
+	bool apply();
+
+	/**
+	 * Calculates the square distance of two colors.
+	 *
+	 * @note In SSCI this method is Rgb24::Dist, but it is
+	 * only used by SingleRemap.
+	 */
+	int colorDistance(const Color &a, const Color &b) const;
+
+	/**
+	 * Finds the closest index in the next palette matching
+	 * the given RGB color. Returns -1 if no match can be
+	 * found that is closer than `minimumDistance`.
+	 *
+	 * @note In SSCI, this method is SOLPalette::Match, but
+	 * this particular signature is only used by
+	 * SingleRemap.
+	 */
+	int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const;
+};
+
+#pragma mark -
+#pragma mark GfxRemap32
+
+/**
+ * This class provides color remapping support for SCI32
+ * games.
+ */
+class GfxRemap32 : public Common::Serializable {
+public:
+	GfxRemap32();
+
+	void saveLoadWithSerializer(Common::Serializer &s);
+
+	inline uint8 getRemapCount() const { return _numActiveRemaps; }
+	inline uint8 getStartColor() const { return _remapStartColor; }
+	inline uint8 getEndColor() const { return _remapEndColor; }
+	inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; }
+	inline int16 getBlockedRangeCount() const { return _blockedRangeCount; }
+
+	/**
+	 * Turns off remapping of the given color. If `color` is
+	 * 0, all remaps are turned off.
+	 */
+	void remapOff(const uint8 color);
+
+	/**
+	 * Turns off all color remaps.
+	 */
+	void remapAllOff();
+
+	/**
+	 * Configures a SingleRemap for the remap color `color`.
+	 * The SingleRemap will shift palette colors between
+	 * `from` and `to` (inclusive) by `delta` palette
+	 * entries when the remap is applied.
+	 */
+	void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta);
+
+	/**
+	 * Configures a SingleRemap for the remap color `color`
+	 * to modify the brightness of remapped colors by
+	 * `percent`.
+	 */
+	void remapByPercent(const uint8 color, const int16 percent);
+
+	/**
+	 * Configures a SingleRemap for the remap color `color`
+	 * to modify the saturation of remapped colors by
+	 * `gray`.
+	 */
+	void remapToGray(const uint8 color, const int8 gray);
+
+	/**
+	 * Configures a SingleRemap for the remap color `color`
+	 * to modify the brightness of remapped colors by
+	 * `percent`, and saturation of remapped colors by
+	 * `gray`.
+	 */
+	void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent);
+
+	/**
+	 * Prevents GfxRemap32 from using the given range of
+	 * palette entries as potential remap targets.
+	 *
+	 * @NOTE Was DontMapToRange in SSCI.
+	 */
+	void blockRange(const uint8 from, const int16 count);
+
+	/**
+	 * Determines whether or not the given color has an
+	 * active remapper. If it does not, it is treated as a
+	 * skip color and the pixel is not drawn.
+	 *
+	 * @note SSCI uses a boolean array to decide whether a
+	 * a pixel is remapped, but it is possible to get the
+	 * same information from `_remaps`, as this function
+	 * does.
+	 * Presumably, the separate array was created for
+	 * performance reasons, since this is called a lot in
+	 * the most critical section of the renderer.
+	 */
+	inline bool remapEnabled(uint8 color) const {
+		const uint8 index = _remapEndColor - color;
+		assert(index < _remaps.size());
+		return (_remaps[index]._type != kRemapNone);
+	}
+
+	/**
+	 * Calculates the correct color for a target by looking
+	 * up the target color in the SingleRemap that controls
+	 * the given sourceColor. If there is no remap for the
+	 * given color, it will be treated as a skip color.
+	 */
+	inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const {
+		const uint8 index = _remapEndColor - sourceColor;
+		assert(index < _remaps.size());
+		const SingleRemap &singleRemap = _remaps[index];
+		assert(singleRemap._type != kRemapNone);
+		return singleRemap._remapColors[targetColor];
+	}
+
+	/**
+	 * Updates all active remaps in response to a palette
+	 * change or a remap settings change.
+	 *
+	 * `paletteChanged` is true if the next palette in
+	 * GfxPalette32 has been previously modified by other
+	 * palette operations.
+	 */
+	bool remapAllTables(const bool paletteUpdated);
+
+private:
+	typedef Common::Array<SingleRemap> SingleRemapsList;
+
+	/**
+	 * The first index of the remap area in the system
+	 * palette.
+	 */
+	const uint8 _remapStartColor;
+
+	/**
+	 * The last index of the remap area in the system
+	 * palette.
+	 */
+	uint8 _remapEndColor;
+
+	/**
+	 * The number of currently active remaps.
+	 */
+	uint8 _numActiveRemaps;
+
+	/**
+	 * The list of SingleRemaps.
+	 */
+	SingleRemapsList _remaps;
+
+	/**
+	 * If true, indicates that one or more SingleRemaps were
+	 * reconfigured and all remaps need to be recalculated.
+	 */
+	bool _needsUpdate;
+
+	/**
+	 * The first color that is blocked from being used as a
+	 * remap target color.
+	 */
+	uint8 _blockedRangeStart;
+
+	/**
+	 * The size of the range of blocked colors. If zero,
+	 * all colors are potential targets for remapping.
+	 */
+	int16 _blockedRangeCount;
+};
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h
index 9159020..96b48c0 100644
--- a/engines/sci/graphics/view.h
+++ b/engines/sci/graphics/view.h
@@ -55,6 +55,7 @@ struct LoopInfo {
 
 class GfxScreen;
 class GfxPalette;
+class Resource;
 
 /**
  * View class, handles loading of view resources and drawing contained cels to screen
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index ba6c853..5d54e2a 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -88,6 +88,7 @@ MODULE_OBJS += \
 	graphics/paint32.o \
 	graphics/plane32.o \
 	graphics/palette32.o \
+	graphics/remap32.o \
 	graphics/screen_item32.o \
 	graphics/text32.o \
 	sound/audio32.o \
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index b80456c..41fa144 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -65,9 +65,10 @@
 
 #ifdef ENABLE_SCI32
 #include "sci/graphics/controls32.h"
+#include "sci/graphics/frameout.h"
 #include "sci/graphics/palette32.h"
+#include "sci/graphics/remap32.h"
 #include "sci/graphics/text32.h"
-#include "sci/graphics/frameout.h"
 #include "sci/sound/audio32.h"
 #include "sci/video/robot_decoder.h"
 #endif
@@ -699,7 +700,7 @@ void SciEngine::initGraphics() {
 	if (getSciVersion() >= SCI_VERSION_2) {
 		_gfxPalette32 = new GfxPalette32(_resMan, _gfxScreen);
 		_gfxPalette16 = _gfxPalette32;
-		_gfxRemap32 = new GfxRemap32(_gfxPalette32);
+		_gfxRemap32 = new GfxRemap32();
 	} else {
 #endif
 		_gfxPalette16 = new GfxPalette(_resMan, _gfxScreen);


Commit: 6feec9ed724ef6ddbe3b3478b18e64f580f949af
    https://github.com/scummvm/scummvm/commit/6feec9ed724ef6ddbe3b3478b18e64f580f949af
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T12:44:35-05:00

Commit Message:
SCI32: Add more workarounds for SCI2.1early games

These games all share the same bad system script that always sends
NULL as a second argument to kDoSound(play).

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



diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 3a6cf0c..073bb93 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -516,6 +516,8 @@ const SciWorkaroundEntry kDisposeScript_workarounds[] = {
 //    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround
 const SciWorkaroundEntry kDoSoundPlay_workarounds[] = {
 	{ GID_LSL6HIRES,    -1,  64989,   0,          NULL,          "play",                    NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+	{ GID_QFG4,         -1,  64989,   0,          NULL,          "play",                    NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+	{ GID_PQ4,          -1,  64989,   0,          NULL,          "play",                    NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
 	SCI_WORKAROUNDENTRY_TERMINATOR
 };
 


Commit: 26e7d3d9e9bf0711d4dfb73a64749cf04855ffac
    https://github.com/scummvm/scummvm/commit/26e7d3d9e9bf0711d4dfb73a64749cf04855ffac
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T12:44:35-05:00

Commit Message:
SCI32: Remove no-longer-relevant comment

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



diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index cbd6146..09e705a 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -972,10 +972,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry
 	_frameNowVisible = true;
 }
 
-// TODO: What does the bit masking for the show rects do,
-// and does it cause an off-by-one error in rect calculations
-// since SOL_Rect is BR inclusive and Common::Rect is BR
-// exclusive?
 void GfxFrameout::showBits() {
 	for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
 		Common::Rect rounded(**rect);


Commit: 3ffde88b18b0373f60407b7f7eb2a3801734d3ee
    https://github.com/scummvm/scummvm/commit/3ffde88b18b0373f60407b7f7eb2a3801734d3ee
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T12:44:35-05:00

Commit Message:
SCI32: Fix typo

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



diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index 70237c3..7dda53e 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -133,7 +133,7 @@ public:
 	void applyAll();
 
 #pragma mark -
-#pragma mark color look-up
+#pragma mark Color look-up
 private:
 	/**
 	 * An optional lookup table used to remap RGB565 colors to a palette


Commit: 5e6f08d9fea3a3b9f2903bad0aa039d9004e0664
    https://github.com/scummvm/scummvm/commit/5e6f08d9fea3a3b9f2903bad0aa039d9004e0664
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T12:44:35-05:00

Commit Message:
SCI32: Enable aspect ratio correction on QFG4CD

Changed paths:
    engines/sci/detection_tables.h



diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index c016132..0b69aa9 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -3591,7 +3591,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {
 		{"resource.map", 0, "aba367f2102e81782d961b14fbe3d630", 10246},
 		{"resource.000", 0, "263dce4aa34c49d3ad29bec889007b1c", 11571394},
 		AD_LISTEND},
-		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI)	},
+		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI)	},
 
 	// RAMA - English DOS/Windows Demo
 	// Executable scanning reports "2.100.002", VERSION file reports "000.000.008"


Commit: 60849a16cb34a35a29734ff86ca4776a0f21e868
    https://github.com/scummvm/scummvm/commit/60849a16cb34a35a29734ff86ca4776a0f21e868
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T12:44:35-05:00

Commit Message:
SCI32: Change Color::operator!= to all operator== directly

Changed paths:
    engines/sci/graphics/helpers.h



diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index 32e7140..3fcc83c 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -237,7 +237,7 @@ struct Color {
 		return used == other.used && r == other.r && g == other.g && b == other.b;
 	}
 	inline bool operator!=(const Color &other) const {
-		return !(*this == other);
+		return !operator==(other);
 	}
 #endif
 };


Commit: 1632bd50c890c9e50139c57b52d8e1bfc8f61c61
    https://github.com/scummvm/scummvm/commit/1632bd50c890c9e50139c57b52d8e1bfc8f61c61
Author: Colin Snover (github.com at zetafleet.com)
Date: 2016-06-26T12:44:36-05:00

Commit Message:
SCI32: Fix benchmarking for QFG4

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 09e705a..cb77730 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -253,6 +253,28 @@ void GfxFrameout::syncWithScripts(bool addElements) {
 }
 
 #pragma mark -
+#pragma mark Benchmarking
+
+bool GfxFrameout::checkForFred(const reg_t object) {
+	const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view));
+	const SciGameId gameId = g_sci->getGameId();
+
+	if (gameId == GID_QFG4 && viewId == 9999) {
+		return true;
+	}
+
+	if (gameId != GID_QFG4 && viewId == -556) {
+		return true;
+	}
+
+	if (Common::String(_segMan->getObjectName(object)) == "fred") {
+		return true;
+	}
+
+	return false;
+}
+
+#pragma mark -
 #pragma mark Screen items
 
 void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, Plane *plane) {
@@ -276,10 +298,7 @@ void GfxFrameout::kernelAddScreenItem(const reg_t object) {
 	// it is impacted by framerate throttling, so disable the
 	// throttling when this item is on the screen for the
 	// performance check to pass.
-	if (!_benchmarkingFinished && (
-		(int16)readSelectorValue(_segMan, object, SELECTOR(view)) == -556 ||
-		Common::String(_segMan->getObjectName(object)) == "fred"
-	)) {
+	if (!_benchmarkingFinished && _throttleFrameOut && checkForFred(object)) {
 		_throttleFrameOut = false;
 	}
 
@@ -326,10 +345,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
 	// it is impacted by framerate throttling, so disable the
 	// throttling when this item is on the screen for the
 	// performance check to pass.
-	if (!_benchmarkingFinished && (
-		(int16)readSelectorValue(_segMan, object, SELECTOR(view)) == -556 ||
-		Common::String(_segMan->getObjectName(object)) == "fred"
-	)) {
+	if (!_benchmarkingFinished && checkForFred(object)) {
 		_benchmarkingFinished = true;
 		_throttleFrameOut = true;
 	}
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index 53df5d9..0be5aec 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -189,6 +189,12 @@ private:
 	 */
 	bool _throttleFrameOut;
 
+	/**
+	 * Determines whether or not a screen item is the "Fred"
+	 * object.
+	 */
+	bool checkForFred(const reg_t object);
+
 #pragma mark -
 #pragma mark Screen items
 private:






More information about the Scummvm-git-logs mailing list