mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-03 09:23:37 +00:00
4e636920ee
By basically copying the previous to last row and column pixels' color respectively
1486 lines
48 KiB
C++
1486 lines
48 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "bladerunner/vqa_decoder.h"
|
|
|
|
#include "bladerunner/bladerunner.h"
|
|
#include "bladerunner/decompress_lcw.h"
|
|
#include "bladerunner/decompress_lzo.h"
|
|
#include "bladerunner/game_info.h"
|
|
#include "bladerunner/lights.h"
|
|
#include "bladerunner/screen_effects.h"
|
|
#include "bladerunner/view.h"
|
|
#include "bladerunner/zbuffer.h"
|
|
|
|
#include "audio/decoders/raw.h"
|
|
|
|
#include "common/array.h"
|
|
#include "common/util.h"
|
|
#include "common/memstream.h"
|
|
|
|
namespace BladeRunner {
|
|
|
|
#define kAESC 0x41455343
|
|
#define kCBFZ 0x4342465A
|
|
#define kCBPZ 0x4342505A
|
|
#define kCIND 0x43494E44
|
|
#define kCINF 0x43494E46
|
|
#define kCINH 0x43494E48
|
|
#define kCLIP 0x434C4950
|
|
#define kFINF 0x46494E46
|
|
#define kFORM 0x464f524d
|
|
#define kLIND 0x4C494E44
|
|
#define kLINF 0x4C494E46
|
|
#define kLINH 0x4C494E48
|
|
#define kLITE 0x4C495445
|
|
#define kLNID 0x4C4E4944
|
|
#define kLNIH 0x4C4E4948
|
|
#define kLNIN 0x4C4E494E
|
|
#define kLNIO 0x4C4E494F
|
|
#define kMFCD 0x4D464344
|
|
#define kMFCH 0x4D464348
|
|
#define kMFCI 0x4D464349
|
|
#define kMFCT 0x4D464354
|
|
#define kMSCH 0x4D534348
|
|
#define kMSCI 0x4D534349
|
|
#define kMSCT 0x4D534354
|
|
#define kSN2J 0x534e324a
|
|
#define kSND2 0x534e4432
|
|
#define kVIEW 0x56494557
|
|
#define kVPTR 0x56505452
|
|
#define kVQFL 0x5651464C
|
|
#define kVQFR 0x56514652
|
|
#define kVQHD 0x56514844
|
|
#define kWVQA 0x57565141
|
|
#define kZBUF 0x5A425546
|
|
#define kCPL0 0x43504C30
|
|
#define kVPTZ 0x5650545A
|
|
|
|
int32 remain(Common::SeekableReadStream *s) {
|
|
int32 pos = s->pos();
|
|
if (pos == -1) return -1;
|
|
|
|
int32 size = s->size();
|
|
if (size == -1) return -1;
|
|
|
|
return size - pos;
|
|
}
|
|
|
|
struct IFFChunkHeader {
|
|
IFFChunkHeader()
|
|
: id(0), size(0)
|
|
{}
|
|
|
|
uint32 id;
|
|
uint32 size;
|
|
};
|
|
|
|
static bool readIFFChunkHeader(Common::SeekableReadStream *s, IFFChunkHeader *ts) {
|
|
if (remain(s) < 8)
|
|
return false;
|
|
|
|
ts->id = s->readUint32BE();
|
|
ts->size = s->readUint32BE();
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline uint32 roundup(uint32 v) {
|
|
return (v + 1) & ~1u;
|
|
}
|
|
|
|
VQADecoder::VQADecoder() {
|
|
_s = nullptr;
|
|
_frameInfo = nullptr;
|
|
_videoTrack = nullptr;
|
|
_audioTrack = nullptr;
|
|
_maxVIEWChunkSize = 0;
|
|
_maxZBUFChunkSize = 0;
|
|
_maxAESCChunkSize = 0;
|
|
_header.version = 0;
|
|
_header.flags = 0;
|
|
_header.numFrames = 0;
|
|
_header.width = 0;
|
|
_header.height = 0;
|
|
_header.blockW = 0;
|
|
_header.blockH = 0;
|
|
_header.frameRate = 0;
|
|
_header.cbParts = 0;
|
|
_header.colors = 0;
|
|
_header.maxBlocks = 0;
|
|
_header.offsetX = 0;
|
|
_header.offsetY = 0;
|
|
_header.maxVPTRSize = 0;
|
|
_header.freq = 0;
|
|
_header.channels = 0;
|
|
_header.bits = 0;
|
|
_header.unk3 = 0;
|
|
_header.unk4 = 0;
|
|
_header.maxCBFZSize = 0;
|
|
_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() {
|
|
for (uint i = _codebooks.size(); i != 0; --i) {
|
|
delete[] _codebooks[i - 1].data;
|
|
}
|
|
delete _audioTrack;
|
|
delete _videoTrack;
|
|
delete[] _frameInfo;
|
|
deleteVQPTable();
|
|
}
|
|
|
|
bool VQADecoder::loadStream(Common::SeekableReadStream *s) {
|
|
// close();
|
|
_s = s;
|
|
|
|
IFFChunkHeader chd;
|
|
uint32 type;
|
|
|
|
readIFFChunkHeader(s, &chd);
|
|
if (chd.id != kFORM || !chd.size)
|
|
return false;
|
|
|
|
type = s->readUint32BE();
|
|
|
|
if (type != kWVQA)
|
|
return false;
|
|
|
|
do {
|
|
if (!readIFFChunkHeader(_s, &chd))
|
|
return false;
|
|
|
|
bool rc = false;
|
|
switch (chd.id) {
|
|
case kCINF: rc = readCINF(s, chd.size); break;
|
|
case kCLIP: rc = readCLIP(s, chd.size); break;
|
|
case kFINF: rc = readFINF(s, chd.size); break;
|
|
case kLINF: rc = readLINF(s, chd.size); break;
|
|
case kLNIN: rc = readLNIN(s, chd.size); break;
|
|
case kMFCI: rc = readMFCI(s, chd.size); break;
|
|
case kMSCI: rc = readMSCI(s, chd.size); break;
|
|
case kVQHD: rc = readVQHD(s, chd.size); break;
|
|
default:
|
|
warning("Unhandled chunk '%s'", tag2str(chd.id));
|
|
s->skip(roundup(chd.size));
|
|
rc = true;
|
|
}
|
|
|
|
if (!rc) {
|
|
warning("failed to handle chunk %s", tag2str(chd.id));
|
|
return false;
|
|
}
|
|
} while (chd.id != kFINF);
|
|
|
|
_videoTrack = new VQAVideoTrack(this);
|
|
_audioTrack = new VQAAudioTrack(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
void VQADecoder::decodeVideoFrame(Graphics::Surface *surface, int frame, bool forceDraw) {
|
|
_decodingFrame = frame;
|
|
_videoTrack->decodeVideoFrame(surface, forceDraw);
|
|
}
|
|
|
|
void VQADecoder::decodeZBuffer(ZBuffer *zbuffer) {
|
|
_videoTrack->decodeZBuffer(zbuffer);
|
|
}
|
|
|
|
Audio::SeekableAudioStream *VQADecoder::decodeAudioFrame() {
|
|
return _audioTrack->decodeAudioFrame();
|
|
}
|
|
|
|
void VQADecoder::decodeView(View *view) {
|
|
_videoTrack->decodeView(view);
|
|
}
|
|
|
|
void VQADecoder::decodeScreenEffects(ScreenEffects *screenEffects) {
|
|
_videoTrack->decodeScreenEffects(screenEffects);
|
|
}
|
|
|
|
void VQADecoder::decodeLights(Lights *lights) {
|
|
_videoTrack->decodeLights(lights);
|
|
}
|
|
|
|
void VQADecoder::readPacket(uint readFlags) {
|
|
IFFChunkHeader chd;
|
|
|
|
if (remain(_s) < 8) {
|
|
warning("VQADecoder::readPacket(): remain: %d", remain(_s));
|
|
assert(remain(_s) < 8);
|
|
}
|
|
|
|
do {
|
|
if (!readIFFChunkHeader(_s, &chd)) {
|
|
error("VQADecoder::readPacket(): Error reading chunk header");
|
|
}
|
|
|
|
bool rc = false;
|
|
// Video track
|
|
switch (chd.id) {
|
|
case kAESC: rc = ((readFlags & kVQAReadCustom) == 0) ? _s->skip(roundup(chd.size)) : _videoTrack->readAESC(_s, chd.size); break;
|
|
case kLITE: rc = ((readFlags & kVQAReadCustom) == 0) ? _s->skip(roundup(chd.size)) : _videoTrack->readLITE(_s, chd.size); break;
|
|
case kVIEW: rc = ((readFlags & kVQAReadCustom) == 0) ? _s->skip(roundup(chd.size)) : _videoTrack->readVIEW(_s, chd.size); break;
|
|
case kVQFL: rc = ((readFlags & kVQAReadVideo ) == 0) ? _s->skip(roundup(chd.size)) : _videoTrack->readVQFL(_s, chd.size, readFlags); break;
|
|
case kVQFR: rc = ((readFlags & kVQAReadVideo ) == 0) ? _s->skip(roundup(chd.size)) : _videoTrack->readVQFR(_s, chd.size, readFlags); break;
|
|
case kZBUF: rc = ((readFlags & kVQAReadCustom) == 0) ? _s->skip(roundup(chd.size)) : _videoTrack->readZBUF(_s, chd.size); break;
|
|
// Sound track
|
|
case kSN2J: rc = ((readFlags & kVQAReadAudio) == 0) ? _s->skip(roundup(chd.size)) : _audioTrack->readSN2J(_s, chd.size); break;
|
|
case kSND2: rc = ((readFlags & kVQAReadAudio) == 0) ? _s->skip(roundup(chd.size)) : _audioTrack->readSND2(_s, chd.size); break;
|
|
default:
|
|
rc = false;
|
|
_s->skip(roundup(chd.size));
|
|
}
|
|
|
|
if (!rc) {
|
|
warning("VQADecoder::readPacket(): Error handling chunk %s", tag2str(chd.id));
|
|
return;
|
|
}
|
|
} while (chd.id != kVQFR);
|
|
}
|
|
|
|
void VQADecoder::readFrame(int frame, uint readFlags) {
|
|
if (frame < 0 || frame >= numFrames()) {
|
|
error("VQADecoder::readFrame(): frame %d out of bounds, frame count is %d", frame, numFrames());
|
|
}
|
|
|
|
uint32 frameOffset = 2 * (_frameInfo[frame] & 0x0FFFFFFF);
|
|
_s->seek(frameOffset);
|
|
|
|
_readingFrame = frame;
|
|
readPacket(readFlags);
|
|
}
|
|
|
|
bool VQADecoder::readVQHD(Common::SeekableReadStream *s, uint32 size) {
|
|
if (size != 42)
|
|
return false;
|
|
|
|
_header.version = s->readUint16LE();
|
|
_header.flags = s->readUint16LE();
|
|
_header.numFrames = s->readUint16LE();
|
|
_header.width = s->readUint16LE();
|
|
_header.height = s->readUint16LE();
|
|
_header.blockW = s->readByte();
|
|
_header.blockH = s->readByte();
|
|
_header.frameRate = s->readByte();
|
|
_header.cbParts = s->readByte();
|
|
_header.colors = s->readUint16LE();
|
|
_header.maxBlocks = s->readUint16LE();
|
|
_header.offsetX = s->readUint16LE();
|
|
_header.offsetY = s->readUint16LE();
|
|
_header.maxVPTRSize = s->readUint16LE();
|
|
_header.freq = s->readUint16LE();
|
|
_header.channels = s->readByte();
|
|
_header.bits = s->readByte();
|
|
_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) {
|
|
// debug("_header.version %d", _header.version);
|
|
// debug("_header.flags %04x", _header.flags);
|
|
// debug("_header.numFrames %d", _header.numFrames);
|
|
// debug("_header.width %d", _header.width);
|
|
// debug("_header.height %d", _header.height);
|
|
// debug("_header.blockW %d", _header.blockW);
|
|
// debug("_header.blockH %d", _header.blockH);
|
|
// debug("_header.frameRate %d", _header.frameRate);
|
|
// debug("_header.cbParts %d", _header.cbParts);
|
|
// debug("_header.colors %d", _header.colors);
|
|
// debug("_header.maxBlocks %d", _header.maxBlocks);
|
|
// debug("_header.offsetX %d", _header.offsetX);
|
|
// debug("_header.offsetY %d", _header.offsetY);
|
|
// debug("_header.maxVPTRSize %d", _header.maxVPTRSize);
|
|
// debug("_header.freq %d", _header.freq);
|
|
// debug("_header.channels %d", _header.channels);
|
|
// debug("_header.bits %d", _header.bits);
|
|
// debug("_header.unk3 %d", _header.unk3);
|
|
// debug("_header.unk4 %d", _header.unk4);
|
|
// debug("_header.maxCBFZSize %d", _header.maxCBFZSize);
|
|
// debug("_header.unk5 %d", _header.unk5);
|
|
// debug("\n");
|
|
// }
|
|
|
|
assert(_header.version == 2);
|
|
if (_header.channels != 0) {
|
|
assert(_header.freq == 22050);
|
|
assert(_header.channels == 1);
|
|
assert(_header.bits == 16);
|
|
}
|
|
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;
|
|
sizeLeft -= roundup(chd.size) + 8;
|
|
|
|
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)) : 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));
|
|
}
|
|
|
|
if (!rc) {
|
|
error("VQADecoder::VQAVideoTrack::readVQFR(): error handling chunk %s", tag2str(chd.id));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool VQADecoder::readMSCI(Common::SeekableReadStream *s, uint32 size) {
|
|
IFFChunkHeader chd;
|
|
readIFFChunkHeader(_s, &chd);
|
|
|
|
if (chd.id != kMSCH)
|
|
return false;
|
|
|
|
uint32 count, unk0;
|
|
count = s->readUint32LE();
|
|
unk0 = s->readUint32LE();
|
|
assert(unk0 == 0);
|
|
|
|
readIFFChunkHeader(_s, &chd);
|
|
if (chd.id != kMSCT || chd.size != count * 0x10)
|
|
return false;
|
|
|
|
for (uint32 i = count; i != 0; --i) {
|
|
uint32 tag, max_size;
|
|
tag = s->readUint32BE();
|
|
max_size = s->readUint32LE();
|
|
|
|
switch (tag) {
|
|
case kVIEW:
|
|
_maxVIEWChunkSize = max_size;
|
|
break;
|
|
case kZBUF:
|
|
_maxZBUFChunkSize = max_size;
|
|
break;
|
|
case kAESC:
|
|
_maxAESCChunkSize = max_size;
|
|
break;
|
|
default:
|
|
warning("Unknown tag in MSCT: %s", tag2str(tag));
|
|
}
|
|
|
|
uint32 zero;
|
|
zero = s->readUint32LE(); assert(zero == 0);
|
|
zero = s->readUint32LE(); assert(zero == 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VQADecoder::readLINF(Common::SeekableReadStream *s, uint32 size) {
|
|
IFFChunkHeader chd;
|
|
readIFFChunkHeader(_s, &chd);
|
|
|
|
if (chd.id != kLINH || chd.size != 6)
|
|
return false;
|
|
|
|
_loopInfo.loopCount = s->readUint16LE();
|
|
_loopInfo.flags = s->readUint32LE();
|
|
|
|
if ((_loopInfo.flags & 3) == 0)
|
|
return false;
|
|
|
|
readIFFChunkHeader(_s, &chd);
|
|
if (chd.id != kLIND || chd.size != 4u * _loopInfo.loopCount)
|
|
return false;
|
|
|
|
_loopInfo.loops = new Loop[_loopInfo.loopCount];
|
|
for (uint16 i = _loopInfo.loopCount; i != 0; --i) {
|
|
_loopInfo.loops[_loopInfo.loopCount - i].begin = s->readUint16LE();
|
|
_loopInfo.loops[_loopInfo.loopCount - i].end = s->readUint16LE();
|
|
|
|
// debug("Loop %d: %04x %04x", _loopInfo.loopCount - i, _loopInfo.loops[_loopInfo.loopCount - i].begin, _loopInfo.loops[_loopInfo.loopCount - i].end);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VQADecoder::CodebookInfo &VQADecoder::codebookInfoForFrame(int frame) {
|
|
assert(frame < numFrames());
|
|
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];
|
|
}
|
|
}
|
|
|
|
assert(ci && "No codebook found");
|
|
return _codebooks[0];
|
|
}
|
|
|
|
bool VQADecoder::readCINF(Common::SeekableReadStream *s, uint32 size) {
|
|
IFFChunkHeader chd;
|
|
|
|
readIFFChunkHeader(_s, &chd);
|
|
if (chd.id != kCINH || chd.size != 8u)
|
|
return false;
|
|
|
|
uint16 codebookCount = s->readUint16LE();
|
|
_codebooks.resize(codebookCount);
|
|
|
|
s->skip(6);
|
|
|
|
readIFFChunkHeader(_s, &chd);
|
|
if (chd.id != kCIND || chd.size != 6u * codebookCount)
|
|
return false;
|
|
|
|
for (uint16 i = codebookCount; i != 0; --i) {
|
|
_codebooks[codebookCount - i].frame = s->readUint16LE();
|
|
_codebooks[codebookCount - i].size = s->readUint32LE();
|
|
_codebooks[codebookCount - i].data = nullptr;
|
|
|
|
// debug("Codebook %2u: %4d %8d", codebookCount - i, _codebooks[codebookCount - i].frame, _codebooks[codebookCount - i].size);
|
|
|
|
assert(_codebooks[codebookCount - i].frame < numFrames());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VQADecoder::readFINF(Common::SeekableReadStream *s, uint32 size) {
|
|
if (size != 4u * _header.numFrames)
|
|
return false;
|
|
|
|
_frameInfo = new uint32[_header.numFrames];
|
|
|
|
for (uint16 i = _header.numFrames; i != 0; --i)
|
|
_frameInfo[ _header.numFrames - i] = s->readUint32LE();
|
|
|
|
// if (false) {
|
|
// uint32 last = 0;
|
|
// for (uint32 i = 0; i != _header.numFrames; ++i) {
|
|
// uint32 diff = _frameInfo[i] - last;
|
|
// debug("_frameInfo[%4d] = 0x%08x - %08x", i, _frameInfo[i], diff);
|
|
// last = _frameInfo[i];
|
|
// }
|
|
// }
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VQADecoder::readLNIN(Common::SeekableReadStream *s, uint32 size) {
|
|
IFFChunkHeader chd;
|
|
|
|
readIFFChunkHeader(_s, &chd);
|
|
if (chd.id != kLNIH || chd.size != 10)
|
|
return false;
|
|
|
|
uint16 loopNamesCount = s->readUint16LE();
|
|
uint16 loopUnk1 = s->readUint16LE();
|
|
uint16 loopUnk2 = s->readUint16LE();
|
|
uint16 loopUnk3 = s->readUint16LE();
|
|
uint16 loopUnk4 = s->readUint16LE();
|
|
|
|
#if BLADERUNNER_DEBUG_CONSOLE
|
|
debug("VQADecoder::readLNIN() Unknown Values: 0x%04x 0x%04x 0x%04x 0x%04x", loopUnk1, loopUnk2, loopUnk3, loopUnk4);
|
|
#else
|
|
(void)loopUnk1;
|
|
(void)loopUnk2;
|
|
(void)loopUnk3;
|
|
(void)loopUnk4;
|
|
#endif
|
|
|
|
if (loopNamesCount != _loopInfo.loopCount)
|
|
return false;
|
|
|
|
readIFFChunkHeader(_s, &chd);
|
|
if (chd.id != kLNIO || chd.size != 4u * loopNamesCount)
|
|
return false;
|
|
|
|
uint32 *loopNameOffsets = (uint32 *)malloc(loopNamesCount * sizeof(uint32));
|
|
for (uint16 i = loopNamesCount; i != 0; --i) {
|
|
loopNameOffsets[loopNamesCount - i] = s->readUint32LE();
|
|
}
|
|
|
|
readIFFChunkHeader(_s, &chd);
|
|
if (chd.id != kLNID) {
|
|
free(loopNameOffsets);
|
|
return false;
|
|
}
|
|
|
|
char *names = (char *)malloc(roundup(chd.size));
|
|
s->read(names, roundup(chd.size));
|
|
|
|
for (uint16 i = loopNamesCount; i != 0; --i) {
|
|
char *begin = names + loopNameOffsets[loopNamesCount - i];
|
|
uint32 len = ((i == 1) ? chd.size : loopNameOffsets[loopNamesCount - i + 1]) - loopNameOffsets[loopNamesCount - i];
|
|
|
|
_loopInfo.loops[loopNamesCount - i].name = Common::String(begin, len);
|
|
|
|
// debug("%2u: %s", loopNamesCount - i, _loopInfo.loops[loopNamesCount - i].name.c_str());
|
|
}
|
|
|
|
free(loopNameOffsets);
|
|
free(names);
|
|
return true;
|
|
}
|
|
|
|
bool VQADecoder::getLoopBeginAndEndFrame(int loop, int *begin, int *end) {
|
|
assert(begin && end);
|
|
|
|
if (loop < 0 || loop >= _loopInfo.loopCount)
|
|
return false;
|
|
|
|
*begin = _loopInfo.loops[loop].begin;
|
|
*end = _loopInfo.loops[loop].end;
|
|
|
|
return true;
|
|
}
|
|
|
|
int VQADecoder::getLoopIdFromFrame(int frame) {
|
|
if (frame >= 0) {
|
|
for (int loopId = 0; loopId < _loopInfo.loopCount; ++loopId) {
|
|
if (frame >= _loopInfo.loops[loopId].begin && frame <= _loopInfo.loops[loopId].end) {
|
|
return loopId;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
bool VQADecoder::readMFCI(Common::SeekableReadStream *s, uint32 size) {
|
|
s->skip(roundup(size));
|
|
return true;
|
|
}
|
|
|
|
VQADecoder::VQAVideoTrack::VQAVideoTrack(VQADecoder *vqaDecoder) {
|
|
_vqaDecoder = vqaDecoder;
|
|
_hasNewFrame = false;
|
|
|
|
VQADecoder::Header *header = &vqaDecoder->_header;
|
|
_numFrames = header->numFrames;
|
|
_width = header->width;
|
|
_height = header->height;
|
|
_blockW = header->blockW;
|
|
_blockH = header->blockH;
|
|
_frameRate = header->frameRate;
|
|
_cbParts = header->cbParts;
|
|
_maxBlocks = header->maxBlocks;
|
|
_offsetX = header->offsetX;
|
|
_offsetY = header->offsetY;
|
|
|
|
_maxVPTRSize = header->maxVPTRSize;
|
|
_maxCBFZSize = header->maxCBFZSize;
|
|
_maxZBUFChunkSize = vqaDecoder->_maxZBUFChunkSize;
|
|
|
|
_codebook = nullptr;
|
|
_cbfz = nullptr;
|
|
|
|
_vpointerSize = 0;
|
|
_vpointer = nullptr;
|
|
|
|
_curFrame = -1;
|
|
|
|
_zbufChunkSize = 0;
|
|
_zbufChunk = new uint8[roundup(_maxZBUFChunkSize)];
|
|
|
|
_viewDataSize = 0;
|
|
_viewData = nullptr;
|
|
|
|
_screenEffectsDataSize = 0;
|
|
_screenEffectsData = nullptr;
|
|
|
|
_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() {
|
|
delete[] _cbfz;
|
|
delete[] _zbufChunk;
|
|
delete[] _vpointer;
|
|
|
|
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 {
|
|
return _width;
|
|
}
|
|
|
|
uint16 VQADecoder::VQAVideoTrack::getHeight() const {
|
|
return _height;
|
|
}
|
|
|
|
int VQADecoder::VQAVideoTrack::getFrameCount() const {
|
|
return _numFrames;
|
|
}
|
|
|
|
Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const {
|
|
return _frameRate;
|
|
}
|
|
|
|
void VQADecoder::VQAVideoTrack::decodeVideoFrame(Graphics::Surface *surface, bool forceDraw) {
|
|
if (_hasNewFrame || forceDraw) {
|
|
assert(surface);
|
|
decodeFrame(surface);
|
|
_hasNewFrame = false;
|
|
}
|
|
}
|
|
|
|
bool VQADecoder::VQAVideoTrack::readVQFL(Common::SeekableReadStream *s, uint32 size, uint readFlags) {
|
|
IFFChunkHeader chd;
|
|
|
|
signed int sizeLeft = size; // we have to use signed int to avoid underflow
|
|
|
|
while (sizeLeft >= 8) {
|
|
if (!readIFFChunkHeader(s, &chd))
|
|
return false;
|
|
sizeLeft -= roundup(chd.size) + 8;
|
|
|
|
bool rc = false;
|
|
switch (chd.id) {
|
|
case kCBFZ: rc = readCBFZ(s, chd.size); break;
|
|
default:
|
|
s->skip(roundup(chd.size));
|
|
}
|
|
|
|
if (!rc) {
|
|
warning("VQFL: error handling chunk %s", tag2str(chd.id));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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);
|
|
return false;
|
|
}
|
|
|
|
CodebookInfo &codebookInfo = _vqaDecoder->codebookInfoForFrame(_vqaDecoder->_readingFrame);
|
|
if (codebookInfo.data) {
|
|
s->skip(roundup(size));
|
|
return true;
|
|
}
|
|
|
|
uint32 codebookSize = 2 * _maxBlocks * _blockW * _blockH;
|
|
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)];
|
|
}
|
|
|
|
s->read(_cbfz, roundup(size));
|
|
|
|
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;
|
|
}
|
|
|
|
bool VQADecoder::VQAVideoTrack::readZBUF(Common::SeekableReadStream *s, uint32 size) {
|
|
if (size > _maxZBUFChunkSize) {
|
|
warning("VQA ERROR: ZBUF chunk size: %08x > %08x", size, _maxZBUFChunkSize);
|
|
s->skip(roundup(size));
|
|
return false;
|
|
}
|
|
|
|
_zbufChunkSize = size;
|
|
s->read(_zbufChunk, roundup(size));
|
|
|
|
return true;
|
|
}
|
|
|
|
void VQADecoder::VQAVideoTrack::decodeZBuffer(ZBuffer *zbuffer) {
|
|
if (_zbufChunkSize == 0) {
|
|
return;
|
|
}
|
|
|
|
zbuffer->decodeData(_zbufChunk, _zbufChunkSize);
|
|
}
|
|
|
|
bool VQADecoder::VQAVideoTrack::readVIEW(Common::SeekableReadStream *s, uint32 size) {
|
|
if (size != 56) {
|
|
return false;
|
|
}
|
|
|
|
if (_viewData) {
|
|
delete[] _viewData;
|
|
}
|
|
|
|
_viewDataSize = roundup(size);
|
|
_viewData = new uint8[_viewDataSize];
|
|
s->read(_viewData, _viewDataSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
void VQADecoder::VQAVideoTrack::decodeView(View *view) {
|
|
if (!view || !_viewData) {
|
|
return;
|
|
}
|
|
|
|
Common::MemoryReadStream s(_viewData, _viewDataSize);
|
|
view->readVqa(&s);
|
|
|
|
delete[] _viewData;
|
|
_viewData = nullptr;
|
|
}
|
|
|
|
bool VQADecoder::VQAVideoTrack::readAESC(Common::SeekableReadStream *s, uint32 size) {
|
|
if (_screenEffectsData) {
|
|
delete[] _screenEffectsData;
|
|
}
|
|
|
|
_screenEffectsDataSize = roundup(size);
|
|
_screenEffectsData = new uint8[_screenEffectsDataSize];
|
|
s->read(_screenEffectsData, _screenEffectsDataSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
void VQADecoder::VQAVideoTrack::decodeScreenEffects(ScreenEffects *aesc) {
|
|
if (!aesc || !_screenEffectsData) {
|
|
return;
|
|
}
|
|
|
|
Common::MemoryReadStream s(_screenEffectsData, _screenEffectsDataSize);
|
|
aesc->readVqa(&s);
|
|
|
|
delete[] _screenEffectsData;
|
|
_screenEffectsData = nullptr;
|
|
}
|
|
|
|
bool VQADecoder::VQAVideoTrack::readLITE(Common::SeekableReadStream *s, uint32 size) {
|
|
if (_lightsData) {
|
|
delete[] _lightsData;
|
|
}
|
|
|
|
_lightsDataSize = roundup(size);
|
|
_lightsData = new uint8[_lightsDataSize];
|
|
s->read(_lightsData, _lightsDataSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void VQADecoder::VQAVideoTrack::decodeLights(Lights *lights) {
|
|
if (!lights || !_lightsData) {
|
|
return;
|
|
}
|
|
|
|
Common::MemoryReadStream s(_lightsData, _lightsDataSize);
|
|
lights->readVqa(&s);
|
|
|
|
delete[] _lightsData;
|
|
_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)
|
|
return false;
|
|
|
|
if (!_vpointer) {
|
|
_vpointer = new uint8[roundup(_maxVPTRSize)];
|
|
}
|
|
|
|
_vpointerSize = size;
|
|
s->read(_vpointer, roundup(size));
|
|
|
|
_hasNewFrame = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void VQADecoder::VQAVideoTrack::VPTRWriteBlock(Graphics::Surface *surface, unsigned int dstBlock, unsigned int srcBlock, int count, bool alpha) {
|
|
const uint8 *const block_src = &_codebook[2 * srcBlock * _blockW * _blockH];
|
|
|
|
uint16 blocks_per_line = _width / _blockW;
|
|
|
|
uint32 intermDiv = 0;
|
|
uint32 dst_x = 0;
|
|
uint32 dst_y = 0;
|
|
uint16 vqaColor = 0;
|
|
uint8 a, r, g, b;
|
|
|
|
for (uint i = count; i != 0; --i) {
|
|
// 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;
|
|
|
|
const uint8 *src_p = block_src;
|
|
|
|
for (uint y = _blockH; y != 0; --y) {
|
|
for (uint x = _blockW; x != 0; --x) {
|
|
vqaColor = READ_LE_UINT16(src_p);
|
|
src_p += 2;
|
|
|
|
getGameDataColor(vqaColor, a, r, g, b);
|
|
|
|
if (!(alpha && a)) {
|
|
// 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
|
|
drawPixel(*surface, dstPtr, surface->format.RGBToColor(r, g, b));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VQADecoder::VQAVideoTrack::decodeFrame(Graphics::Surface *surface) {
|
|
CodebookInfo &codebookInfo = _vqaDecoder->codebookInfoForFrame(_vqaDecoder->_decodingFrame);
|
|
|
|
if (!codebookInfo.data) {
|
|
_vqaDecoder->readFrame(codebookInfo.frame, kVQAReadCodebook);
|
|
}
|
|
|
|
_codebook = codebookInfo.data;
|
|
if (!_codebook || !_vpointer)
|
|
return false;
|
|
|
|
uint8 *src = _vpointer;
|
|
uint8 *end = _vpointer + _vpointerSize;
|
|
|
|
// 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;
|
|
|
|
VPTRWriteBlock(surface, dstBlock, srcBlock, 1);
|
|
++dstBlock;
|
|
|
|
for (uint16 i = count; i != 0; --i) {
|
|
srcBlock = *src++;
|
|
VPTRWriteBlock(surface, dstBlock, srcBlock, 1);
|
|
++dstBlock;
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
// fall through
|
|
case 4:
|
|
count = 1;
|
|
srcBlock = command & 0x1fff;
|
|
|
|
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);
|
|
}
|
|
}
|
|
} 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
|
|
}
|
|
|
|
uint8 *topBlockRowColorIndexForPalette = nullptr;
|
|
uint8 *currBlockRowColorIndexForPalette = nullptr;
|
|
if (scale2xPossible && !(_vqaDecoder->_allowVerticalScanlines && _vqaDecoder->_allowHorizontalScanlines)) {
|
|
topBlockRowColorIndexForPalette = new uint8[2 * _blockH * 2 * _width];
|
|
currBlockRowColorIndexForPalette = new uint8[2 * _blockH * 2 * _width];
|
|
}
|
|
|
|
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 * _width) + ((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 * _width) + ((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
|
|
// Since we don't have info on a right pixel color for the last entry, just copy the left pixel color.
|
|
// 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)
|
|
// 4. For the last row of pixels, since we don't have info on a below pixel color, just copy the previous row of pixels.
|
|
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 * _width; j += 2) {
|
|
leftColorIdx = currBlockRowColorIndexForPalette[(i * 2 * _width) + j - 1];
|
|
if (j == 2 * _width - 1) {
|
|
rightColorIdx = leftColorIdx;
|
|
} else {
|
|
rightColorIdx = currBlockRowColorIndexForPalette[(i * 2 * _width) + j + 1];
|
|
}
|
|
midColorIdx = _vqaDecoder->_vqpPalsArr[_currentPaletteId].interpol2D[leftColorIdx][rightColorIdx];
|
|
currBlockRowColorIndexForPalette[(i * 2 * _width) + 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 * _width; j += jIncr) {
|
|
topColorIdx = currBlockRowColorIndexForPalette[((i - 1) * 2 * _width) + j];
|
|
botColorIdx = currBlockRowColorIndexForPalette[((i + 1) * 2 * _width) + j];
|
|
midColorIdx = _vqaDecoder->_vqpPalsArr[_currentPaletteId].interpol2D[topColorIdx][botColorIdx];
|
|
currBlockRowColorIndexForPalette[(i * 2 * _width) + 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 currLineBlock > 0, then use info
|
|
// from the previous to last row of topBlockRowColorIndexForPalette
|
|
// and the 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 * _width; j += jIncr) {
|
|
topColorIdx = topBlockRowColorIndexForPalette[((2 * _blockH - 2) * 2 * _width) + j];
|
|
botColorIdx = currBlockRowColorIndexForPalette[j];
|
|
midColorIdx = _vqaDecoder->_vqpPalsArr[_currentPaletteId].interpol2D[topColorIdx][botColorIdx];
|
|
topBlockRowColorIndexForPalette[((2 * _blockH - 1) * 2 * _width) + 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));
|
|
if (currLineBlock == blocks_per_column - 1) {
|
|
// For last row of blocks, fill in the last row pixels by assuming that
|
|
// "bottom" pixel color is the same as previous to last row pixel's color
|
|
topColorIdx = currBlockRowColorIndexForPalette[((2 * _blockH - 2) * 2 * _width) + j];
|
|
botColorIdx = topColorIdx;
|
|
midColorIdx = _vqaDecoder->_vqpPalsArr[_currentPaletteId].interpol2D[topColorIdx][botColorIdx];
|
|
currBlockRowColorIndexForPalette[((2 * _blockH - 1) * 2 * _width) + 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 + (blocks_per_column * 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;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
VQADecoder::VQAAudioTrack::VQAAudioTrack(VQADecoder *vqaDecoder) {
|
|
if (vqaDecoder != nullptr) {
|
|
_frequency = vqaDecoder->_header.freq;
|
|
} else {
|
|
warning("VQADecoder::VQAAudioTrack::VQAAudioTrack: null pointer for vqaDecoder parameter");
|
|
// TODO use some typical value?
|
|
_frequency = 0;
|
|
}
|
|
memset(_compressedAudioFrame, 0, kSizeInBytesOfCompressedAudioFrameMax * sizeof(uint8));
|
|
_bigCompressedAudioFrame = false;
|
|
}
|
|
|
|
VQADecoder::VQAAudioTrack::~VQAAudioTrack() {
|
|
}
|
|
|
|
Audio::SeekableAudioStream *VQADecoder::VQAAudioTrack::decodeAudioFrame() {
|
|
int16* audioFrame = nullptr;
|
|
if (_bigCompressedAudioFrame) {
|
|
audioFrame = (int16*)malloc(kSizeInShortsAllocatedToAudioFrameMax);
|
|
} else {
|
|
audioFrame = (int16*)malloc(kSizeInShortsAllocatedToAudioFrame);
|
|
}
|
|
if (audioFrame != nullptr) {
|
|
|
|
uint flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool VQADecoder::VQAAudioTrack::readSND2(Common::SeekableReadStream *s, uint32 size) {
|
|
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;
|
|
}
|
|
|
|
bool VQADecoder::VQAAudioTrack::readSN2J(Common::SeekableReadStream *s, uint32 size) {
|
|
if (size != 6)
|
|
return false;
|
|
|
|
uint16 stepIndex = s->readUint16LE();
|
|
uint32 predictor = s->readUint32LE();
|
|
|
|
_adpcmDecoder.setParameters(stepIndex >> 5, predictor);
|
|
|
|
return true;
|
|
}
|
|
|
|
} // End of namespace BladeRunner
|