aboutsummaryrefslogtreecommitdiff
path: root/engines/kyra/resource/resource_intern.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/kyra/resource/resource_intern.cpp')
-rw-r--r--engines/kyra/resource/resource_intern.cpp1351
1 files changed, 1351 insertions, 0 deletions
diff --git a/engines/kyra/resource/resource_intern.cpp b/engines/kyra/resource/resource_intern.cpp
new file mode 100644
index 0000000000..c32de44bc2
--- /dev/null
+++ b/engines/kyra/resource/resource_intern.cpp
@@ -0,0 +1,1351 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "kyra/resource/resource_intern.h"
+#include "kyra/resource/resource.h"
+
+#include "common/endian.h"
+#include "common/memstream.h"
+#include "common/substream.h"
+
+namespace Kyra {
+
+// Implementation of various Archive subclasses
+
+// -> PlainArchive implementation
+
+PlainArchive::PlainArchive(Common::ArchiveMemberPtr file)
+ : _file(file), _files() {
+}
+
+bool PlainArchive::hasFile(const Common::String &name) const {
+ return (_files.find(name) != _files.end());
+}
+
+int PlainArchive::listMembers(Common::ArchiveMemberList &list) const {
+ int count = 0;
+
+ for (FileMap::const_iterator i = _files.begin(); i != _files.end(); ++i) {
+ list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(i->_key, this)));
+ ++count;
+ }
+
+ return count;
+}
+
+const Common::ArchiveMemberPtr PlainArchive::getMember(const Common::String &name) const {
+ if (!hasFile(name))
+ return Common::ArchiveMemberPtr();
+
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *PlainArchive::createReadStreamForMember(const Common::String &name) const {
+ FileMap::const_iterator fDesc = _files.find(name);
+ if (fDesc == _files.end())
+ return 0;
+
+ Common::SeekableReadStream *parent = _file->createReadStream();
+ if (!parent)
+ return 0;
+
+ return new Common::SeekableSubReadStream(parent, fDesc->_value.offset, fDesc->_value.offset + fDesc->_value.size, DisposeAfterUse::YES);
+}
+
+void PlainArchive::addFileEntry(const Common::String &name, const Entry entry) {
+ _files[name] = entry;
+}
+
+PlainArchive::Entry PlainArchive::getFileEntry(const Common::String &name) const {
+ FileMap::const_iterator fDesc = _files.find(name);
+ if (fDesc == _files.end())
+ return Entry();
+ return fDesc->_value;
+}
+
+// -> TlkArchive implementation
+
+TlkArchive::TlkArchive(Common::ArchiveMemberPtr file, uint16 entryCount, const uint32 *fileEntries)
+ : _file(file), _entryCount(entryCount), _fileEntries(fileEntries) {
+}
+
+TlkArchive::~TlkArchive() {
+ delete[] _fileEntries;
+}
+
+bool TlkArchive::hasFile(const Common::String &name) const {
+ return (findFile(name) != 0);
+}
+
+int TlkArchive::listMembers(Common::ArchiveMemberList &list) const {
+ uint count = 0;
+
+ for (; count < _entryCount; ++count) {
+ const Common::String name = Common::String::format("%08u.AUD", _fileEntries[count * 2 + 0]);
+ list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(name, this)));
+ }
+
+ return count;
+}
+
+const Common::ArchiveMemberPtr TlkArchive::getMember(const Common::String &name) const {
+ if (!hasFile(name))
+ return Common::ArchiveMemberPtr();
+
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *TlkArchive::createReadStreamForMember(const Common::String &name) const {
+ const uint32 *fileDesc = findFile(name);
+ if (!fileDesc)
+ return 0;
+
+ Common::SeekableReadStream *parent = _file->createReadStream();
+ if (!parent)
+ return 0;
+
+ parent->seek(fileDesc[1], SEEK_SET);
+ const uint32 size = parent->readUint32LE();
+ const uint32 fileStart = fileDesc[1] + 4;
+
+ return new Common::SeekableSubReadStream(parent, fileStart, fileStart + size, DisposeAfterUse::YES);
+}
+
+const uint32 *TlkArchive::findFile(const Common::String &name) const {
+ Common::String uppercaseName = name;
+ uppercaseName.toUppercase();
+
+ if (!uppercaseName.hasSuffix(".AUD"))
+ return 0;
+
+ uint32 id;
+ if (sscanf(uppercaseName.c_str(), "%08u.AUD", &id) != 1)
+ return 0;
+
+ // Binary search for the file entry
+ int leftIndex = 0;
+ int rightIndex = _entryCount - 1;
+
+ while (leftIndex <= rightIndex) {
+ int mid = (leftIndex + rightIndex) / 2;
+
+ const uint32 key = _fileEntries[mid * 2];
+ if (key == id) {
+ // Found
+ return &_fileEntries[mid * 2];
+ } else if (key > id) {
+ // Take the left sub-tree
+ rightIndex = mid - 1;
+ } else {
+ // Take the right sub-tree
+ leftIndex = mid + 1;
+ }
+ }
+
+ return 0;
+}
+
+// -> CachedArchive implementation
+
+CachedArchive::CachedArchive(const FileInputList &files)
+ : _files() {
+ for (FileInputList::const_iterator i = files.begin(); i != files.end(); ++i) {
+ Entry entry;
+
+ entry.data = i->data;
+ entry.size = i->size;
+
+ Common::String name = i->name;
+ name.toLowercase();
+ _files[name] = entry;
+ }
+}
+
+CachedArchive::~CachedArchive() {
+ for (FileMap::iterator i = _files.begin(); i != _files.end(); ++i)
+ delete[] i->_value.data;
+ _files.clear();
+}
+
+bool CachedArchive::hasFile(const Common::String &name) const {
+ return (_files.find(name) != _files.end());
+}
+
+int CachedArchive::listMembers(Common::ArchiveMemberList &list) const {
+ int count = 0;
+
+ for (FileMap::const_iterator i = _files.begin(); i != _files.end(); ++i) {
+ list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(i->_key, this)));
+ ++count;
+ }
+
+ return count;
+}
+
+const Common::ArchiveMemberPtr CachedArchive::getMember(const Common::String &name) const {
+ if (!hasFile(name))
+ return Common::ArchiveMemberPtr();
+
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *CachedArchive::createReadStreamForMember(const Common::String &name) const {
+ FileMap::const_iterator fDesc = _files.find(name);
+ if (fDesc == _files.end())
+ return 0;
+
+ return new Common::MemoryReadStream(fDesc->_value.data, fDesc->_value.size, DisposeAfterUse::NO);
+}
+
+// ResFileLoader implementations
+
+// -> ResLoaderPak implementation
+
+bool ResLoaderPak::checkFilename(Common::String filename) const {
+ filename.toUppercase();
+ return (filename.hasSuffix(".PAK") || filename.hasSuffix(".APK") || filename.hasSuffix(".VRM") || filename.hasSuffix(".CMP") || filename.hasSuffix(".TLK") || filename.equalsIgnoreCase(StaticResource::staticDataFilename()));
+}
+
+namespace {
+
+Common::String readString(Common::SeekableReadStream &stream) {
+ Common::String result;
+ char c = 0;
+
+ while ((c = stream.readByte()) != 0)
+ result += c;
+
+ return result;
+}
+
+} // end of anonymous namespace
+
+bool ResLoaderPak::isLoadable(const Common::String &filename, Common::SeekableReadStream &stream) const {
+ int32 filesize = stream.size();
+ if (filesize < 0)
+ return false;
+
+ int32 offset = 0;
+ bool switchEndian = false;
+ bool firstFile = true;
+
+ offset = stream.readUint32LE();
+ if (offset > filesize || offset < 0) {
+ switchEndian = true;
+ offset = SWAP_BYTES_32(offset);
+ }
+
+ int32 firstOffset = offset;
+
+ Common::String file;
+ while (!stream.eos()) {
+ // The start offset of a file should never be in the filelist
+ if (offset < stream.pos() || offset > filesize || offset < 0)
+ return false;
+
+ file = readString(stream);
+
+ if (stream.eos())
+ return false;
+
+ // Quit now if we encounter an empty string
+ if (file.empty()) {
+ if (firstFile)
+ return false;
+ else
+ break;
+ }
+
+ firstFile = false;
+ offset = switchEndian ? stream.readUint32BE() : stream.readUint32LE();
+
+ if (!offset || offset == filesize || firstOffset == stream.pos())
+ break;
+ }
+
+ return true;
+}
+
+Common::Archive *ResLoaderPak::load(Common::ArchiveMemberPtr memberFile, Common::SeekableReadStream &stream) const {
+ int32 filesize = stream.size();
+ if (filesize < 0)
+ return 0;
+
+ Common::ScopedPtr<PlainArchive> result(new PlainArchive(memberFile));
+ if (!result)
+ return 0;
+
+ int32 startoffset = 0, endoffset = 0;
+ bool switchEndian = false;
+ bool firstFile = true;
+
+ startoffset = stream.readUint32LE();
+ int32 firstOffset = startoffset;
+ if (startoffset > filesize || startoffset < 0) {
+ switchEndian = true;
+ startoffset = SWAP_BYTES_32(startoffset);
+ }
+
+ Common::String file;
+ while (!stream.eos()) {
+ // The start offset of a file should never be in the filelist
+ if (startoffset < stream.pos() || startoffset > filesize || startoffset < 0) {
+ warning("PAK file '%s' is corrupted", memberFile->getDisplayName().c_str());
+ return 0;
+ }
+
+ file = readString(stream);
+
+ if (stream.eos()) {
+ warning("PAK file '%s' is corrupted", memberFile->getDisplayName().c_str());
+ return 0;
+ }
+
+ // Quit now if we encounter an empty string
+ if (file.empty()) {
+ if (firstFile) {
+ warning("PAK file '%s' is corrupted", memberFile->getDisplayName().c_str());
+ return 0;
+ } else {
+ break;
+ }
+ }
+
+ firstFile = false;
+ endoffset = switchEndian ? stream.readUint32BE() : stream.readUint32LE();
+
+ if (endoffset < 0 && stream.pos() != firstOffset) {
+ warning("PAK file '%s' is corrupted", memberFile->getDisplayName().c_str());
+ return 0;
+ }
+
+ if (!endoffset || stream.pos() == firstOffset)
+ endoffset = filesize;
+
+ if (startoffset != endoffset)
+ result->addFileEntry(file, PlainArchive::Entry(startoffset, endoffset - startoffset));
+
+ if (endoffset == filesize)
+ break;
+
+ startoffset = endoffset;
+ }
+
+ PlainArchive::Entry linklistFile = result->getFileEntry("LINKLIST");
+ if (linklistFile.size != 0) {
+ stream.seek(linklistFile.offset, SEEK_SET);
+
+ const uint32 magic = stream.readUint32BE();
+
+ if (magic != MKTAG('S', 'C', 'V', 'M'))
+ error("LINKLIST file does not contain 'SCVM' header");
+
+ const uint32 links = stream.readUint32BE();
+ for (uint32 i = 0; i < links; ++i) {
+ const Common::String linksTo = readString(stream);
+ const uint32 sources = stream.readUint32BE();
+
+ PlainArchive::Entry destination = result->getFileEntry(linksTo);
+ if (destination.size == 0)
+ error("PAK file link destination '%s' not found", linksTo.c_str());
+
+ for (uint32 j = 0; j < sources; ++j) {
+ const Common::String dest = readString(stream);
+ result->addFileEntry(dest, destination);
+ }
+ }
+ }
+
+ return result.release();
+}
+
+// -> ResLoaderInsMalcolm implementation
+
+bool ResLoaderInsMalcolm::checkFilename(Common::String filename) const {
+ filename.toUppercase();
+ if (!filename.hasSuffix(".001"))
+ return false;
+ return true;
+}
+
+bool ResLoaderInsMalcolm::isLoadable(const Common::String &filename, Common::SeekableReadStream &stream) const {
+ stream.seek(3, SEEK_SET);
+ int32 size = stream.readUint32LE();
+
+ if (size + 7 > stream.size())
+ return false;
+
+ stream.seek(size + 5, SEEK_SET);
+ uint8 buffer[2];
+ stream.read(&buffer, 2);
+
+ return (buffer[0] == 0x0D && buffer[1] == 0x0A);
+}
+
+Common::Archive *ResLoaderInsMalcolm::load(Common::ArchiveMemberPtr memberFile, Common::SeekableReadStream &stream) const {
+ Common::List<Common::String> filenames;
+ Common::ScopedPtr<PlainArchive> result(new PlainArchive(memberFile));
+ if (!result)
+ return 0;
+
+ // thanks to eriktorbjorn for this code (a bit modified though)
+ stream.seek(3, SEEK_SET);
+
+ // first file is the index table
+ uint32 size = stream.readUint32LE();
+ Common::String temp;
+
+ for (uint32 i = 0; i < size; ++i) {
+ byte c = stream.readByte();
+
+ if (c == '\\') {
+ temp.clear();
+ } else if (c == 0x0D) {
+ // line endings are CRLF
+ c = stream.readByte();
+ assert(c == 0x0A);
+ ++i;
+
+ filenames.push_back(temp);
+ } else {
+ temp += (char)c;
+ }
+ }
+
+ stream.seek(3, SEEK_SET);
+
+ for (Common::List<Common::String>::iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ const uint32 fileSize = stream.readUint32LE();
+ const uint32 fileOffset = stream.pos();
+
+ result->addFileEntry(*file, PlainArchive::Entry(fileOffset, fileSize));
+ stream.seek(fileSize, SEEK_CUR);
+ }
+
+ return result.release();
+}
+
+bool ResLoaderTlk::checkFilename(Common::String filename) const {
+ filename.toUppercase();
+ return (filename.hasSuffix(".TLK"));
+}
+
+bool ResLoaderTlk::isLoadable(const Common::String &filename, Common::SeekableReadStream &stream) const {
+ uint16 entries = stream.readUint16LE();
+ int32 entryTableSize = (entries * 8);
+
+ if (entryTableSize + 2 > stream.size())
+ return false;
+
+ int32 offset = 0;
+
+ for (uint i = 0; i < entries; ++i) {
+ stream.readUint32LE();
+ offset = stream.readUint32LE();
+
+ if (offset > stream.size())
+ return false;
+ }
+
+ return true;
+}
+
+Common::Archive *ResLoaderTlk::load(Common::ArchiveMemberPtr file, Common::SeekableReadStream &stream) const {
+ const uint16 entryCount = stream.readUint16LE();
+
+ uint32 *fileEntries = new uint32[entryCount * 2];
+ assert(fileEntries);
+ stream.read(fileEntries, sizeof(uint32) * entryCount * 2);
+
+ for (uint i = 0; i < entryCount; ++i) {
+ fileEntries[i * 2 + 0] = READ_LE_UINT32(&fileEntries[i * 2 + 0]);
+ fileEntries[i * 2 + 1] = READ_LE_UINT32(&fileEntries[i * 2 + 1]);
+ }
+
+
+ return new TlkArchive(file, entryCount, fileEntries);
+}
+
+// InstallerLoader implementation
+
+class FileExpanderSource {
+public:
+ FileExpanderSource(const uint8 *data, int dataSize) : _dataPtr(data), _endofBuffer(data + dataSize), _bitsLeft(8), _key(0), _index(0) {}
+ ~FileExpanderSource() {}
+
+ void advSrcRefresh();
+ void advSrcBitsBy1();
+ void advSrcBitsByIndex(uint8 newIndex);
+
+ uint8 getKeyLower() const { return _key & 0xFF; }
+ void setIndex(uint8 index) { _index = index; }
+ uint16 getKeyMasked(uint8 newIndex);
+ uint16 keyMaskedAlign(uint16 val);
+
+ void copyBytes(uint8 *& dst);
+
+private:
+ const uint8 *_dataPtr;
+ const uint8 *_endofBuffer;
+ uint16 _key;
+ int8 _bitsLeft;
+ uint8 _index;
+};
+
+void FileExpanderSource::advSrcBitsBy1() {
+ _key >>= 1;
+ if (!--_bitsLeft) {
+ if (_dataPtr < _endofBuffer)
+ _key = ((*_dataPtr++) << 8) | (_key & 0xFF);
+ _bitsLeft = 8;
+ }
+}
+
+void FileExpanderSource::advSrcBitsByIndex(uint8 newIndex) {
+ _index = newIndex;
+ _bitsLeft -= _index;
+ if (_bitsLeft <= 0) {
+ _key >>= (_index + _bitsLeft);
+ _index = -_bitsLeft;
+ _bitsLeft = 8 - _index;
+ if (_dataPtr < _endofBuffer)
+ _key = (*_dataPtr++ << 8) | (_key & 0xFF);
+ }
+ _key >>= _index;
+}
+
+uint16 FileExpanderSource::getKeyMasked(uint8 newIndex) {
+ static const uint8 mskTable[] = { 0x0F, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
+ _index = newIndex;
+ uint16 res = 0;
+
+ if (_index > 8) {
+ newIndex = _index - 8;
+ res = (_key & 0xFF) & mskTable[8];
+ advSrcBitsByIndex(8);
+ _index = newIndex;
+ res |= (((_key & 0xFF) & mskTable[_index]) << 8);
+ advSrcBitsByIndex(_index);
+ } else {
+ res = (_key & 0xFF) & mskTable[_index];
+ advSrcBitsByIndex(_index);
+ }
+
+ return res;
+}
+
+void FileExpanderSource::copyBytes(uint8 *& dst) {
+ advSrcBitsByIndex(_bitsLeft);
+ uint16 r = (READ_LE_UINT16(_dataPtr) ^ _key) + 1;
+ _dataPtr += 2;
+
+ if (r)
+ error("decompression failure");
+
+ memcpy(dst, _dataPtr, _key);
+ _dataPtr += _key;
+ dst += _key;
+}
+
+uint16 FileExpanderSource::keyMaskedAlign(uint16 val) {
+ val -= 0x101;
+ _index = (val & 0xFF) >> 2;
+ int16 b = ((_bitsLeft << 8) | _index) - 1;
+ _bitsLeft = b >> 8;
+ _index = b & 0xFF;
+ uint16 res = (((val & 3) + 4) << _index) + 0x101;
+ return res + getKeyMasked(_index);
+}
+
+void FileExpanderSource::advSrcRefresh() {
+ _key = READ_LE_UINT16(_dataPtr);
+ if (_dataPtr < _endofBuffer - 1)
+ _dataPtr += 2;
+ _bitsLeft = 8;
+}
+
+class FileExpander {
+public:
+ FileExpander();
+ ~FileExpander();
+
+ bool process(uint8 *dst, const uint8 *src, uint32 outsize, uint32 insize);
+
+private:
+ void generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex2, int cnt);
+ uint8 calcCmdAndIndex(const uint8 *tbl, int16 &para);
+
+ FileExpanderSource *_src;
+ uint8 *_tables[9];
+ uint16 *_tables16[3];
+};
+
+FileExpander::FileExpander() : _src(0) {
+ _tables[0] = new uint8[3914];
+ assert(_tables[0]);
+
+ _tables[1] = _tables[0] + 320;
+ _tables[2] = _tables[0] + 352;
+ _tables[3] = _tables[0] + 864;
+ _tables[4] = _tables[0] + 2016;
+ _tables[5] = _tables[0] + 2528;
+ _tables[6] = _tables[0] + 2656;
+ _tables[7] = _tables[0] + 2736;
+ _tables[8] = _tables[0] + 2756;
+
+ _tables16[0] = (uint16 *)(_tables[0] + 3268);
+ _tables16[1] = (uint16 *)(_tables[0] + 3302);
+ _tables16[2] = (uint16 *)(_tables[0] + 3338);
+}
+
+FileExpander::~FileExpander() {
+ delete _src;
+ delete[] _tables[0];
+}
+
+bool FileExpander::process(uint8 *dst, const uint8 *src, uint32 outsize, uint32 compressedSize) {
+ static const uint8 indexTable[] = {
+ 0x10, 0x11, 0x12, 0x00, 0x08, 0x07, 0x09, 0x06, 0x0A,
+ 0x05, 0x0B, 0x04, 0x0C, 0x03, 0x0D, 0x02, 0x0E, 0x01, 0x0F
+ };
+
+ memset(_tables[0], 0, 3914);
+
+ uint8 *d = dst;
+ uint16 tableSize0 = 0;
+ uint16 tableSize1 = 0;
+ bool needrefresh = true;
+ bool postprocess = false;
+
+ _src = new FileExpanderSource(src, compressedSize);
+
+ while (d < dst + outsize) {
+
+ if (needrefresh) {
+ needrefresh = false;
+ _src->advSrcRefresh();
+ }
+
+ _src->advSrcBitsBy1();
+
+ int mode = _src->getKeyMasked(2) - 1;
+ if (mode == 1) {
+ tableSize0 = _src->getKeyMasked(5) + 257;
+ tableSize1 = _src->getKeyMasked(5) + 1;
+ memset(_tables[7], 0, 19);
+
+ const uint8 *itbl = indexTable;
+ int numbytes = _src->getKeyMasked(4) + 4;
+
+ while (numbytes--)
+ _tables[7][*itbl++] = _src->getKeyMasked(3);
+
+ generateTables(7, 8, 255, 19);
+
+ int cnt = tableSize0 + tableSize1;
+ uint8 *tmp = _tables[0];
+
+ while (cnt) {
+ uint16 cmd = _src->getKeyLower();
+ cmd = READ_LE_UINT16(&_tables[8][cmd << 1]);
+ _src->advSrcBitsByIndex(_tables[7][cmd]);
+
+ if (cmd < 16) {
+ *tmp++ = cmd;
+ cnt--;
+ } else {
+ uint8 tmpI = 0;
+ if (cmd == 16) {
+ cmd = _src->getKeyMasked(2) + 3;
+ tmpI = *(tmp - 1);
+ } else if (cmd == 17) {
+ cmd = _src->getKeyMasked(3) + 3;
+ } else {
+ cmd = _src->getKeyMasked(7) + 11;
+ }
+ _src->setIndex(tmpI);
+ memset(tmp, tmpI, cmd);
+ tmp += cmd;
+
+ cnt -= cmd;
+ if (cnt < 0)
+ error("decompression failure");
+ }
+ }
+
+ memcpy(_tables[1], _tables[0] + tableSize0, tableSize1);
+ generateTables(0, 2, 3, tableSize0);
+ generateTables(1, 4, 5, tableSize1);
+ postprocess = true;
+ } else if (mode < 0) {
+ _src->copyBytes(d);
+ postprocess = false;
+ needrefresh = true;
+ } else if (mode == 0) {
+ uint8 *d2 = _tables[0];
+ memset(d2, 8, 144);
+ memset(d2 + 144, 9, 112);
+ memset(d2 + 256, 7, 24);
+ memset(d2 + 280, 8, 8);
+ d2 = _tables[1];
+ memset(d2, 5, 32);
+ tableSize0 = 288;
+ tableSize1 = 32;
+
+ generateTables(0, 2, 3, tableSize0);
+ generateTables(1, 4, 5, tableSize1);
+ postprocess = true;
+ } else {
+ error("decompression failure");
+ }
+
+ if (!postprocess)
+ continue;
+
+ int16 cmd = 0;
+
+ do {
+ cmd = ((int16 *)_tables[2])[_src->getKeyLower()];
+ _src->advSrcBitsByIndex(cmd < 0 ? calcCmdAndIndex(_tables[3], cmd) : _tables[0][cmd]);
+
+ if (cmd == 0x11D) {
+ cmd = 0x200;
+ } else if (cmd > 0x108) {
+ cmd = _src->keyMaskedAlign(cmd);
+ }
+
+ if (!(cmd >> 8)) {
+ *d++ = cmd & 0xFF;
+ } else if (cmd != 0x100) {
+ cmd -= 0xFE;
+ int16 offset = ((int16 *)_tables[4])[_src->getKeyLower()];
+ _src->advSrcBitsByIndex(offset < 0 ? calcCmdAndIndex(_tables[5], offset) : _tables[1][offset]);
+ if ((offset & 0xFF) >= 4) {
+ uint8 newIndex = ((offset & 0xFF) >> 1) - 1;
+ offset = (((offset & 1) + 2) << newIndex);
+ offset += _src->getKeyMasked(newIndex);
+ }
+
+ uint8 *s2 = d - 1 - offset;
+ if (s2 >= dst) {
+ while (cmd--)
+ *d++ = *s2++;
+ } else {
+ uint32 pos = dst - s2;
+ s2 += (d - dst);
+
+ if (pos < (uint32) cmd) {
+ cmd -= pos;
+ while (pos--)
+ *d++ = *s2++;
+ s2 = dst;
+ }
+ while (cmd--)
+ *d++ = *s2++;
+ }
+ }
+ } while (cmd != 0x100);
+ }
+
+ delete _src;
+ _src = 0;
+
+ return true;
+}
+
+void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex2, int cnt) {
+ uint8 *tbl1 = _tables[srcIndex];
+ uint8 *tbl2 = _tables[dstIndex];
+ uint8 *tbl3 = dstIndex2 == 0xFF ? 0 : _tables[dstIndex2];
+
+ if (!cnt)
+ return;
+
+ const uint8 *s = tbl1;
+ memset(_tables16[0], 0, 32);
+
+ for (int i = 0; i < cnt; i++)
+ _tables16[0][(*s++)]++;
+
+ _tables16[1][1] = 0;
+
+ for (uint16 i = 1, r = 0; i < 16; i++) {
+ r = (r + _tables16[0][i]) << 1;
+ _tables16[1][i + 1] = r;
+ }
+
+ if (_tables16[1][16]) {
+ uint16 r = 0;
+ for (uint16 i = 1; i < 16; i++)
+ r += _tables16[0][i];
+ if (r > 1)
+ error("decompression failure");
+ }
+
+ s = tbl1;
+ uint16 *d = _tables16[2];
+ for (int i = 0; i < cnt; i++) {
+ uint16 t = *s++;
+ if (t) {
+ _tables16[1][t]++;
+ t = _tables16[1][t] - 1;
+ }
+ *d++ = t;
+ }
+
+ s = tbl1;
+ d = _tables16[2];
+ for (int i = 0; i < cnt; i++) {
+ int8 t = ((int8)(*s++)) - 1;
+ if (t > 0) {
+ uint16 v1 = *d;
+ uint16 v2 = 0;
+
+ do {
+ v2 = (v2 << 1) | (v1 & 1);
+ v1 >>= 1;
+ } while (--t && v1);
+
+ t++;
+ uint8 c1 = (v1 & 1);
+ while (t--) {
+ uint8 c2 = v2 >> 15;
+ v2 = (v2 << 1) | c1;
+ c1 = c2;
+ };
+
+ *d++ = v2;
+ } else {
+ d++;
+ }
+ }
+
+ memset(tbl2, 0, 512);
+
+ cnt--;
+ s = tbl1 + cnt;
+ d = &_tables16[2][cnt];
+ uint16 *bt = (uint16 *)tbl3;
+ uint16 inc = 0;
+ uint16 cnt2 = 0;
+
+ do {
+ uint8 t = *s--;
+ uint16 *s2 = (uint16 *)tbl2;
+
+ if (t && t < 9) {
+ inc = 1 << t;
+ uint16 o = *d;
+
+ do {
+ s2[o] = cnt;
+ o += inc;
+ } while (!(o & 0xF00));
+
+ } else if (t > 8) {
+ if (!bt)
+ error("decompression failure");
+
+ t -= 8;
+ uint8 shiftCnt = 1;
+ uint8 v = (*d) >> 8;
+ s2 = &((uint16 *)tbl2)[*d & 0xFF];
+
+ do {
+ if (!*s2) {
+ *s2 = (uint16)(~cnt2);
+ *(uint32 *)&bt[cnt2] = 0;
+ cnt2 += 2;
+ }
+
+ s2 = &bt[(uint16)(~*s2)];
+ if (v & shiftCnt)
+ s2++;
+
+ shiftCnt <<= 1;
+ } while (--t);
+ *s2 = cnt;
+ }
+ d--;
+ } while (--cnt >= 0);
+}
+
+uint8 FileExpander::calcCmdAndIndex(const uint8 *tbl, int16 &para) {
+ const uint16 *t = (const uint16 *)tbl;
+ _src->advSrcBitsByIndex(8);
+ uint8 newIndex = 0;
+ uint16 v = _src->getKeyLower();
+
+ do {
+ newIndex++;
+ para = t[((~para) & 0xFFFE) | (v & 1)];
+ v >>= 1;
+ } while (para < 0);
+
+ return newIndex;
+}
+
+namespace {
+
+struct InsArchive {
+ Common::String filename;
+ uint32 firstFile;
+ uint32 startOffset;
+ uint32 lastFile;
+ uint32 endOffset;
+ uint32 totalSize;
+};
+
+} // end of anonymouse namespace
+
+class CmpVocDecoder {
+public:
+ CmpVocDecoder();
+ ~CmpVocDecoder();
+ uint8 *process(uint8 *src, uint32 insize, uint32 *outsize, bool disposeInput = true);
+
+private:
+ void decodeHelper(int p);
+
+ int32 *_vtbl;
+ int32 *_tbl1, *_p1, *_tbl2, *_p2, *_tbl3, *_p3, *_tbl4, *_p4, *_floatArray, *_stTbl;
+ uint8 *_sndArray;
+};
+
+Common::Archive *InstallerLoader::load(Resource *owner, const Common::String &filename, const Common::String &extension, const uint8 containerOffset) {
+ uint32 pos = 0;
+ uint32 bytesleft = 0;
+ bool startFile = true;
+
+ Common::String filenameBase = filename;
+ Common::String filenameTemp;
+ char filenameExt[4];
+
+ if (filenameBase.lastChar() != '.')
+ filenameBase += '.';
+
+ InsArchive newArchive;
+ Common::List<InsArchive> archives;
+
+ Common::SeekableReadStream *tmpFile = 0;
+
+ for (int8 currentFile = 1; currentFile; currentFile++) {
+ sprintf(filenameExt, extension.c_str(), currentFile);
+ filenameTemp = filenameBase + Common::String(filenameExt);
+
+ if (!(tmpFile = owner->createReadStream(filenameTemp))) {
+ debug(3, "couldn't open file '%s'\n", filenameTemp.c_str());
+ break;
+ }
+
+ tmpFile->seek(pos, SEEK_SET);
+ uint8 fileId = tmpFile->readByte();
+ pos++;
+
+ uint32 size = tmpFile->size() - 1;
+ if (startFile) {
+ size -= 4;
+ if (fileId == currentFile) {
+ size -= containerOffset;
+ pos += containerOffset;
+ tmpFile->seek(containerOffset, SEEK_CUR);
+ } else {
+ size = size + 1 - pos;
+ }
+ newArchive.filename = filenameBase;
+ bytesleft = newArchive.totalSize = tmpFile->readUint32LE();
+ pos += 4;
+ newArchive.firstFile = currentFile;
+ newArchive.startOffset = pos;
+ startFile = false;
+ }
+
+ uint32 cs = MIN(size, bytesleft);
+ bytesleft -= cs;
+
+ delete tmpFile;
+ tmpFile = 0;
+
+ pos += cs;
+ if (cs == size) {
+ if (!bytesleft) {
+ newArchive.lastFile = currentFile;
+ newArchive.endOffset = --pos;
+ archives.push_back(newArchive);
+ currentFile = -1;
+ } else {
+ pos = 0;
+ }
+ } else {
+ startFile = true;
+ bytesleft = size - cs;
+ newArchive.lastFile = currentFile--;
+ newArchive.endOffset = --pos;
+ archives.push_back(newArchive);
+ }
+ }
+
+ FileExpander exp;
+ CmpVocDecoder cvdec;
+ CachedArchive::InputEntry newEntry;
+ uint32 insize = 0;
+ uint32 outsize = 0;
+ uint8 *inbuffer = 0;
+ uint8 *outbuffer = 0;
+ uint32 inPart1 = 0;
+ uint32 inPart2 = 0;
+ uint8 compressionType = 0;
+ Common::String entryStr;
+
+ CachedArchive::FileInputList fileList;
+
+ pos = 0;
+
+ const uint32 kExecSize = 0x0BBA;
+ const uint32 kHeaderSize = 30;
+ const uint32 kHeaderSize2 = 46;
+
+ for (Common::List<InsArchive>::iterator a = archives.begin(); a != archives.end(); ++a) {
+ startFile = true;
+ for (uint32 i = a->firstFile; i != (a->lastFile + 1); i++) {
+ sprintf(filenameExt, extension.c_str(), i);
+ filenameTemp = a->filename + Common::String(filenameExt);
+
+ if (!(tmpFile = owner->createReadStream(filenameTemp))) {
+ debug(3, "couldn't open file '%s'\n", filenameTemp.c_str());
+ break;
+ }
+
+ uint32 size = (i == a->lastFile) ? a->endOffset : tmpFile->size();
+
+ if (startFile) {
+ startFile = false;
+ pos = a->startOffset + kExecSize;
+ if (pos > size) {
+ pos -= size;
+ delete tmpFile;
+ tmpFile = 0;
+ continue;
+ }
+ } else {
+ if (inPart2) {
+ tmpFile->seek(1, SEEK_SET);
+ tmpFile->read(inbuffer + inPart1, inPart2);
+ inPart2 = 0;
+
+ if (compressionType > 0)
+ exp.process(outbuffer, inbuffer, outsize, insize);
+ else
+ memcpy(outbuffer, inbuffer, outsize);
+
+ delete[] inbuffer;
+ inbuffer = 0;
+
+ newEntry.data = outbuffer;
+ newEntry.size = outsize;
+ newEntry.name = entryStr;
+
+ entryStr.toUppercase();
+ if (entryStr.hasSuffix(".CMP")) {
+ entryStr.deleteLastChar();
+ entryStr.deleteLastChar();
+ entryStr.deleteLastChar();
+ entryStr += "PAK";
+ newEntry.data = cvdec.process(outbuffer, outsize, &outsize);
+ newEntry.size = outsize;
+ newEntry.name = entryStr;
+ }
+
+ fileList.push_back(newEntry);
+ }
+ pos++;
+ }
+
+ while (pos < size) {
+ uint8 hdr[43];
+ uint32 m = 0;
+ tmpFile->seek(pos, SEEK_SET);
+
+ if (pos + 42 > size) {
+ m = size - pos;
+ uint32 b = 42 - m;
+
+ if (m >= 4) {
+ uint32 id = tmpFile->readUint32LE();
+ if (id == 0x06054B50) {
+ startFile = true;
+ break;
+ } else {
+ tmpFile->seek(pos, SEEK_SET);
+ }
+ }
+
+ sprintf(filenameExt, extension.c_str(), i + 1);
+ filenameTemp = a->filename + Common::String(filenameExt);
+
+ Common::SeekableReadStream *tmpFile2 = owner->createReadStream(filenameTemp);
+ tmpFile->read(hdr, m);
+ tmpFile2->read(hdr + m, b);
+ delete tmpFile2;
+ } else {
+ tmpFile->read(hdr, 42);
+ }
+
+ uint32 id = READ_LE_UINT32(hdr);
+
+ if (id == 0x04034B50) {
+ compressionType = hdr[8];
+ insize = READ_LE_UINT32(hdr + 18);
+ outsize = READ_LE_UINT32(hdr + 22);
+
+ uint16 filestrlen = READ_LE_UINT16(hdr + 26);
+ *(hdr + 30 + filestrlen) = 0;
+ entryStr = Common::String((const char *)(hdr + 30));
+ pos += (kHeaderSize + filestrlen - m);
+ tmpFile->seek(pos, SEEK_SET);
+
+ outbuffer = new uint8[outsize];
+ if (!outbuffer)
+ error("Out of memory: Can't uncompress installer files");
+
+ if (!inbuffer) {
+ inbuffer = new uint8[insize];
+ if (!inbuffer)
+ error("Out of memory: Can't uncompress installer files");
+ }
+
+ if ((pos + insize) > size) {
+ // this is for files that are split between two archive files
+ inPart1 = size - pos;
+ inPart2 = insize - inPart1;
+ tmpFile->read(inbuffer, inPart1);
+ } else {
+ tmpFile->read(inbuffer, insize);
+ inPart2 = 0;
+
+ if (compressionType > 0)
+ exp.process(outbuffer, inbuffer, outsize, insize);
+ else
+ memcpy(outbuffer, inbuffer, outsize);
+
+ delete[] inbuffer;
+ inbuffer = 0;
+ newEntry.data = outbuffer;
+ newEntry.size = outsize;
+ newEntry.name = entryStr;
+
+ entryStr.toUppercase();
+ if (entryStr.hasSuffix(".CMP")) {
+ entryStr.deleteLastChar();
+ entryStr.deleteLastChar();
+ entryStr.deleteLastChar();
+ entryStr += "PAK";
+ newEntry.data = cvdec.process(outbuffer, outsize, &outsize);
+ newEntry.size = outsize;
+ newEntry.name = entryStr;
+ }
+
+ fileList.push_back(newEntry);
+ }
+
+ pos += insize;
+ if (pos > size) {
+ pos -= size;
+ break;
+ }
+ } else {
+ uint32 filestrlen = READ_LE_UINT32(hdr + 28);
+ pos += (kHeaderSize2 + filestrlen - m);
+ }
+ }
+ delete tmpFile;
+ tmpFile = 0;
+ }
+ }
+
+ archives.clear();
+ return new CachedArchive(fileList);
+}
+
+CmpVocDecoder::CmpVocDecoder() {
+ _tbl1 = new int32[4000];
+ _p1 = _tbl1 + 2000;
+ _tbl2 = new int32[4000];
+ _p2 = _tbl2 + 2000;
+ _tbl3 = new int32[4000];
+ _p3 = _tbl3 + 2000;
+ _tbl4 = new int32[4000];
+ _p4 = _tbl4 + 2000;
+
+ _vtbl = new int32[8193];
+ _floatArray = new int32[8193];
+ _sndArray = new uint8[8192];
+ _stTbl = new int32[256];
+
+ assert(_tbl1);
+ assert(_tbl2);
+ assert(_tbl3);
+ assert(_tbl4);
+ assert(_vtbl);
+ assert(_floatArray);
+ assert(_sndArray);
+ assert(_stTbl);
+
+ for (int32 i = -2000; i < 2000; i++) {
+ int32 x = i + 2000;
+ _tbl1[x] = (int32)(0.4829629131445341 * (double)i * 256.0);
+ _tbl2[x] = (int32)(0.8365163037378079 * (double)i * 256.0);
+ _tbl3[x] = (int32)(0.2241438680420134 * (double)i * 256.0);
+ _tbl4[x] = (int32)(-0.1294095225512604 * (double)i * 256.0);
+ }
+}
+
+CmpVocDecoder::~CmpVocDecoder() {
+ delete[] _stTbl;
+ delete[] _sndArray;
+ delete[] _floatArray;
+ delete[] _vtbl;
+ delete[] _tbl1;
+ delete[] _tbl2;
+ delete[] _tbl3;
+ delete[] _tbl4;
+}
+
+uint8 *CmpVocDecoder::process(uint8 *src, uint32 insize, uint32 *outsize, bool disposeInput) {
+ *outsize = 0;
+ uint8 *outTemp = new uint8[insize];
+
+ uint8 *inPosH = src;
+ uint8 *outPosH = outTemp;
+ uint8 *outPosD = outTemp + READ_LE_UINT32(src);
+ uint8 *end = outPosD;
+
+ while (outPosH < end) {
+ uint8 *spos = inPosH;
+ uint32 offset = READ_LE_UINT32(inPosH);
+ inPosH += 4;
+ char *name = (char *)inPosH;
+ inPosH += strlen(name) + 1;
+
+ if (!name[0]) {
+ *outsize = outPosD - outTemp;
+ WRITE_LE_UINT32(outPosH, *outsize);
+ memset(outPosH + 4, 0, 5);
+ break;
+ }
+
+ uint32 fileSize = READ_LE_UINT32(inPosH) - offset;
+ int headerEntryLen = inPosH - spos;
+
+ if (scumm_stricmp(name + strlen(name) - 4, ".voc")) {
+ memcpy(outPosH, spos, headerEntryLen);
+ WRITE_LE_UINT32(outPosH, outPosD - outTemp);
+ outPosH += headerEntryLen;
+ memcpy(outPosD, src + offset, fileSize);
+ outPosD += fileSize;
+ continue;
+ }
+
+ uint8 *vocPtr = src + offset;
+ uint32 vocLen = (vocPtr[27] | (vocPtr[28] << 8) | (vocPtr[29] << 16)) - 2;
+
+ uint8 *vocOutEnd = outPosD + vocLen + 32;
+ uint8 *vocInEnd = src + offset + fileSize;
+ memcpy(outPosD, vocPtr, 32);
+ uint8 *dst = outPosD + 32;
+ vocPtr += 32;
+ float t = 0.0f;
+
+ while (dst < vocOutEnd) {
+ memcpy(&t, vocPtr, 4);
+ vocPtr += 4;
+ uint32 readSize = MIN<uint32>(8192, vocInEnd - vocPtr);
+ memcpy(_sndArray, vocPtr, readSize);
+ vocPtr += readSize;
+
+ for (int i = -128; i < 128; i++)
+ _stTbl[i + 128] = (int32)((float)i / t + 0.5f);
+
+ int8 *ps = (int8 *)_sndArray;
+ for (int i = 0; i < 8192; i++)
+ _floatArray[i + 1] = _stTbl[128 + *ps++];
+
+ for (int i = 4; i <= 8192; i <<= 1)
+ decodeHelper(i);
+
+ for (int i = 1; i <= 8192; i++) {
+ int32 v = CLIP<int32>(_floatArray[i] + 128, 0, 255);
+ _sndArray[i - 1] = v;
+ }
+
+ uint32 numBytesOut = MIN<uint32>(vocOutEnd - dst, 8192);
+ memcpy(dst, _sndArray, numBytesOut);
+ dst += numBytesOut;
+ }
+
+ *dst++ = 0;
+ memcpy(outPosH, spos, headerEntryLen);
+ WRITE_LE_UINT32(outPosH, outPosD - outTemp);
+ outPosH += headerEntryLen;
+ outPosD += (vocLen + 33);
+ }
+
+ if (disposeInput)
+ delete[] src;
+
+ uint8 *outFinal = new uint8[*outsize];
+ memcpy(outFinal, outTemp, *outsize);
+ delete[] outTemp;
+
+ return outFinal;
+}
+
+void CmpVocDecoder::decodeHelper(int p1) {
+ int p2 = p1 >> 1;
+ int p3 = p2 + 1;
+
+ int16 fi1 = _floatArray[1];
+ int16 fi2 = _floatArray[p2];
+ int16 fi3 = _floatArray[p3];
+ int16 fi4 = _floatArray[p1];
+
+ _vtbl[1] = (*(_p3 + fi2) + *(_p2 + fi4) + *(_p1 + fi1) + *(_p4 + fi3)) >> 8;
+ _vtbl[2] = (*(_p4 + fi2) - *(_p1 + fi4) + *(_p2 + fi1) - *(_p3 + fi3)) >> 8;
+
+ int d = 3;
+ int s = 1;
+
+ while (s < p2) {
+ fi2 = _floatArray[s];
+ fi1 = _floatArray[s + 1];
+ fi4 = _floatArray[s + p2];
+ fi3 = _floatArray[s + p3];
+
+ _vtbl[d++] = (*(_p3 + fi2) + *(_p2 + fi4) + *(_p1 + fi1) + *(_p4 + fi3)) >> 8;
+ _vtbl[d++] = (*(_p4 + fi2) - *(_p1 + fi4) + *(_p2 + fi1) - *(_p3 + fi3)) >> 8;
+ s++;
+ }
+
+ memcpy(&_floatArray[1], &_vtbl[1], p1 * sizeof(int32));
+}
+
+} // End of namespace Kyra