[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