mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-16 06:49:58 +00:00
caa5136b5c
svn-id: r45502
914 lines
28 KiB
C++
914 lines
28 KiB
C++
/* 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/archive.h"
|
|
#include "common/file.h"
|
|
#include "common/str.h"
|
|
#include "common/savefile.h"
|
|
|
|
#include "sci/sci.h"
|
|
#include "sci/engine/state.h"
|
|
#include "sci/engine/kernel.h"
|
|
#include "sci/engine/savegame.h"
|
|
|
|
namespace Sci {
|
|
|
|
enum {
|
|
MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */
|
|
};
|
|
|
|
struct SavegameDesc {
|
|
int id;
|
|
int date;
|
|
int time;
|
|
};
|
|
|
|
/*
|
|
* 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
|
|
* support this. The only files on can create are savestates. But SCI has an
|
|
* opcode to create and write to seemingly 'arbitrary' files.
|
|
* To implement that opcode, we combine the SaveFileManager with regular file
|
|
* code, similarly to how the SCUMM HE engine does it.
|
|
*
|
|
* To handle opening a file called "foobar", what we do is this: First, we
|
|
* create an 'augmented file name', by prepending the game target and a dash,
|
|
* so if we running game target sq1sci, the name becomes "sq1sci-foobar".
|
|
* Next, we check if such a file is known to the SaveFileManager. If so, we
|
|
* we use that for reading/writing, delete it, whatever.
|
|
*
|
|
* If no such file is present but we were only asked to *read* the file,
|
|
* we fallback to looking for a regular file called "foobar", and open that
|
|
* for reading only.
|
|
*
|
|
* There are some caveats to this: First off, SCI apparently has no way
|
|
* to signal that a file is supposed to be opened for reading only. For now,
|
|
* we hackishly just assume that this is what _K_FILE_MODE_OPEN_OR_FAIL is for.
|
|
*
|
|
* Secondly, at least in theory, a file could be opened for both reading and
|
|
* writing. We currently do not support this. If it turns out that we *have*
|
|
* to support it, we could do it as follows: Initially open the file for
|
|
* reading. If a write is attempted, store the file offset, close the file,
|
|
* if necessary create a mirror clone (i.e., clone it into a suitably named
|
|
* savefile), then open the file (resp. its clone for writing) and seek to the
|
|
* correct position. If later a read is attempted, we again close and re-open.
|
|
*
|
|
* However, before putting any effort into implementing such an error-prone
|
|
* scheme, we are well advised to first determine whether any game needs this
|
|
* at all, and for what. Based on that, we can maybe come up with a better waybill
|
|
* to provide this functionality.
|
|
*/
|
|
|
|
|
|
|
|
FileHandle::FileHandle() : _in(0), _out(0) {
|
|
}
|
|
|
|
FileHandle::~FileHandle() {
|
|
close();
|
|
}
|
|
|
|
void FileHandle::close() {
|
|
delete _in;
|
|
delete _out;
|
|
_in = 0;
|
|
_out = 0;
|
|
_name.clear();
|
|
}
|
|
|
|
bool FileHandle::isOpen() const {
|
|
return _in || _out;
|
|
}
|
|
|
|
|
|
|
|
enum {
|
|
_K_FILE_MODE_OPEN_OR_CREATE = 0,
|
|
_K_FILE_MODE_OPEN_OR_FAIL = 1,
|
|
_K_FILE_MODE_CREATE = 2
|
|
};
|
|
|
|
|
|
|
|
void file_open(EngineState *s, const char *filename, int mode) {
|
|
// QfG3 character import prepends /\ to the filenames.
|
|
if (filename[0] == '/' && filename[1] == '\\')
|
|
filename += 2;
|
|
|
|
Common::String englishName = s->getLanguageString(filename, K_LANG_ENGLISH);
|
|
const Common::String wrappedName = ((Sci::SciEngine*)g_engine)->wrapFilename(englishName);
|
|
Common::SeekableReadStream *inFile = 0;
|
|
Common::WriteStream *outFile = 0;
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
|
|
if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
|
|
// Try to open file, abort if not possible
|
|
inFile = saveFileMan->openForLoading(wrappedName);
|
|
// If no matching savestate exists: fall back to reading from a regular file
|
|
if (!inFile)
|
|
inFile = SearchMan.createReadStreamForMember(englishName);
|
|
|
|
// Special case for LSL3: It tries to create a new dummy file, LARRY3.DRV
|
|
// Apparently, if the file doesn't exist here, it should be created. The game
|
|
// scripts then go ahead and fill its contents with data. It seems to be a similar
|
|
// case as the dummy MEMORY.DRV file in LSL5, but LSL5 creates the file if it can't
|
|
// find it with a separate call to file_open()
|
|
if (!inFile && englishName == "LARRY3.DRV") {
|
|
outFile = saveFileMan->openForSaving(wrappedName);
|
|
outFile->finalize();
|
|
delete outFile;
|
|
outFile = 0;
|
|
inFile = SearchMan.createReadStreamForMember(wrappedName);
|
|
}
|
|
|
|
if (!inFile)
|
|
warning("file_open(_K_FILE_MODE_OPEN_OR_FAIL) failed to open file '%s'", englishName.c_str());
|
|
} else if (mode == _K_FILE_MODE_CREATE) {
|
|
// Create the file, destroying any content it might have had
|
|
outFile = saveFileMan->openForSaving(wrappedName);
|
|
if (!outFile)
|
|
warning("file_open(_K_FILE_MODE_CREATE) failed to create file '%s'", englishName.c_str());
|
|
} else if (mode == _K_FILE_MODE_OPEN_OR_CREATE) {
|
|
// Try to open file, create it if it doesn't exist
|
|
|
|
// FIXME: I am disabling this for now, as it's not quite clear what
|
|
// should happen if the given file already exists... open it for appending?
|
|
// Or (more likely), open it for reading *and* writing? We may have to
|
|
// clone the file for that, etc., see also the long comment at the start
|
|
// of this file.
|
|
// We really need some examples on how this is used.
|
|
error("file_open(_K_FILE_MODE_OPEN_OR_CREATE) File creation currently not supported (filename '%s')", englishName.c_str());
|
|
} else {
|
|
error("file_open: unsupported mode %d (filename '%s')", mode, englishName.c_str());
|
|
}
|
|
|
|
if (!inFile && !outFile) { // Failed
|
|
debug(3, "file_open() failed");
|
|
s->r_acc = SIGNAL_REG;
|
|
return;
|
|
}
|
|
|
|
|
|
#if 0
|
|
// FIXME: The old FreeSCI code for opening a file. Left as a reference, as apparently
|
|
// the implementation below used to work well enough.
|
|
|
|
debugC(2, kDebugLevelFile, "Opening file %s with mode %d\n", englishName.c_str(), mode);
|
|
if ((mode == _K_FILE_MODE_OPEN_OR_FAIL) || (mode == _K_FILE_MODE_OPEN_OR_CREATE)) {
|
|
file = sci_fopen(englishName.c_str(), "r" FO_BINARY "+"); // Attempt to open existing file
|
|
debugC(2, kDebugLevelFile, "Opening file %s with mode %d\n", englishName.c_str(), mode);
|
|
if (!file) {
|
|
debugC(2, kDebugLevelFile, "Failed. Attempting to copy from resource dir...\n");
|
|
file = f_open_mirrored(s, englishName.c_str());
|
|
if (file)
|
|
debugC(2, kDebugLevelFile, "Success!\n");
|
|
else
|
|
debugC(2, kDebugLevelFile, "Not found.\n");
|
|
}
|
|
}
|
|
|
|
if ((!file) && ((mode == _K_FILE_MODE_OPEN_OR_CREATE) || (mode == _K_FILE_MODE_CREATE))) {
|
|
file = sci_fopen(englishName.c_str(), "w" FO_BINARY "+"); /* Attempt to create file */
|
|
debugC(2, kDebugLevelFile, "Creating file %s with mode %d\n", englishName.c_str(), mode);
|
|
}
|
|
if (!file) { // Failed
|
|
debugC(2, kDebugLevelFile, "file_open() failed\n");
|
|
s->r_acc = make_reg(0, 0xffff);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// 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);
|
|
}
|
|
|
|
s->_fileHandles[handle]._in = inFile;
|
|
s->_fileHandles[handle]._out = outFile;
|
|
s->_fileHandles[handle]._name = englishName;
|
|
|
|
s->r_acc = make_reg(0, handle);
|
|
|
|
debug(3, " -> opened file '%s' with handle %d", englishName.c_str(), handle);
|
|
}
|
|
|
|
reg_t kFOpen(EngineState *s, int argc, reg_t *argv) {
|
|
Common::String name = s->_segMan->getString(argv[0]);
|
|
int mode = argv[1].toUint16();
|
|
|
|
debug(3, "kFOpen(%s,0x%x)", name.c_str(), mode);
|
|
file_open(s, name.c_str(), mode);
|
|
return s->r_acc;
|
|
}
|
|
|
|
static FileHandle *getFileFromHandle(EngineState *s, uint handle) {
|
|
if (handle == 0) {
|
|
error("Attempt to use file handle 0");
|
|
return 0;
|
|
}
|
|
|
|
if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) {
|
|
error("Attempt to use invalid/unused file handle %d", handle);
|
|
return 0;
|
|
}
|
|
|
|
return &s->_fileHandles[handle];
|
|
}
|
|
|
|
void file_close(EngineState *s, int handle) {
|
|
debugC(2, kDebugLevelFile, "Closing file %d\n", handle);
|
|
|
|
FileHandle *f = getFileFromHandle(s, handle);
|
|
if (f)
|
|
f->close();
|
|
}
|
|
|
|
reg_t kFClose(EngineState *s, int argc, reg_t *argv) {
|
|
debug(3, "kFClose(%d)", argv[0].toUint16());
|
|
if (argv[0] != SIGNAL_REG)
|
|
file_close(s, argv[0].toUint16());
|
|
return s->r_acc;
|
|
}
|
|
|
|
void fwrite_wrapper(EngineState *s, int handle, const char *data, int length) {
|
|
debugC(2, kDebugLevelFile, "fwrite()'ing \"%s\" to handle %d\n", data, handle);
|
|
|
|
FileHandle *f = getFileFromHandle(s, handle);
|
|
if (!f)
|
|
return;
|
|
|
|
if (!f->_out) {
|
|
error("fwrite_wrapper: Trying to write to file '%s' opened for reading", f->_name.c_str());
|
|
return;
|
|
}
|
|
|
|
f->_out->write(data, length);
|
|
}
|
|
|
|
reg_t kFPuts(EngineState *s, int argc, reg_t *argv) {
|
|
int handle = argv[0].toUint16();
|
|
Common::String data = s->_segMan->getString(argv[1]);
|
|
|
|
fwrite_wrapper(s, handle, data.c_str(), data.size());
|
|
return s->r_acc;
|
|
}
|
|
|
|
static void fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
|
|
debugC(2, kDebugLevelFile, "FGets'ing %d bytes from handle %d\n", maxsize, handle);
|
|
|
|
FileHandle *f = getFileFromHandle(s, handle);
|
|
if (!f)
|
|
return;
|
|
|
|
if (!f->_in) {
|
|
error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
|
|
return;
|
|
}
|
|
f->_in->readLine_NEW(dest, maxsize);
|
|
// The returned string must not have an ending LF
|
|
int strSize = strlen(dest);
|
|
if (strSize > 0) {
|
|
if (dest[strSize - 1] == 0x0A)
|
|
dest[strSize - 1] = 0;
|
|
}
|
|
|
|
debugC(2, kDebugLevelFile, "FGets'ed \"%s\"\n", dest);
|
|
}
|
|
|
|
static void fread_wrapper(EngineState *s, char *dest, int bytes, int handle) {
|
|
debugC(2, kDebugLevelFile, "fread()'ing %d bytes from handle %d\n", bytes, handle);
|
|
|
|
FileHandle *f = getFileFromHandle(s, handle);
|
|
if (!f)
|
|
return;
|
|
|
|
if (!f->_in) {
|
|
error("fread_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
|
|
return;
|
|
}
|
|
|
|
s->r_acc = make_reg(0, f->_in->read(dest, bytes));
|
|
}
|
|
|
|
static void fseek_wrapper(EngineState *s, int handle, int offset, int whence) {
|
|
FileHandle *f = getFileFromHandle(s, handle);
|
|
if (!f)
|
|
return;
|
|
|
|
if (!f->_in) {
|
|
error("fseek_wrapper: Trying to seek in file '%s' opened for writing", f->_name.c_str());
|
|
return;
|
|
}
|
|
|
|
s->r_acc = make_reg(0, f->_in->seek(offset, whence));
|
|
}
|
|
|
|
static int _savegame_index_struct_compare(const void *a, const void *b) {
|
|
const SavegameDesc *A = (const SavegameDesc *)a;
|
|
const SavegameDesc *B = (const SavegameDesc *)b;
|
|
|
|
if (B->date != A->date)
|
|
return B->date - A->date;
|
|
return B->time - A->time;
|
|
}
|
|
|
|
void listSavegames(Common::Array<SavegameDesc> &saves) {
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
|
|
// Load all saves
|
|
Common::StringList saveNames = saveFileMan->listSavefiles(((SciEngine *)g_engine)->getSavegamePattern());
|
|
|
|
for (Common::StringList::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
|
|
Common::String filename = *iter;
|
|
Common::SeekableReadStream *in;
|
|
if ((in = saveFileMan->openForLoading(filename))) {
|
|
SavegameMetadata meta;
|
|
if (!get_savegame_metadata(in, &meta)) {
|
|
// invalid
|
|
delete in;
|
|
continue;
|
|
}
|
|
delete in;
|
|
|
|
SavegameDesc desc;
|
|
desc.id = strtol(filename.end() - 3, NULL, 10);
|
|
desc.date = meta.savegame_date;
|
|
desc.time = meta.savegame_time;
|
|
debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id);
|
|
|
|
saves.push_back(desc);
|
|
}
|
|
}
|
|
|
|
// Sort the list by creation date of the saves
|
|
qsort(saves.begin(), saves.size(), sizeof(SavegameDesc), _savegame_index_struct_compare);
|
|
}
|
|
|
|
reg_t kFGets(EngineState *s, int argc, reg_t *argv) {
|
|
int maxsize = argv[1].toUint16();
|
|
char *buf = new char[maxsize];
|
|
int handle = argv[2].toUint16();
|
|
|
|
debug(3, "kFGets(%d,%d)", handle, maxsize);
|
|
fgets_wrapper(s, buf, maxsize, handle);
|
|
s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize);
|
|
return argv[0];
|
|
}
|
|
|
|
/**
|
|
* Writes the cwd to the supplied address and returns the address in acc.
|
|
*/
|
|
reg_t kGetCWD(EngineState *s, int argc, reg_t *argv) {
|
|
// We do not let the scripts see the file system, instead pretending
|
|
// we are always in the same directory.
|
|
// TODO/FIXME: Is "/" a good value? Maybe "" or "." or "C:\" are better?
|
|
s->_segMan->strcpy(argv[0], "/");
|
|
|
|
debug(3, "kGetCWD() -> %s", "/");
|
|
|
|
return argv[0];
|
|
}
|
|
|
|
enum {
|
|
K_DEVICE_INFO_GET_DEVICE = 0,
|
|
K_DEVICE_INFO_GET_CURRENT_DEVICE = 1,
|
|
K_DEVICE_INFO_PATHS_EQUAL = 2,
|
|
K_DEVICE_INFO_IS_FLOPPY = 3,
|
|
K_DEVICE_INFO_GET_SAVECAT_NAME = 7,
|
|
K_DEVICE_INFO_GET_SAVEFILE_NAME = 8
|
|
};
|
|
|
|
reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) {
|
|
int mode = argv[0].toUint16();
|
|
|
|
switch (mode) {
|
|
case K_DEVICE_INFO_GET_DEVICE: {
|
|
Common::String input_str = s->_segMan->getString(argv[1]);
|
|
|
|
s->_segMan->strcpy(argv[2], "/");
|
|
debug(3, "K_DEVICE_INFO_GET_DEVICE(%s) -> %s", input_str.c_str(), "/");
|
|
break;
|
|
}
|
|
case K_DEVICE_INFO_GET_CURRENT_DEVICE:
|
|
s->_segMan->strcpy(argv[1], "/");
|
|
debug(3, "K_DEVICE_INFO_GET_CURRENT_DEVICE() -> %s", "/");
|
|
break;
|
|
|
|
case K_DEVICE_INFO_PATHS_EQUAL: {
|
|
Common::String path1_s = s->_segMan->getString(argv[1]);
|
|
Common::String path2_s = s->_segMan->getString(argv[2]);
|
|
debug(3, "K_DEVICE_INFO_PATHS_EQUAL(%s,%s)", path1_s.c_str(), path2_s.c_str());
|
|
|
|
return make_reg(0, Common::matchString(path2_s.c_str(), path1_s.c_str(), false, true));
|
|
}
|
|
break;
|
|
|
|
case K_DEVICE_INFO_IS_FLOPPY: {
|
|
Common::String input_str = s->_segMan->getString(argv[1]);
|
|
debug(3, "K_DEVICE_INFO_IS_FLOPPY(%s)", input_str.c_str());
|
|
return NULL_REG; /* Never */
|
|
}
|
|
/* SCI uses these in a less-than-portable way to delete savegames.
|
|
** Read http://www-plan.cs.colorado.edu/creichen/freesci-logs/2005.10/log20051019.html
|
|
** for more information on our workaround for this.
|
|
*/
|
|
case K_DEVICE_INFO_GET_SAVECAT_NAME: {
|
|
Common::String game_prefix = s->_segMan->getString(argv[2]);
|
|
s->_segMan->strcpy(argv[1], "__throwaway");
|
|
debug(3, "K_DEVICE_INFO_GET_SAVECAT_NAME(%s) -> %s", game_prefix.c_str(), "__throwaway");
|
|
}
|
|
|
|
break;
|
|
case K_DEVICE_INFO_GET_SAVEFILE_NAME: {
|
|
Common::String game_prefix = s->_segMan->getString(argv[2]);
|
|
int savegame_id = argv[3].toUint16();
|
|
s->_segMan->strcpy(argv[1], "__throwaway");
|
|
debug(3, "K_DEVICE_INFO_GET_SAVEFILE_NAME(%s,%d) -> %s", game_prefix.c_str(), savegame_id, "__throwaway");
|
|
Common::Array<SavegameDesc> saves;
|
|
listSavegames(saves);
|
|
int savedir_nr = saves[savegame_id].id;
|
|
Common::String filename = ((Sci::SciEngine*)g_engine)->getSavegameName(savedir_nr);
|
|
//printf("Deleting savegame '%s'\n", filename.c_str());
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
saveFileMan->removeSavefile(filename);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// TODO: Not all sub-commands are handled. E.g. KQ5CD calls sub-command 5
|
|
warning("Unknown DeviceInfo() sub-command: %d", mode);
|
|
break;
|
|
}
|
|
|
|
return s->r_acc;
|
|
}
|
|
|
|
reg_t kGetSaveDir(EngineState *s, int argc, reg_t *argv) {
|
|
#ifdef ENABLE_SCI32
|
|
// TODO: SCI32 uses a parameter here.
|
|
if (argc > 0)
|
|
warning("kGetSaveDir called with a parameter");
|
|
#endif
|
|
|
|
return make_reg(s->sys_strings_segment, SYS_STRING_SAVEDIR);
|
|
}
|
|
|
|
reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) {
|
|
Common::String path = s->_segMan->getString(argv[0]);
|
|
|
|
debug(3, "kCheckFreeSpace(%s)", path.c_str());
|
|
// We simply always pretend that there is enough space.
|
|
// The alternative would be to write a big test file, which is not nice
|
|
// on systems where doing so is very slow.
|
|
return make_reg(0, 1);
|
|
}
|
|
|
|
reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) {
|
|
Common::String game_id = s->_segMan->getString(argv[0]);
|
|
int savedir_nr = argv[1].toUint16();
|
|
|
|
debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), savedir_nr);
|
|
|
|
Common::Array<SavegameDesc> saves;
|
|
listSavegames(saves);
|
|
|
|
savedir_nr = saves[savedir_nr].id;
|
|
|
|
if (savedir_nr > MAX_SAVEGAME_NR - 1) {
|
|
return NULL_REG;
|
|
}
|
|
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
Common::String filename = ((Sci::SciEngine*)g_engine)->getSavegameName(savedir_nr);
|
|
Common::SeekableReadStream *in;
|
|
if ((in = saveFileMan->openForLoading(filename))) {
|
|
// found a savegame file
|
|
|
|
SavegameMetadata meta;
|
|
if (!get_savegame_metadata(in, &meta)) {
|
|
// invalid
|
|
s->r_acc = make_reg(0, 0);
|
|
} else {
|
|
s->r_acc = make_reg(0, 1);
|
|
}
|
|
delete in;
|
|
} else {
|
|
s->r_acc = make_reg(0, 1);
|
|
}
|
|
|
|
return s->r_acc;
|
|
}
|
|
|
|
reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
|
|
Common::String game_id = s->_segMan->getString(argv[0]);
|
|
reg_t nametarget = argv[1];
|
|
reg_t *nameoffsets = s->_segMan->derefRegPtr(argv[2], 0);
|
|
|
|
debug(3, "kGetSaveFiles(%s)", game_id.c_str());
|
|
|
|
Common::Array<SavegameDesc> saves;
|
|
listSavegames(saves);
|
|
|
|
s->r_acc = NULL_REG;
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
|
|
for (uint i = 0; i < saves.size(); i++) {
|
|
Common::String filename = ((Sci::SciEngine*)g_engine)->getSavegameName(saves[i].id);
|
|
Common::SeekableReadStream *in;
|
|
if ((in = saveFileMan->openForLoading(filename))) {
|
|
// found a savegame file
|
|
|
|
SavegameMetadata meta;
|
|
if (!get_savegame_metadata(in, &meta)) {
|
|
// invalid
|
|
delete in;
|
|
continue;
|
|
}
|
|
|
|
if (!meta.savegame_name.empty()) {
|
|
if (meta.savegame_name.lastChar() == '\n')
|
|
meta.savegame_name.deleteLastChar();
|
|
|
|
*nameoffsets = s->r_acc; // Store savegame ID
|
|
++s->r_acc.offset; // Increase number of files found
|
|
|
|
nameoffsets++; // Make sure the next ID string address is written to the next pointer
|
|
Common::String name = meta.savegame_name;
|
|
if (name.size() > SCI_MAX_SAVENAME_LENGTH-1)
|
|
name = Common::String(meta.savegame_name.c_str(), SCI_MAX_SAVENAME_LENGTH-1);
|
|
s->_segMan->strcpy(nametarget, name.c_str());
|
|
|
|
// Increase name offset pointer accordingly
|
|
nametarget.offset += SCI_MAX_SAVENAME_LENGTH;
|
|
}
|
|
delete in;
|
|
}
|
|
}
|
|
|
|
//free(gfname);
|
|
s->_segMan->strcpy(nametarget, ""); // Terminate list
|
|
|
|
return s->r_acc;
|
|
}
|
|
|
|
reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
|
|
Common::String game_id = s->_segMan->getString(argv[0]);
|
|
int savedir_nr = argv[1].toUint16();
|
|
int savedir_id; // Savegame ID, derived from savedir_nr and the savegame ID list
|
|
Common::String game_description = s->_segMan->getString(argv[2]);
|
|
Common::String version;
|
|
if (argc > 3)
|
|
version = s->_segMan->getString(argv[3]);
|
|
|
|
debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), savedir_nr, game_description.c_str(), version.c_str());
|
|
|
|
Common::Array<SavegameDesc> saves;
|
|
listSavegames(saves);
|
|
|
|
fprintf(stderr, "savedir_nr = %d\n", savedir_nr);
|
|
|
|
if (savedir_nr >= 0 && (uint)savedir_nr < saves.size()) {
|
|
// Overwrite
|
|
savedir_id = saves[savedir_nr].id;
|
|
} else if (savedir_nr >= 0 && savedir_nr < MAX_SAVEGAME_NR) {
|
|
uint i = 0;
|
|
|
|
fprintf(stderr, "searching for hole\n");
|
|
|
|
savedir_id = 0;
|
|
|
|
// First, look for holes
|
|
while (i < saves.size()) {
|
|
if (saves[i].id == savedir_id) {
|
|
++savedir_id;
|
|
i = 0;
|
|
} else
|
|
++i;
|
|
}
|
|
if (savedir_id >= MAX_SAVEGAME_NR) {
|
|
warning("Internal error: Free savegame ID is %d, shouldn't happen", savedir_id);
|
|
return NULL_REG;
|
|
}
|
|
|
|
// This loop terminates when savedir_id is not in [x | ex. n. saves [n].id = x]
|
|
} else {
|
|
warning("Savegame ID %d is not allowed", savedir_nr);
|
|
return NULL_REG;
|
|
}
|
|
|
|
Common::String filename = ((Sci::SciEngine*)g_engine)->getSavegameName(savedir_id);
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
Common::OutSaveFile *out;
|
|
if (!(out = saveFileMan->openForSaving(filename))) {
|
|
warning("Error opening savegame \"%s\" for writing", filename.c_str());
|
|
s->r_acc = NULL_REG;
|
|
return NULL_REG;
|
|
}
|
|
|
|
if (gamestate_save(s, out, game_description.c_str(), version.c_str())) {
|
|
warning("Saving the game failed.");
|
|
s->r_acc = NULL_REG;
|
|
} else {
|
|
out->finalize();
|
|
if (out->err()) {
|
|
delete out;
|
|
warning("Writing the savegame failed.");
|
|
s->r_acc = NULL_REG;
|
|
} else {
|
|
delete out;
|
|
s->r_acc = make_reg(0, 1);
|
|
}
|
|
}
|
|
|
|
return s->r_acc;
|
|
}
|
|
|
|
reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
|
|
Common::String game_id = s->_segMan->getString(argv[0]);
|
|
int savedir_nr = argv[1].toUint16();
|
|
|
|
debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savedir_nr);
|
|
|
|
Common::Array<SavegameDesc> saves;
|
|
listSavegames(saves);
|
|
|
|
savedir_nr = saves[savedir_nr].id;
|
|
|
|
if (savedir_nr > -1) {
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
Common::String filename = ((Sci::SciEngine*)g_engine)->getSavegameName(savedir_nr);
|
|
Common::SeekableReadStream *in;
|
|
if ((in = saveFileMan->openForLoading(filename))) {
|
|
// found a savegame file
|
|
|
|
EngineState *newstate = gamestate_restore(s, in);
|
|
delete in;
|
|
|
|
if (newstate) {
|
|
s->successor = newstate;
|
|
script_abort_flag = 2; // Abort current game with replay
|
|
shrink_execution_stack(s, s->execution_stack_base + 1);
|
|
} else {
|
|
s->r_acc = make_reg(0, 1);
|
|
warning("Restoring failed (game_id = '%s')", game_id.c_str());
|
|
}
|
|
return s->r_acc;
|
|
}
|
|
}
|
|
|
|
s->r_acc = make_reg(0, 1);
|
|
warning("Savegame #%d not found", savedir_nr);
|
|
|
|
return s->r_acc;
|
|
}
|
|
|
|
reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
|
|
Common::String path = s->_segMan->getString(argv[0]);
|
|
|
|
// FIXME: For now, we only accept the (fake) root dir "/" as a valid path.
|
|
s->r_acc = make_reg(0, path == "/");
|
|
|
|
debug(3, "kValidPath(%s) -> %d", path.c_str(), s->r_acc.offset);
|
|
|
|
return s->r_acc;
|
|
}
|
|
|
|
enum {
|
|
K_FILEIO_OPEN = 0,
|
|
K_FILEIO_CLOSE = 1,
|
|
K_FILEIO_READ_RAW = 2,
|
|
K_FILEIO_WRITE_RAW = 3,
|
|
K_FILEIO_UNLINK = 4,
|
|
K_FILEIO_READ_STRING = 5,
|
|
K_FILEIO_WRITE_STRING = 6,
|
|
K_FILEIO_SEEK = 7,
|
|
K_FILEIO_FIND_FIRST = 8,
|
|
K_FILEIO_FIND_NEXT = 9,
|
|
K_FILEIO_FILE_EXISTS = 10
|
|
};
|
|
|
|
|
|
void DirSeeker::firstFile(const char *mask, reg_t buffer) {
|
|
|
|
// Verify that we are given a valid buffer
|
|
if (!buffer.segment) {
|
|
error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask);
|
|
_state->r_acc = NULL_REG;
|
|
return;
|
|
}
|
|
_outbuffer = buffer;
|
|
|
|
// Obtain a list of all savefiles matching the given mask
|
|
// TODO: Modify the mask, e.g. by prefixing "TARGET-".
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
_savefiles = saveFileMan->listSavefiles(mask);
|
|
|
|
// Reset the list iterator and write the first match to the output buffer, if any.
|
|
_iter = _savefiles.begin();
|
|
nextFile();
|
|
}
|
|
|
|
void DirSeeker::nextFile() {
|
|
if (_iter == _savefiles.end()) {
|
|
_state->r_acc = NULL_REG;
|
|
return;
|
|
}
|
|
|
|
// TODO: Transform the string back into a format usable by the SCI scripts.
|
|
// I.e., strip any TARGET- prefix.
|
|
Common::String string = *_iter;
|
|
if (string.size() > 12)
|
|
string = Common::String(string.c_str(), 12);
|
|
_state->_segMan->strcpy(_outbuffer, string.c_str());
|
|
|
|
// Return the result and advance the list iterator :)
|
|
_state->r_acc = _outbuffer;
|
|
++_iter;
|
|
}
|
|
|
|
|
|
|
|
reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
|
|
int func_nr = argv[0].toUint16();
|
|
|
|
switch (func_nr) {
|
|
case K_FILEIO_OPEN : {
|
|
Common::String name = s->_segMan->getString(argv[1]);
|
|
int mode = argv[2].toUint16();
|
|
|
|
if (name.empty()) {
|
|
warning("Attempted to open a file with an empty filename");
|
|
return SIGNAL_REG;
|
|
}
|
|
file_open(s, name.c_str(), mode);
|
|
debug(3, "K_FILEIO_OPEN(%s,0x%x)", name.c_str(), mode);
|
|
break;
|
|
}
|
|
case K_FILEIO_CLOSE : {
|
|
int handle = argv[1].toUint16();
|
|
debug(3, "K_FILEIO_CLOSE(%d)", handle);
|
|
|
|
file_close(s, handle);
|
|
break;
|
|
}
|
|
case K_FILEIO_READ_RAW : {
|
|
int handle = argv[1].toUint16();
|
|
int size = argv[3].toUint16();
|
|
char *buf = new char[size];
|
|
debug(3, "K_FILEIO_READ_RAW(%d,%d)", handle, size);
|
|
|
|
fread_wrapper(s, buf, size, handle);
|
|
s->_segMan->memcpy(argv[2], (const byte*)buf, size);
|
|
delete[] buf;
|
|
break;
|
|
}
|
|
case K_FILEIO_WRITE_RAW : {
|
|
int handle = argv[1].toUint16();
|
|
int size = argv[3].toUint16();
|
|
char *buf = new char[size];
|
|
s->_segMan->memcpy((byte*)buf, argv[2], size);
|
|
debug(3, "K_FILEIO_WRITE_RAW(%d,%d)", handle, size);
|
|
|
|
fwrite_wrapper(s, handle, buf, size);
|
|
delete[] buf;
|
|
break;
|
|
}
|
|
case K_FILEIO_UNLINK : {
|
|
Common::String name = s->_segMan->getString(argv[1]);
|
|
debug(3, "K_FILEIO_UNLINK(%s)", name.c_str());
|
|
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
const Common::String wrappedName = ((Sci::SciEngine*)g_engine)->wrapFilename(name);
|
|
saveFileMan->removeSavefile(wrappedName);
|
|
// TODO/FIXME: Should we return something (like, a bool indicating
|
|
// whether deleting the save succeeded or failed)?
|
|
break;
|
|
}
|
|
case K_FILEIO_READ_STRING : {
|
|
int size = argv[2].toUint16();
|
|
char *buf = new char[size];
|
|
int handle = argv[3].toUint16();
|
|
debug(3, "K_FILEIO_READ_STRING(%d,%d)", handle, size);
|
|
|
|
fgets_wrapper(s, buf, size, handle);
|
|
s->_segMan->memcpy(argv[1], (const byte*)buf, size);
|
|
delete[] buf;
|
|
return argv[1];
|
|
}
|
|
case K_FILEIO_WRITE_STRING : {
|
|
int handle = argv[1].toUint16();
|
|
int size = argv[3].toUint16();
|
|
Common::String str = s->_segMan->getString(argv[2]);
|
|
debug(3, "K_FILEIO_WRITE_STRING(%d,%d)", handle, size);
|
|
|
|
// CHECKME: Is the size parameter used at all?
|
|
// In the LSL5 password protection it is zero, and we should
|
|
// then write a full string. (Not sure if it should write the
|
|
// terminating zero.)
|
|
fwrite_wrapper(s, handle, str.c_str(), str.size());
|
|
break;
|
|
}
|
|
case K_FILEIO_SEEK : {
|
|
int handle = argv[1].toUint16();
|
|
int offset = argv[2].toUint16();
|
|
int whence = argv[3].toUint16();
|
|
debug(3, "K_FILEIO_SEEK(%d,%d,%d)", handle, offset, whence);
|
|
|
|
fseek_wrapper(s, handle, offset, whence);
|
|
break;
|
|
}
|
|
case K_FILEIO_FIND_FIRST : {
|
|
Common::String mask = s->_segMan->getString(argv[1]);
|
|
reg_t buf = argv[2];
|
|
int attr = argv[3].toUint16(); // We won't use this, Win32 might, though...
|
|
debug(3, "K_FILEIO_FIND_FIRST(%s,0x%x)", mask.c_str(), attr);
|
|
|
|
// QfG3 uses this mask for the character import
|
|
if (mask == "/\\*.*")
|
|
mask = "*.*";
|
|
#ifndef WIN32
|
|
if (mask == "*.*")
|
|
mask = "*"; // For UNIX
|
|
#endif
|
|
s->_dirseeker.firstFile(mask.c_str(), buf);
|
|
|
|
break;
|
|
}
|
|
case K_FILEIO_FIND_NEXT : {
|
|
debug(3, "K_FILEIO_FIND_NEXT()");
|
|
s->_dirseeker.nextFile();
|
|
break;
|
|
}
|
|
case K_FILEIO_FILE_EXISTS : {
|
|
Common::String name = s->_segMan->getString(argv[1]);
|
|
|
|
// Check for regular file
|
|
bool exists = Common::File::exists(name);
|
|
Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
|
|
const Common::String wrappedName = ((Sci::SciEngine*)g_engine)->wrapFilename(name);
|
|
|
|
if (!exists)
|
|
exists = !saveFileMan->listSavefiles(name).empty();
|
|
|
|
if (!exists) {
|
|
// Try searching for the file prepending target-
|
|
Common::SeekableReadStream *inFile = saveFileMan->openForLoading(wrappedName);
|
|
exists = (inFile != 0);
|
|
delete inFile;
|
|
}
|
|
|
|
// Special case for non-English versions of LSL5: The English version of LSL5 calls
|
|
// kFileIO(), case K_FILEIO_OPEN for reading to check if memory.drv exists (which is
|
|
// where the game's password is stored). If it's not found, it calls kFileIO() again,
|
|
// case K_FILEIO_OPEN for writing and creates a new file. Non-English versions call
|
|
// kFileIO(), case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be found.
|
|
// We create a default memory.drv file with no password, so that the game can continue
|
|
if (!exists && name == "memory.drv") {
|
|
// Create a new file, and write the bytes for the empty password string inside
|
|
byte defaultContent[] = { 0xE9, 0xE9, 0xEB, 0xE1, 0x0D, 0x0A, 0x31, 0x30, 0x30, 0x30 };
|
|
Common::WriteStream *outFile = saveFileMan->openForSaving(wrappedName);
|
|
for (int i = 0; i < 10; i++)
|
|
outFile->writeByte(defaultContent[i]);
|
|
outFile->finalize();
|
|
delete outFile;
|
|
exists = true;
|
|
}
|
|
|
|
debug(3, "K_FILEIO_FILE_EXISTS(%s) -> %d", name.c_str(), exists);
|
|
return make_reg(0, exists);
|
|
}
|
|
default :
|
|
error("Unknown FileIO() sub-command: %d", func_nr);
|
|
}
|
|
|
|
return s->r_acc;
|
|
}
|
|
|
|
} // End of namespace Sci
|