diff options
author | Paul Gilbert | 2017-08-12 20:08:49 -0400 |
---|---|---|
committer | Paul Gilbert | 2017-08-12 20:08:49 -0400 |
commit | 6a5c52019458e13941abb3f6ab3f4cd1607c3989 (patch) | |
tree | dd69ca2f915987ba0ef567811d4dac5d512c0e7f /devtools | |
parent | a03183760a428144987832956355054736173878 (diff) | |
download | scummvm-rg350-6a5c52019458e13941abb3f6ab3f4cd1607c3989.tar.gz scummvm-rg350-6a5c52019458e13941abb3f6ab3f4cd1607c3989.tar.bz2 scummvm-rg350-6a5c52019458e13941abb3f6ab3f4cd1607c3989.zip |
DEVTOOLS: Add compression for titanic.dat bitmaps
Diffstat (limited to 'devtools')
-rw-r--r-- | devtools/create_titanic/create_titanic_dat.cpp | 62 | ||||
-rw-r--r-- | devtools/create_titanic/module.mk | 3 | ||||
-rw-r--r-- | devtools/create_titanic/zlib.cpp | 455 | ||||
-rw-r--r-- | devtools/create_titanic/zlib.h | 136 |
4 files changed, 643 insertions, 13 deletions
diff --git a/devtools/create_titanic/create_titanic_dat.cpp b/devtools/create_titanic/create_titanic_dat.cpp index 6d9b713846..4a24ec98d4 100644 --- a/devtools/create_titanic/create_titanic_dat.cpp +++ b/devtools/create_titanic/create_titanic_dat.cpp @@ -29,11 +29,17 @@ #undef main #endif // main +#ifndef USE_ZLIB +#error "Project should have USE_ZLIB defined" +#endif + #include <stdio.h> #include <stdlib.h> #include <string.h> #include "common/language.h" +#include "common/memstream.h" #include "common/rect.h" +#include "zlib.h" #include "winexe_pe.h" #include "file.h" #include "script_preresponses.h" @@ -54,8 +60,8 @@ * ASCIIZ - name of the resource */ -#define VERSION_NUMBER 1 -#define HEADER_SIZE 0x1200 +#define VERSION_NUMBER 2 +#define HEADER_SIZE 0x1380 Common::File inputFile, outputFile; Common::PEResources resEng, resGer; @@ -1022,14 +1028,24 @@ void NORETURN_PRE error(const char *s, ...) { exit(1); } -void writeEntryHeader(const char *name, uint offset, uint size) { +void writeEntryHeader(const char *name, uint offset, uint size, uint flags) { assert(headerOffset < HEADER_SIZE); outputFile.seek(headerOffset); outputFile.writeLong(offset); outputFile.writeLong(size); + outputFile.writeWord(flags); outputFile.writeString(name); - headerOffset += 8 + strlen(name) + 1; + headerOffset += 10 + strlen(name) + 1; +} + +void writeEntryHeader(const char *name, uint offset, uint size) { + writeEntryHeader(name, offset, size, 0); +} + +void writeEntryHeader(const char *name, uint offset, uint size, bool isCompressed) { + uint flags = isCompressed ? 1 : 0; + writeEntryHeader(name, offset, size, flags); } void writeFinalEntryHeader() { @@ -1039,6 +1055,10 @@ void writeFinalEntryHeader() { outputFile.writeLong(0); } +void writeCompressedRes(Common::File *src) { + +} + void writeStringArray(const char *name, uint offset, int count) { outputFile.seek(dataOffset); @@ -1130,18 +1150,36 @@ void writeResource(const char *sectionStr, const char *resId, bool isEnglish = t void writeBitmap(const char *name, Common::File *file) { outputFile.seek(dataOffset); + // Set up a memory stream for the compressed data, and wrap + // it with a zlib compressor + Common::MemoryWriteStreamDynamic *compressedData = + new Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES); + Common::WriteStream *zlib = Common::wrapCompressedWriteStream(compressedData); + // Write out the necessary bitmap header so that the ScummVM // BMP decoder can properly handle decoding the bitmaps - outputFile.write("BM", 2); - outputFile.writeLong(file->size() + 14); // Filesize - outputFile.writeLong(0); // res1 & res2 - outputFile.writeLong(0x436); // image offset + zlib->write("BM", 2); + zlib->writeUint32LE(file->size() + 14); // Filesize + zlib->writeUint32LE(0); // res1 & res2 + zlib->writeUint32LE(0x436); // image offset - outputFile.write(*file, file->size()); + // Transfer the bitmap data + int srcSize = file->size(); + byte *data = new byte[srcSize]; + file->read(data, srcSize); + zlib->write(data, srcSize); - writeEntryHeader(name, dataOffset, file->size() + 14); - dataOffset += file->size() + 14; - delete file; + delete[] data; + zlib->finalize(); + + // Write out the compressed data + outputFile.write(compressedData->getData(), compressedData->size()); + + writeEntryHeader(name, dataOffset, compressedData->size() + 14, true); + dataOffset += compressedData->size() + 14; + + // Free the zlib write stream + delete zlib; } void writeBitmap(const char *sectionStr, const char *resId, bool isEnglish = true) { diff --git a/devtools/create_titanic/module.mk b/devtools/create_titanic/module.mk index 9f77866d45..a762af3ab2 100644 --- a/devtools/create_titanic/module.mk +++ b/devtools/create_titanic/module.mk @@ -13,7 +13,8 @@ MODULE_OBJS := \ str.o \ tag_maps.o \ winexe.o \ - winexe_pe.o + winexe_pe.o \ + zlib.o # Set the name of the executable TOOL_EXECUTABLE := create_titanic diff --git a/devtools/create_titanic/zlib.cpp b/devtools/create_titanic/zlib.cpp new file mode 100644 index 0000000000..3221d61813 --- /dev/null +++ b/devtools/create_titanic/zlib.cpp @@ -0,0 +1,455 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use zlib.h +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "zlib.h" +#include "common/ptr.h" +#include "common/util.h" +#include "common/stream.h" + +#if defined(USE_ZLIB) + #ifdef __SYMBIAN32__ + #include <zlib\zlib.h> + #else + #include <zlib.h> + #endif + + #if ZLIB_VERNUM < 0x1204 + #error Version 1.2.0.4 or newer of zlib is required for this code + #endif +#endif + + +namespace Common { + +/** + * Stubs for ScummVM stuff I don't want to have to link in + */ +void debug(int level, const char *s, ...) {} + +char *SeekableReadStream::readLine(char *buf, size_t bufSize) { + return nullptr; +} + +String SeekableReadStream::readLine() { + return String(); +} + +#if defined(USE_ZLIB) + +bool uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen) { + return Z_OK == ::uncompress(dst, dstLen, src, srcLen); +} + +bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen, const byte *dict, uint dictLen) { + if (!dst || !dstLen || !src || !srcLen) + return false; + + // Initialize zlib + z_stream stream; + stream.next_in = const_cast<byte *>(src); + stream.avail_in = srcLen; + stream.next_out = dst; + stream.avail_out = dstLen; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + + // Negative MAX_WBITS tells zlib there's no zlib header + int err = inflateInit2(&stream, -MAX_WBITS); + if (err != Z_OK) + return false; + + // Set the dictionary, if provided + if (dict != 0) { + err = inflateSetDictionary(&stream, const_cast<byte *>(dict), dictLen); + if (err != Z_OK) + return false; + } + + err = inflate(&stream, Z_SYNC_FLUSH); + if (err != Z_OK && err != Z_STREAM_END) { + inflateEnd(&stream); + return false; + } + + inflateEnd(&stream); + return true; +} + +enum { + kTempBufSize = 65536 +}; + +bool inflateZlibInstallShield(byte *dst, uint dstLen, const byte *src, uint srcLen) { + if (!dst || !dstLen || !src || !srcLen) + return false; + + // See if we have sync bytes. If so, just use our function for that. + if (srcLen >= 4 && READ_BE_UINT32(src + srcLen - 4) == 0xFFFF) + return inflateZlibHeaderless(dst, dstLen, src, srcLen); + + // Otherwise, we have some custom code we get to use here. + + byte *temp = (byte *)malloc(kTempBufSize); + + uint32 bytesRead = 0, bytesProcessed = 0; + while (bytesRead < srcLen) { + uint16 chunkSize = READ_LE_UINT16(src + bytesRead); + bytesRead += 2; + + // Initialize zlib + z_stream stream; + stream.next_in = const_cast<byte *>(src + bytesRead); + stream.avail_in = chunkSize; + stream.next_out = temp; + stream.avail_out = kTempBufSize; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + + // Negative MAX_WBITS tells zlib there's no zlib header + int err = inflateInit2(&stream, -MAX_WBITS); + if (err != Z_OK) + return false; + + err = inflate(&stream, Z_FINISH); + if (err != Z_OK && err != Z_STREAM_END) { + inflateEnd(&stream); + free(temp); + return false; + } + + memcpy(dst + bytesProcessed, temp, stream.total_out); + bytesProcessed += stream.total_out; + + inflateEnd(&stream); + bytesRead += chunkSize; + } + + free(temp); + return true; +} + +#ifndef RELEASE_BUILD +static bool _shownBackwardSeekingWarning = false; +#endif + +/** + * A simple wrapper class which can be used to wrap around an arbitrary + * other SeekableReadStream and will then provide on-the-fly decompression support. + * Assumes the compressed data to be in gzip format. + */ +class GZipReadStream : public SeekableReadStream { +protected: + enum { + BUFSIZE = 16384 // 1 << MAX_WBITS + }; + + byte _buf[BUFSIZE]; + + ScopedPtr<SeekableReadStream> _wrapped; + z_stream _stream; + int _zlibErr; + uint32 _pos; + uint32 _origSize; + bool _eos; + +public: + + GZipReadStream(SeekableReadStream *w, uint32 knownSize = 0) : _wrapped(w), _stream() { + assert(w != 0); + + // Verify file header is correct + w->seek(0, SEEK_SET); + uint16 header = w->readUint16BE(); + assert(header == 0x1F8B || + ((header & 0x0F00) == 0x0800 && header % 31 == 0)); + + if (header == 0x1F8B) { + // Retrieve the original file size + w->seek(-4, SEEK_END); + _origSize = w->readUint32LE(); + } else { + // Original size not available in zlib format + // use an otherwise known size if supplied. + _origSize = knownSize; + } + _pos = 0; + w->seek(0, SEEK_SET); + _eos = false; + + // Adding 32 to windowBits indicates to zlib that it is supposed to + // automatically detect whether gzip or zlib headers are used for + // the compressed file. This feature was added in zlib 1.2.0.4, + // released 10 August 2003. + // Note: This is *crucial* for savegame compatibility, do *not* remove! + _zlibErr = inflateInit2(&_stream, MAX_WBITS + 32); + if (_zlibErr != Z_OK) + return; + + // Setup input buffer + _stream.next_in = _buf; + _stream.avail_in = 0; + } + + ~GZipReadStream() { + inflateEnd(&_stream); + } + + bool err() const { return (_zlibErr != Z_OK) && (_zlibErr != Z_STREAM_END); } + void clearErr() { + // only reset _eos; I/O errors are not recoverable + _eos = false; + } + + uint32 read(void *dataPtr, uint32 dataSize) { + _stream.next_out = (byte *)dataPtr; + _stream.avail_out = dataSize; + + // Keep going while we get no error + while (_zlibErr == Z_OK && _stream.avail_out) { + if (_stream.avail_in == 0 && !_wrapped->eos()) { + // If we are out of input data: Read more data, if available. + _stream.next_in = _buf; + _stream.avail_in = _wrapped->read(_buf, BUFSIZE); + } + _zlibErr = inflate(&_stream, Z_NO_FLUSH); + } + + // Update the position counter + _pos += dataSize - _stream.avail_out; + + if (_zlibErr == Z_STREAM_END && _stream.avail_out > 0) + _eos = true; + + return dataSize - _stream.avail_out; + } + + bool eos() const { + return _eos; + } + int32 pos() const { + return _pos; + } + int32 size() const { + return _origSize; + } + bool seek(int32 offset, int whence = SEEK_SET) { + int32 newPos = 0; + switch (whence) { + case SEEK_SET: + newPos = offset; + break; + case SEEK_CUR: + newPos = _pos + offset; + break; + case SEEK_END: + // NOTE: This can be an expensive operation (see below). + newPos = size() + offset; + break; + } + + assert(newPos >= 0); + + if ((uint32)newPos < _pos) { + // To search backward, we have to restart the whole decompression + // from the start of the file. A rather wasteful operation, best + // to avoid it. :/ + +#ifndef RELEASE_BUILD + if (!_shownBackwardSeekingWarning) { + // We only throw this warning once per stream, to avoid + // getting the console swarmed with warnings when consecutive + // seeks are made. + debug(1, "Backward seeking in GZipReadStream detected"); + _shownBackwardSeekingWarning = true; + } +#endif + + _pos = 0; + _wrapped->seek(0, SEEK_SET); + _zlibErr = inflateReset(&_stream); + if (_zlibErr != Z_OK) + return false; // FIXME: STREAM REWRITE + _stream.next_in = _buf; + _stream.avail_in = 0; + } + + offset = newPos - _pos; + + // Skip the given amount of data (very inefficient if one tries to skip + // huge amounts of data, but usually client code will only skip a few + // bytes, so this should be fine. + byte tmpBuf[1024]; + while (!err() && offset > 0) { + offset -= read(tmpBuf, MIN((int32)sizeof(tmpBuf), offset)); + } + + _eos = false; + return true; // FIXME: STREAM REWRITE + } +}; + +/** + * A simple wrapper class which can be used to wrap around an arbitrary + * other WriteStream and will then provide on-the-fly compression support. + * The compressed data is written in the gzip format. + */ +class GZipWriteStream : public WriteStream { +protected: + enum { + BUFSIZE = 16384 // 1 << MAX_WBITS + }; + + byte _buf[BUFSIZE]; + ScopedPtr<WriteStream> _wrapped; + z_stream _stream; + int _zlibErr; + uint32 _pos; + + void processData(int flushType) { + // This function is called by both write() and finalize(). + while (_zlibErr == Z_OK && (_stream.avail_in || flushType == Z_FINISH)) { + if (_stream.avail_out == 0) { + if (_wrapped->write(_buf, BUFSIZE) != BUFSIZE) { + _zlibErr = Z_ERRNO; + break; + } + _stream.next_out = _buf; + _stream.avail_out = BUFSIZE; + } + _zlibErr = deflate(&_stream, flushType); + } + } + +public: + GZipWriteStream(WriteStream *w) : _wrapped(w), _stream(), _pos(0) { + assert(w != 0); + + // Adding 16 to windowBits indicates to zlib that it is supposed to + // write gzip headers. This feature was added in zlib 1.2.0.4, + // released 10 August 2003. + // Note: This is *crucial* for savegame compatibility, do *not* remove! + _zlibErr = deflateInit2(&_stream, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + MAX_WBITS + 16, + 8, + Z_DEFAULT_STRATEGY); + assert(_zlibErr == Z_OK); + + _stream.next_out = _buf; + _stream.avail_out = BUFSIZE; + _stream.avail_in = 0; + _stream.next_in = 0; + } + + ~GZipWriteStream() { + finalize(); + deflateEnd(&_stream); + } + + bool err() const { + // CHECKME: does Z_STREAM_END make sense here? + return (_zlibErr != Z_OK && _zlibErr != Z_STREAM_END) || _wrapped->err(); + } + + void clearErr() { + // Note: we don't reset the _zlibErr here, as it is not + // clear in general how + _wrapped->clearErr(); + } + + void finalize() { + if (_zlibErr != Z_OK) + return; + + // Process whatever remaining data there is. + processData(Z_FINISH); + + // Since processData only writes out blocks of size BUFSIZE, + // we may have to flush some stragglers. + uint remainder = BUFSIZE - _stream.avail_out; + if (remainder > 0) { + if (_wrapped->write(_buf, remainder) != remainder) { + _zlibErr = Z_ERRNO; + } + } + + // Finalize the wrapped savefile, too + _wrapped->finalize(); + } + + uint32 write(const void *dataPtr, uint32 dataSize) { + if (err()) + return 0; + + // Hook in the new data ... + // Note: We need to make a const_cast here, as zlib is not aware + // of the const keyword. + _stream.next_in = const_cast<byte *>((const byte *)dataPtr); + _stream.avail_in = dataSize; + + // ... and flush it to disk + processData(Z_NO_FLUSH); + + _pos += dataSize - _stream.avail_in; + return dataSize - _stream.avail_in; + } + + virtual int32 pos() const { return _pos; } +}; + +#endif // USE_ZLIB + +SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped, uint32 knownSize) { + if (toBeWrapped) { + uint16 header = toBeWrapped->readUint16BE(); + bool isCompressed = (header == 0x1F8B || + ((header & 0x0F00) == 0x0800 && + header % 31 == 0)); + toBeWrapped->seek(-2, SEEK_CUR); + if (isCompressed) { +#if defined(USE_ZLIB) + return new GZipReadStream(toBeWrapped, knownSize); +#else + delete toBeWrapped; + return NULL; +#endif + } + } + return toBeWrapped; +} + +WriteStream *wrapCompressedWriteStream(WriteStream *toBeWrapped) { +#if defined(USE_ZLIB) + if (toBeWrapped) + return new GZipWriteStream(toBeWrapped); +#endif + return toBeWrapped; +} + + +} // End of namespace Common diff --git a/devtools/create_titanic/zlib.h b/devtools/create_titanic/zlib.h new file mode 100644 index 0000000000..5adba64076 --- /dev/null +++ b/devtools/create_titanic/zlib.h @@ -0,0 +1,136 @@ +/* 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 COMMON_ZLIB_H +#define COMMON_ZLIB_H + +#include "common/scummsys.h" + +namespace Common { + +class SeekableReadStream; +class WriteStream; + +#if defined(USE_ZLIB) + +/** + * Thin wrapper around zlib's uncompress() function. This wrapper makes + * it possible to uncompress data in engines without being forced to link + * them against zlib, thus simplifying the build system. + * + * Taken from the zlib manual: + * Decompresses the src buffer into the dst buffer. + * srcLen is the byte length of the source buffer. Upon entry, dstLen is the + * total size of the destination buffer, which must be large enough to hold + * the entire uncompressed data. Upon exit, dstLen is the actual size of the + * compressed buffer. + * + * @param dst the buffer to store into. + * @param dstLen a pointer to the size of the destination buffer. + * @param src the data to be decompressed. + * @param srcLen the size of the compressed data. + * + * @return true on success (i.e. Z_OK), false otherwise. + */ +bool uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen); + +/** + * Wrapper around zlib's inflate functions. This function will call the + * necessary inflate functions to uncompress data compressed with deflate + * but *not* with the standard zlib header. + * + * Decompresses the src buffer into the dst buffer. + * srcLen is the byte length of the source buffer, dstLen is the byte + * length of the output buffer. + * It decompress as much data as possible, up to dstLen bytes. + * If a dictionary is provided through the dict buffer, uses it to initializes + * the internal decompression dictionary, before the decompression takes place. + * + * @param dst the buffer to store into. + * @param dstLen the size of the destination buffer. + * @param src the data to be decompressed. + * @param srcLen the size of the compressed data. + * @param dict (optional) a decompress dictionary. + * @param dictLen (optional) the size of the dictionary. + * Mandatory if dict is not 0. + * + * @return true on success (Z_OK or Z_STREAM_END), false otherwise. + */ +bool inflateZlibHeaderless(byte *dst, uint dstLen, const byte *src, uint srcLen, const byte *dict = 0, uint dictLen = 0); + +/** + * Wrapper around zlib's inflate functions. This function will call the + * necessary inflate functions to uncompress data compressed for InstallShield + * cabinet files. + * + * Decompresses the src buffer into the dst buffer. + * srcLen is the byte length of the source buffer, dstLen is the byte + * length of the output buffer. + * It decompress as much data as possible, up to dstLen bytes. + * + * @param dst the buffer to store into. + * @param dstLen the size of the destination buffer. + * @param src the data to be decompressed. + * @param srcLen the size of the compressed data. + * + * @return true on success (Z_OK or Z_STREAM_END), false otherwise. + */ +bool inflateZlibInstallShield(byte *dst, uint dstLen, const byte *src, uint srcLen); + +#endif + +/** + * Take an arbitrary SeekableReadStream and wrap it in a custom stream which + * provides transparent on-the-fly decompression. Assumes the data it + * retrieves from the wrapped stream to be either uncompressed or in gzip + * format. In the former case, the original stream is returned unmodified + * (and in particular, not wrapped). In the latter case the stream is + * returned wrapped, unless there is no ZLIB support, then NULL is returned + * and the old stream is destroyed. + * + * Certain GZip-formats don't supply an easily readable length, if you + * still need the length carried along with the stream, and you know + * the decompressed length at wrap-time, then it can be supplied as knownSize + * here. knownSize will be ignored if the GZip-stream DOES include a length. + * + * It is safe to call this with a NULL parameter (in this case, NULL is + * returned). + * + * @param toBeWrapped the stream to be wrapped (if it is in gzip-format) + * @param knownSize a supplied length of the compressed data (if not available directly) + */ +SeekableReadStream *wrapCompressedReadStream(SeekableReadStream *toBeWrapped, uint32 knownSize = 0); + +/** + * Take an arbitrary WriteStream and wrap it in a custom stream which provides + * transparent on-the-fly compression. The compressed data is written in the + * gzip format, unless ZLIB support has been disabled, in which case the given + * stream is returned unmodified (and in particular, not wrapped). + * + * It is safe to call this with a NULL parameter (in this case, NULL is + * returned). + */ +WriteStream *wrapCompressedWriteStream(WriteStream *toBeWrapped); + +} // End of namespace Common + +#endif |