Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "zvision/file/file_manager.h"
#include "zvision/file/zfs_archive.h"
namespace ZVision {
const char* genExcluded[] {"*.dll", "*.ini", "*.exe", "*.isu", "*.inf", "*path*.txt", "r.svr", "*.zix", "*.hlp", "*.gid"};
const char* zgiAlternates[] {
"c000h01q.raw", "cm00h01q.raw", "dm00h01q.raw", "e000h01q.raw", "em00h11p.raw", "em00h50q.raw", "gjnph65p.raw",
"gjnph72p.raw", "h000h01q.raw", "m000h01q.raw", "p000h01q.raw", "q000h01q.raw", "sw00h01q.raw", "t000h01q.raw",
"u000h01q.raw"
};
FileManager::FileManager(ZVision *engine) {
}
Common::File *FileManager::open(const Common::Path &filePath, bool allowSrc) {
debugC(5, kDebugFile, "FileManager::open()");
Common::File *file = new Common::File();
Common::File *out = nullptr;
Common::String fileName = filePath.baseName();
bool open = false;
bool altFound = false;
bool altOpen = false;
bool found = SearchMan.hasFile(filePath);
if(found) {
debugC(5, kDebugFile,"File %s found", fileName.c_str());
open = file->open(filePath);
if(open)
debugC(5, kDebugFile,"File %s opened", fileName.c_str());
}
if (allowSrc) {
Common::File *altFile = new Common::File();
Common::String altName = fileName;
altName.setChar('s', altName.size() - 3);
altName.setChar('r', altName.size() - 2);
altName.setChar('c', altName.size() - 1);
Common::Path altPath = filePath.getParent().appendComponent(altName);
altFound = SearchMan.hasFile(altPath);
if (altFound) {
debugC(5, kDebugFile,"Alternate file %s found", altName.c_str());
altOpen = altFile->open(altPath);
if (altOpen)
debugC(5, kDebugFile,"Alternate file %s opened", altName.c_str());
}
if(altOpen) {
if(open)
out = file->size() < altFile->size() ? altFile : file;
else
out = altFile;
}
else if(open)
out = file;
else {
if (found && altFound)
warning("Found file %s and alternate file %s but unable to open either", fileName.c_str(), altName.c_str());
else if (found)
warning("Found file %s but unable to open; alternate file %s not found", fileName.c_str(), altName.c_str());
else if (altFound)
warning("File %s not found; alternate file %s found but but unable to open", fileName.c_str(), altName.c_str());
else
warning("Unable to find file %s or alternate file %s", fileName.c_str(), altName.c_str());
}
if (out == altFile)
debugC(5, kDebugFile,"Returning alternate file %s", altName.c_str());
else {
if(altOpen)
altFile->close();
delete altFile;
}
}
else {
if(open)
out = file;
else if (found)
warning("File %s found, but unable to open", fileName.c_str());
else
warning("File %s not found", fileName.c_str());
}
if (out == file)
debugC(5, kDebugFile,"Returning file %s", fileName.c_str());
else {
if(open)
file->close();
delete file;
}
return out;
}
bool FileManager::exists(Common::Path filePath, bool allowSrc) {
Common::File file;
if (file.exists(filePath))
return true;
else if (allowSrc) {
if (file.exists(srcPath(filePath)))
return true;
}
return false;
}
Common::Path FileManager::srcPath(Common::Path filePath) {
Common::String name = filePath.baseName();
name.setChar('s', name.size() - 3);
name.setChar('r', name.size() - 2);
name.setChar('c', name.size() - 1);
return filePath.getParent().appendComponent(name);
}
bool FileManager::loadZix(const Common::Path &zixPath, const Common::FSNode &gameDataDir) {
Common::File zixFile;
if (!zixFile.open(zixPath))
return false;
Common::String line;
// Skip first block
while (!zixFile.eos()) {
line = zixFile.readLine();
if (line.matchString("----------*", true))
break;
}
if (zixFile.eos())
error("Corrupt ZIX file: %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
uint8 archives = 0;
// Parse subdirectories & archives
debugC(1, kDebugFile, "Parsing list of subdirectories & archives in %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
while (!zixFile.eos()) {
line = zixFile.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)) {
line = Common::String(line.c_str() + 5);
for (uint i = 0; i < line.size(); i++)
if (line[i] == '\\')
line.setChar('/', i);
// Check if NEMESIS.ZIX/MEDIUM.ZIX refers to the znemesis folder, and
// check the game root folder instead
if (line.hasPrefix("znemesis/"))
line = Common::String(line.c_str() + 9);
// Check if INQUIS.ZIX refers to the ZGI folder, and check the game
// root folder instead
if (line.hasPrefix("zgi/"))
line = Common::String(line.c_str() + 4);
if (line.hasPrefix("zgi_e/"))
line = Common::String(line.c_str() + 6);
if (line.size() && line[0] == '.')
line.deleteChar(0);
if (line.size() && line[0] == '/')
line.deleteChar(0);
if (line.size() && line.hasSuffix("/"))
line.deleteLastChar();
Common::Path path(line, '/');
if (line.matchString("*.zfs", true)) {
if (!SearchMan.hasArchive(line)) {
path = path.getLastComponent(); //We are using the search manager in "flat" mode, so only filenames are needed
debugC(1, kDebugFile, "Adding archive %s to search manager.", path.toString().c_str());
Common::Archive *arc;
arc = new ZfsArchive(path);
SearchMan.add(line, arc);
}
}
else {
debugC(1, kDebugFile, "Adding directory %s to search manager.", path.toString().c_str());
SearchMan.addSubDirectoryMatching(gameDataDir,path.toString());
}
archives++;
}
}
if (zixFile.eos())
error("Corrupt ZIX file: %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
//Parse files
debugC(1, kDebugFile, "Parsing list of individual resource files in %s", zixPath.toString(Common::Path::kNativeSeparator).c_str());
while (!zixFile.eos()) {
line = zixFile.readLine();
line.trim();
uint dr = 0;
char buf[32];
if (sscanf(line.c_str(), "%u %s", &dr, buf) == 2) {
if (dr <= archives && dr > 0) {
Common::String name(buf);
bool exclude = false;
bool allowSrc = false;
for (auto excName : genExcluded)
if(name.matchString(excName, true)) {
exclude = true;
break;
}
for (auto altName : zgiAlternates)
if(name.matchString(altName, true)) {
allowSrc = true;
break;
}
if (!exclude) {
Common::Path path(name);
// No need to add file, just verify that it exists
if (allowSrc) {
Common::Path altPath = srcPath(path);
if (!SearchMan.hasFile(path) && !SearchMan.hasFile(altPath))
warning("Missing files %s and/or %s", path.toString().c_str(), altPath.toString().c_str());
else if (SearchMan.hasFile(path))
debugC(5, kDebugFile, "File found: %s", path.toString().c_str());
else
debugC(5, kDebugFile, "Alternate file found: %s", altPath.toString().c_str());
}
else {
if (!SearchMan.hasFile(path))
warning("Missing file %s", path.toString().c_str());
else
debugC(5, kDebugFile, "File found: %s", path.toString().c_str());
}
if (name.matchString("*.zfs", true))
if (!SearchMan.hasArchive(name)) {
Common::Path path_(path);
debugC(kDebugFile, "Adding archive %s to search manager.", path.toString().c_str());
Common::Archive *arc;
arc = new ZfsArchive(path_);
SearchMan.add(name, arc);
}
}
}
}
}
return true;
}
} // End of namespace Zvision

View File

@@ -0,0 +1,46 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/path.h"
#include "zvision/zvision.h"
#ifndef ZVISION_FILE_MANAGER
#define ZVISION_FILE_MANAGER
namespace ZVision {
class FileManager {
public:
FileManager(ZVision *engine);
~FileManager() {};
bool loadZix(const Common::Path &zixPath, const Common::FSNode &gameDataDir);
Common::File *open(const Common::Path &fileName, bool allowSrc=true); // Wrapper to automatically handle loading of files which may be empty & have an alternate .src file
bool exists(Common::Path filePath, bool allowSrc=true); // Wrapper to automatically handle checking existence of files which may be empty & have an alternate .src file
private:
Common::Path srcPath(Common::Path filePath);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,101 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_LZSS_STREAM_H
#define ZVISION_LZSS_STREAM_H
#include "common/array.h"
#include "common/stream.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 override;
uint32 read(void *dataPtr, uint32 dataSize) override;
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

View File

@@ -0,0 +1,275 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/file.h"
#include "common/scummsys.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"
#include "zvision/zvision.h"
#include "zvision/file/save_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.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 didn't 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) {
Common::ErrorCode result = loadGame(slot).getCode();
return (result == Common::kNoError);
}
saveGame(slot, desc, false);
return true;
}
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->getSaveStateName(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::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);
file->writeUint32LE(g_engine->getTotalPlayTime() / 1000);
}
Common::Error SaveManager::loadGame(int slot) {
Common::SeekableReadStream *saveFile = NULL;
if (slot < 0) {
// Restart game, used by ZGI death screen only
_engine->getScriptManager()->initialize(true);
return Common::kNoError;
}
saveFile = getSlotFile(slot);
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 (_engine->getGameId() == GID_NEMESIS) {
// Zork Nemesis has no in-game option to select panorama quality or animation options
// We set them here to ensure loaded games don't override current game configuration
scriptManager->setStateValue(StateKey_HighQuality, ConfMan.getBool("highquality"));
scriptManager->setStateValue(StateKey_NoTurnAnim, ConfMan.getBool("noanimwhileturning"));
if (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);
}
}
}
g_engine->setTotalPlayTime(header.playTime * 1000);
return Common::kNoError;
}
bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header, bool skipThumbnail) {
header.saveYear = 0;
header.saveMonth = 0;
header.saveDay = 0;
header.saveHour = 0;
header.saveMinutes = 0;
header.playTime = 0;
header.saveName.clear();
header.thumbnail = nullptr;
header.version = 0;
uint32 tag = in->readUint32BE();
// Check if it's original savegame than fill header structure
if (tag == MKTAG('Z', 'N', 'S', 'G')) {
header.saveName = "Original Save";
header.version = SAVE_ORIGINAL;
in->seek(-4, SEEK_CUR);
return true;
}
if (tag != SAVEGAME_ID) {
warning("File is not a Z-Vision saved game. 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::U32String::format(
_("This saved game uses version %u, but this engine only "
"supports up to version %d. You will need an updated version "
"of the engine to use this saved game."), tempVersion, SAVE_VERSION
));
dialog.runModal();
}
// Read in the save name
char ch;
while ((ch = (char)in->readByte()) != '\0')
header.saveName += ch;
// Get the thumbnail
if (!Graphics::loadThumbnail(*in, header.thumbnail, skipThumbnail)) {
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();
if (header.version >= 2) {
header.playTime = in->readUint32LE();
}
return true;
}
Common::SeekableReadStream *SaveManager::getSlotFile(uint slot) {
Common::SeekableReadStream *saveFile = g_system->getSavefileManager()->openForLoading(_engine->getSaveStateName(slot));
if (saveFile == NULL) {
// Try to load standard save file
Common::Path filename;
if (_engine->getGameId() == GID_GRANDINQUISITOR)
filename = Common::Path(Common::String::format("inqsav%u.sav", slot));
else if (_engine->getGameId() == GID_NEMESIS)
filename = Common::Path(Common::String::format("nemsav%u.sav", slot));
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(DisposeAfterUse::YES);
Graphics::saveThumbnail(*_tempThumbnail);
delete _tempSave;
_tempSave = new Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
_engine->getScriptManager()->serialize(_tempSave);
}
void SaveManager::flushSaveBuffer() {
delete _tempThumbnail;
_tempThumbnail = NULL;
delete _tempSave;
_tempSave = NULL;
}
} // End of namespace ZVision

