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

antoniou79 noreply at scummvm.org
Sat May 7 19:56:25 UTC 2022


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

Summary:
043cc1c8fb BLADERUNNER: Add extra conditions for canSaveGameStateCurrently()
e68142d3e6 BLADERUNNER: Add support for old v2 VQAs


Commit: 043cc1c8fbdb7d93dc57bc108c396fd541676ee5
    https://github.com/scummvm/scummvm/commit/043cc1c8fbdb7d93dc57bc108c396fd541676ee5
Author: antoniou79 (a.antoniou79 at gmail.com)
Date: 2022-05-07T22:54:25+03:00

Commit Message:
BLADERUNNER: Add extra conditions for canSaveGameStateCurrently()

Changed paths:
    engines/bladerunner/bladerunner.cpp


diff --git a/engines/bladerunner/bladerunner.cpp b/engines/bladerunner/bladerunner.cpp
index 2af3b364895..df3c914e3af 100644
--- a/engines/bladerunner/bladerunner.cpp
+++ b/engines/bladerunner/bladerunner.cpp
@@ -296,6 +296,8 @@ bool BladeRunnerEngine::canSaveGameStateCurrently() {
 	return
 		playerHasControl() &&
 		_gameIsRunning &&
+		!_actorIsSpeaking &&
+		!_vqaIsPlaying &&
 		!_gameJustLaunched &&
 		!_sceneScript->isInsideScript() &&
 		!_aiScripts->isInsideScript() &&


Commit: e68142d3e6f57d027a2c7ee13e415f8a29b18a4b
    https://github.com/scummvm/scummvm/commit/e68142d3e6f57d027a2c7ee13e415f8a29b18a4b
Author: antoniou79 (a.antoniou79 at gmail.com)
Date: 2022-05-07T22:54:25+03:00

Commit Message:
BLADERUNNER: Add support for old v2 VQAs

These are VQAs with companion VQP files used for Blade Runner teaser (Sizzle reel)

Changed paths:
    engines/bladerunner/bladerunner.cpp
    engines/bladerunner/bladerunner.h
    engines/bladerunner/debugger.cpp
    engines/bladerunner/debugger.h
    engines/bladerunner/outtake.cpp
    engines/bladerunner/vqa_decoder.cpp
    engines/bladerunner/vqa_decoder.h
    engines/bladerunner/vqa_player.cpp
    engines/bladerunner/vqa_player.h


diff --git a/engines/bladerunner/bladerunner.cpp b/engines/bladerunner/bladerunner.cpp
index df3c914e3af..2636e0755bd 100644
--- a/engines/bladerunner/bladerunner.cpp
+++ b/engines/bladerunner/bladerunner.cpp
@@ -1220,7 +1220,11 @@ void BladeRunnerEngine::gameTick() {
 		//      is too cumbersome to be worth it.
 		int ambientSoundsPreOuttakeVol = _mixer->getVolumeForSoundType(_mixer->kPlainSoundType);
 		_mixer->setVolumeForSoundType(_mixer->kPlainSoundType, 0);
-		outtakePlay(_debugger->_dbgPendingOuttake.outtakeId, _debugger->_dbgPendingOuttake.notLocalized, _debugger->_dbgPendingOuttake.container);
+		if (_debugger->_dbgPendingOuttake.outtakeId == -1 && _debugger->_dbgPendingOuttake.container < -1) {
+			outtakePlay(_debugger->_dbgPendingOuttake.externalFilename, _debugger->_dbgPendingOuttake.notLocalized, _debugger->_dbgPendingOuttake.container);
+		} else {
+			outtakePlay(_debugger->_dbgPendingOuttake.outtakeId, _debugger->_dbgPendingOuttake.notLocalized, _debugger->_dbgPendingOuttake.container);
+		}
 		_mixer->setVolumeForSoundType(_mixer->kPlainSoundType, ambientSoundsPreOuttakeVol);
 		_debugger->resetPendingOuttake();
 	}
@@ -2258,9 +2262,13 @@ void BladeRunnerEngine::loopQueuedDialogueStillPlaying() {
 void BladeRunnerEngine::outtakePlay(int id, bool noLocalization, int container) {
 	Common::String name = _gameInfo->getOuttake(id);
 
+	outtakePlay(name, noLocalization, container);
+}
+
+void BladeRunnerEngine::outtakePlay(const Common::String &basenameNoExt, bool noLocalization, int container) {
 	OuttakePlayer player(this);
 
-	player.play(name, noLocalization, container);
+	player.play(basenameNoExt, noLocalization, container);
 }
 
 bool BladeRunnerEngine::openArchive(const Common::String &name) {
diff --git a/engines/bladerunner/bladerunner.h b/engines/bladerunner/bladerunner.h
index b987529b664..8acb28f1ecc 100644
--- a/engines/bladerunner/bladerunner.h
+++ b/engines/bladerunner/bladerunner.h
@@ -396,6 +396,7 @@ public:
 	void loopQueuedDialogueStillPlaying();
 
 	void outtakePlay(int id, bool no_localization, int container = -1);
+	void outtakePlay(const Common::String &basenameNoExt, bool no_localization, int container = -3);
 
 	bool openArchive(const Common::String &name);
 	bool closeArchive(const Common::String &name);
diff --git a/engines/bladerunner/debugger.cpp b/engines/bladerunner/debugger.cpp
index df79c12fad8..512e6ffdca4 100644
--- a/engines/bladerunner/debugger.cpp
+++ b/engines/bladerunner/debugger.cpp
@@ -141,6 +141,7 @@ Debugger::Debugger(BladeRunnerEngine *vm) : GUI::Debugger() {
 	registerCmd("mouse", WRAP_METHOD(Debugger, cmdMouse));
 	registerCmd("difficulty", WRAP_METHOD(Debugger, cmdDifficulty));
 	registerCmd("outtake", WRAP_METHOD(Debugger, cmdOuttake));
+	registerCmd("playvqa", WRAP_METHOD(Debugger, cmdPlayVqa));
 #if BLADERUNNER_ORIGINAL_BUGS
 #else
 	registerCmd("effect", WRAP_METHOD(Debugger, cmdEffect));
@@ -1014,6 +1015,10 @@ bool Debugger::cmdLoad(int argc, const char **argv) {
 		return true;
 	}
 
+	// NOTE Using FSNode here means that ScummVM will not use SearchMan to find the file,
+	//      so the file should be at folder where ScummVM was launched from.
+	// TODO Consider using Common::File instead similar to how
+	//      BladeRunnerEngine::getResourceStream() is implemented)
 	Common::FSNode fs(argv[1]);
 
 	if (!fs.isReadable()) {
@@ -1042,6 +1047,9 @@ bool Debugger::cmdSave(int argc, const char **argv) {
 		return true;
 	}
 
+	// NOTE Using FSNode here means that ScummVM will ouput the saved game file
+	//      into the folder ScummVM was launched from.
+	// TODO Maybe output the saved game file into the game data (top most) folder?
 	Common::FSNode fs(argv[1]);
 
 	if (fs.exists() && !fs.isWritable()) {
@@ -1162,12 +1170,11 @@ bool Debugger::cmdOverlay(int argc, const char **argv) {
 	bool invalidSyntax = false;
 
 	if (_vm->_kia->isOpen()
-		|| _vm->_esper->isOpen()
-		|| _vm->_spinner->isOpen()
-		|| _vm->_elevator->isOpen()
-		|| _vm->_vk->isOpen()
-		|| _vm->_scores->isOpen()
-	) {
+	    || _vm->_esper->isOpen()
+	    || _vm->_spinner->isOpen()
+	    || _vm->_elevator->isOpen()
+	    || _vm->_vk->isOpen()
+	    || _vm->_scores->isOpen() ) {
 		debugPrintf("Sorry, playing custom overlays in KIA, ESPER, Voigt-Kampff, Spinner GPS,\nScores or Elevator mode is not supported\n");
 		return true;
 	}
@@ -2844,8 +2851,7 @@ bool Debugger::cmdOuttake(int argc, const char** argv) {
 		    || _vm->_spinner->isOpen()
 		    || _vm->_elevator->isOpen()
 		    || _vm->_vk->isOpen()
-		    || _vm->_scores->isOpen()
-		    ) {
+		    || _vm->_scores->isOpen() ) {
 			debugPrintf("Sorry, playing custom outtakes in KIA, ESPER, Voigt-Kampff, Spinner GPS,\nScores or Elevator mode is not supported\n");
 			return true;
 		}
@@ -2911,6 +2917,72 @@ void Debugger::resetPendingOuttake() {
 	_dbgPendingOuttake.outtakeId = -1;
 	_dbgPendingOuttake.notLocalized = false;
 	_dbgPendingOuttake.container = -1;
+	_dbgPendingOuttake.externalFilename.clear();
+}
+
+bool Debugger::cmdPlayVqa(int argc, const char** argv) {
+	if (argc != 2) {
+		debugPrintf("Loads a VQA file to play.\n");
+		debugPrintf("Usage: %s <file path>\n", argv[0]);
+		return true;
+	}
+
+	if (_vm->_kia->isOpen()
+	    || _vm->_esper->isOpen()
+	    || _vm->_spinner->isOpen()
+	    || _vm->_elevator->isOpen()
+	    || _vm->_vk->isOpen()
+	    || _vm->_scores->isOpen() ) {
+		debugPrintf("Sorry, playing custom outtakes in KIA, ESPER, Voigt-Kampff, Spinner GPS,\nScores or Elevator mode is not supported\n");
+		return true;
+	}
+
+	if (!_vm->canSaveGameStateCurrently()) {
+		debugPrintf("Sorry, playing custom outtakes while player control is disabled or an in-game script is running, is not supported\n");
+		return true;
+	}
+
+	Common::String filenameArg = argv[1];
+	Common::String basename = filenameArg;
+
+	// Strip the base name of the file of any extension given
+	// to check for existence of basename.VQP and basename.VQA files
+	size_t startOfExt = basename.findLastOf('.');
+	if (startOfExt != Common::String::npos && (basename.size() - startOfExt - 1) == 3) {
+		basename.erase(startOfExt);
+	}
+
+	Common::String basenameVQA = Common::String::format("%s.VQA", basename.c_str());
+	Common::String basenameVQP = Common::String::format("%s.VQP", basename.c_str());
+
+	// Check for existence of VQP
+	bool vqpFileExists = false;
+
+	// Use Common::File exists() check instead of Common::FSNode directly
+	// to allow the file to be placed within SearchMan accessible locations
+	if (!Common::File::exists(basenameVQP)) {
+		debugPrintf("Warning: VQP file %s does not exist\n", basenameVQP.c_str());
+	} else {
+		vqpFileExists = true;
+	}
+
+	if (!Common::File::exists(basenameVQA)) {
+		debugPrintf("Warning: VQA file %s does not exist\n", basenameVQA.c_str());
+		return true;
+	}
+
+	_dbgPendingOuttake.pending = true;
+	_dbgPendingOuttake.outtakeId = -1;
+	if (vqpFileExists) {
+		_dbgPendingOuttake.container = -2; // indicates that an external outtake file with possible VQP companion should be read
+	} else {
+		_dbgPendingOuttake.container = -3; // indicates that an external outtake file but no VQP companion was found so don't check again
+	}
+	_dbgPendingOuttake.notLocalized = true;
+	_dbgPendingOuttake.externalFilename = basename; // external base filename
+
+	// close debugger (to play the outtake)
+	return false;
 }
 
 } // End of namespace BladeRunner
diff --git a/engines/bladerunner/debugger.h b/engines/bladerunner/debugger.h
index 62d8a1dfbfd..5dfff041b89 100644
--- a/engines/bladerunner/debugger.h
+++ b/engines/bladerunner/debugger.h
@@ -53,7 +53,7 @@ enum DebuggerDrawnObjectType {
 	debuggerObjTypeFog           = 11
 };
 
-class Debugger : public GUI::Debugger{
+class Debugger : public GUI::Debugger {
 	BladeRunnerEngine *_vm;
 
 	static const uint kMaxSpecificObjectsDrawnCount = 100;
@@ -63,6 +63,8 @@ class Debugger : public GUI::Debugger{
 		int                     setId;
 		int                     objId;
 		DebuggerDrawnObjectType type;
+
+		DebuggerDrawnObject() : sceneId(0), setId(0), objId(0), type(debuggerObjTypeUndefined) {};
 	};
 
 	struct DebuggerPendingOuttake {
@@ -70,6 +72,9 @@ class Debugger : public GUI::Debugger{
 		int  outtakeId;
 		bool notLocalized;
 		int  container;
+		Common::String externalFilename;
+
+		DebuggerPendingOuttake() : pending(false), outtakeId(-1), notLocalized(true), container(-1), externalFilename("") {};
 	};
 
 public:
@@ -128,6 +133,7 @@ public:
 	bool cmdMouse(int argc, const char **argv);
 	bool cmdDifficulty(int argc, const char **argv);
 	bool cmdOuttake(int argc, const char** argv);
+	bool cmdPlayVqa(int argc, const char** argv);
 #if BLADERUNNER_ORIGINAL_BUGS
 #else
 	bool cmdEffect(int argc, const char **argv);
diff --git a/engines/bladerunner/outtake.cpp b/engines/bladerunner/outtake.cpp
index ac14e8ff272..59491491d04 100644
--- a/engines/bladerunner/outtake.cpp
+++ b/engines/bladerunner/outtake.cpp
@@ -63,14 +63,19 @@ void OuttakePlayer::play(const Common::String &name, bool noLocalization, int co
 
 	_vm->playerLosesControl();
 
-	Common::String resName = name;
+	Common::String resNameNoVQASuffix = name;
 	if (!noLocalization) {
-		resName = resName + "_" + _vm->_languageCode;
+		resNameNoVQASuffix = resNameNoVQASuffix + "_" + _vm->_languageCode;
 	}
-	Common::String resNameNoVQASuffix = resName;
-	resName = resName + ".VQA";
 
-	VQAPlayer vqaPlayer(_vm, &_surfaceVideo, resName); // in original game _surfaceFront is used here, but for proper subtitles rendering we need separate surface
+	VQAPlayer vqaPlayer(_vm, &_surfaceVideo, resNameNoVQASuffix + ".VQA"); // in original game _surfaceFront is used here, but for proper subtitles rendering we need separate surface
+
+	if (container == -2) {
+		// container value: -2 indicates potential existence of VQP file
+		if (!vqaPlayer.loadVQPTable(resNameNoVQASuffix + ".VQP")) {
+			debug("Unable to load VQP table");
+		}
+	}
 	vqaPlayer.open();
 
 	_vm->_vqaIsPlaying = true;
@@ -86,7 +91,7 @@ void OuttakePlayer::play(const Common::String &name, bool noLocalization, int co
 		int frame = vqaPlayer.update();
 		blit(_surfaceVideo, _vm->_surfaceFront); // This helps to make subtitles disappear properly, if the video is rendered in separate surface and then pushed to the front surface
 		if (frame == -3) { // end of video
-			if (_vm->_cutContent && resName.equals("FLYTRU_E.VQA")) {
+			if (_vm->_cutContent && resNameNoVQASuffix.equals("FLYTRU_E")) {
 				_vm->_ambientSounds->removeAllNonLoopingSounds(true);
 				_vm->_ambientSounds->removeAllLoopingSounds(1u);
 			}
@@ -97,7 +102,7 @@ void OuttakePlayer::play(const Common::String &name, bool noLocalization, int co
 			_vm->_subtitles->loadOuttakeSubsText(resNameNoVQASuffix, frame);
 			_vm->_subtitles->tickOuttakes(_vm->_surfaceFront);
 			_vm->blitToScreen(_vm->_surfaceFront);
-			if (_vm->_cutContent && resName.equals("FLYTRU_E.VQA")) {
+			if (_vm->_cutContent && resNameNoVQASuffix.equals("FLYTRU_E")) {
 				// This FLYTRU_E outtake has 150 frames
 				//
 				// We can have at most kLoopingSounds (3) looping ambient tracks
@@ -149,7 +154,7 @@ void OuttakePlayer::play(const Common::String &name, bool noLocalization, int co
 	}
 
 	if ((_vm->_vqaStopIsRequested || _vm->shouldQuit())
-		&& _vm->_cutContent && resName.equals("FLYTRU_E.VQA")) {
+		&& _vm->_cutContent && resNameNoVQASuffix.equals("FLYTRU_E")) {
 		_vm->_ambientSounds->removeAllNonLoopingSounds(true);
 		_vm->_ambientSounds->removeAllLoopingSounds(0u);
 		_vm->_audioPlayer->stopAll();
diff --git a/engines/bladerunner/vqa_decoder.cpp b/engines/bladerunner/vqa_decoder.cpp
index 0ae6df4f64c..33290c9beed 100644
--- a/engines/bladerunner/vqa_decoder.cpp
+++ b/engines/bladerunner/vqa_decoder.cpp
@@ -71,6 +71,8 @@ namespace BladeRunner {
 #define kVQHD 0x56514844
 #define kWVQA 0x57565141
 #define kZBUF 0x5A425546
+#define kCPL0 0x43504C30
+#define kVPTZ 0x5650545A
 
 int32 remain(Common::SeekableReadStream *s) {
 	int32 pos = s->pos();
@@ -136,6 +138,13 @@ VQADecoder::VQADecoder() {
 	_header.unk5         = 0;
 	_readingFrame        = -1;
 	_decodingFrame       = -1;
+	_vqpPalsArr          = nullptr;
+	_numOfVQPPalettes    = 0;
+	_oldV2VQA                 = false;
+	_allowHorizontalScanlines = false; // only for old VQA(with optional VQP) version 2 videos
+	_allowVerticalScanlines   = false; // only for old VQA(with optional VQP) version 2 videos
+	_scaleVideoTo2xRequested  = true;  // only for old VQA(with optional VQP) version 2 videos
+	_centerVideoRequested     = true;  // only for old VQA(with optional VQP) version 2 videos
 }
 
 VQADecoder::~VQADecoder() {
@@ -145,6 +154,7 @@ VQADecoder::~VQADecoder() {
 	delete _audioTrack;
 	delete _videoTrack;
 	delete[] _frameInfo;
+	deleteVQPTable();
 }
 
 bool VQADecoder::loadStream(Common::SeekableReadStream *s) {
@@ -293,6 +303,10 @@ bool VQADecoder::readVQHD(Common::SeekableReadStream *s, uint32 size) {
 	_header.unk3        = s->readUint32LE();
 	_header.unk4        = s->readUint16LE();
 	_header.maxCBFZSize = s->readUint32LE();
+	if (_header.flags == 1) {
+		_oldV2VQA = true;
+		_header.maxCBFZSize = _header.cbParts * _header.maxBlocks;
+	}
 	_header.unk5        = s->readUint32LE();
 
 	// if (_header.unk3 || _header.unk4 != 4 || _header.unk5 || _header.flags != 0x0014) {
@@ -326,16 +340,43 @@ bool VQADecoder::readVQHD(Common::SeekableReadStream *s, uint32 size) {
 		assert(_header.channels == 1);
 		assert(_header.bits == 16);
 	}
-	assert(_header.colors == 0);
+	assert(_header.colors == 0 || (_oldV2VQA && _header.colors == 256));
 
 	return true;
 }
 
 bool VQADecoder::VQAVideoTrack::readVQFR(Common::SeekableReadStream *s, uint32 size, uint readFlags) {
 	IFFChunkHeader chd;
-
 	signed int sizeLeft = size; // we have to use signed int to avoid underflow
 
+	// NOTE This check should be here before the start of a actually reading a new VQFR segment.
+	//      When using codebooks that get updated during the video playback, for the case where
+	//      a codebook is constructed from parts and there's no starting frame codebook info,
+	//      the new frame should be rendered with the existing codebook within the same VQFR chunk,
+	//      if one was active. The newly constructed codebook should come in effect with the next VQFR chunk.
+	if (_vqaDecoder->_oldV2VQA) {
+		if (_cbParts != 0 && _countOfCBPsToCBF == _cbParts) {
+			CodebookInfo& codebookInfo = _vqaDecoder->codebookInfoForFrame(_vqaDecoder->_readingFrame);
+			const uint32 codebookPartialSize = _maxBlocks;
+			uint32 bytesDecomprsd = decompress_lcw(_cbfzNext,
+			                                       _accumulatedCBPZsizeToCBF,
+			                                       _codebookInfoNext->data,
+			                                       _cbParts * codebookPartialSize);
+			assert(bytesDecomprsd <= _cbParts * codebookPartialSize);
+
+			if (!codebookInfo.data) {
+				codebookInfo.data = new uint8[roundup(_cbParts * codebookPartialSize)];
+			}
+			uint8* intermediateSwapPtr = codebookInfo.data;
+			codebookInfo.data = _codebookInfoNext->data;
+			codebookInfo.size = bytesDecomprsd;
+			_codebookInfoNext->data = intermediateSwapPtr;
+
+			_countOfCBPsToCBF = 0;
+			_accumulatedCBPZsizeToCBF = 0;
+		}
+	}
+
 	while (sizeLeft >= 8) {
 		if (!readIFFChunkHeader(s, &chd))
 			return false;
@@ -344,8 +385,10 @@ bool VQADecoder::VQAVideoTrack::readVQFR(Common::SeekableReadStream *s, uint32 s
 		bool rc = false;
 		switch (chd.id) {
 		case kCBFZ: rc = ((readFlags & kVQAReadCodebook          ) == 0) ? s->skip(roundup(chd.size)) : readCBFZ(s, chd.size); break;
-		case kCBPZ: rc = ((readFlags & kVQAReadCodebook          ) == 0) ? s->skip(roundup(chd.size)) : readCBFZ(s, chd.size); break;
+		case kCBPZ: rc = ((readFlags & kVQAReadCodebook          ) == 0) ? s->skip(roundup(chd.size)) : readCBPZ(s, chd.size); break;
 		case kVPTR: rc = ((readFlags & kVQAReadVectorPointerTable) == 0) ? s->skip(roundup(chd.size)) : readVPTR(s, chd.size); break;
+		case kVPTZ: rc = ((readFlags & kVQAReadVectorPointerTable) == 0) ? s->skip(roundup(chd.size)) : readVPTZ(s, chd.size); break;
+		case kCPL0: rc = readCPL0(s, chd.size); break;
 		default:
 			s->skip(roundup(chd.size));
 		}
@@ -355,7 +398,6 @@ bool VQADecoder::VQAVideoTrack::readVQFR(Common::SeekableReadStream *s, uint32 s
 			return false;
 		}
 	}
-
 	return true;
 }
 
@@ -432,11 +474,19 @@ bool VQADecoder::readLINF(Common::SeekableReadStream *s, uint32 size) {
 
 VQADecoder::CodebookInfo &VQADecoder::codebookInfoForFrame(int frame) {
 	assert(frame < numFrames());
-	assert(!_codebooks.empty());
+	assert(!_codebooks.empty() || _oldV2VQA);
+
+	if (_codebooks.empty() && _oldV2VQA) {
+		_codebooks.resize(1);
+		_codebooks[0].frame = 0;
+		_codebooks[0].size = 0;
+		_codebooks[0].data = nullptr;
+	}
 
 	CodebookInfo *ci = nullptr;
 	uint count = _codebooks.size();
 
+	// find the last stored CodebookInfo where the current frame (param) belongs based on the codebook's CodebookInfo frame field.
 	for (uint i = count; i != 0; --i) {
 		if (frame >= _codebooks[i - 1].frame) {
 			return _codebooks[i - 1];
@@ -577,6 +627,35 @@ int VQADecoder::getLoopIdFromFrame(int frame) {
 	return -1;
 }
 
+// Aux method for allocating new memory space for VQP palette table
+// for the old version 2 VQP/VQA videos (eg. sizzle reels)
+void VQADecoder::allocatePaletteVQPTable(const uint32 numOfPalettes) {
+	deleteVQPTable();
+	_vqpPalsArr = new VQPPalette[numOfPalettes];
+	_numOfVQPPalettes = numOfPalettes;
+}
+
+// Aux method for populating the VQP 2d table per palette Id
+// with an index (colorByte) to the palette's r,g,b entry
+// for the old version 2 VQP/VQA videos (eg. sizzle reels)
+void VQADecoder::updatePaletteVQPTable(uint32 palId, uint16 j, uint16 k, uint8 colorByte) {
+	// the call assumes that there's space allocated for the _vqpPalsArr table
+	if (palId < _numOfVQPPalettes) {
+		_vqpPalsArr[palId].interpol2D[j][k] = colorByte;
+		_vqpPalsArr[palId].interpol2D[k][j] = colorByte;
+	}
+}
+
+// Aux method for deleting old memory space for VQP palette table
+// for the old version 2 VQP/VQA videos (eg. sizzle reels)
+void VQADecoder::deleteVQPTable() {
+	if (_vqpPalsArr) {
+		delete[] _vqpPalsArr;
+		_vqpPalsArr = nullptr;
+	}
+	_numOfVQPPalettes = 0;
+}
+
 bool VQADecoder::readCLIP(Common::SeekableReadStream *s, uint32 size) {
 	s->skip(roundup(size));
 	return true;
@@ -598,6 +677,7 @@ VQADecoder::VQAVideoTrack::VQAVideoTrack(VQADecoder *vqaDecoder) {
 	_blockW    = header->blockW;
 	_blockH    = header->blockH;
 	_frameRate = header->frameRate;
+	_cbParts   = header->cbParts;
 	_maxBlocks = header->maxBlocks;
 	_offsetX   = header->offsetX;
 	_offsetY   = header->offsetY;
@@ -625,6 +705,18 @@ VQADecoder::VQAVideoTrack::VQAVideoTrack(VQADecoder *vqaDecoder) {
 
 	_lightsDataSize = 0;
 	_lightsData     = nullptr;
+
+	_currentPaletteId         = -1; // init to -1 so that first increase will make it 0 (first valid index for palette)
+	_cpalPointerSize          = 0;
+	_cpalPointerSizeNext      = 0;
+	_cpalPointer              = nullptr;
+	_cpalPointerNext          = nullptr;
+	_vptz                     = nullptr;
+
+	_cbfzNext                 = nullptr; // stores all _cbParts compressed codebook parts and then decompress
+	_codebookInfoNext         = nullptr; // stores the decompressed codebook parts and it's swapped with the active codebook
+	_countOfCBPsToCBF         = 0;
+	_accumulatedCBPZsizeToCBF = 0;
 }
 
 VQADecoder::VQAVideoTrack::~VQAVideoTrack() {
@@ -635,6 +727,16 @@ VQADecoder::VQAVideoTrack::~VQAVideoTrack() {
 	delete[] _viewData;
 	delete[] _screenEffectsData;
 	delete[] _lightsData;
+
+	delete[] _cpalPointer;
+	delete[] _cpalPointerNext;
+	delete[] _vptz;
+
+	delete[] _cbfzNext;
+	if (_codebookInfoNext != nullptr && _codebookInfoNext->data != nullptr) {
+		delete[] _codebookInfoNext->data;
+	}
+	delete _codebookInfoNext;
 }
 
 uint16 VQADecoder::VQAVideoTrack::getWidth() const {
@@ -687,6 +789,7 @@ bool VQADecoder::VQAVideoTrack::readVQFL(Common::SeekableReadStream *s, uint32 s
 	return true;
 }
 
+// Read full codebook
 bool VQADecoder::VQAVideoTrack::readCBFZ(Common::SeekableReadStream *s, uint32 size) {
 	if (size > _maxCBFZSize) {
 		warning("readCBFZ: chunk too large: %d > %d", size, _maxCBFZSize);
@@ -700,7 +803,12 @@ bool VQADecoder::VQAVideoTrack::readCBFZ(Common::SeekableReadStream *s, uint32 s
 	}
 
 	uint32 codebookSize = 2 * _maxBlocks * _blockW * _blockH;
-	codebookInfo.data = new uint8[codebookSize];
+	if (_vqaDecoder->_oldV2VQA) {
+		codebookSize = _maxBlocks * _cbParts;
+	}
+
+	// This is released in VQADecoder::~VQADecoder()
+	codebookInfo.data = new uint8[roundup(codebookSize)];
 
 	if (!_cbfz) {
 		_cbfz = new uint8[roundup(_maxCBFZSize)];
@@ -708,8 +816,42 @@ bool VQADecoder::VQAVideoTrack::readCBFZ(Common::SeekableReadStream *s, uint32 s
 
 	s->read(_cbfz, roundup(size));
 
-	decompress_lcw(_cbfz, size, codebookInfo.data, codebookSize);
+	uint32 bytesDecomprsd = decompress_lcw(_cbfz, size, codebookInfo.data, codebookSize);
+	codebookInfo.size = bytesDecomprsd;
+	return true;
+}
+
+// Read partial codebook
+bool VQADecoder::VQAVideoTrack::readCBPZ(Common::SeekableReadStream* s, uint32 size) {
+	if (size > _maxCBFZSize) {
+		warning("readCBPZ: chunk too large: %d > %d", size, _maxCBFZSize);
+		return false;
+	}
 
+	// NOTE The Lands of Lore 2 reel VQA/VQP only has CBPZ chunks (no CBF* chunks)
+	//      so we construct the first full codebook from the compressed parts.
+	//      This means that we don't get a full codebook first, as in the case of
+	//      the Blade Runner VQA/VQP reel.
+	if (_cbParts == 0) {
+		s->skip(roundup(size));
+		return true;
+	}
+
+	if (!_cbfzNext) {
+		_cbfzNext = new uint8[roundup(_maxCBFZSize)];
+		_codebookInfoNext = new CodebookInfo();
+		_codebookInfoNext->frame = 0;
+		_codebookInfoNext->data = new uint8[roundup(_cbParts * _maxBlocks)];
+		_codebookInfoNext->size = roundup(_cbParts * _maxBlocks);
+		_countOfCBPsToCBF = 0;
+		_accumulatedCBPZsizeToCBF = 0;
+	}
+
+	s->read(_cbfzNext + _accumulatedCBPZsizeToCBF, roundup(size));
+
+	_accumulatedCBPZsizeToCBF += size;
+	assert(_accumulatedCBPZsizeToCBF <= roundup(_maxCBFZSize));
+	++_countOfCBPsToCBF;
 	return true;
 }
 
@@ -811,6 +953,98 @@ void VQADecoder::VQAVideoTrack::decodeLights(Lights *lights) {
 	_lightsData = nullptr;
 }
 
+bool VQADecoder::VQAVideoTrack::readCPL0(Common::SeekableReadStream *s, uint32 size) {
+	// The size of a full Palette chunk is 0x300 = 3 x 256 = 768 bytes,
+	// ie. r,g,b (in this order, 1 byte each)
+	// When reading a new CPL0 section, that is not identical to the immediately previous one,
+	// the active palette (_cpalPointer) is updated and the _currentPaletteId is increased
+	// in order to index the corresponding palette segment in the VQP table from
+	// the accompanying VQP file, if such exists.
+	// For the purposes of the filtering process we use two buffers for the palette chunks,
+	// ie. _cpalPointer and _cpalPointerNext.
+	// CPL0 sections number (after filtering out the subsequent identical ones)
+	// should match the number of palettes reported in VQP file, if such exists.
+	if (size != kSizeInBytesOfCPL0Chunk)
+		return false;
+
+	// NOTE The Lands of Lore 2 reel VQA has many palette chunks
+	//      but most are identical to their immediately preceding one.
+	//      Its accompanying VQP file only specifies 52 palettes,
+	//      which is the number we end up with,
+	//      if we filter the consecutive duplicates.
+	//
+	// NOTE The roundup() is not really needed for kSizeInBytesOfCPL0Chunk
+	//      since 768 is an even number. But we keep it for consistency,
+	//      as it doesn't hurt.
+	if (!_cpalPointer) {
+		_cpalPointer = new uint8[roundup(kSizeInBytesOfCPL0Chunk)];
+		memset(_cpalPointer, 0, roundup(kSizeInBytesOfCPL0Chunk));
+	}
+
+	if (!_cpalPointerNext) {
+		_cpalPointerNext = new uint8[roundup(kSizeInBytesOfCPL0Chunk)];
+		memset(_cpalPointerNext, 0, roundup(kSizeInBytesOfCPL0Chunk));
+	}
+
+	// Add a new palette ONLY if the previous is not identical.
+	// This accomodates the VQA reel for Lands of Lore 2.
+	if (_currentPaletteId == -1) {
+		_cpalPointerSize = size;
+		s->read(_cpalPointer, roundup(size));
+		++_currentPaletteId;
+	} else {
+		_cpalPointerSizeNext = size;
+		s->read(_cpalPointerNext, roundup(size));
+		if (memcmp(_cpalPointer, _cpalPointerNext, roundup(kSizeInBytesOfCPL0Chunk)) != 0) {
+			// Swap the current active palette buffer
+			// with the one we've just read-in.
+			uint8* tmpPalPointer = _cpalPointer;
+			_cpalPointer = _cpalPointerNext;
+			_cpalPointerNext = tmpPalPointer;
+			_cpalPointerSize = _cpalPointerSizeNext;
+			++_currentPaletteId;
+		}
+	}
+
+	if (_vqaDecoder->_scaleVideoTo2xRequested || (_vqaDecoder->_allowHorizontalScanlines && _vqaDecoder->_allowVerticalScanlines)) {
+		if (_vqaDecoder->_vqpPalsArr != nullptr && _vqaDecoder->_numOfVQPPalettes > 0) {
+			if ((uint32)_currentPaletteId >= _vqaDecoder->_numOfVQPPalettes) {
+				debug("cpl0 **ERROR** ID %d Vs palettes max valid id %d", (uint32)_currentPaletteId, _vqaDecoder->_numOfVQPPalettes - 1);
+				// This is an unexpected case.
+				// We can try cycling from the beginning of palette ids:
+				// _currentPaletteId = 0;
+				// Or we can stay in the last valid value for _currentPaletteId:
+				_currentPaletteId = _vqaDecoder->_numOfVQPPalettes - 1;
+				// We could also return false here:
+				// return false;
+			}
+		}
+	}
+	return true;
+}
+
+bool VQADecoder::VQAVideoTrack::readVPTZ(Common::SeekableReadStream* s, uint32 size) {
+	if (size > _maxVPTRSize)
+		return false;
+
+	if (!_vptz) {
+		_vptz = new uint8[roundup(_maxVPTRSize)];
+	}
+
+	s->read(_vptz, roundup(size));
+
+	if (!_vpointer) {
+		_vpointer = new uint8[roundup(_maxBlocks * _blockW * _blockH)];
+	}
+
+	uint32 bytesDecomprsd = decompress_lcw(_vptz, size, _vpointer, _maxBlocks * _blockW * _blockH);
+	assert(bytesDecomprsd <= roundup(_maxBlocks * _blockW * _blockH));
+	_vpointerSize = bytesDecomprsd;
+
+	_hasNewFrame = true;
+
+	return true;
+}
 
 bool VQADecoder::VQAVideoTrack::readVPTR(Common::SeekableReadStream *s, uint32 size) {
 	if (size > _maxVPTRSize)
@@ -840,7 +1074,8 @@ void VQADecoder::VQAVideoTrack::VPTRWriteBlock(Graphics::Surface *surface, unsig
 	uint8 a, r, g, b;
 
 	for (uint i = count; i != 0; --i) {
-		intermDiv = (dstBlock + count - i) / blocks_per_line;
+		// aux variable to avoid duplicate division and a modulo operation
+		intermDiv = (dstBlock + count - i) / blocks_per_line; // start of current blocks line
 		dst_x = ((dstBlock + count - i) - intermDiv * blocks_per_line) * _blockW + _offsetX;
 		dst_y = intermDiv * _blockH + _offsetY;
 
@@ -854,7 +1089,7 @@ void VQADecoder::VQAVideoTrack::VPTRWriteBlock(Graphics::Surface *surface, unsig
 				getGameDataColor(vqaColor, a, r, g, b);
 
 				if (!(alpha && a)) {
-					// clip is too slow and it is not needed
+					// CLIP() is too slow and it is not needed.
 					// void* dstPtr = surface->getBasePtr(CLIP(dst_x + x, (uint32)0, (uint32)(surface->w - 1)), CLIP(dst_y + y, (uint32)0, (uint32)(surface->h - 1)));
 					void* dstPtr = surface->getBasePtr(dst_x + _blockW - x, dst_y + _blockH - y);
 					// Ignore the alpha in the output as it is inversed in the input
@@ -879,57 +1114,273 @@ bool VQADecoder::VQAVideoTrack::decodeFrame(Graphics::Surface *surface) {
 	uint8 *src = _vpointer;
 	uint8 *end = _vpointer + _vpointerSize;
 
-	uint16 count, srcBlock, dstBlock = 0;
-	(void)srcBlock;
-
-	while (end - src >= 2) {
-		uint16 command = src[0] | (src[1] << 8);
-		uint8  prefix = command >> 13;
-		src += 2;
+	// count    is the number of blocks to be covered (written or skipped)
+	// dstBlock is each time the starting dstBlock (to be written);
+	//          it is increased by count blocks, which might be 1 for some commands.
+	// srcBlock is each time the starting srcBlock to be coppied, and used to calculate
+	//          the proper index for the block data from the codebook data table.
+	uint16 count = 0, srcBlock = 0, dstBlock = 0;
+
+	if (!_vqaDecoder->_oldV2VQA) {
+		while (end - src >= 2) {
+			uint16 command = src[0] | (src[1] << 8);
+			uint8  prefix = command >> 13;
+			src += 2;
+
+			switch (prefix) {
+			case 0:
+				count = command & 0x1fff;
+				dstBlock += count;
+				break;
+
+			case 1:
+				count = 2 * (((command >> 8) & 0x1f) + 1);
+				srcBlock = command & 0x00ff;
+
+				VPTRWriteBlock(surface, dstBlock, srcBlock, count);
+				dstBlock += count;
+				break;
+
+			case 2:
+				count = 2 * (((command >> 8) & 0x1f) + 1);
+				srcBlock = command & 0x00ff;
 
-		switch (prefix) {
-		case 0:
-			count = command & 0x1fff;
-			dstBlock += count;
-			break;
-		case 1:
-			count = 2 * (((command >> 8) & 0x1f) + 1);
-			srcBlock = command & 0x00ff;
+				VPTRWriteBlock(surface, dstBlock, srcBlock, 1);
+				++dstBlock;
 
-			VPTRWriteBlock(surface, dstBlock, srcBlock, count);
-			dstBlock += count;
-			break;
-		case 2:
-			count = 2 * (((command >> 8) & 0x1f) + 1);
-			srcBlock = command & 0x00ff;
+				for (uint16 i = count; i != 0; --i) {
+					srcBlock = *src++;
+					VPTRWriteBlock(surface, dstBlock, srcBlock, 1);
+					++dstBlock;
+				}
+				break;
 
-			VPTRWriteBlock(surface, dstBlock, srcBlock, 1);
-			++dstBlock;
+			case 3:
+				// fall through
+			case 4:
+				count = 1;
+				srcBlock = command & 0x1fff;
 
-			for (uint16 i = count; i != 0; --i) {
-				srcBlock = *src++;
-				VPTRWriteBlock(surface, dstBlock, srcBlock, 1);
+				VPTRWriteBlock(surface, dstBlock, srcBlock, count, prefix == 4);
 				++dstBlock;
+				break;
+
+			case 5:
+				// fall through
+			case 6:
+				count = *src++;
+				srcBlock = command & 0x1fff;
+
+				VPTRWriteBlock(surface, dstBlock, srcBlock, count, prefix == 6);
+				dstBlock += count;
+				break;
+
+			default:
+				warning("VQAVideoTrack::decodeFrame: Undefined case %d", command >> 13);
 			}
-			break;
-		case 3:
-		case 4:
-			count = 1;
-			srcBlock = command & 0x1fff;
+		}
+	} else {
+		uint8 srcPartA = 0;
+		uint8 srcPartB = 0;
+		uint16 blocks_per_line   = _width  / _blockW;
+		uint16 blocks_per_column = _height / _blockH;
+		uint16 currLineBlock = 0;
+		uint16 currColumnBlock = 0;
+		uint32 dst_x = 0;
+		uint32 dst_y = 0;
+		void  *dstPtr = nullptr;
+
+		assert(_vpointerSize == 2 * (blocks_per_column * blocks_per_line));
+		// Create a pointer to the second half of the frame data:
+		const uint8 *srcB = src + (blocks_per_column * blocks_per_line);
+
+		bool scale2xPossible = (_vqaDecoder->_scaleVideoTo2xRequested
+		                        && (   (_vqaDecoder->_vqpPalsArr != nullptr && _vqaDecoder->_numOfVQPPalettes > 0)
+		                            || (_vqaDecoder->_allowVerticalScanlines && _vqaDecoder->_allowHorizontalScanlines) )
+			                    && ((2 * getWidth()) <=  surface->w && (2 * getHeight()) <= surface->h) );
+		// These offsets are different from the header _offsetX and _offsetY
+		// They are explicitly evaluated in order to center (if requested) the video image
+		uint16 xOffs = 0;
+		uint16 yOffs = 0;
+		if (_vqaDecoder->_centerVideoRequested
+		    && !(surface->w == getWidth() && surface->h == getHeight())
+		    && !(scale2xPossible && (surface->w == (2 * getWidth()) && surface->h == (2 * getHeight()))) ) {
+			xOffs = (surface->w - (scale2xPossible? 2 : 1) * getWidth())  / 2; // leftmost x start
+			yOffs = (surface->h - (scale2xPossible? 2 : 1) * getHeight()) / 2; // topmost  y start
+		}
 
-			VPTRWriteBlock(surface, dstBlock, srcBlock, count, prefix == 4);
-			++dstBlock;
-			break;
-		case 5:
-		case 6:
-			count = *src++;
-			srcBlock = command & 0x1fff;
+		uint8 *topBlockRowColorIndexForPalette = nullptr;
+		uint8 *currBlockRowColorIndexForPalette = nullptr;
+		if (scale2xPossible && !(_vqaDecoder->_allowVerticalScanlines && _vqaDecoder->_allowHorizontalScanlines)) {
+			topBlockRowColorIndexForPalette  = new uint8[2 * _blockH * 2 * _blockW * blocks_per_line];
+			currBlockRowColorIndexForPalette = new uint8[2 * _blockH * 2 * _blockW * blocks_per_line];
+		}
 
-			VPTRWriteBlock(surface, dstBlock, srcBlock, count, prefix == 6);
-			dstBlock += count;
-			break;
-		default:
-			warning("VQAVideoTrack::decodeFrame: Undefined case %d", command >> 13);
+		uint8 r, g, b;
+
+		while (end - src > (blocks_per_column * blocks_per_line)) {
+			srcPartA = *src++;	// next byte from first half of data
+			srcPartB = *srcB++;	// next byte from second half of data
+
+			if (srcPartB < 0x0F) {
+				assert(_blockW * _blockH * (((uint32)srcPartB << 8) | srcPartA) < codebookInfo.size);
+
+				// Copy a block from codebook data to the surface
+				// The for() loop traverses the whole block of (_blockH) rows of (_blockW) pixels
+				for (int i = 0; i < _blockH; ++i) {
+					for (int k = 0; k < _blockW; ++k) {
+						const uint8 cbdbyte = _codebook[_blockW * _blockH * (((uint32)srcPartB << 8) | srcPartA) + i * _blockW + k];
+
+						if (scale2xPossible && !(_vqaDecoder->_allowVerticalScanlines && _vqaDecoder->_allowHorizontalScanlines)) {
+							currBlockRowColorIndexForPalette[(2 * i * 2 * _blockW * blocks_per_line) + ((currColumnBlock * 2 * _blockW) + 2 * k)] = cbdbyte;
+						}
+
+						// R, G, B values are in 6bits which are converted to 8bits
+						// NOTE Some R, G, B values are > 63 and are typically in [224, 255], ie. between 0xE0 and 0xFF.
+						//      This makes bits 7 and 8 always set (1) in those cases
+						//      TODO What is the significance of these extra bits?
+						r = (0x3F & _cpalPointer[cbdbyte * 3]);
+						g = (0x3F & _cpalPointer[cbdbyte * 3 + 1]);
+						b = (0x3F & _cpalPointer[cbdbyte * 3 + 2]);
+
+						r = (r << 2) | (r >> 4); // 6 to 8 bits
+						g = (g << 2) | (g >> 4); // 6 to 8 bits
+						b = (b << 2) | (b >> 4); // 6 to 8 bits
+
+						dst_x = xOffs + ((currColumnBlock * _blockW) + k) * (scale2xPossible ? 2 : 1);
+						dst_y = yOffs + ((currLineBlock * _blockH) + i) * (scale2xPossible ? 2 : 1);
+						dstPtr = surface->getBasePtr(dst_x, dst_y);
+						drawPixel(*surface, dstPtr, surface->format.RGBToColor(r, g, b));
+					}
+				}
+			} else {
+				// Fill the next block with colorByte
+				// The for() loop traverses the whole block of (_blockH) rows of (_blockW) pixels
+				for (int i = 0; i < _blockH; ++i) {
+					for (int k = 0; k < _blockW; ++k) {
+						const uint8 colorByte = srcPartA;
+
+						if (scale2xPossible && !(_vqaDecoder->_allowVerticalScanlines && _vqaDecoder->_allowHorizontalScanlines)) {
+							currBlockRowColorIndexForPalette[(2 * i * 2 * _blockW * blocks_per_line) + ((currColumnBlock * 2 * _blockW) + 2 * k)] = colorByte;
+						}
+
+						r = (0x3F & _cpalPointer[colorByte * 3]);
+						g = (0x3F & _cpalPointer[colorByte * 3 + 1]);
+						b = (0x3F & _cpalPointer[colorByte * 3 + 2]);
+						r = (r << 2) | (r >> 4); // 6 to 8 bits
+						g = (g << 2) | (g >> 4); // 6 to 8 bits
+						b = (b << 2) | (b >> 4); // 6 to 8 bits
+
+						dst_x = xOffs + ((currColumnBlock * _blockW) + k) * (scale2xPossible ? 2 : 1);
+						dst_y = yOffs + ((currLineBlock * _blockH) + i) * (scale2xPossible ? 2 : 1);
+						dstPtr = surface->getBasePtr(dst_x, dst_y);
+						drawPixel(*surface, dstPtr, surface->format.RGBToColor(r, g, b));
+					}
+				}
+			}
+			if (currColumnBlock == blocks_per_line - 1) {
+				// Here the initial pass for a row of blocks was completed
+				if (scale2xPossible && !(_vqaDecoder->_allowVerticalScanlines && _vqaDecoder->_allowHorizontalScanlines)) {
+					//    When VQP data is available, we can fill in any gaps in the 2x scaled video image:
+					// 1. First go through currBlockRowColorIndexForPalette and fill in the gaps in the even rows of blocks
+					//    Skip the last entry since we don't have info on a right pixel color for it.
+					// 2. Then go through the now filled currBlockRowColorIndexForPalette and the topBlockRowColorIndexForPalette,
+					//    (if the latter exists --so not for the first line of blocks) and fill in the last pixels' row of the
+					//    topBlockRowColorIndexForPalette, by using the data on the color of the previous to last pixels' row in
+					//    topBlockRowColorIndexForPalette and the color of the first pixels' row in currBlockRowColorIndexForPalette.
+					// 3. Swap topBlockRowColorIndexForPalette and currBlockRowColorIndexForPalette (to preserve it as previous row of blocks)
+					uint8 midColorIdx = 0;
+					if (!_vqaDecoder->_allowVerticalScanlines) {
+						uint8 leftColorIdx = 0;
+						uint8 rightColorIdx = 0;
+						for (int i = 0; i < 2 * _blockH - 1; i += 2) {
+							for (int j = 1; j < 2 * _blockW * blocks_per_line - 1; j += 2) {
+								// Skip last column, because we lack info for pixels to the right to interpolate
+								leftColorIdx = currBlockRowColorIndexForPalette[(i * 2 * _blockW * blocks_per_line) + j - 1];
+								rightColorIdx = currBlockRowColorIndexForPalette[(i * 2 * _blockW * blocks_per_line) + j + 1];
+								midColorIdx = _vqaDecoder->_vqpPalsArr[_currentPaletteId].interpol2D[leftColorIdx][rightColorIdx];
+								currBlockRowColorIndexForPalette[(i * 2 * _blockW * blocks_per_line) + j] = midColorIdx;
+								r = (0x3F & _cpalPointer[midColorIdx * 3]);
+								g = (0x3F & _cpalPointer[midColorIdx * 3 + 1]);
+								b = (0x3F & _cpalPointer[midColorIdx * 3 + 2]);
+								r = (r << 2) | (r >> 4); // 6 to 8 bits
+								g = (g << 2) | (g >> 4); // 6 to 8 bits
+								b = (b << 2) | (b >> 4); // 6 to 8 bits
+
+								dst_x = xOffs + j;
+								dst_y = yOffs + (currLineBlock * 2 * _blockH) + i;
+								dstPtr = surface->getBasePtr(dst_x, dst_y);
+								drawPixel(*surface, dstPtr, surface->format.RGBToColor(r, g, b));
+							}
+						}
+					}
+					if (!_vqaDecoder->_allowHorizontalScanlines) {
+						// With the even rows of the line blocks completely filled, go to the odd rows
+						// which are empty and completely fill them in, too.
+						// NOTE if _allowVerticalScanlines is true, then the even rows are not completely filled.
+						//      In that case we need to skip the odd columns here 
+						uint8 topColorIdx = 0;
+						uint8 botColorIdx = 0;
+						uint8 jIncr = 1;
+						if (_vqaDecoder->_allowVerticalScanlines) {
+							jIncr = 2;
+						}
+						for (int i = 1; i < 2 * _blockH - 1; i += 2) {
+							for (int j = 0; j < 2 * _blockW * blocks_per_line - 1; j += jIncr) {
+								topColorIdx = currBlockRowColorIndexForPalette[((i - 1) * 2 * _blockW * blocks_per_line) + j];
+								botColorIdx = currBlockRowColorIndexForPalette[((i + 1) * 2 * _blockW * blocks_per_line) + j];
+								midColorIdx = _vqaDecoder->_vqpPalsArr[_currentPaletteId].interpol2D[topColorIdx][botColorIdx];
+								currBlockRowColorIndexForPalette[(i * 2 * _blockW * blocks_per_line) + j] = midColorIdx;
+								r = (0x3F & _cpalPointer[midColorIdx * 3]);
+								g = (0x3F & _cpalPointer[midColorIdx * 3 + 1]);
+								b = (0x3F & _cpalPointer[midColorIdx * 3 + 2]);
+								r = (r << 2) | (r >> 4); // 6 to 8 bits
+								g = (g << 2) | (g >> 4); // 6 to 8 bits
+								b = (b << 2) | (b >> 4); // 6 to 8 bits
+
+								dst_x = xOffs + j;
+								dst_y = yOffs + (currLineBlock * 2 * _blockH) + i;
+								dstPtr = surface->getBasePtr(dst_x, dst_y);
+								drawPixel(*surface, dstPtr, surface->format.RGBToColor(r, g, b));
+							}
+						}
+						// If we are at a row of blocks with index y > 0, then use info from the previous to last row of topBlockRowColorIndexForPalette
+						// and first row of currBlockRowColorIndexForPalette to fill the last row of topBlockRowColorIndexForPalette and draw those pixels too.
+						if (currLineBlock > 0) {
+							for (int j = 0; j < 2 * _blockW * blocks_per_line - 1; j += jIncr) {
+								topColorIdx = topBlockRowColorIndexForPalette[((2 * _blockH - 2) * 2 * _blockW * blocks_per_line) + j];
+								botColorIdx = currBlockRowColorIndexForPalette[j];
+								midColorIdx = _vqaDecoder->_vqpPalsArr[_currentPaletteId].interpol2D[topColorIdx][botColorIdx];
+								topBlockRowColorIndexForPalette[((2 * _blockH - 1) * 2 * _blockW * blocks_per_line) + j] = midColorIdx;
+								r = (0x3F & _cpalPointer[midColorIdx * 3]);
+								g = (0x3F & _cpalPointer[midColorIdx * 3 + 1]);
+								b = (0x3F & _cpalPointer[midColorIdx * 3 + 2]);
+								r = (r << 2) | (r >> 4); // 6 to 8 bits
+								g = (g << 2) | (g >> 4); // 6 to 8 bits
+								b = (b << 2) | (b >> 4); // 6 to 8 bits
+
+								dst_x = xOffs + j;
+								dst_y = yOffs + (currLineBlock * 2 * _blockH) - 1;
+								dstPtr = surface->getBasePtr(dst_x, dst_y);
+								drawPixel(*surface, dstPtr, surface->format.RGBToColor(r, g, b));
+							}
+						}
+					}
+					// Do the swap
+					uint8* tmpSwapPointer = topBlockRowColorIndexForPalette;
+					topBlockRowColorIndexForPalette = currBlockRowColorIndexForPalette;
+					currBlockRowColorIndexForPalette = tmpSwapPointer;
+				}
+				++currLineBlock;     // advance by 1 line block
+				currColumnBlock = 0; // and go to the first column block of the line
+			} else {
+				++currColumnBlock;   // advance by 1 column block
+			}
+		}
+
+		if (scale2xPossible && !(_vqaDecoder->_allowVerticalScanlines && _vqaDecoder->_allowHorizontalScanlines)) {
+			delete[] topBlockRowColorIndexForPalette;
+			delete[] currBlockRowColorIndexForPalette;
 		}
 	}
 
@@ -944,22 +1395,34 @@ VQADecoder::VQAAudioTrack::VQAAudioTrack(VQADecoder *vqaDecoder) {
 		// TODO use some typical value?
 		_frequency = 0;
 	}
-	memset(_compressedAudioFrame, 0, sizeof(uint8));
+	memset(_compressedAudioFrame, 0, kSizeInBytesOfCompressedAudioFrameMax * sizeof(uint8));
+	_bigCompressedAudioFrame = false;
 }
 
 VQADecoder::VQAAudioTrack::~VQAAudioTrack() {
 }
 
 Audio::SeekableAudioStream *VQADecoder::VQAAudioTrack::decodeAudioFrame() {
-	int16 *audioFrame = (int16 *)malloc(kSizeInShortsAllocatedToAudioFrame);
+	int16* audioFrame = nullptr;
+	if (_bigCompressedAudioFrame) {
+		audioFrame = (int16*)malloc(kSizeInShortsAllocatedToAudioFrameMax);
+	} else {
+		audioFrame = (int16*)malloc(kSizeInShortsAllocatedToAudioFrame);
+	}
 	if (audioFrame != nullptr) {
-		memset(audioFrame, 0, kSizeInShortsAllocatedToAudioFrame);
-
-		_adpcmDecoder.decode(_compressedAudioFrame, kSizeInBytesOfCompressedAudioFrame, audioFrame, true);
 
 		uint flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
 
-		return Audio::makeRawStream((byte *)audioFrame, kSizeInShortsAllocatedToAudioFrame, _frequency, flags, DisposeAfterUse::YES);
+		if (_bigCompressedAudioFrame) {
+			memset(audioFrame, 0, kSizeInShortsAllocatedToAudioFrameMax);
+			_adpcmDecoder.decode(_compressedAudioFrame, kSizeInBytesOfCompressedAudioFrameMax, audioFrame, true);
+			_bigCompressedAudioFrame = false;
+			return Audio::makeRawStream((byte*)audioFrame, kSizeInShortsAllocatedToAudioFrameMax, _frequency, flags, DisposeAfterUse::YES);
+		} else {
+			memset(audioFrame, 0, kSizeInShortsAllocatedToAudioFrame);
+			_adpcmDecoder.decode(_compressedAudioFrame, kSizeInBytesOfCompressedAudioFrame, audioFrame, true);
+			return Audio::makeRawStream((byte*)audioFrame, kSizeInShortsAllocatedToAudioFrame, _frequency, flags, DisposeAfterUse::YES);
+		}
 	} else {
 		warning("VQADecoder::VQAAudioTrack::decodeAudioFrame: Insufficient memory to allocate for audio frame");
 		return nullptr;
@@ -967,11 +1430,16 @@ Audio::SeekableAudioStream *VQADecoder::VQAAudioTrack::decodeAudioFrame() {
 }
 
 bool VQADecoder::VQAAudioTrack::readSND2(Common::SeekableReadStream *s, uint32 size) {
-	if (size != kSizeInBytesOfCompressedAudioFrame) {
+	if (size != kSizeInBytesOfCompressedAudioFrame
+	    && size != kSizeInBytesOfCompressedAudioFrameMax) {
 		warning("audio frame size: %d", size);
 		return false;
 	}
 
+	if (size == kSizeInBytesOfCompressedAudioFrameMax) {
+		_bigCompressedAudioFrame = true;
+	}
+
 	s->read(_compressedAudioFrame, roundup(size));
 
 	return true;
diff --git a/engines/bladerunner/vqa_decoder.h b/engines/bladerunner/vqa_decoder.h
index 0b20e5e69bc..909b6ba11c4 100644
--- a/engines/bladerunner/vqa_decoder.h
+++ b/engines/bladerunner/vqa_decoder.h
@@ -55,6 +55,10 @@ enum VQADecoderSkipFlags {
 class VQADecoder {
 	friend class Debugger;
 
+	struct VQPPalette {
+		uint8 interpol2D[256][256];
+	};
+
 public:
 	VQADecoder();
 	~VQADecoder();
@@ -82,6 +86,10 @@ public:
 	bool getLoopBeginAndEndFrame(int loop, int *begin, int *end);
 	int  getLoopIdFromFrame(int frame);
 
+	void allocatePaletteVQPTable(const uint32 numOfPalettes);
+	void updatePaletteVQPTable(uint32 palId, uint16 j, uint16 k, uint8 colorByte);
+	void deleteVQPTable();
+
 	struct Header {
 		uint16 version;     // 0x00
 		uint16 flags;       // 0x02
@@ -145,6 +153,16 @@ public:
 	int      _decodingFrame;
 	LoopInfo _loopInfo;
 
+	VQPPalette *_vqpPalsArr;
+	uint16      _numOfVQPPalettes;
+
+	bool        _oldV2VQA;
+	// TODO Maybe add public methods for setting these member vars (essentially flags)?
+	bool        _allowHorizontalScanlines;
+	bool        _allowVerticalScanlines;
+	bool        _scaleVideoTo2xRequested;
+	bool        _scale2xPossible;
+	bool        _centerVideoRequested;
 	Common::Array<CodebookInfo> _codebooks;
 
 	uint32  *_frameInfo;
@@ -170,6 +188,7 @@ public:
 	CodebookInfo &codebookInfoForFrame(int frame);
 
 	class VQAVideoTrack {
+		static const uint     kSizeInBytesOfCPL0Chunk = 768; // 3 * 256
 	public:
 		VQAVideoTrack(VQADecoder *vqaDecoder);
 		~VQAVideoTrack();
@@ -186,13 +205,16 @@ public:
 		void decodeLights(Lights *lights);
 
 		bool readVQFR(Common::SeekableReadStream *s, uint32 size, uint readFlags);
+		bool readVPTZ(Common::SeekableReadStream* s, uint32 size); // (for old type v2 VQA, eg. SIZZLE.VQAs)
 		bool readVPTR(Common::SeekableReadStream *s, uint32 size);
 		bool readVQFL(Common::SeekableReadStream *s, uint32 size, uint readFlags);
 		bool readCBFZ(Common::SeekableReadStream *s, uint32 size);
+		bool readCBPZ(Common::SeekableReadStream* s, uint32 size); // (for old type v2 VQA, eg. SIZZLE.VQAs)
 		bool readZBUF(Common::SeekableReadStream *s, uint32 size);
 		bool readVIEW(Common::SeekableReadStream *s, uint32 size);
 		bool readAESC(Common::SeekableReadStream *s, uint32 size);
 		bool readLITE(Common::SeekableReadStream *s, uint32 size);
+		bool readCPL0(Common::SeekableReadStream *s, uint32 size); // (for old type v2 VQA, eg. SIZZLE.VQAs)
 
 	protected:
 		Common::Rational getFrameRate() const;
@@ -208,6 +230,7 @@ public:
 		uint16 _width, _height;
 		uint8  _blockW, _blockH;
 		uint8  _frameRate;
+		uint8  _cbParts;
 		uint16 _maxBlocks;
 		uint16 _offsetX, _offsetY;
 
@@ -232,6 +255,18 @@ public:
 		uint8   *_screenEffectsData;
 		uint32   _screenEffectsDataSize;
 
+		int32          _currentPaletteId;
+		uint8         *_cpalPointer;
+		uint8         *_cpalPointerNext;
+		uint32	       _cpalPointerSize;
+		uint32	       _cpalPointerSizeNext;
+		uint8         *_vptz;
+		uint8         *_cbfzNext;         // Used to store of compressed parts of a codebook data
+		uint8          _countOfCBPsToCBF;
+		uint32         _accumulatedCBPZsizeToCBF;
+
+		CodebookInfo  *_codebookInfoNext; // Used to store the decompressed codebook data and swap with the active codebook
+
 		void VPTRWriteBlock(Graphics::Surface *surface, unsigned int dstBlock, unsigned int srcBlock, int count, bool alpha = false);
 		bool decodeFrame(Graphics::Surface *surface);
 	};
@@ -239,6 +274,8 @@ public:
 	class VQAAudioTrack {
 		static const uint     kSizeInShortsAllocatedToAudioFrame = 2940; // 4 * 735
 		static const uint     kSizeInBytesOfCompressedAudioFrame = 735;
+		static const uint     kSizeInShortsAllocatedToAudioFrameMax = 22048; // 4 * 5512 (for old type v2 VQA, eg. SIZZLE.VQAs)
+		static const uint     kSizeInBytesOfCompressedAudioFrameMax = 5512; // (for old type v2 VQA, eg. SIZZLE.VQAs)
 	public:
 		VQAAudioTrack(VQADecoder *vqaDecoder);
 		~VQAAudioTrack();
@@ -252,7 +289,9 @@ public:
 	private:
 		uint16               _frequency;
 		ADPCMWestwoodDecoder _adpcmDecoder;
-		uint8                _compressedAudioFrame[kSizeInBytesOfCompressedAudioFrame];
+		bool                 _bigCompressedAudioFrame;
+		uint8                _compressedAudioFrame[kSizeInBytesOfCompressedAudioFrameMax];
+//		uint8                _compressedAudioFrame[kSizeInBytesOfCompressedAudioFrame + 1]; // +1 because we use roundup for read-in and 735 is odd
 	};
 };
 
diff --git a/engines/bladerunner/vqa_player.cpp b/engines/bladerunner/vqa_player.cpp
index 638ce576896..9792b9ac0ae 100644
--- a/engines/bladerunner/vqa_player.cpp
+++ b/engines/bladerunner/vqa_player.cpp
@@ -88,6 +88,68 @@ void VQAPlayer::close() {
 	_s = nullptr;
 }
 
+bool VQAPlayer::loadVQPTable(const Common::String &vqpResName) {
+	Common::SeekableReadStream *vqpFileSRS = _vm->getResourceStream(vqpResName);
+	if (!vqpFileSRS) {
+		return false;
+	}
+
+	bool vqpFileReadError = false;
+
+	if (vqpFileSRS != nullptr) {
+		uint32 numOfPalettes = vqpFileSRS->readUint32LE();
+		uint32 palettesReadIn = 0;
+
+		if (vqpFileSRS->eos() || vqpFileSRS->err()) {
+			vqpFileReadError = true;
+		} else {
+			_decoder.allocatePaletteVQPTable(numOfPalettes);
+			uint8 colorb = 0;
+			uint32 colorsReadIn = 0;
+			bool endVqpFileParsing = false;
+			for (uint32 i = 0; i < numOfPalettes && !endVqpFileParsing; ++i) {
+				colorsReadIn = 0;
+				// For each palette read a 2d array for color combinations
+				for (uint16 j = 0; j < 256 && !endVqpFileParsing; ++j) {
+					for (uint16 k = 0; k <= j && !endVqpFileParsing; ++k) {
+						colorb = vqpFileSRS->readByte();
+						if (vqpFileSRS->eos() || vqpFileSRS->err()) {
+							endVqpFileParsing = true;
+							break;
+						}
+						++colorsReadIn;
+						_decoder.updatePaletteVQPTable(i, j, k, colorb);
+					}
+				}
+				// Since VQP is omitting the duplicates the palette entries are
+				// the number of combinations of 256 items in tuples of 2 items
+				// with the addition of the number of tuples of the same item
+				// (ie. (0,0), (1,1)) which are 256 (the whole diagonal of the 2d array).
+				// Thus:
+				// (256! / (2! * (256-2)!)) + 256 = ((256 * 255) / 2) + 256 = 32896 entries per palette
+				if (colorsReadIn == 32896) {
+					++palettesReadIn;
+				}
+			}
+			// Quick validation check
+			if (palettesReadIn != numOfPalettes) {
+				debug("Error: [VQP] Palettes Read-In: %d mismatch with number in header: %d\n", palettesReadIn, numOfPalettes);
+				_decoder.deleteVQPTable();
+				vqpFileReadError = true;
+			}
+		}
+		delete vqpFileSRS;
+		vqpFileSRS = nullptr;
+
+		if (!vqpFileReadError) {
+			//debug("Info: [VQP] Palettes Read-In: %d matches number in header", palettesReadIn);
+			return true;
+		}
+	}
+
+	return false;
+}
+
 int VQAPlayer::update(bool forceDraw, bool advanceFrame, bool useTime, Graphics::Surface *customSurface) {
 	uint32 now = 60 * _vm->_time->currentSystem();
 	int result = -1;
diff --git a/engines/bladerunner/vqa_player.h b/engines/bladerunner/vqa_player.h
index fde4369f8be..f0a00891385 100644
--- a/engines/bladerunner/vqa_player.h
+++ b/engines/bladerunner/vqa_player.h
@@ -122,6 +122,8 @@ public:
 	bool open();
 	void close();
 
+	bool loadVQPTable(const Common::String& vqpResName);
+
 	int  update(bool forceDraw = false, bool advanceFrame = true, bool useTime = true, Graphics::Surface *customSurface = nullptr);
 	void updateZBuffer(ZBuffer *zbuffer);
 	void updateView(View *view);




More information about the Scummvm-git-logs mailing list