/* 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