aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWillem Jan Palenstijn2016-07-02 21:19:29 +0200
committerWillem Jan Palenstijn2016-07-06 21:06:12 +0200
commitd832a232411e3f931fba41b1a92155264156d1a5 (patch)
treea6ef9f51581b04b5aca9decd2d101d54bb0fff55
parente243147f1ea8715b06312d66c6b48d473feb3cdc (diff)
downloadscummvm-rg350-d832a232411e3f931fba41b1a92155264156d1a5.tar.gz
scummvm-rg350-d832a232411e3f931fba41b1a92155264156d1a5.tar.bz2
scummvm-rg350-d832a232411e3f931fba41b1a92155264156d1a5.zip
SCI32: Add SaveFileRewriteStream for read/write access to files
At least Phantasmagoria and PQ:SWAT require this. This adds a ReadStream/WriteStream to support this. It replaces the previous VirtualIndexFile which required reimplementation of most read/write functions.
-rw-r--r--engines/sci/engine/file.cpp261
-rw-r--r--engines/sci/engine/file.h46
-rw-r--r--engines/sci/engine/kfile.cpp109
-rw-r--r--engines/sci/engine/state.cpp6
-rw-r--r--engines/sci/engine/state.h4
5 files changed, 158 insertions, 268 deletions
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 0b1001bfda..156f6f51f7 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -22,6 +22,7 @@
#include "common/savefile.h"
#include "common/stream.h"
+#include "common/memstream.h"
#include "sci/sci.h"
#include "sci/engine/file.h"
@@ -32,6 +33,112 @@
namespace Sci {
+#ifdef ENABLE_SCI32
+/**
+ * A MemoryWriteStreamDynamic with additional read functionality.
+ * The read and write functions share a single stream position.
+ */
+class MemoryDynamicRWStream : public Common::MemoryWriteStreamDynamic, public Common::SeekableReadStream {
+protected:
+ bool _eos;
+public:
+ MemoryDynamicRWStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : MemoryWriteStreamDynamic(disposeMemory), _eos(false) { }
+
+ uint32 read(void *dataPtr, uint32 dataSize);
+
+ bool eos() const { return _eos; }
+ int32 pos() const { return _pos; }
+ int32 size() const { return _size; }
+ void clearErr() { _eos = false; Common::MemoryWriteStreamDynamic::clearErr(); }
+ bool seek(int32 offs, int whence = SEEK_SET) { return Common::MemoryWriteStreamDynamic::seek(offs, whence); }
+
+};
+
+uint32 MemoryDynamicRWStream::read(void *dataPtr, uint32 dataSize)
+{
+ // Read at most as many bytes as are still available...
+ if (dataSize > _size - _pos) {
+ dataSize = _size - _pos;
+ _eos = true;
+ }
+ memcpy(dataPtr, _ptr, dataSize);
+
+ _ptr += dataSize;
+ _pos += dataSize;
+
+ return dataSize;
+}
+
+/**
+ * A MemoryDynamicRWStream intended to re-write a file.
+ * It reads the contents of `inFile` in the constructor, and writes back
+ * the changes to `fileName` in the destructor (and when calling commit() ).
+ */
+class SaveFileRewriteStream : public MemoryDynamicRWStream {
+public:
+ SaveFileRewriteStream(Common::String fileName,
+ Common::SeekableReadStream *inFile,
+ bool truncate, bool compress);
+ virtual ~SaveFileRewriteStream();
+
+ virtual uint32 write(const void *dataPtr, uint32 dataSize) { _changed = true; return MemoryDynamicRWStream::write(dataPtr, dataSize); }
+
+ void commit(); //< Save back to disk
+
+protected:
+ Common::String _fileName;
+ bool _compress;
+ bool _changed;
+};
+
+SaveFileRewriteStream::SaveFileRewriteStream(Common::String fileName,
+ Common::SeekableReadStream *inFile,
+ bool truncate,
+ bool compress)
+: MemoryDynamicRWStream(DisposeAfterUse::YES),
+ _fileName(fileName), _compress(compress)
+{
+ if (!truncate && inFile) {
+ unsigned int s = inFile->size();
+ ensureCapacity(s);
+ inFile->read(_data, s);
+ _changed = false;
+ } else {
+ _changed = true;
+ }
+}
+
+SaveFileRewriteStream::~SaveFileRewriteStream() {
+ commit();
+}
+
+void SaveFileRewriteStream::commit() {
+ // Write contents of buffer back to file
+
+ if (_changed) {
+ Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName, _compress);
+ outFile->write(_data, _size);
+ delete outFile;
+ _changed = false;
+ }
+}
+
+#endif
+
+uint findFreeFileHandle(EngineState *s) {
+ // Find a free file handle
+ uint handle = 1; // Ignore _fileHandles[0]
+ while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
+ handle++;
+
+ if (handle == s->_fileHandles.size()) {
+ // Hit size limit => Allocate more space
+ s->_fileHandles.resize(s->_fileHandles.size() + 1);
+ }
+
+ return handle;
+}
+
/*
* Note on how file I/O is implemented: In ScummVM, one can not create/write
* arbitrary data files, simply because many of our target platforms do not
@@ -91,6 +198,27 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u
break;
}
+#ifdef ENABLE_SCI32
+ if (mode != _K_FILE_MODE_OPEN_OR_FAIL && (
+ (g_sci->getGameId() == GID_PHANTASMAGORIA && filename == "phantsg.dir") ||
+ (g_sci->getGameId() == GID_PQSWAT && filename == "swat.dat"))) {
+ debugC(kDebugLevelFile, " -> file_open opening %s for rewriting", wrappedName.c_str());
+
+ inFile = saveFileMan->openForLoading(wrappedName);
+ // If no matching savestate exists: fall back to reading from a regular
+ // file
+ if (!inFile)
+ inFile = SearchMan.createReadStreamForMember(englishName);
+
+ SaveFileRewriteStream *stream;
+ stream = new SaveFileRewriteStream(wrappedName, inFile, mode == _K_FILE_MODE_CREATE, isCompressed);
+
+ delete inFile;
+
+ inFile = stream;
+ outFile = stream;
+ } else
+#endif
if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
// Try to open file, abort if not possible
inFile = saveFileMan->openForLoading(wrappedName);
@@ -126,15 +254,7 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u
return SIGNAL_REG;
}
- // Find a free file handle
- uint handle = 1; // Ignore _fileHandles[0]
- while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
- handle++;
-
- if (handle == s->_fileHandles.size()) {
- // Hit size limit => Allocate more space
- s->_fileHandles.resize(s->_fileHandles.size() + 1);
- }
+ uint handle = findFreeFileHandle(s);
s->_fileHandles[handle]._in = inFile;
s->_fileHandles[handle]._out = outFile;
@@ -252,8 +372,12 @@ FileHandle::~FileHandle() {
}
void FileHandle::close() {
- delete _in;
- delete _out;
+ // NB: It is possible _in and _out are both non-null, but
+ // then they point to the same object.
+ if (_in)
+ delete _in;
+ else
+ delete _out;
_in = 0;
_out = 0;
_name.clear();
@@ -365,119 +489,4 @@ reg_t DirSeeker::nextFile(SegManager *segMan) {
return _outbuffer;
}
-
-#ifdef ENABLE_SCI32
-
-VirtualIndexFile::VirtualIndexFile(Common::String fileName) : _fileName(fileName), _changed(false) {
- Common::SeekableReadStream *inFile = g_sci->getSaveFileManager()->openForLoading(fileName);
-
- _bufferSize = inFile->size();
- _buffer = new char[_bufferSize];
- inFile->read(_buffer, _bufferSize);
- _ptr = _buffer;
- delete inFile;
-}
-
-VirtualIndexFile::VirtualIndexFile(uint32 initialSize) : _changed(false) {
- _bufferSize = initialSize;
- _buffer = new char[_bufferSize];
- _ptr = _buffer;
-}
-
-VirtualIndexFile::~VirtualIndexFile() {
- close();
-
- _bufferSize = 0;
- delete[] _buffer;
- _buffer = 0;
-}
-
-uint32 VirtualIndexFile::read(char *buffer, uint32 size) {
- uint32 curPos = _ptr - _buffer;
- uint32 finalSize = MIN<uint32>(size, _bufferSize - curPos);
- char *localPtr = buffer;
-
- for (uint32 i = 0; i < finalSize; i++)
- *localPtr++ = *_ptr++;
-
- return finalSize;
-}
-
-uint32 VirtualIndexFile::write(const char *buffer, uint32 size) {
- _changed = true;
- uint32 curPos = _ptr - _buffer;
-
- // Check if the buffer needs to be resized
- if (curPos + size >= _bufferSize) {
- _bufferSize = curPos + size + 1;
- char *tmp = _buffer;
- _buffer = new char[_bufferSize];
- _ptr = _buffer + curPos;
- memcpy(_buffer, tmp, _bufferSize);
- delete[] tmp;
- }
-
- for (uint32 i = 0; i < size; i++)
- *_ptr++ = *buffer++;
-
- return size;
-}
-
-uint32 VirtualIndexFile::readLine(char *buffer, uint32 size) {
- uint32 startPos = _ptr - _buffer;
- uint32 bytesRead = 0;
- char *localPtr = buffer;
-
- // This is not a full-blown implementation of readLine, but it
- // suffices for Phantasmagoria
- while (startPos + bytesRead < size) {
- bytesRead++;
-
- if (*_ptr == 0 || *_ptr == 0x0A) {
- _ptr++;
- *localPtr = 0;
- return bytesRead;
- } else {
- *localPtr++ = *_ptr++;
- }
- }
-
- return bytesRead;
-}
-
-bool VirtualIndexFile::seek(int32 offset, int whence) {
- uint32 startPos = _ptr - _buffer;
- assert(offset >= 0);
-
- switch (whence) {
- case SEEK_CUR:
- assert(startPos + offset < _bufferSize);
- _ptr += offset;
- break;
- case SEEK_SET:
- assert(offset < (int32)_bufferSize);
- _ptr = _buffer + offset;
- break;
- case SEEK_END:
- assert((int32)_bufferSize - offset >= 0);
- _ptr = _buffer + (_bufferSize - offset);
- break;
- }
-
- return true;
-}
-
-void VirtualIndexFile::close() {
- if (_changed && !_fileName.empty()) {
- Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName);
- outFile->write(_buffer, _bufferSize);
- delete outFile;
- }
-
- // Maintain the buffer, and seek to the beginning of it
- _ptr = _buffer;
-}
-
-#endif
-
} // End of namespace Sci
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index 54627d5228..982d7b7823 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -43,7 +43,6 @@ enum {
#define VIRTUALFILE_HANDLE_START 32000
#define VIRTUALFILE_HANDLE_SCI32SAVE 32100
-#define PHANTASMAGORIA_SAVEGAME_INDEX "phantsg.dir"
#define VIRTUALFILE_HANDLE_SCIAUDIO 32300
#define VIRTUALFILE_HANDLE_END 32300
@@ -93,50 +92,7 @@ private:
void addAsVirtualFiles(Common::String title, Common::String fileMask);
};
-
-#ifdef ENABLE_SCI32
-
-/**
- * An implementation of a virtual file that supports basic read and write
- * operations simultaneously.
- *
- * This class has been initially implemented for Phantasmagoria, which has its
- * own custom save/load code. The load code keeps checking for the existence
- * of the save index file and keeps closing and reopening it for each save
- * slot. This is notoriously slow and clumsy, and introduces noticeable delays,
- * especially for non-desktop systems. Also, its game scripts request to open
- * the index file for reading and writing with the same parameters
- * (SaveManager::setCurrentSave and SaveManager::getCurrentSave). Moreover,
- * the game scripts reopen the index file for writing in order to update it
- * and seek within it. We do not support seeking in writeable streams, and the
- * fact that our saved games are ZIP files makes this operation even more
- * expensive. Finally, the savegame index file is supposed to be expanded when
- * a new save slot is added.
- * For the aforementioned reasons, this class has been implemented, which offers
- * the basic functionality needed by the game scripts in Phantasmagoria.
- */
-class VirtualIndexFile {
-public:
- VirtualIndexFile(Common::String fileName);
- VirtualIndexFile(uint32 initialSize);
- ~VirtualIndexFile();
-
- uint32 read(char *buffer, uint32 size);
- uint32 readLine(char *buffer, uint32 size);
- uint32 write(const char *buffer, uint32 size);
- bool seek(int32 offset, int whence);
- void close();
-
-private:
- char *_buffer;
- uint32 _bufferSize;
- char *_ptr;
-
- Common::String _fileName;
- bool _changed;
-};
-
-#endif
+uint findFreeFileHandle(EngineState *s);
} // End of namespace Sci
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index d604bb85d0..4508a481a0 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -29,6 +29,7 @@
#include "common/savefile.h"
#include "common/system.h"
#include "common/translation.h"
+#include "common/memstream.h"
#include "gui/saveload.h"
@@ -263,19 +264,6 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
- if (name == PHANTASMAGORIA_SAVEGAME_INDEX) {
- if (s->_virtualIndexFile) {
- return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
- } else {
- Common::String englishName = g_sci->getSciLanguageString(name, K_LANG_ENGLISH);
- Common::String wrappedName = g_sci->wrapFilename(englishName);
- if (!g_sci->getSaveFileManager()->listSavefiles(wrappedName).empty()) {
- s->_virtualIndexFile = new VirtualIndexFile(wrappedName);
- return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
- }
- }
- }
-
// Shivers is trying to store savegame descriptions and current spots in
// separate .SG files, which are hardcoded in the scripts.
// Essentially, there is a normal save file, created by the executable
@@ -313,18 +301,18 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
listSavegames(saves);
int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START);
- if (!s->_virtualIndexFile) {
- // Make the virtual file buffer big enough to avoid having it grow dynamically.
- // 50 bytes should be more than enough.
- s->_virtualIndexFile = new VirtualIndexFile(50);
- }
+ int size = strlen(saves[savegameNr].name) + 2;
+ char *buf = (char *)malloc(size);
+ strcpy(buf, saves[savegameNr].name);
+ buf[size - 1] = 0; // Spot description (empty)
- s->_virtualIndexFile->seek(0, SEEK_SET);
- s->_virtualIndexFile->write(saves[savegameNr].name, strlen(saves[savegameNr].name));
- s->_virtualIndexFile->write("\0", 1);
- s->_virtualIndexFile->write("\0", 1); // Spot description (empty)
- s->_virtualIndexFile->seek(0, SEEK_SET);
- return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ uint handle = findFreeFileHandle(s);
+
+ s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buf, size, DisposeAfterUse::YES);
+ s->_fileHandles[handle]._out = nullptr;
+ s->_fileHandles[handle]._name = "";
+
+ return make_reg(0, handle);
}
}
#endif
@@ -349,13 +337,6 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
uint16 handle = argv[0].toUint16();
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- s->_virtualIndexFile->close();
- return SIGNAL_REG;
- }
-#endif
-
if (handle >= VIRTUALFILE_HANDLE_START) {
// it's a virtual handle? ignore it
return SIGNAL_REG;
@@ -381,17 +362,9 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) {
char *buf = new char[size];
debugC(kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size);
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- bytesRead = s->_virtualIndexFile->read(buf, size);
- } else {
-#endif
- FileHandle *f = getFileFromHandle(s, handle);
- if (f)
- bytesRead = f->_in->read(buf, size);
-#ifdef ENABLE_SCI32
- }
-#endif
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (f)
+ bytesRead = f->_in->read(buf, size);
// TODO: What happens if less bytes are read than what has
// been requested? (i.e. if bytesRead is non-zero, but still
@@ -411,20 +384,11 @@ reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) {
s->_segMan->memcpy((byte *)buf, argv[1], size);
debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size);
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- s->_virtualIndexFile->write(buf, size);
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (f) {
+ f->_out->write(buf, size);
success = true;
- } else {
-#endif
- FileHandle *f = getFileFromHandle(s, handle);
- if (f) {
- f->_out->write(buf, size);
- success = true;
- }
-#ifdef ENABLE_SCI32
}
-#endif
delete[] buf;
if (success)
@@ -463,13 +427,6 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
const Common::String wrappedName = g_sci->wrapFilename(name);
result = saveFileMan->removeSavefile(wrappedName);
}
-
-#ifdef ENABLE_SCI32
- if (name == PHANTASMAGORIA_SAVEGAME_INDEX) {
- delete s->_virtualIndexFile;
- s->_virtualIndexFile = 0;
- }
-#endif
} else {
const Common::String wrappedName = g_sci->wrapFilename(name);
result = saveFileMan->removeSavefile(wrappedName);
@@ -488,12 +445,7 @@ reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, maxsize);
uint32 bytesRead;
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE)
- bytesRead = s->_virtualIndexFile->readLine(buf, maxsize);
- else
-#endif
- bytesRead = fgets_wrapper(s, buf, maxsize, handle);
+ bytesRead = fgets_wrapper(s, buf, maxsize, handle);
s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize);
delete[] buf;
@@ -520,13 +472,6 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
- s->_virtualIndexFile->write(str.c_str(), str.size());
- return NULL_REG;
- }
-#endif
-
FileHandle *f = getFileFromHandle(s, handle);
if (f) {
@@ -547,11 +492,6 @@ reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) {
uint16 whence = argv[2].toUint16();
debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence);
-#ifdef ENABLE_SCI32
- if (handle == VIRTUALFILE_HANDLE_SCI32SAVE)
- return make_reg(0, s->_virtualIndexFile->seek(offset, whence));
-#endif
-
FileHandle *f = getFileFromHandle(s, handle);
if (f && f->_in) {
@@ -591,14 +531,6 @@ reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
-#ifdef ENABLE_SCI32
- // Cache the file existence result for the Phantasmagoria
- // save index file, as the game scripts keep checking for
- // its existence.
- if (name == PHANTASMAGORIA_SAVEGAME_INDEX && s->_virtualIndexFile)
- return TRUE_REG;
-#endif
-
bool exists = false;
if (g_sci->getGameId() == GID_PEPPER) {
@@ -611,6 +543,9 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+ // TODO: It may apparently be worth caching the existence of
+ // phantsg.dir, and possibly even keeping it open persistently
+
// Check for regular file
exists = Common::File::exists(name);
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index fda78317b5..2c85907628 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -70,9 +70,6 @@ static const uint16 s_halfWidthSJISMap[256] = {
EngineState::EngineState(SegManager *segMan)
: _segMan(segMan),
-#ifdef ENABLE_SCI32
- _virtualIndexFile(0),
-#endif
_dirseeker() {
reset(false);
@@ -80,9 +77,6 @@ EngineState::EngineState(SegManager *segMan)
EngineState::~EngineState() {
delete _msgState;
-#ifdef ENABLE_SCI32
- delete _virtualIndexFile;
-#endif
}
void EngineState::reset(bool isRestoring) {
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index cf9a753f5c..baca4a503b 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -131,10 +131,6 @@ public:
int16 _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween
int16 _lastSaveNewId; // last newly created filename-id by kSaveGame
-#ifdef ENABLE_SCI32
- VirtualIndexFile *_virtualIndexFile;
-#endif
-
// see detection.cpp / SciEngine::loadGameState()
bool _delayedRestoreGame; // boolean, that triggers delayed restore (triggered by ScummVM menu)
int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu)