diff options
Diffstat (limited to 'engines/bladerunner/vqa_decoder.cpp')
-rw-r--r-- | engines/bladerunner/vqa_decoder.cpp | 933 |
1 files changed, 933 insertions, 0 deletions
diff --git a/engines/bladerunner/vqa_decoder.cpp b/engines/bladerunner/vqa_decoder.cpp new file mode 100644 index 0000000000..b717964f5f --- /dev/null +++ b/engines/bladerunner/vqa_decoder.cpp @@ -0,0 +1,933 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BLADERUNNER_VQA_DECODER_H +#define BLADERUNNER_VQA_DECODER_H + +#include "bladerunner/vqa_decoder.h" + +#include "bladerunner/decompress_lcw.h" +#include "bladerunner/decompress_lzo.h" + +#include "common/array.h" +#include "common/util.h" + +namespace BladeRunner { + +#define kAESC 0x41455343 +#define kCBFZ 0x4342465A +#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 + +VQADecoder::VQADecoder(Common::SeekableReadStream *r) + : r(r), + frame(0), + zbuf(0), + codebook(0), + cbfz(0), + vptr(0), + cur_frame(-1), + cur_loop(-1), + loop_special(-1), + loop_default(-1), + has_view(false), + audio_frame(0), + max_view_chunk_size(0), + max_zbuf_chunk_size(0), + max_aesc_chunk_size(0) +{ + // debug("Opening VQA: '%s'\n", r->get_name()); +} + +VQADecoder::~VQADecoder() +{ +} + +struct iff_chunk_header_s +{ + iff_chunk_header_s() + : id(0), size(0) + {} + + uint32 id; + uint32 size; +}; + +static inline uint32 roundup(uint32 v) +{ + return (v + 1) & ~1u; +} + +const char *str_tag(uint32 tag); + +int32 stream_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; +} + +static +bool read_iff_chunk_header(Common::SeekableReadStream *r, iff_chunk_header_s *ts) +{ + if (stream_remain(r) < 8) + return false; + + ts->id = r->readUint32BE(); + ts->size = r->readUint32BE(); + + // if (ts->size != roundup(ts->size)) + // debug("%s: %d\n", str_tag(ts->id), ts->size); + + return true; +} + +const char *str_tag(uint32 tag) +{ + static char s[5]; + + sprintf(s, "%c%c%c%c", + (tag >> 24) & 0xff, + (tag >> 16) & 0xff, + (tag >> 8) & 0xff, + (tag >> 0) & 0xff); + + return s; +} + +bool VQADecoder::read_header() +{ + iff_chunk_header_s chd; + uint32 type; + bool rc; + + read_iff_chunk_header(r, &chd); + if (chd.id != kFORM || !chd.size) + return false; + + type = r->readUint32BE(); + + if (type != kWVQA) + return false; + + do { + if (!read_iff_chunk_header(r, &chd)) + return false; + + debug("\t%s : %x\n", str_tag(chd.id), chd.size); + + rc = false; + switch (chd.id) + { + case kCINF: rc = read_cinf(chd.size); break; + case kCLIP: rc = read_clip(chd.size); break; + case kFINF: rc = read_finf(chd.size); break; + case kLINF: rc = read_linf(chd.size); break; + case kLNIN: rc = read_lnin(chd.size); break; + case kMFCI: rc = read_mfci(chd.size); break; + case kMSCI: rc = read_msci(chd.size); break; + case kVQHD: rc = read_vqhd(chd.size); break; + default: + debug("Unhandled chunk '%s'\n", str_tag(chd.id)); + r->skip(roundup(chd.size)); + rc = true; + } + + if (!rc) + { + debug("failed to handle chunk %s\n", str_tag(chd.id)); + return false; + } + + } while (chd.id != kFINF); + + for (int i = 0; i != loop_info.loop_count; ++i) { + debug("LOOP %2d: %4d %4d %s\n", i, + loop_info.loops[i].begin, + loop_info.loops[i].end, + loop_info.loops[i].name.c_str()); + } + + return true; +} + +bool VQADecoder::read_vqhd(uint32 size) +{ + if (size != 42) + return false; + + header.version = r->readUint16LE(); + header.flags = r->readUint16LE(); + header.numFrames = r->readUint16LE(); + header.width = r->readUint16LE(); + header.height = r->readUint16LE(); + header.blockW = r->readByte(); + header.blockH = r->readByte(); + header.frameRate = r->readByte(); + header.cbParts = r->readByte(); + header.colors = r->readUint16LE(); + header.maxBlocks = r->readUint16LE(); + header.offset_x = r->readUint16LE(); + header.offset_y = r->readUint16LE(); + header.maxVPTRSize = r->readUint16LE(); + header.freq = r->readUint16LE(); + header.channels = r->readByte(); + header.bits = r->readByte(); + header.unk3 = r->readUint32LE(); + header.unk4 = r->readUint16LE(); + header.maxCBFZSize = r->readUint32LE(); + header.unk5 = r->readUint32LE(); + + if (header.offset_x || header.offset_y) + { + debug("header.offset_x, header.offset_y: %d %d\n", header.offset_x, header.offset_y); + } + + // if (header.unk3 || header.unk4 != 4 || header.unk5 || header.flags != 0x0014) + { + debug("header.version %d\n", header.version); + debug("header.flags %04x\n", header.flags); + debug("header.numFrames %d\n", header.numFrames); + debug("header.width %d\n", header.width); + debug("header.height %d\n", header.height); + debug("header.blockW %d\n", header.blockW); + debug("header.blockH %d\n", header.blockH); + debug("header.frameRate %d\n", header.frameRate); + debug("header.cbParts %d\n", header.cbParts); + debug("header.colors %d\n", header.colors); + debug("header.maxBlocks %d\n", header.maxBlocks); + debug("header.offsetX %d\n", header.offset_x); + debug("header.offsetY %d\n", header.offset_y); + debug("header.maxVPTRSize %d\n", header.maxVPTRSize); + debug("header.freq %d\n", header.freq); + debug("header.channels %d\n", header.channels); + debug("header.bits %d\n", header.bits); + debug("header.unk3 %d\n", header.unk3); + debug("header.unk4 %d\n", header.unk4); + debug("header.maxCBFZSize %d\n", header.maxCBFZSize); + debug("header.unk5 %d\n", header.unk5); + } + + // exit(-1); + + return true; +} + +bool VQADecoder::read_msci(uint32 size) +{ + iff_chunk_header_s chd; + read_iff_chunk_header(r, &chd); + + if (chd.id != kMSCH) + return false; + + uint32 count, unk0; + count = r->readUint32LE(); + unk0 = r->readUint32LE(); + assert(unk0 == 0); + + read_iff_chunk_header(r, &chd); + if (chd.id != kMSCT || chd.size != count * 0x10) + return false; + + for (uint32 i = 0; i < count; ++i) + { + uint32 tag, size; + tag = r->readUint32BE(); + size = r->readUint32LE(); + + switch (tag) + { + case kVIEW: + max_view_chunk_size = size; + debug("max VIEW size: %08x\n", max_view_chunk_size); + break; + case kZBUF: + max_zbuf_chunk_size = size; + zbuf_chunk = new uint8[roundup(max_zbuf_chunk_size)]; + debug("max ZBUF size: %08x\n", max_zbuf_chunk_size); + break; + case kAESC: + max_aesc_chunk_size = size; + debug("max AESC size: %08x\n", max_aesc_chunk_size); + break; + default: + debug("Unknown tag in MSCT: %s\n", str_tag(tag)); + } + + uint32 zero; + zero = r->readUint32LE(); assert(zero == 0); + zero = r->readUint32LE(); assert(zero == 0); + } + + return true; +} + +bool VQADecoder::read_linf(uint32 size) +{ + iff_chunk_header_s chd; + read_iff_chunk_header(r, &chd); + + if (chd.id != kLINH || chd.size != 6) + return false; + + loop_info.loop_count = r->readUint16LE(); + loop_info.flags = r->readUint32LE(); + + if ((loop_info.flags & 3) == 0) + return false; + + read_iff_chunk_header(r, &chd); + if (chd.id != kLIND || chd.size != 4u * loop_info.loop_count) + return false; + + loop_info.loops = new Loop[loop_info.loop_count]; + for (int i = 0; i != loop_info.loop_count; ++i) + { + loop_info.loops[i].begin = r->readUint16LE(); + loop_info.loops[i].end = r->readUint16LE(); + + // debug("Loop %d: %04x %04x\n", i, loop_info.loops[i].begin, loop_info.loops[i].end); + } + + return true; +} + +bool VQADecoder::read_cinf(uint32 size) +{ + iff_chunk_header_s chd; + + read_iff_chunk_header(r, &chd); + if (chd.id != kCINH || chd.size != 8u) + return false; + + clip_info.clip_count = r->readUint16LE(); + r->skip(6); + + read_iff_chunk_header(r, &chd); + if (chd.id != kCIND || chd.size != 6u * clip_info.clip_count) + return false; + + for (int i = 0; i != clip_info.clip_count; ++i) + { + uint16 a; + uint32 b; + a = r->readUint16LE(); + b = r->readUint32LE(); + debug("%4d %08x\n", a, b); + } + + return true; +} + +bool VQADecoder::read_finf(uint32 size) +{ + if (size != 4u * header.numFrames) + return false; + + frame_info = new uint32[header.numFrames]; + + for (uint32 i = 0; i != header.numFrames; ++i) + frame_info[i] = r->readUint32LE(); + + if (false) { + uint32 last = 0; + for (uint32 i = 0; i != header.numFrames; ++i) + { + uint32 diff = frame_info[i] - last; + debug("frame_info[%4d] = 0x%08x - %08x\n", i, frame_info[i], diff); + last = frame_info[i]; + } + } + + return true; +} + +bool VQADecoder::read_lnin(uint32 size) +{ + iff_chunk_header_s chd; + + read_iff_chunk_header(r, &chd); + if (chd.id != kLNIH || chd.size != 10) + return false; + + uint16 loop_names_count, loop_unk_1, loop_unk_2, loop_unk_3, loop_unk_4; + + loop_names_count = r->readUint16LE(); + loop_unk_1 = r->readUint16LE(); + loop_unk_2 = r->readUint16LE(); + loop_unk_3 = r->readUint16LE(); + loop_unk_4 = r->readUint16LE(); + + if (loop_names_count != loop_info.loop_count) + return false; + + read_iff_chunk_header(r, &chd); + if (chd.id != kLNIO || chd.size != 4u * loop_names_count) + return false; + + uint32 *loop_name_offsets = (uint32*)alloca(loop_names_count * sizeof(uint32)); + for (int i = 0; i != loop_names_count; ++i) { + loop_name_offsets[i] = r->readUint32LE(); + } + + read_iff_chunk_header(r, &chd); + if (chd.id != kLNID) + return false; + + char *names = (char*)alloca(roundup(chd.size)); + r->read(names, roundup(chd.size)); + + for (int i = 0; i != loop_names_count; ++i) { + char *begin = names + loop_name_offsets[i]; + size_t len = ((i == loop_names_count) ? chd.size : loop_name_offsets[i+1]) - loop_name_offsets[i]; + + loop_info.loops[i].name = Common::String(begin, len); + } + + return true; +} + +bool VQADecoder::read_clip(uint32 size) +{ + r->skip(roundup(size)); + return true; +} + +bool VQADecoder::read_mfci(uint32 size) +{ + r->skip(roundup(size)); + return true; +} + +int VQADecoder::read_frame() +{ + // debug("VQADecoder::read_frame(): %d, %d, %d, %d\n", loop_default, loop_special, cur_loop, cur_frame); + + if (loop_info.loop_count) + { + if (loop_special >= 0) + { + cur_loop = loop_special; + loop_special = -1; + + cur_frame = loop_info.loops[cur_loop].begin; + seek_to_frame(cur_frame); + } + else if (cur_loop == -1 && loop_default >= 0) + { + cur_loop = loop_default; + cur_frame = loop_info.loops[cur_loop].begin; + seek_to_frame(cur_frame); + } + else if (cur_loop >= -1 && cur_frame == loop_info.loops[cur_loop].end) + { + if (loop_default == -1) + return -1; + + cur_loop = loop_default; + cur_frame = loop_info.loops[cur_loop].begin; + seek_to_frame(cur_frame); + } + else + ++cur_frame; + } + else + ++cur_frame; + + if (cur_frame >= header.numFrames) + return -1; + + iff_chunk_header_s chd; + + has_view = false; + + if (stream_remain(r) < 8) { + debug("remain: %d\n", stream_remain(r)); + return -1; + } + + do { + if (!read_iff_chunk_header(r, &chd)) { + debug("Error reading chunk header\n"); + return -1; + } + + // debug("%s ", str_tag(chd.id)); + + bool rc = false; + switch (chd.id) + { + case kAESC: rc = read_aesc(chd.size); break; + case kLITE: rc = read_lite(chd.size); break; + case kSN2J: rc = read_sn2j(chd.size); break; + case kSND2: rc = read_snd2(chd.size); break; + case kVIEW: rc = read_view(chd.size); break; + case kVQFL: rc = read_vqfl(chd.size); break; + case kVQFR: rc = read_vqfr(chd.size); break; + case kZBUF: rc = read_zbuf(chd.size); break; + default: + r->skip(roundup(chd.size)); + rc = true; + } + + if (!rc) + { + debug("Error handling chunk %s\n", str_tag(chd.id)); + return -1; + } + } while (chd.id != kVQFR); + + return cur_frame; +} + + +bool VQADecoder::read_sn2j(uint32 size) +{ + if (size != 6) + return false; + + uint16 step_index; + uint32 predictor; + + step_index = r->readUint16LE(); + predictor = r->readUint32LE(); + + // ima_adpcm_ws_decoder.set_parameters(step_index >> 5, predictor); + + return true; +} + +bool VQADecoder::read_snd2(uint32 size) +{ + if (size != 735) + { + debug("audio frame size: %d\n", size); + return false; + } + + if (!audio_frame) + audio_frame = new int16[2 * size]; + memset(audio_frame, 0, 4 * size); + + uint8 *in_frame = new uint8[roundup(size)]; + r->read(in_frame, roundup(size)); + + // ima_adpcm_ws_decoder.decode(in_frame, size, audio_frame); + + delete[] in_frame; + + return true; +} + +bool VQADecoder::read_vqfr(uint32 size) +{ + iff_chunk_header_s chd; + + while (size >= 8) + { + if (!read_iff_chunk_header(r, &chd)) + return false; + size -= roundup(chd.size) + 8; + + // debug("(%s) ", str_tag(chd.id)); fflush(0); + + bool rc = false; + switch (chd.id) + { + case kCBFZ: rc = read_cbfz(chd.size); break; + case kVPTR: rc = read_vptr(chd.size); break; + default: + r->skip(roundup(chd.size)); + } + + if (!rc) + { + debug("VQFR: error handling chunk %s\n", str_tag(chd.id)); + return false; + } + } + + return true; +} + +bool VQADecoder::read_vqfl(uint32 size) +{ + iff_chunk_header_s chd; + + while (size >= 8) + { + if (!read_iff_chunk_header(r, &chd)) + return false; + size -= roundup(chd.size) + 8; + + bool rc = false; + switch (chd.id) + { + case kCBFZ: rc = read_cbfz(chd.size); break; + default: + r->skip(roundup(chd.size)); + } + + if (!rc) + { + debug("VQFL: error handling chunk %s\n", str_tag(chd.id)); + return false; + } + } + + return true; +} + +bool VQADecoder::read_cbfz(uint32 size) +{ + if (size > header.maxCBFZSize) + { + debug("%d > %d\n", size, header.maxCBFZSize); + return false; + } + + if (!codebook) + { + codebookSize = 2 * header.maxBlocks * header.blockW * header.blockH; + codebook = new uint8[codebookSize]; + } + if (!cbfz) + cbfz = new uint8[roundup(header.maxCBFZSize)]; + + r->read(cbfz, roundup(size)); + + decompress_lcw(cbfz, size, codebook, codebookSize); + + return true; +} + +static +int decodeZBUF_partial(uint8 *src, uint16 *cur_zbuf, uint32 src_len) +{ + uint32 dst_size = 640 * 480; // This is taken from global variables? + uint32 dst_remain = dst_size; + + uint16 *curz_p = cur_zbuf; + uint16 *in_p = (uint16*)src; + + while (dst_remain && (in_p - (uint16*)src) < (ptrdiff_t)src_len) + { + uint32 count = FROM_LE_16(*in_p++); + + if (count & 0x8000) + { + count = MIN(count & 0x7fff, dst_remain); + dst_remain -= count; + + while (count--) + { + uint16 value = FROM_LE_16(*in_p++); + if (value) + *curz_p = value; + ++curz_p; + } + } + else + { + count = MIN(count, dst_remain); + dst_remain -= count; + uint16 value = FROM_LE_16(*in_p++); + + if (!value) + curz_p += count; + else + { + while (count--) + *curz_p++ = value; + } + } + } + return dst_size - dst_remain; +} + +bool VQADecoder::read_zbuf(uint32 size) +{ + if (size > max_zbuf_chunk_size) { + debug("VQA ERROR: ZBUF chunk size: %08x > %08x\n", size, max_zbuf_chunk_size); + r->skip(roundup(size)); + return false; + } + + uint32 width, height, complete, unk0; + width = r->readUint32LE(); + height = r->readUint32LE(); + complete = r->readUint32LE(); + unk0 = r->readUint32LE(); + + uint32 remain = size - 16; + + if (width != header.width || height != header.height) + { + debug("%d, %d, %d, %d\n", width, height, complete, unk0); + r->skip(roundup(remain)); + return false; + } + + if (!zbuf) + { + if (!complete) { + r->skip(roundup(remain)); + return false; + } + zbuf = new uint16[width * height]; + } + + r->read(zbuf_chunk, roundup(remain)); + + if (complete) { + size_t zbuf_out_size; + decompress_lzo1x(zbuf_chunk, remain, (uint8*)zbuf, &zbuf_out_size); + } else { + decodeZBUF_partial(zbuf_chunk, zbuf, remain); + } + + return true; +} + +bool VQADecoder::get_zbuf(uint16 *a_zbuf) +{ + if (!zbuf) + return false; + + memcpy(a_zbuf, zbuf, 2 * header.width * header.height); + return true; +} + +bool VQADecoder::read_view(uint32 size) +{ + if (size != 56) + return false; + + r->skip(size); + // has_view = true; + + return true; +} + +bool VQADecoder::read_aesc(uint32 size) +{ + r->skip(roundup(size)); + return true; +} + +bool VQADecoder::read_lite(uint32 size) +{ + r->skip(roundup(size)); + return true; +} + +bool VQADecoder::read_vptr(uint32 size) +{ + if (size > header.maxVPTRSize) + return false; + + if (!vptr) + vptr = new uint8[roundup(header.maxVPTRSize)]; + + vptrSize = size; + r->read(vptr, roundup(size)); + + return true; +} + +void VQADecoder::vptr_write_block(uint16 *frame, unsigned int dst_block, unsigned int src_block, int count, bool alpha) const +{ + uint16 frame_width = header.width; + uint32 frame_stride = 640; + uint16 block_width = header.blockW; + uint16 block_height = header.blockH; + + const uint8 *const block_src = + &codebook[2 * src_block * block_width * block_height]; + + int blocks_per_line = frame_width / block_width; + + do + { + uint32 frame_x = dst_block % blocks_per_line * block_width + header.offset_x / 2; + uint32 frame_y = dst_block / blocks_per_line * block_height + header.offset_y; + + uint32 dst_offset = frame_x + frame_y * frame_stride; + + const uint8 *__restrict src = block_src; + uint16 *__restrict dst = frame + dst_offset; + + unsigned int block_y; + for (block_y = 0; block_y != block_height; ++block_y) + { + unsigned int block_x; + for (block_x = 0; block_x != block_width; ++block_x) + { + uint16 rgb555 = src[0] | (src[1] << 8); + src += 2; + + if (!(alpha && (rgb555 & 0x8000))) + *dst = rgb555; + ++dst; + } + dst += frame_stride - block_width; + } + + ++dst_block; + } + while (--count); +} + +void VQADecoder::set_loop_special(int loop, bool wait) +{ + loop_special = loop; + if (!wait) + cur_loop = -1; +} + +void VQADecoder::set_loop_default(int loop) +{ + loop_default = loop; +} + +bool VQADecoder::seek_to_frame(int frame) +{ + if (frame < 0 || frame >= header.numFrames) + return false; + + r->seek(2 * (frame_info[frame] & 0x0fffffff), SEEK_SET); + return true; +} + +bool VQADecoder::decode_frame(uint16 *a_frame) +{ + if (!codebook || !vptr) + return false; + + if (!frame) + frame = new uint16[header.width * header.height]; + + uint8 *src = vptr; + uint8 *end = vptr + vptrSize; + + uint16 count, src_block, dst_block = 0; + (void)src_block; + + while (end - src >= 2) + { + uint16 command = src[0] | (src[1] << 8); + uint8 prefix = command >> 13; + src += 2; + + switch (prefix) + { + case 0: + count = command & 0x1fff; + dst_block += count; + break; + case 1: + count = 2 * (((command >> 8) & 0x1f) + 1); + src_block = command & 0x00ff; + + vptr_write_block(frame, dst_block, src_block, count); + dst_block += count; + break; + case 2: + count = 2 * (((command >> 8) & 0x1f) + 1); + src_block = command & 0x00ff; + + vptr_write_block(frame, dst_block, src_block, 1); + ++dst_block; + + for (int i = 0; i < count; ++i) + { + src_block = *src++; + vptr_write_block(frame, dst_block, src_block, 1); + ++dst_block; + } + break; + case 3: + case 4: + count = 1; + src_block = command & 0x1fff; + + vptr_write_block(frame, dst_block, src_block, count, prefix == 4); + ++dst_block; + break; + case 5: + case 6: + count = *src++; + src_block = command & 0x1fff; + + vptr_write_block(frame, dst_block, src_block, count, prefix == 6); + dst_block += count; + break; + default: + debug("Undefined case %d\n", command >> 13); + } + } + + memcpy(a_frame, frame, 2 * 640 * 480); + + return true; +} + +int16 *VQADecoder::get_audio_frame() +{ + return audio_frame; +} + +}; // End of namespace BladeRunner + +#endif |