View File

@@ -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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SAVE_MANAGER_H
#define ZVISION_SAVE_MANAGER_H
#include "common/memstream.h"
#include "common/savefile.h"
namespace Common {
class String;
}
namespace Graphics {
struct Surface;
}
namespace ZVision {
class ZVision;
struct SaveGameHeader {
byte version;
Common::String saveName;
Graphics::Surface *thumbnail;
int16 saveYear, saveMonth, saveDay;
int16 saveHour, saveMinutes;
uint32 playTime;
};
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 = 2
};
Common::MemoryWriteStreamDynamic *_tempThumbnail;
Common::MemoryWriteStreamDynamic *_tempSave;
public:
/**
* 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, bool skipThumbnail = true);
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

View File

@@ -0,0 +1,156 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/scummsys.h"
#include "zvision/detection.h"
#include "zvision/file/zfs_archive.h"
namespace ZVision {
ZfsArchive::ZfsArchive(const Common::Path &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);
debugC(1, kDebugFile, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.toString(Common::Path::kNativeSeparator).c_str(), _entryHeaders.size());
}
ZfsArchive::ZfsArchive(const Common::Path &fileName, Common::SeekableReadStream *stream) : _fileName(fileName) {
readHeaders(stream);
debugC(1, kDebugFile, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.toString(Common::Path::kNativeSeparator).c_str(), _entryHeaders.size());
}
ZfsArchive::~ZfsArchive() {
debugC(1, kDebugFile, "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::Path &path) const {
Common::String name = path.toString();
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::Path &path) const {
if (!hasFile(path))
return Common::ArchiveMemberPtr();
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
}
Common::SeekableReadStream *ZfsArchive::createReadStreamForMember(const Common::Path &path) const {
Common::String name = path.toString();
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

View File

@@ -0,0 +1,124 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#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::Path &fileName);
ZfsArchive(const Common::Path &fileName, Common::SeekableReadStream *stream);
~ZfsArchive() override;
/**
* 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::Path &path) const override;
/**
* 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 override;
/**
* Returns a ArchiveMember representation of the given file.
*/
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
/**
* 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::Path &path) const override;
private:
const Common::Path _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