diff options
Diffstat (limited to 'engines/zvision/file')
-rw-r--r-- | engines/zvision/file/lzss_read_stream.cpp | 103 | ||||
-rw-r--r-- | engines/zvision/file/lzss_read_stream.h | 71 | ||||
-rw-r--r-- | engines/zvision/file/save_manager.cpp | 292 | ||||
-rw-r--r-- | engines/zvision/file/save_manager.h | 108 | ||||
-rw-r--r-- | engines/zvision/file/search_manager.cpp | 285 | ||||
-rw-r--r-- | engines/zvision/file/search_manager.h | 70 | ||||
-rw-r--r-- | engines/zvision/file/zfs_archive.cpp | 155 | ||||
-rw-r--r-- | engines/zvision/file/zfs_archive.h | 125 |
8 files changed, 1209 insertions, 0 deletions
diff --git a/engines/zvision/file/lzss_read_stream.cpp b/engines/zvision/file/lzss_read_stream.cpp new file mode 100644 index 0000000000..ca10af7d72 --- /dev/null +++ b/engines/zvision/file/lzss_read_stream.cpp @@ -0,0 +1,103 @@ +/* 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 "common/scummsys.h" + +#include "zvision/file/lzss_read_stream.h" + +namespace ZVision { + +LzssReadStream::LzssReadStream(Common::SeekableReadStream *source) + : _source(source), + // It's convention to set the starting cursor position to blockSize - 16 + _windowCursor(0x0FEE), + _eosFlag(false) { + // All values up to _windowCursor inits by 0x20 + memset(_window, 0x20, _windowCursor); + memset(_window + _windowCursor, 0, BLOCK_SIZE - _windowCursor); +} + +uint32 LzssReadStream::decompressBytes(byte *destination, uint32 numberOfBytes) { + uint32 destinationCursor = 0; + + while (destinationCursor < numberOfBytes) { + byte flagbyte = _source->readByte(); + if (_source->eos()) + break; + uint mask = 1; + + for (int i = 0; i < 8; ++i) { + if ((flagbyte & mask) == mask) { + byte data = _source->readByte(); + if (_source->eos()) { + return destinationCursor; + } + + _window[_windowCursor] = data; + destination[destinationCursor++] = data; + + // Increment and wrap the window cursor + _windowCursor = (_windowCursor + 1) & 0xFFF; + } else { + byte low = _source->readByte(); + if (_source->eos()) { + return destinationCursor; + } + + byte high = _source->readByte(); + if (_source->eos()) { + return destinationCursor; + } + + uint16 length = (high & 0xF) + 2; + uint16 offset = low | ((high & 0xF0) << 4); + + for (int j = 0; j <= length; ++j) { + byte temp = _window[(offset + j) & 0xFFF]; + _window[_windowCursor] = temp; + destination[destinationCursor++] = temp; + _windowCursor = (_windowCursor + 1) & 0xFFF; + } + } + + mask = mask << 1; + } + } + + return destinationCursor; +} + +bool LzssReadStream::eos() const { + return _eosFlag; +} + +uint32 LzssReadStream::read(void *dataPtr, uint32 dataSize) { + uint32 bytesRead = decompressBytes(static_cast<byte *>(dataPtr), dataSize); + if (bytesRead < dataSize) { + // Flag that we're at EOS + _eosFlag = true; + } + + return dataSize; +} + +} // End of namespace ZVision diff --git a/engines/zvision/file/lzss_read_stream.h b/engines/zvision/file/lzss_read_stream.h new file mode 100644 index 0000000000..1420621f13 --- /dev/null +++ b/engines/zvision/file/lzss_read_stream.h @@ -0,0 +1,71 @@ +/* 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 ZVISION_LZSS_STREAM_H +#define ZVISION_LZSS_STREAM_H + +#include "common/stream.h" +#include "common/array.h" + +namespace Common { +class SeekableReadStream; +} + +namespace ZVision { + +class LzssReadStream : public Common::ReadStream { +public: + /** + * A class that decompresses LZSS data and implements ReadStream for easy access + * to the decompiled data. + * + * @param source The source data + */ + LzssReadStream(Common::SeekableReadStream *source); + +private: + enum { + BLOCK_SIZE = 0x1000 + }; + +private: + Common::SeekableReadStream *_source; + byte _window[BLOCK_SIZE]; + uint _windowCursor; + bool _eosFlag; + +public: + bool eos() const; + uint32 read(void *dataPtr, uint32 dataSize); + +private: + /** + * Decompress the next <numberOfBytes> from the source stream. Or until EOS + * + * @param numberOfBytes How many bytes to decompress. This is a count of source bytes, not destination bytes + */ + uint32 decompressBytes(byte *destination, uint32 numberOfBytes); +}; + +} + +#endif diff --git a/engines/zvision/file/save_manager.cpp b/engines/zvision/file/save_manager.cpp new file mode 100644 index 0000000000..63b54269de --- /dev/null +++ b/engines/zvision/file/save_manager.cpp @@ -0,0 +1,292 @@ +/* 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 "common/scummsys.h" + +#include "zvision/zvision.h" +#include "zvision/file/save_manager.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/system.h" +#include "common/translation.h" + +#include "graphics/surface.h" +#include "graphics/thumbnail.h" + +#include "gui/message.h" +#include "gui/saveload.h" + +namespace ZVision { + +const uint32 SaveManager::SAVEGAME_ID = MKTAG('Z', 'E', 'N', 'G'); + +bool SaveManager::scummVMSaveLoadDialog(bool isSave) { + GUI::SaveLoadChooser *dialog; + Common::String desc; + int slot; + + if (isSave) { + dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); + + slot = dialog->runModalWithCurrentTarget(); + desc = dialog->getResultString(); + + if (desc.empty()) { + // create our own description for the saved game, the user didnt enter it + desc = dialog->createDefaultSaveDescription(slot); + } + + if (desc.size() > 28) + desc = Common::String(desc.c_str(), 28); + } else { + dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); + slot = dialog->runModalWithCurrentTarget(); + } + + delete dialog; + + if (slot < 0) + return false; + + if (isSave) { + saveGame(slot, desc, false); + return true; + } else { + Common::ErrorCode result = loadGame(slot).getCode(); + return (result == Common::kNoError); + } +} + +void SaveManager::saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer) { + if (!_tempSave && useSaveBuffer) + return; + + Common::SaveFileManager *saveFileManager = g_system->getSavefileManager(); + Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot)); + + writeSaveGameHeader(file, saveName, useSaveBuffer); + + if (useSaveBuffer) + file->write(_tempSave->getData(), _tempSave->size()); + else + _engine->getScriptManager()->serialize(file); + + file->finalize(); + delete file; + + if (useSaveBuffer) + flushSaveBuffer(); + + _lastSaveTime = g_system->getMillis(); +} + +void SaveManager::autoSave() { + saveGame(0, "Auto save", false); +} + +void SaveManager::writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer) { + file->writeUint32BE(SAVEGAME_ID); + + // Write version + file->writeByte(SAVE_VERSION); + + // Write savegame name + file->writeString(saveName); + file->writeByte(0); + + // Save the game thumbnail + if (useSaveBuffer) + file->write(_tempThumbnail->getData(), _tempThumbnail->size()); + else + Graphics::saveThumbnail(*file); + + // Write out the save date/time + TimeDate td; + g_system->getTimeAndDate(td); + file->writeSint16LE(td.tm_year + 1900); + file->writeSint16LE(td.tm_mon + 1); + file->writeSint16LE(td.tm_mday); + file->writeSint16LE(td.tm_hour); + file->writeSint16LE(td.tm_min); +} + +Common::Error SaveManager::loadGame(int slot) { + Common::SeekableReadStream *saveFile = NULL; + + if (slot >= 0) { + saveFile = getSlotFile(slot); + } else { + saveFile = _engine->getSearchManager()->openFile("r.svr"); + if (!saveFile) { + Common::File *restoreFile = new Common::File(); + if (!restoreFile->open("r.svr")) { + delete restoreFile; + return Common::kPathDoesNotExist; + } + + saveFile = restoreFile; + } + } + + if (!saveFile) + return Common::kPathDoesNotExist; + + // Read the header + SaveGameHeader header; + if (!readSaveGameHeader(saveFile, header)) { + return Common::kUnknownError; + } + + ScriptManager *scriptManager = _engine->getScriptManager(); + // Update the state table values + scriptManager->deserialize(saveFile); + + delete saveFile; + if (header.thumbnail) + delete header.thumbnail; + + if (_engine->getGameId() == GID_NEMESIS && scriptManager->getCurrentLocation() == "tv2f") { + // WORKAROUND for script bug #6793: location tv2f (stairs) has two states: + // one at the top of the stairs, and one at the bottom. When the player + // goes to the bottom of the stairs, the screen changes, and hotspot + // 4652 (exit opposite the stairs) is enabled. However, the variable that + // controls the state (2408) is reset when the player goes down the stairs. + // Furthermore, the room's initialization script disables the stair exit + // control (4652). This leads to an impossible situation, where all the + // exit controls are disabled, and the player can't more anywhere. Thus, + // when loading a game in that room, we check for that impossible + // situation, which only occurs after the player has moved down the stairs, + // and fix it here by setting the correct background, and enabling the + // stair exit hotspot. + if ((scriptManager->getStateFlag(2411) & Puzzle::DISABLED) && + (scriptManager->getStateFlag(2408) & Puzzle::DISABLED) && + (scriptManager->getStateFlag(4652) & Puzzle::DISABLED)) { + _engine->getRenderManager()->setBackgroundImage("tv2fb21c.tga"); + scriptManager->unsetStateFlag(4652, Puzzle::DISABLED); + } + } + + return Common::kNoError; +} + +bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header) { + uint32 tag = in->readUint32BE(); + // Check if it's original savegame than fill header structure + if (tag == MKTAG('Z', 'N', 'S', 'G')) { + header.saveYear = 0; + header.saveMonth = 0; + header.saveDay = 0; + header.saveHour = 0; + header.saveMinutes = 0; + header.saveName = "Original Save"; + header.thumbnail = NULL; + header.version = SAVE_ORIGINAL; + in->seek(-4, SEEK_CUR); + return true; + } + if (tag != SAVEGAME_ID) { + warning("File is not a ZVision save file. Aborting load"); + return false; + } + + // Read in the version + header.version = in->readByte(); + + // Check that the save version isn't newer than this binary + if (header.version > SAVE_VERSION) { + uint tempVersion = header.version; + GUI::MessageDialog dialog( + Common::String::format( + "This save file uses version %u, but this engine only " + "supports up to version %d. You will need an updated version " + "of the engine to use this save file.", tempVersion, SAVE_VERSION + ), + "OK"); + dialog.runModal(); + } + + // Read in the save name + header.saveName.clear(); + char ch; + while ((ch = (char)in->readByte()) != '\0') + header.saveName += ch; + + // Get the thumbnail + header.thumbnail = Graphics::loadThumbnail(*in); + if (!header.thumbnail) + return false; + + // Read in save date/time + header.saveYear = in->readSint16LE(); + header.saveMonth = in->readSint16LE(); + header.saveDay = in->readSint16LE(); + header.saveHour = in->readSint16LE(); + header.saveMinutes = in->readSint16LE(); + + return true; +} + +Common::SeekableReadStream *SaveManager::getSlotFile(uint slot) { + Common::SeekableReadStream *saveFile = g_system->getSavefileManager()->openForLoading(_engine->generateSaveFileName(slot)); + if (saveFile == NULL) { + // Try to load standard save file + Common::String filename; + if (_engine->getGameId() == GID_GRANDINQUISITOR) + filename = Common::String::format("inqsav%u.sav", slot); + else if (_engine->getGameId() == GID_NEMESIS) + filename = Common::String::format("nemsav%u.sav", slot); + + saveFile = _engine->getSearchManager()->openFile(filename); + if (saveFile == NULL) { + Common::File *tmpFile = new Common::File; + if (!tmpFile->open(filename)) { + delete tmpFile; + } else { + saveFile = tmpFile; + } + } + + } + + return saveFile; +} + +void SaveManager::prepareSaveBuffer() { + delete _tempThumbnail; + _tempThumbnail = new Common::MemoryWriteStreamDynamic; + Graphics::saveThumbnail(*_tempThumbnail); + + delete _tempSave; + _tempSave = new Common::MemoryWriteStreamDynamic; + _engine->getScriptManager()->serialize(_tempSave); +} + +void SaveManager::flushSaveBuffer() { + delete _tempThumbnail; + _tempThumbnail = NULL; + + delete _tempSave; + _tempSave = NULL; +} + +} // End of namespace ZVision diff --git a/engines/zvision/file/save_manager.h b/engines/zvision/file/save_manager.h new file mode 100644 index 0000000000..9e816373ea --- /dev/null +++ b/engines/zvision/file/save_manager.h @@ -0,0 +1,108 @@ +/* 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 ZVISION_SAVE_MANAGER_H +#define ZVISION_SAVE_MANAGER_H + +#include "common/savefile.h" +#include "common/memstream.h" + +namespace Common { +class String; +} + +namespace Graphics { +struct Surface; +} + +namespace ZVision { + +class ZVision; + +struct SaveGameHeader { + byte version; + Common::String saveName; + Graphics::Surface *thumbnail; + int saveYear, saveMonth, saveDay; + int saveHour, saveMinutes; +}; + +class SaveManager { +public: + SaveManager(ZVision *engine) : _engine(engine), _tempSave(NULL), _tempThumbnail(NULL), _lastSaveTime(0) {} + ~SaveManager() { + flushSaveBuffer(); + } + + uint32 getLastSaveTime() const { + return _lastSaveTime; + } + +private: + ZVision *_engine; + uint32 _lastSaveTime; + static const uint32 SAVEGAME_ID; + + enum { + SAVE_ORIGINAL = 0, + SAVE_VERSION = 1 + }; + + Common::MemoryWriteStreamDynamic *_tempThumbnail; + Common::MemoryWriteStreamDynamic *_tempSave; + +public: + /** + * Called every room change. Saves the state of the room just before + * the room changes. + */ + void autoSave(); + /** + * Copies the data from the last auto-save into a new save file. We + * can't use the current state data because the save menu *IS* a room. + * The file is named using ZVision::generateSaveFileName(slot) + * + * @param slot The save slot this save pertains to. Must be [1, 20] + * @param saveName The internal name for this save. This is NOT the name of the actual save file. + */ + void saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer); + /** + * Loads the state data from the save file that slot references. Uses + * ZVision::generateSaveFileName(slot) to get the save file name. + * + * @param slot The save slot to load. Must be [1, 20] + */ + Common::Error loadGame(int slot); + + Common::SeekableReadStream *getSlotFile(uint slot); + bool readSaveGameHeader(Common::SeekableReadStream *in, SaveGameHeader &header); + + void prepareSaveBuffer(); + void flushSaveBuffer(); + bool scummVMSaveLoadDialog(bool isSave); +private: + void writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/file/search_manager.cpp b/engines/zvision/file/search_manager.cpp new file mode 100644 index 0000000000..821b85b053 --- /dev/null +++ b/engines/zvision/file/search_manager.cpp @@ -0,0 +1,285 @@ +/* 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 "common/debug.h" +#include "common/fs.h" +#include "common/stream.h" + +#include "zvision/file/search_manager.h" +#include "zvision/file/zfs_archive.h" + +namespace ZVision { + +SearchManager::SearchManager(const Common::String &rootPath, int depth) { + _root = rootPath; + if (_root[_root.size() - 1] == '\\' || _root[_root.size() - 1] == '/') + _root.deleteLastChar(); + + Common::FSNode fsNode(_root); + + listDirRecursive(_dirList, fsNode, depth); + + for (Common::List<Common::String>::iterator it = _dirList.begin(); it != _dirList.end();) { + if ((*it).hasSuffix("\\") || (*it).hasSuffix("/")) + (*it).deleteLastChar(); + + if (it->size() == _root.size()) + it = _dirList.erase(it); + else if (it->size() > _root.size()) { + *it = Common::String(it->c_str() + _root.size() + 1); + it++; + } else + it++; + } +} + +SearchManager::~SearchManager() { + Common::List<Common::Archive *>::iterator it = _archList.begin(); + while (it != _archList.end()) { + delete *it; + it++; + } + + _archList.clear(); +} + +void SearchManager::addFile(const Common::String &name, Common::Archive *arch) { + bool addArch = true; + Common::List<Common::Archive *>::iterator it = _archList.begin(); + while (it != _archList.end()) { + if (*it == arch) { + addArch = false; + break; + } + it++; + } + if (addArch) + _archList.push_back(arch); + + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::Node nod; + nod.name = lowerCaseName; + nod.arch = arch; + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit == _files.end()) { + _files[lowerCaseName] = nod; + } else { + Common::SeekableReadStream *stream = fit->_value.arch->createReadStreamForMember(fit->_value.name); + if (stream) { + if (stream->size() < 10) + fit->_value.arch = arch; + delete stream; + } else { + _files[lowerCaseName] = nod; + } + } +} + +Common::File *SearchManager::openFile(const Common::String &name) { + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit != _files.end()) { + Common::File *tmp = new Common::File(); + tmp->open(fit->_value.name, *fit->_value.arch); + return tmp; + } + return NULL; +} + +bool SearchManager::openFile(Common::File &file, const Common::String &name) { + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit != _files.end()) + return file.open(fit->_value.name, *fit->_value.arch); + return false; +} + +bool SearchManager::hasFile(const Common::String &name) { + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit != _files.end()) + return true; + return false; +} + +bool SearchManager::loadZix(const Common::String &name) { + Common::File file; + if (!file.open(name)) + return false; + + Common::String line; + + while (!file.eos()) { + line = file.readLine(); + if (line.matchString("----------*", true)) + break; + } + + if (file.eos()) + error("Corrupt ZIX file: %s", name.c_str()); + + Common::Array<Common::Archive *> archives; + + while (!file.eos()) { + line = file.readLine(); + line.trim(); + if (line.matchString("----------*", true)) + break; + else if (line.matchString("DIR:*", true) || line.matchString("CD0:*", true) || line.matchString("CD1:*", true) || line.matchString("CD2:*", true)) { + Common::Archive *arc; + + Common::String path(line.c_str() + 5); + for (uint i = 0; i < path.size(); i++) + if (path[i] == '\\') + path.setChar('/', i); + + // Check if NEMESIS.ZIX/MEDIUM.ZIX refers to the znemesis folder, and + // check the game root folder instead + if (path.hasPrefix("znemesis/")) + path = Common::String(path.c_str() + 9); + + // Check if INQUIS.ZIX refers to the ZGI folder, and check the game + // root folder instead + if (path.hasPrefix("zgi/")) + path = Common::String(path.c_str() + 4); + if (path.hasPrefix("zgi_e/")) + path = Common::String(path.c_str() + 6); + + if (path.size() && path[0] == '.') + path.deleteChar(0); + if (path.size() && path[0] == '/') + path.deleteChar(0); + if (path.size() && path.hasSuffix("/")) + path.deleteLastChar(); + + // Handle paths in case-sensitive file systems (bug #6775) + if (path.size()) { + for (Common::List<Common::String>::iterator it = _dirList.begin(); it != _dirList.end(); ++it) { + if (path.equalsIgnoreCase(*it)) { + path = *it; + break; + } + } + } + + if (path.matchString("*.zfs", true)) { + arc = new ZfsArchive(path); + } else { + path = Common::String::format("%s/%s", _root.c_str(), path.c_str()); + arc = new Common::FSDirectory(path); + } + archives.push_back(arc); + } + } + + if (file.eos()) + error("Corrupt ZIX file: %s", name.c_str()); + + while (!file.eos()) { + line = file.readLine(); + line.trim(); + uint dr = 0; + char buf[32]; + if (sscanf(line.c_str(), "%u %s", &dr, buf) == 2) { + if (dr <= archives.size() && dr > 0) { + addFile(Common::String(buf), archives[dr - 1]); + } + } + } + + return true; +} + +void SearchManager::addDir(const Common::String &name) { + Common::String path; + for (Common::List<Common::String>::iterator it = _dirList.begin(); it != _dirList.end(); ++it) + if (name.equalsIgnoreCase(*it)) { + path = *it; + break; + } + + if (path.size() == 0) + return; + + path = Common::String::format("%s/%s", _root.c_str(), path.c_str()); + + Common::FSDirectory *dir = new Common::FSDirectory(path); + + Common::ArchiveMemberList list; + dir->listMatchingMembers(list, "*.zfs"); + + for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { + Common::String flname = (*iter)->getName(); + + ZfsArchive *zfs = new ZfsArchive(Common::String::format("%s/%s", name.c_str(), flname.c_str())); + + Common::ArchiveMemberList zfslist; + zfs->listMembers(zfslist); + + for (Common::ArchiveMemberList::iterator ziter = zfslist.begin(); ziter != zfslist.end(); ++ziter) { + Common::String zfsFileName = (*ziter)->getName(); + addFile(zfsFileName, zfs); + } + } + + list.clear(); + dir->listMembers(list); + + for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { + Common::String flname = (*iter)->getName(); + addFile(flname, dir); + } +} + +void SearchManager::listDirRecursive(Common::List<Common::String> &_list, const Common::FSNode &fsNode, int depth) { + Common::FSList fsList; + if (fsNode.getChildren(fsList)) { + + _list.push_back(fsNode.getPath()); + + if (depth > 1) + for (Common::FSList::const_iterator it = fsList.begin(); it != fsList.end(); ++it) + listDirRecursive(_list, *it, depth - 1); + } +} + +void SearchManager::listMembersWithExtension(MatchList &fileList, Common::String extension) { + for (SearchManager::MatchList::iterator it = _files.begin(); it != _files.end(); ++it) { + if (it->_key.hasSuffix(extension)) + fileList[it->_key] = it->_value; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/file/search_manager.h b/engines/zvision/file/search_manager.h new file mode 100644 index 0000000000..0d0ab14d31 --- /dev/null +++ b/engines/zvision/file/search_manager.h @@ -0,0 +1,70 @@ +/* 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 ZVISION_SEARCH_MANAGER_H +#define ZVISION_SEARCH_MANAGER_H + +#include "common/str.h" +#include "common/hash-str.h" +#include "common/hashmap.h" +#include "common/archive.h" +#include "common/file.h" +#include "common/list.h" + +namespace ZVision { + +class SearchManager { +public: + SearchManager(const Common::String &rootPath, int depth); + ~SearchManager(); + + void addFile(const Common::String &name, Common::Archive *arch); + void addDir(const Common::String &name); + + Common::File *openFile(const Common::String &name); + bool openFile(Common::File &file, const Common::String &name); + bool hasFile(const Common::String &name); + + bool loadZix(const Common::String &name); + + struct Node { + Common::String name; + Common::Archive *arch; + }; + + typedef Common::HashMap<Common::String, Node, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> MatchList; + + void listMembersWithExtension(MatchList &fileList, Common::String extension); + +private: + + void listDirRecursive(Common::List<Common::String> &dirList, const Common::FSNode &fsNode, int depth); + + Common::List<Common::String> _dirList; + Common::List<Common::Archive *> _archList; + Common::String _root; + MatchList _files; +}; + +} + +#endif // ZVISION_SEARCH_MANAGER_H diff --git a/engines/zvision/file/zfs_archive.cpp b/engines/zvision/file/zfs_archive.cpp new file mode 100644 index 0000000000..3a385cd8fd --- /dev/null +++ b/engines/zvision/file/zfs_archive.cpp @@ -0,0 +1,155 @@ +/* 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 "common/scummsys.h" +#include "common/memstream.h" +#include "common/debug.h" +#include "common/file.h" + +#include "zvision/file/zfs_archive.h" + +namespace ZVision { + +ZfsArchive::ZfsArchive(const Common::String &fileName) : _fileName(fileName) { + Common::File zfsFile; + memset(&_header, 0, sizeof(_header)); + + if (!zfsFile.open(_fileName)) { + warning("ZFSArchive::ZFSArchive(): Could not find the archive file"); + return; + } + + readHeaders(&zfsFile); + + debug(1, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size()); +} + +ZfsArchive::ZfsArchive(const Common::String &fileName, Common::SeekableReadStream *stream) : _fileName(fileName) { + readHeaders(stream); + + debug(1, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size()); +} + +ZfsArchive::~ZfsArchive() { + debug(1, "ZfsArchive Destructor Called"); + ZfsEntryHeaderMap::iterator it = _entryHeaders.begin(); + for (; it != _entryHeaders.end(); ++it) { + delete it->_value; + } +} + +void ZfsArchive::readHeaders(Common::SeekableReadStream *stream) { + // Don't do a straight struct cast since we can't guarantee endianness + _header.magic = stream->readUint32LE(); + _header.unknown1 = stream->readUint32LE(); + _header.maxNameLength = stream->readUint32LE(); + _header.filesPerBlock = stream->readUint32LE(); + _header.fileCount = stream->readUint32LE(); + _header.xorKey[0] = stream->readByte(); + _header.xorKey[1] = stream->readByte(); + _header.xorKey[2] = stream->readByte(); + _header.xorKey[3] = stream->readByte(); + _header.fileSectionOffset = stream->readUint32LE(); + + uint32 nextOffset; + + do { + // Read the offset to the next block + nextOffset = stream->readUint32LE(); + + // Read in each entry header + for (uint32 i = 0; i < _header.filesPerBlock; ++i) { + ZfsEntryHeader entryHeader; + + entryHeader.name = readEntryName(stream); + entryHeader.offset = stream->readUint32LE(); + entryHeader.id = stream->readUint32LE(); + entryHeader.size = stream->readUint32LE(); + entryHeader.time = stream->readUint32LE(); + entryHeader.unknown = stream->readUint32LE(); + + if (entryHeader.size != 0) + _entryHeaders[entryHeader.name] = new ZfsEntryHeader(entryHeader); + } + + // Seek to the next block of headers + stream->seek(nextOffset); + } while (nextOffset != 0); +} + +Common::String ZfsArchive::readEntryName(Common::SeekableReadStream *stream) const { + // Entry Names are at most 16 bytes and are null padded + char buffer[16]; + stream->read(buffer, 16); + + return Common::String(buffer); +} + +bool ZfsArchive::hasFile(const Common::String &name) const { + return _entryHeaders.contains(name); +} + +int ZfsArchive::listMembers(Common::ArchiveMemberList &list) const { + int matches = 0; + + for (ZfsEntryHeaderMap::const_iterator it = _entryHeaders.begin(); it != _entryHeaders.end(); ++it) { + list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(it->_value->name, this))); + matches++; + } + + return matches; +} + +const Common::ArchiveMemberPtr ZfsArchive::getMember(const Common::String &name) const { + if (!_entryHeaders.contains(name)) + return Common::ArchiveMemberPtr(); + + return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this)); +} + +Common::SeekableReadStream *ZfsArchive::createReadStreamForMember(const Common::String &name) const { + if (!_entryHeaders.contains(name)) { + return 0; + } + + ZfsEntryHeader *entryHeader = _entryHeaders[name]; + + Common::File zfsArchive; + zfsArchive.open(_fileName); + zfsArchive.seek(entryHeader->offset); + + // This *HAS* to be malloc (not new[]) because MemoryReadStream uses free() to free the memory + byte *buffer = (byte *)malloc(entryHeader->size); + zfsArchive.read(buffer, entryHeader->size); + // Decrypt the data in place + if (_header.xorKey[0] + _header.xorKey[1] + _header.xorKey[2] + _header.xorKey[3] != 0) + unXor(buffer, entryHeader->size, _header.xorKey); + + return new Common::MemoryReadStream(buffer, entryHeader->size, DisposeAfterUse::YES); +} + +void ZfsArchive::unXor(byte *buffer, uint32 length, const byte *xorKey) const { + for (uint32 i = 0; i < length; ++i) + buffer[i] ^= xorKey[i % 4]; +} + +} // End of namespace ZVision diff --git a/engines/zvision/file/zfs_archive.h b/engines/zvision/file/zfs_archive.h new file mode 100644 index 0000000000..fe0221416d --- /dev/null +++ b/engines/zvision/file/zfs_archive.h @@ -0,0 +1,125 @@ +/* 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 ZVISION_ZFS_ARCHIVE_H +#define ZVISION_ZFS_ARCHIVE_H + +#include "common/archive.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +namespace Common { +class String; +} + +namespace ZVision { + +struct ZfsHeader { + uint32 magic; + uint32 unknown1; + uint32 maxNameLength; + uint32 filesPerBlock; + uint32 fileCount; + uint8 xorKey[4]; + uint32 fileSectionOffset; +}; + +struct ZfsEntryHeader { + Common::String name; + uint32 offset; + uint32 id; + uint32 size; + uint32 time; + uint32 unknown; +}; + +typedef Common::HashMap<Common::String, ZfsEntryHeader *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ZfsEntryHeaderMap; + +class ZfsArchive : public Common::Archive { +public: + ZfsArchive(const Common::String &fileName); + ZfsArchive(const Common::String &fileName, Common::SeekableReadStream *stream); + ~ZfsArchive(); + + /** + * Check if a member with the given name is present in the Archive. + * Patterns are not allowed, as this is meant to be a quick File::exists() + * replacement. + */ + bool hasFile(const Common::String &fileName) const; + + /** + * Add all members of the Archive to list. + * Must only append to list, and not remove elements from it. + * + * @return The number of names added to list + */ + int listMembers(Common::ArchiveMemberList &list) const; + + /** + * Returns a ArchiveMember representation of the given file. + */ + const Common::ArchiveMemberPtr getMember(const Common::String &name) const; + + /** + * Create a stream bound to a member with the specified name in the + * archive. If no member with this name exists, 0 is returned. + * + * @return The newly created input stream + */ + Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; + +private: + const Common::String _fileName; + ZfsHeader _header; + ZfsEntryHeaderMap _entryHeaders; + + /** + * Parses the zfs file into file entry headers that can be used later + * to get the entry data. + * + * @param stream The contents of the zfs file + */ + void readHeaders(Common::SeekableReadStream *stream); + + /** + * Entry names are contained within a 16 byte block. This reads the block + * and converts it the name to a Common::String + * + * @param stream The zfs file stream + * @return The entry file name + */ + Common::String readEntryName(Common::SeekableReadStream *stream) const; + + /** + * ZFS file entries can be encrypted using XOR encoding. This method + * decodes the buffer in place using the supplied xorKey. + * + * @param buffer The data to decode + * @param length Length of buffer + */ + void unXor(byte *buffer, uint32 length, const byte *xorKey) const; +}; + +} // End of namespace ZVision + +#endif |