From f403c0f8a862573d46957f8adaf8dc32b7548e2e Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sun, 28 Nov 2010 22:55:00 +0000 Subject: COMMON: Add support for loading NE resources and cursors Needed for Mohawk (and Hugo Win eventually). Based on DrMcCoy's excellent Dark Seed II code. svn-id: r54557 --- common/module.mk | 1 + common/ne_exe.cpp | 612 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ common/ne_exe.h | 207 ++++++++++++++++++ 3 files changed, 820 insertions(+) create mode 100644 common/ne_exe.cpp create mode 100644 common/ne_exe.h diff --git a/common/module.mk b/common/module.mk index 1bff1b6c29..30684c0582 100644 --- a/common/module.mk +++ b/common/module.mk @@ -16,6 +16,7 @@ MODULE_OBJS := \ memorypool.o \ md5.o \ mutex.o \ + ne_exe.o \ random.o \ rational.o \ str.o \ diff --git a/common/ne_exe.cpp b/common/ne_exe.cpp new file mode 100644 index 0000000000..a216ef6446 --- /dev/null +++ b/common/ne_exe.cpp @@ -0,0 +1,612 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/debug.h" +#include "common/file.h" +#include "common/memstream.h" +#include "common/ne_exe.h" +#include "common/str.h" +#include "common/stream.h" + +namespace Common { + +NECursor::NECursor() { + _width = 0; + _height = 0; + _hotspotX = 0; + _hotspotY = 0; + _surface = 0; + memset(_palette, 0, 256 * 4); +} + +NECursor::~NECursor() { + clear(); +} + +uint16 NECursor::getWidth() const { + return _width; +} + +uint16 NECursor::getHeight() const { + return _height; +} + +uint16 NECursor::getHotspotX() const { + return _hotspotX; +} + +uint16 NECursor::getHotspotY() const { + return _hotspotY; +} + +void NECursor::setDimensions(uint16 width, uint16 height) { + _width = width; + _height = height; +} + +void NECursor::setHotspot(uint16 x, uint16 y) { + _hotspotX = x; + _hotspotY = y; +} + +bool NECursor::readCursor(SeekableReadStream &stream, uint32 count) { + clear(); + + SeekableReadStream *bitmap = stream.readStream(count); + _surface = new byte[_width * _height]; + + uint32 width = _width; + uint32 height = _height * 2; + + // Sanity checks + assert((width > 0) && (height > 0)); + + // Check header size + if (bitmap->readUint32LE() != 40) + return false; + + // Check dimensions + if (bitmap->readUint32LE() != width) + return false; + if (bitmap->readUint32LE() != height) + return false; + + // Color planes + if (bitmap->readUint16LE() != 1) + return false; + // Bits per pixel + if (bitmap->readUint16LE() != 1) + return false; + // Compression + if (bitmap->readUint32LE() != 0) + return false; + + // Image size + X resolution + Y resolution + bitmap->skip(12); + + uint32 numColors = bitmap->readUint32LE(); + + if (numColors == 0) + numColors = 2; + else if (numColors > 2) + return false; + + // Assert that enough data is there for the whole cursor + if ((uint32)bitmap->size() < 40 + numColors * 4 + width * height / 8) + return false; + + // Height includes AND-mask and XOR-mask + height /= 2; + + // Standard palette: transparent, black, white + _palette[8] = 0xff; + _palette[9] = 0xff; + _palette[10] = 0xff; + + // Reading the palette + bitmap->seek(40); + for (uint32 i = 0 ; i < numColors; i++) { + _palette[(i + 1) * 4 + 2] = bitmap->readByte(); + _palette[(i + 1) * 4 + 1] = bitmap->readByte(); + _palette[(i + 1) * 4 + 0] = bitmap->readByte(); + bitmap->readByte(); + } + + // Reading the bitmap data + uint32 dataSize = bitmap->size() - 40 - numColors * 4; + byte *initialSource = new byte[dataSize]; + bitmap->read(initialSource, dataSize); + const byte *srcP = initialSource; + const byte *srcM = srcP + ((width * height) / 8); + byte *dest = _surface + width * (height - 1); + + for (uint32 i = 0; i < height; i++) { + byte *rowDest = dest; + + for (uint32 j = 0; j < (width / 8); j++) { + byte p = srcP[j]; + byte m = srcM[j]; + + for (int k = 0; k < 8; k++, rowDest++, p <<= 1, m <<= 1) { + if ((m & 0x80) != 0x80) { + if ((p & 0x80) == 0x80) + *rowDest = 2; + else + *rowDest = 1; + } else + *rowDest = 0; + } + } + + dest -= width; + srcP += width / 8; + srcM += width / 8; + } + + delete bitmap; + delete[] initialSource; + return true; +} + +void NECursor::clear() { + delete[] _surface; _surface = 0; +} + +NEResourceID &NEResourceID::operator=(String string) { + _name = string; + _idType = kIDTypeString; + return *this; +} + +NEResourceID &NEResourceID::operator=(uint16 x) { + _id = x; + _idType = kIDTypeNumerical; + return *this; +} + +bool NEResourceID::operator==(const String &x) const { + return _idType == kIDTypeString && _name.equalsIgnoreCase(x); +} + +bool NEResourceID::operator==(const uint16 &x) const { + return _idType == kIDTypeNumerical && _id == x; +} + +bool NEResourceID::operator==(const NEResourceID &x) const { + if (_idType != x._idType) + return false; + if (_idType == kIDTypeString) + return _name.equalsIgnoreCase(x._name); + if (_idType == kIDTypeNumerical) + return _id == x._id; + return true; +} + +String NEResourceID::getString() const { + if (_idType != kIDTypeString) + return ""; + + return _name; +} + +uint16 NEResourceID::getID() const { + if (_idType != kIDTypeNumerical) + return 0xffff; + + return _idType; +} + +String NEResourceID::toString() const { + if (_idType == kIDTypeString) + return _name; + else if (_idType == kIDTypeNumerical) + return String::format("%04x", _id); + + return ""; +} + +NEResources::NEResources() { + _exe = 0; +} + +NEResources::~NEResources() { + clear(); +} + +void NEResources::clear() { + if (_exe) { + delete _exe; + _exe = 0; + } + + _resources.clear(); + + for (uint32 i = 0; i < _cursors.size(); i++) + for (uint32 j = 0; j < _cursors[i].cursors.size(); j++) + delete _cursors[i].cursors[j]; + + _cursors.clear(); +} + +const Array &NEResources::getCursors() const { + return _cursors; +} + +bool NEResources::loadFromEXE(const String &fileName) { + if (fileName.empty()) + return false; + + File *file = new File(); + + if (!file->open(fileName)) { + delete file; + return false; + } + + return loadFromEXE(file); +} + +bool NEResources::loadFromEXE(SeekableReadStream *stream) { + clear(); + + if (!stream) + return false; + + _exe = stream; + + uint32 offsetResourceTable = getResourceTableOffset(); + if (offsetResourceTable == 0xFFFFFFFF) + return false; + if (offsetResourceTable == 0) + return true; + + if (!readResourceTable(offsetResourceTable)) + return false; + + if (!readCursors()) + return false; + + return true; +} + +bool NEResources::loadFromCompressedEXE(const String &fileName) { + // Based on http://www.cabextract.org.uk/libmspack/doc/szdd_kwaj_format.html + + File file; + + if (!file.open(fileName)) + return false; + + // First part of the signature + if (file.readUint32BE() != MKID_BE('SZDD')) + return false; + + // Second part of the signature + if (file.readUint32BE() != 0x88F02733) + return false; + + // Compression mode must be 'A' + if (file.readByte() != 'A') + return false; + + file.readByte(); // file name character change + uint32 unpackedLength = file.readUint32LE(); + + byte *window = new byte[0x1000]; + int pos = 0x1000 - 16; + memset(window, 0x20, 0x1000); // Initialize to all spaces + + byte *unpackedData = (byte *)malloc(unpackedLength); + byte *dataPos = unpackedData; + + // Apply simple LZSS decompression + for (;;) { + byte controlByte = file.readByte(); + + if (file.eos()) + break; + + for (byte i = 0; i < 8; i++) { + if (controlByte & (1 << i)) { + *dataPos++ = window[pos++] = file.readByte(); + pos &= 0xFFF; + } else { + int matchPos = file.readByte(); + int matchLen = file.readByte(); + matchPos |= (matchLen & 0xF0) << 4; + matchLen = (matchLen & 0xF) + 3; + while (matchLen--) { + *dataPos++ = window[pos++] = window[matchPos++]; + pos &= 0xFFF; + matchPos &= 0xFFF; + } + } + + } + } + + delete[] window; + SeekableReadStream *stream = new MemoryReadStream(unpackedData, unpackedLength); + + return loadFromEXE(stream); +} + +uint32 NEResources::getResourceTableOffset() { + if (!_exe) + return 0xFFFFFFFF; + + if (!_exe->seek(0)) + return 0xFFFFFFFF; + + // 'MZ' + if (_exe->readUint16BE() != 0x4D5A) + return 0xFFFFFFFF; + + if (!_exe->seek(60)) + return 0xFFFFFFFF; + + uint32 offsetSegmentEXE = _exe->readUint16LE(); + if (!_exe->seek(offsetSegmentEXE)) + return 0xFFFFFFFF; + + // 'NE' + if (_exe->readUint16BE() != 0x4E45) + return 0xFFFFFFFF; + + if (!_exe->seek(offsetSegmentEXE + 36)) + return 0xFFFFFFFF; + + uint32 offsetResourceTable = _exe->readUint16LE(); + if (offsetResourceTable == 0) + // No resource table + return 0; + + // Offset relative to the segment _exe header + offsetResourceTable += offsetSegmentEXE; + if (!_exe->seek(offsetResourceTable)) + return 0xFFFFFFFF; + + return offsetResourceTable; +} + +static const char *s_resTypeNames[] = { + "", "cursor", "bitmap", "icon", "menu", "dialog", "string", + "font_dir", "font", "accelerator", "rc_data", "msg_table", + "group_cursor", "group_icon", "version", "dlg_include", + "plug_play", "vxd", "ani_cursor", "ani_icon", "html", + "manifest" +}; + +bool NEResources::readResourceTable(uint32 offset) { + if (!_exe) + return false; + + if (!_exe->seek(offset)) + return false; + + uint32 align = 1 << _exe->readUint16LE(); + + uint16 typeID = _exe->readUint16LE(); + while (typeID != 0) { + uint16 resCount = _exe->readUint16LE(); + + _exe->skip(4); // reserved + + for (int i = 0; i < resCount; i++) { + Resource res; + + // Resource properties + res.offset = _exe->readUint16LE() * align; + res.size = _exe->readUint16LE() * align; + res.flags = _exe->readUint16LE(); + uint16 id = _exe->readUint16LE(); + res.handle = _exe->readUint16LE(); + res.usage = _exe->readUint16LE(); + + res.type = typeID; + + if ((id & 0x8000) == 0) + res.id = getResourceString(*_exe, offset + id); + else + res.id = id & 0x7FFF; + + if (typeID & 0x8000 && ((typeID & 0x7FFF) < ARRAYSIZE(s_resTypeNames))) + debug(2, "Found resource %s %s", s_resTypeNames[typeID & 0x7FFF], res.id.toString().c_str()); + else + debug(2, "Found resource %04x %s", typeID, res.id.toString().c_str()); + + _resources.push_back(res); + } + + typeID = _exe->readUint16LE(); + } + + return true; +} + +String NEResources::getResourceString(SeekableReadStream &exe, uint32 offset) { + uint32 curPos = exe.pos(); + + if (!exe.seek(offset)) { + exe.seek(curPos); + return ""; + } + + uint8 length = exe.readByte(); + + String string; + for (uint16 i = 0; i < length; i++) + string += (char)exe.readByte(); + + exe.seek(curPos); + return string; +} + +const NEResources::Resource *NEResources::findResource(uint16 type, NEResourceID id) const { + for (List::const_iterator it = _resources.begin(); it != _resources.end(); ++it) + if (it->type == type && it->id == id) + return &*it; + + return 0; +} + +SeekableReadStream *NEResources::getResource(uint16 type, NEResourceID id) { + const Resource *res = findResource(type, id); + + if (!res) + return 0; + + _exe->seek(res->offset); + return _exe->readStream(res->size); +} + +const Array NEResources::getIDList(uint16 type) const { + Array idArray; + + for (List::const_iterator it = _resources.begin(); it != _resources.end(); ++it) + if (it->type == type) + idArray.push_back(it->id); + + return idArray; +} + +bool NEResources::readCursors() { + uint32 cursorCount = 0; + + for (List::const_iterator it = _resources.begin(); it != _resources.end(); ++it) + if (it->type == kNEGroupCursor) + cursorCount++; + + if (cursorCount == 0) { + _cursors.clear(); + return true; + } + + _cursors.resize(cursorCount); + + Array::iterator cursorGroup = _cursors.begin(); + for (List::const_iterator it = _resources.begin(); it != _resources.end(); ++it) { + if (it->type == kNEGroupCursor) { + if (!readCursorGroup(*cursorGroup, *it)) + return false; + + ++cursorGroup; + } + } + + return true; +} + +bool NEResources::readCursorGroup(NECursorGroup &group, const Resource &resource) { + if (!_exe) + return false; + + if (resource.size <= 6) + return false; + + if (!_exe->seek(resource.offset)) + return false; + + byte *data = new byte[resource.size]; + + if (!_exe->read(data, resource.size)) { + delete[] data; + return false; + } + + uint32 cursorCount = READ_LE_UINT16(data + 4); + if (resource.size < (6 + cursorCount * 16)) { + delete[] data; + return false; + } + + group.cursors.resize(cursorCount); + + uint32 offset = 6; + for (uint32 i = 0; i < cursorCount; i++) { + group.cursors[i] = new NECursor(); + NECursor *cursor = group.cursors[i]; + + // Plane count + if (READ_LE_UINT16(data + offset + 4) != 1) { + delete[] data; + return false; + } + + // Bit count + if (READ_LE_UINT16(data + offset + 6) != 1) { + delete[] data; + return false; + } + + uint32 id = READ_LE_UINT32(data + offset + 12); + const Resource *cursorResource = findResource(kNECursor, id); + if (!cursorResource) { + delete[] data; + return false; + } + + cursor->setDimensions(READ_LE_UINT16(data + offset), READ_LE_UINT16(data + offset + 2) / 2); + + uint32 dataSize = READ_LE_UINT32(data + offset + 8); + if (!readCursor(*cursor, *cursorResource, dataSize)) { + delete[] data; + return false; + } + + offset += 16; + } + + group.id = resource.id; + + delete[] data; + return true; +} + +bool NEResources::readCursor(NECursor &cursor, const Resource &resource, uint32 size) { + if (!_exe) + return false; + + if (size <= 4) + return false; + if (resource.size < size) + return false; + + if (!_exe->seek(resource.offset)) + return false; + + uint32 hotspotX = _exe->readUint16LE(); + uint32 hotspotY = _exe->readUint16LE(); + cursor.setHotspot(hotspotX, hotspotY); + + size -= 4; + + if (!cursor.readCursor(*_exe, size)) + return false; + + return true; +} + +} // End of namespace Common diff --git a/common/ne_exe.h b/common/ne_exe.h new file mode 100644 index 0000000000..7ab51325c4 --- /dev/null +++ b/common/ne_exe.h @@ -0,0 +1,207 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef COMMON_NE_EXE_H +#define COMMON_NE_EXE_H + +#include "common/array.h" +#include "common/file.h" +#include "common/list.h" + +namespace Common { + +class MemoryReadStream; +class SeekableReadStream; +class String; + +/** A New Executable cursor. */ +class NECursor { +public: + NECursor(); + ~NECursor(); + + /** Return the cursor's width. */ + uint16 getWidth() const; + /** Return the cursor's height. */ + uint16 getHeight() const; + /** Return the cursor's hotspot's x coordinate. */ + uint16 getHotspotX() const; + /** Return the cursor's hotspot's y coordinate. */ + uint16 getHotspotY() const; + + const byte *getSurface() const { return _surface; } + const byte *getPalette() const { return _palette; } + + /** Set the cursor's dimensions. */ + void setDimensions(uint16 width, uint16 height); + /** Set the cursor's hotspot. */ + void setHotspot(uint16 x, uint16 y); + + /** Read the cursor's data out of a stream. */ + bool readCursor(SeekableReadStream &stream, uint32 count); + +private: + byte *_surface; + byte _palette[256 * 4]; + + uint16 _width; ///< The cursor's width. + uint16 _height; ///< The cursor's height. + uint16 _hotspotX; ///< The cursor's hotspot's x coordinate. + uint16 _hotspotY; ///< The cursor's hotspot's y coordinate. + + /** Clear the cursor. */ + void clear(); +}; + +class NEResourceID { +public: + NEResourceID() { _idType = kIDTypeNull; } + NEResourceID(String x) { _idType = kIDTypeString; _name = x; } + NEResourceID(uint16 x) { _idType = kIDTypeNumerical; _id = x; } + + NEResourceID &operator=(String string); + NEResourceID &operator=(uint16 x); + + bool operator==(const String &x) const; + bool operator==(const uint16 &x) const; + bool operator==(const NEResourceID &x) const; + + String getString() const; + uint16 getID() const; + String toString() const; + +private: + /** An ID Type. */ + enum IDType { + kIDTypeNull, ///< No type set + kIDTypeNumerical, ///< A numerical ID. + kIDTypeString ///< A string ID. + } _idType; + + String _name; ///< The resource's string ID. + uint16 _id; ///< The resource's numerical ID. +}; + +/** A New Executable cursor group. */ +struct NECursorGroup { + NEResourceID id; + Array cursors; ///< The cursors. +}; + +/** The default Windows resources. */ +enum NEResourceType { + kNECursor = 0x8001, + kNEBitmap = 0x8002, + kNEIcon = 0x8003, + kNEMenu = 0x8004, + kNEDialog = 0x8005, + kNEString = 0x8006, + kNEFontDir = 0x8007, + kNEFont = 0x8008, + kNEAccelerator = 0x8009, + kNERCData = 0x800A, + kNEMessageTable = 0x800B, + kNEGroupCursor = 0x800C, + kNEGroupIcon = 0x800D, + kNEVersion = 0x8010, + kNEDlgInclude = 0x8011, + kNEPlugPlay = 0x8013, + kNEVXD = 0x8014, + kNEAniCursor = 0x8015, + kNEAniIcon = 0x8016, + kNEHTML = 0x8017, + kNEManifest = 0x8018 +}; + +/** A class able to load resources from a New Executable. */ +class NEResources { +public: + NEResources(); + ~NEResources(); + + /** Clear all information. */ + void clear(); + + /** Load from an EXE file. */ + bool loadFromEXE(const String &fileName); + + /** Load from a Windows compressed EXE file. */ + bool loadFromCompressedEXE(const String &fileName); + + /** Load from a stream. */ + bool loadFromEXE(SeekableReadStream *stream); + + /** Get all cursor's read from the New Executable. */ + const Array &getCursors() const; + + /** Return a list of resources for a given type. */ + const Array getIDList(uint16 type) const; + + /** Return a stream to the specified resource (or 0 if non-existent). */ + SeekableReadStream *getResource(uint16 type, NEResourceID id); + +private: + /** A resource. */ + struct Resource { + NEResourceID id; + + uint16 type; ///< Type of the resource. + + uint32 offset; ///< Offset within the EXE. + uint32 size; ///< Size of the data. + + uint16 flags; + uint16 handle; + uint16 usage; + }; + + SeekableReadStream *_exe; ///< Current file. + + /** All resources. */ + List _resources; + + /** All cursor resources. */ + Array _cursors; + + /** Read the offset to the resource table. */ + uint32 getResourceTableOffset(); + /** Read the resource table. */ + bool readResourceTable(uint32 offset); + + // Cursor reading helpers + bool readCursors(); + bool readCursorGroup(NECursorGroup &group, const Resource &resource); + bool readCursor(NECursor &cursor, const Resource &resource, uint32 size); + + /** Find a specific resource. */ + const Resource *findResource(uint16 type, NEResourceID id) const; + + /** Read a resource string. */ + static String getResourceString(SeekableReadStream &exe, uint32 offset); +}; + +} // End of namespace Common + +#endif // COMMON_NE_EXE_H -- cgit v1.2.3