aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Horn2009-02-27 01:17:24 +0000
committerMax Horn2009-02-27 01:17:24 +0000
commitbf0860fc5e2013883292dc8efabde1e2f919d9d7 (patch)
tree09a39402d5270800ac3006932d756ba5d94ad8a1
parent5a5c51bb489d9b88bc3477dfda5af533e37b19a7 (diff)
downloadscummvm-rg350-bf0860fc5e2013883292dc8efabde1e2f919d9d7.tar.gz
scummvm-rg350-bf0860fc5e2013883292dc8efabde1e2f919d9d7.tar.bz2
scummvm-rg350-bf0860fc5e2013883292dc8efabde1e2f919d9d7.zip
SCI: Commited file handling revamp, work in progress
svn-id: r38919
-rw-r--r--engines/sci/engine/game.cpp9
-rw-r--r--engines/sci/engine/kfile.cpp658
-rw-r--r--engines/sci/engine/scriptdebug.cpp2
-rw-r--r--engines/sci/include/engine.h34
-rw-r--r--engines/sci/sci.cpp15
-rw-r--r--engines/sci/sci.h9
6 files changed, 289 insertions, 438 deletions
diff --git a/engines/sci/engine/game.cpp b/engines/sci/engine/game.cpp
index 337f20f09f..2252573ccb 100644
--- a/engines/sci/engine/game.cpp
+++ b/engines/sci/engine/game.cpp
@@ -440,7 +440,7 @@ static int create_class_table_sci0(EngineState *s) {
return 0;
}
-EngineState::EngineState() {
+EngineState::EngineState() : _dirseeker(this) {
savegame_version = 0;
widget_serial_counter = 0;
@@ -526,9 +526,9 @@ EngineState::EngineState() {
max_version = 0;
min_version = 0;
- kernel_opt_flags = 0;
+ _fileHandles.resize(5);
- dirseeker = 0;
+ kernel_opt_flags = 0;
execution_stack = 0;
execution_stack_size = 0;
@@ -664,8 +664,6 @@ int script_init_engine(EngineState *s, sci_version_t version) {
s->bp_list = NULL; // No breakpoints defined
s->have_bp = 0;
- s->dirseeker = 0; // Used by FileIO for FIND_FIRST, FIND_NEXT
-
if (s->version >= SCI_VERSION_FTU_LOFS_ABSOLUTE &&
s->version < SCI_VERSION(1, 001, 000))
s->seg_manager->setExportWidth(1);
@@ -695,6 +693,7 @@ void script_free_vm_memory(EngineState *s) {
// Close all opened file handles
s->_fileHandles.clear();
+ s->_fileHandles.resize(5);
}
extern void free_kfunct_tables(EngineState *s);
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 4c615ff6b4..65193495b2 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -38,322 +38,130 @@
#include "sci/include/engine.h"
#include "sci/engine/kernel.h"
-#include <errno.h>
-#include <sys/stat.h> // for S_IREAD/S_IWRITE
-
-#define SCI_INVALID_FD -1
-#define IS_VALID_FD(a) ((a) != SCI_INVALID_FD) /* Tests validity of a file descriptor */
-
-// FIXME: rework sci_dir_t to use common/fs.h and remove these includes
-#include <sys/types.h>
-#ifndef _MSC_VER
-#include <dirent.h>
-#else
-#include <io.h>
-#endif
-
-// FIXME: For chdir() etc.
-#ifndef _MSC_VER
-#include <unistd.h>
-#endif
-
-#ifdef WIN32
-# define FO_BINARY "b"
-#else
-# define FO_BINARY ""
-#endif
-
-#ifdef UNIX
-#include <fnmatch.h>
-#include <sys/stat.h>
-#endif
-
-
namespace Sci {
-struct sci_dir_t {
-#ifdef WIN32
- long search;
- struct _finddata_t fileinfo;
-#else
- DIR *dir;
- char *mask_copy;
-#endif
-}; /* used by sci_find_first and friends */
-
-void sci_init_dir(sci_dir_t *dirent);
-/* Initializes an sci directory search structure
-** Parameters: (sci_dir_t *) dirent: The entity to initialize
-** Returns : (void)
-** The entity is initialized to "empty" values, meaning that it can be
-** used in subsequent sci_find_first/sci_find_next constructs. In no
-** event should this function be used upon a structure which has been
-** subjected to any of the other dirent calls.
-*/
-
-char *sci_find_first(sci_dir_t *dirent, const char *mask);
-/* Finds the first file matching the specified file mask
-** Parameters: (sci_dir_t *) dirent: Pointer to an unused dirent structure
-** (const char *) mask: File mask to apply
-** Returns : (char *) Name of the first matching file found, or NULL
-*/
-
-char *sci_find_next(sci_dir_t *dirent);
-/* Finds the next file specified by an sci_dir initialized by sci_find_first()
-** Parameters: (sci_dir_t *) dirent: Pointer to SCI dir entity
-** Returns : (char *) Name of the next matching file, or NULL
-*/
-
-void sci_finish_find(sci_dir_t *dirent);
-/* Completes an 'sci_find_first/next' procedure
-** Parameters: (sci_dir_t *) dirent: Pointer to the dirent used
-** Returns : (void)
-** In the operation sequences
-** sci_init_dir(x); sci_finish_find(x);
-** and
-** sci_finish_find(x); sci_finish_find(x);
-** the second operation is guaranteed to be a no-op.
-*/
-
-FILE *sci_fopen(const char *fname, const char *mode);
-/* Opens a FILE* case-insensitively
-** Parameters: (const char *) fname: Name of the file to open
-** (const char *) mode: Mode to open it with
-** Returns : (FILE *) A valid file handle, or NULL on failure
-** Always refers to the cwd, cannot address subdirectories
-*/
-
-int sci_file_size(const char *fname);
-/* Returns the filesize of a file
-** Parameters: (const char *) fname: Name of file to get filesize of
-** Returns : (int) filesize of the file, -1 on error
-*/
-
-
-#if defined(WIN32)
-void sci_init_dir(sci_dir_t *dir) {
- dir->search = -1;
-}
-
-char *sci_find_first(sci_dir_t *dir, const char *mask) {
- dir->search = _findfirst(mask, &(dir->fileinfo));
-
- if (dir->search != -1) {
- if (dir->fileinfo.name == NULL) {
- return NULL;
- }
-
- if (strcmp(dir->fileinfo.name, ".") == 0 ||
- strcmp(dir->fileinfo.name, "..") == 0) {
- if (sci_find_next(dir) == NULL) {
- return NULL;
- }
- }
-
- return dir->fileinfo.name;
- } else {
- switch (errno) {
- case ENOENT: {
-#ifdef _DEBUG
- printf("_findfirst errno = ENOENT: no match\n");
-
- if (mask)
- printf(" in: %s\n", mask);
- else
- printf(" - searching in undefined directory\n");
-#endif
- break;
- }
- case EINVAL: {
- printf("_findfirst errno = EINVAL: invalid filename\n");
- break;
- }
- default:
- printf("_findfirst errno = unknown (%d)", errno);
- }
- }
-
- return NULL;
-}
-
-char *sci_find_next(sci_dir_t *dir) {
- if (dir->search == -1)
- return NULL;
-
- if (_findnext(dir->search, &(dir->fileinfo)) < 0) {
- _findclose(dir->search);
- dir->search = -1;
- return NULL;
- }
-
- if (strcmp(dir->fileinfo.name, ".") == 0 ||
- strcmp(dir->fileinfo.name, "..") == 0) {
- if (sci_find_next(dir) == NULL) {
- return NULL;
- }
- }
-
- return dir->fileinfo.name;
-}
-
-void sci_finish_find(sci_dir_t *dir) {
- if (dir->search != -1) {
- _findclose(dir->search);
- dir->search = -1;
- }
-}
-
-#else
-
-void sci_init_dir(sci_dir_t *dir) {
- dir->dir = NULL;
- dir->mask_copy = NULL;
-}
-
-char *sci_find_first(sci_dir_t *dir, const char *mask) {
- if (dir->dir)
- closedir(dir->dir);
-
- if (!(dir->dir = opendir("."))) {
- sciprintf("%s, L%d: opendir(\".\") failed!\n", __FILE__, __LINE__);
- return NULL;
- }
-
- dir->mask_copy = sci_strdup(mask);
-
- return sci_find_next(dir);
-}
-
-#ifndef FNM_CASEFOLD
-#define FNM_CASEFOLD 0
-#warning "File searches will not be case-insensitive!"
-#endif
-
-char *sci_find_next(sci_dir_t *dir) {
- struct dirent *match;
-
- while ((match = readdir(dir->dir))) {
- if (match->d_name[0] == '.')
- continue;
-
- if (!fnmatch(dir->mask_copy, match->d_name, FNM_CASEFOLD))
- return match->d_name;
- }
-
- sci_finish_find(dir);
+/*
+ * 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 sq1vga, the name becomes "sq1vga-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.
+ */
+
+
- return NULL;
+FileHandle::FileHandle() : _in(0), _out(0) {
}
-void sci_finish_find(sci_dir_t *dir) {
- if (dir->dir) {
- closedir(dir->dir);
- dir->dir = NULL;
- free(dir->mask_copy);
- dir->mask_copy = NULL;
- }
+FileHandle::~FileHandle() {
+ close();
}
-#endif
-
-
-/* Returns the case-sensitive filename of a file.
-** Expects *dir to be uninitialized and the caller to free it afterwards.
-** Parameters: (const char *) fname: Name of file to get case-sensitive.
-** (sci_dir_t *) dir: Directory to find file within.
-** Returns : (char *) Case-sensitive filename of the file.
-*/
-Common::String _fcaseseek(const char *fname) {
- // Expects *dir to be uninitialized and the caller to
- // free it afterwards */
-
- // Look up the file, ignoring case
- Common::ArchiveMemberList files;
- SearchMan.listMatchingMembers(files, fname);
-
- for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
- const Common::String name = (*x)->getName();
- if (name.equalsIgnoreCase(fname))
- return name;
- }
-
- return Common::String();
+void FileHandle::close() {
+ delete _in;
+ delete _out;
+ _in = 0;
+ _out = 0;
+ _name.clear();
}
-FILE *sci_fopen(const char *fname, const char *mode) {
- Common::String name = _fcaseseek(fname);
- FILE *file = NULL;
-
- if (!name.empty())
- file = fopen(name.c_str(), mode);
- else if (strchr(mode, 'w'))
- file = fopen(fname, mode);
-
- return file;
-}
-
-int sci_file_size(const char *fname) {
- struct stat fn_stat;
-
- if (stat(fname, &fn_stat))
- return -1;
-
- return fn_stat.st_size;
+bool FileHandle::isOpen() const {
+ return _in || _out;
}
static int _savegame_indices_nr = -1; // means 'uninitialized'
-static struct _savegame_index_struct {
+struct SavegameDesc {
int id;
int date;
int time;
-} _savegame_indices[MAX_SAVEGAME_NR];
+};
-// This assumes modern stream implementations. It may break on DOS.
+static SavegameDesc _savegame_indices[MAX_SAVEGAME_NR];
-/* Attempts to mirror a file by copying it from the resource firectory
-** to the working directory. Returns NULL if the file didn't exist.
-** Otherwise, the new file is then opened for reading or writing.
-*/
-static FILE *f_open_mirrored(EngineState *s, char *fname) {
- debug(3, "f_open_mirrored(%s)", fname);
+enum {
+ _K_FILE_MODE_OPEN_OR_CREATE = 0,
+ _K_FILE_MODE_OPEN_OR_FAIL = 1,
+ _K_FILE_MODE_CREATE = 2
+};
-#if 0
- Common::File file;
- if (!file.open(fname))
- return NULL;
-
- int fsize = file.size();
- if (fsize > 0) {
- buf = (char *)sci_malloc(fsize);
- file.read(buf, fsize);
- }
- file.close();
- copy the file to a savegame -> only makes sense to perform this change
- if we at the same time change the code for loading files to look among the
- savestates, and also change *all* file writing code to write to savestates,
- as it should
+void file_open(EngineState *s, const char *filename, int mode) {
+ const Common::String wrappedName = ((Sci::SciEngine*)g_engine)->wrapFilename(filename);
+ Common::SeekableReadStream *inFile = 0;
+ Common::WriteStream *outFile = 0;
+ Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
- Also, we may have to change the filename when creating a matchin savegame,
- e.g. prefix it with the target name
-#endif
+ if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+ // Try to open file, abort if not possible
+ inFile = saveFileMan->openForLoading(wrappedName.c_str());
+ // If no matching savestate exists: fall back to reading from a regular file
+ if (!inFile)
+ inFile = SearchMan.createReadStreamForMember(filename);
+ if (!inFile)
+ warning("file_open(_K_FILE_MODE_OPEN_OR_FAIL) failed to open file '%s'", filename);
+ } else if (mode == _K_FILE_MODE_CREATE) {
+ // Create the file, destroying any content it might have had
+ outFile = saveFileMan->openForSaving(wrappedName.c_str());
+ if (!outFile)
+ warning("file_open(_K_FILE_MODE_CREATE) failed to create file '%s'", filename);
+ } 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");
+ } else {
+ error("file_open: unsupported mode %d", mode);
+ }
- // FIXME: for now we just pretend this has failed
- return 0;
-}
+ if (!inFile && !outFile) { // Failed
+ debug(3, "file_open() failed");
+ s->r_acc = make_reg(0, 0xffff);
+ return;
+ }
-#define _K_FILE_MODE_OPEN_OR_CREATE 0
-#define _K_FILE_MODE_OPEN_OR_FAIL 1
-#define _K_FILE_MODE_CREATE 2
-void file_open(EngineState *s, char *filename, int mode) {
- FILE *file = NULL;
+#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.
SCIkdebug(SCIkFILE, "Opening file %s with mode %d\n", filename, mode);
if ((mode == _K_FILE_MODE_OPEN_OR_FAIL) || (mode == _K_FILE_MODE_OPEN_OR_CREATE)) {
@@ -378,57 +186,55 @@ void file_open(EngineState *s, char *filename, int mode) {
s->r_acc = make_reg(0, 0xffff);
return;
}
+#endif
- uint retval = 0;
- while ((retval < s->_fileHandles.size()) && s->_fileHandles[retval]._file)
- retval++;
-
- // Ignore _fileHandles[0]
- if (retval < 1)
- retval = 1;
+ // Find a free file handle
+ uint handle = 1; // Ignore _fileHandles[0]
+ while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
+ handle++;
- if (retval >= s->_fileHandles.size()) { // Hit size limit => Allocate more space
- s->_fileHandles.resize(retval + 1);
+ if (handle == s->_fileHandles.size()) { // Hit size limit => Allocate more space
+ s->_fileHandles.resize(s->_fileHandles.size() + 1);
}
- s->_fileHandles[retval]._file = file;
+ s->_fileHandles[handle]._in = inFile;
+ s->_fileHandles[handle]._out = outFile;
+ s->_fileHandles[handle]._name = filename;
- s->r_acc = make_reg(0, retval);
+ s->r_acc = make_reg(0, handle);
+
+ debug(3, " -> opened file '%s' with handle %d", filename, handle);
}
reg_t kFOpen(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *name = kernel_dereference_char_pointer(s, argv[0], 0);
int mode = UKPV(1);
+ debug(3, "kFOpen(%s,0x%x)", name, mode);
file_open(s, name, mode);
- debug(3, "kFOpen(%s,0x%x) -> %d", name, mode, s->r_acc.offset);
return s->r_acc;
}
-static FILE *getFileFromHandle(EngineState *s, uint handle) {
+static FileHandle *getFileFromHandle(EngineState *s, uint handle) {
if (handle == 0) {
- SCIkwarn(SCIkERROR, "Attempt to use file handle 0\n");
+ error("Attempt to use file handle 0");
return 0;
}
- if ((handle >= s->_fileHandles.size()) || (s->_fileHandles[handle]._file == NULL)) {
- SCIkwarn(SCIkERROR, "Attempt to use invalid/unused file handle %d\n", handle);
+ 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]._file;
+ return &s->_fileHandles[handle];
}
void file_close(EngineState *s, int handle) {
SCIkdebug(SCIkFILE, "Closing file %d\n", handle);
- FILE *f = getFileFromHandle(s, handle);
- if (!f)
- return;
-
- fclose(f);
-
- s->_fileHandles[handle]._file = NULL;
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (f)
+ f->close();
}
reg_t kFClose(EngineState *s, int funct_nr, int argc, reg_t *argv) {
@@ -437,38 +243,41 @@ reg_t kFClose(EngineState *s, int funct_nr, int argc, reg_t *argv) {
return s->r_acc;
}
-void fputs_wrapper(EngineState *s, int handle, int size, char *data) {
- SCIkdebug(SCIkFILE, "FPuts'ing \"%s\" to handle %d\n", data, handle);
-
- FILE *f = getFileFromHandle(s, handle);
- if (f)
- fwrite(data, 1, size, f);
-}
-
void fwrite_wrapper(EngineState *s, int handle, char *data, int length) {
SCIkdebug(SCIkFILE, "fwrite()'ing \"%s\" to handle %d\n", data, handle);
- FILE *f = getFileFromHandle(s, handle);
- if (f)
- fwrite(data, 1, length, f);
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (!f)
+ return;
+
+ if (!f->_out) {
+ error("fgets_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 funct_nr, int argc, reg_t *argv) {
int handle = UKPV(0);
char *data = kernel_dereference_char_pointer(s, argv[1], 0);
- fputs_wrapper(s, handle, strlen(data), data);
+ fwrite_wrapper(s, handle, data, strlen(data));
return s->r_acc;
}
static void fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
SCIkdebug(SCIkFILE, "FGets'ing %d bytes from handle %d\n", maxsize, handle);
- FILE *f = getFileFromHandle(s, handle);
+ FileHandle *f = getFileFromHandle(s, handle);
if (!f)
return;
- fgets(dest, maxsize, f);
+ 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);
SCIkdebug(SCIkFILE, "FGets'ed \"%s\"\n", dest);
}
@@ -476,22 +285,30 @@ static void fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
static void fread_wrapper(EngineState *s, char *dest, int bytes, int handle) {
SCIkdebug(SCIkFILE, "fread()'ing %d bytes from handle %d\n", bytes, handle);
- FILE *f = getFileFromHandle(s, handle);
+ FileHandle *f = getFileFromHandle(s, handle);
if (!f)
return;
- s->r_acc = make_reg(0, fread(dest, 1, bytes, f));
+ 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) {
- FILE *f = getFileFromHandle(s, handle);
+ FileHandle *f = getFileFromHandle(s, handle);
if (!f)
return;
- s->r_acc = make_reg(0, fseek(f, offset, whence));
-}
+ if (!f->_in) {
+ error("fseek_wrapper: Trying to seek in file '%s' opened for writing", f->_name.c_str());
+ return;
+ }
-#define TEST_DIR_OR_QUIT(dir) if (!dir) { return NULL_REG; }
+ s->r_acc = make_reg(0, f->_in->seek(offset, whence));
+}
reg_t kFGets(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *dest = kernel_dereference_char_pointer(s, argv[0], 0);
@@ -503,15 +320,17 @@ reg_t kFGets(EngineState *s, int funct_nr, int argc, reg_t *argv) {
return argv[0];
}
-/* kGetCWD(address):
-** Writes the cwd to the supplied address and returns the address in acc.
-*/
+/**
+ * Writes the cwd to the supplied address and returns the address in acc.
+ */
reg_t kGetCWD(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *targetaddr = kernel_dereference_char_pointer(s, argv[0], 0);
- getcwd(targetaddr, MAX_SAVE_DIR_SIZE - 1);
+ // 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?
+ strcpy(targetaddr, "/");
- targetaddr[MAX_SAVE_DIR_SIZE - 1] = 0; // Terminate
debug(3, "kGetCWD() -> %s", targetaddr);
return argv[0];
@@ -526,12 +345,14 @@ void delete_savegame(EngineState *s, int savedir_nr) {
saveFileMan->removeSavefile(filename.c_str());
}
-#define K_DEVICE_INFO_GET_DEVICE 0
-#define K_DEVICE_INFO_GET_CURRENT_DEVICE 1
-#define K_DEVICE_INFO_PATHS_EQUAL 2
-#define K_DEVICE_INFO_IS_FLOPPY 3
-#define K_DEVICE_INFO_GET_SAVECAT_NAME 7
-#define K_DEVICE_INFO_GET_SAVEFILE_NAME 8
+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 funct_nr, int argc, reg_t *argv) {
int mode = UKPV(0);
@@ -614,8 +435,8 @@ reg_t kCheckFreeSpace(EngineState *s, int funct_nr, int argc, reg_t *argv) {
}
static int _savegame_index_struct_compare(const void *a, const void *b) {
- struct _savegame_index_struct *A = (struct _savegame_index_struct *)a;
- struct _savegame_index_struct *B = (struct _savegame_index_struct *)b;
+ SavegameDesc *A = (SavegameDesc *)a;
+ SavegameDesc *B = (SavegameDesc *)b;
if (B->date != A->date)
return B->date - A->date;
@@ -649,7 +470,7 @@ static void update_savegame_indices() {
}
}
- qsort(_savegame_indices, _savegame_indices_nr, sizeof(struct _savegame_index_struct), _savegame_index_struct_compare);
+ qsort(_savegame_indices, _savegame_indices_nr, sizeof(SavegameDesc), _savegame_index_struct_compare);
}
reg_t kCheckSaveGame(EngineState *s, int funct_nr, int argc, reg_t *argv) {
@@ -855,94 +676,73 @@ reg_t kRestoreGame(EngineState *s, int funct_nr, int argc, reg_t *argv) {
}
reg_t kValidPath(EngineState *s, int funct_nr, int argc, reg_t *argv) {
- char *pathname = kernel_dereference_char_pointer(s, argv[0], 0);
- char cpath[MAXPATHLEN + 1];
- getcwd(cpath, MAXPATHLEN + 1);
+ const char *path = kernel_dereference_char_pointer(s, argv[0], 0);
+ // FIXME: For now, we only accept the (fake) root dir "/" as a valid path.
+ s->r_acc = make_reg(0, 0 == strcmp(path, "/"));
- s->r_acc = make_reg(0, !chdir(pathname)); // Try to go there. If it works, return 1, 0 otherwise.
- chdir(cpath);
-
- debug(3, "kValidPath(%s) -> %d", pathname, s->r_acc.offset);
+ debug(3, "kValidPath(%s) -> %d", path, s->r_acc.offset);
return s->r_acc;
}
-#define K_FILEIO_OPEN 0
-#define K_FILEIO_CLOSE 1
-#define K_FILEIO_READ_RAW 2
-#define K_FILEIO_WRITE_RAW 3
-#define K_FILEIO_UNLINK 4
-#define K_FILEIO_READ_STRING 5
-#define K_FILEIO_WRITE_STRING 6
-#define K_FILEIO_SEEK 7
-#define K_FILEIO_FIND_FIRST 8
-#define K_FILEIO_FIND_NEXT 9
-#define K_FILEIO_STAT 10
-
-
-class DirSeeker {
-protected:
- EngineState *_vm;
- reg_t _outbuffer;
- sci_dir_t _dir;
-
- const char *write_filename_to_mem(const char *string);
-
-public:
- DirSeeker(EngineState *s) : _vm(s) {
- _outbuffer = NULL_REG;
- sci_init_dir(&_dir);
- }
-
- void first_file(const char *dir, char *mask, reg_t buffer);
- void next_file();
+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
};
-const char *DirSeeker::write_filename_to_mem(const char *string) {
- char *mem = kernel_dereference_char_pointer(_vm, _outbuffer, 0);
-
- if (string) {
- memset(mem, 0, 13);
- strncpy(mem, string, 12);
+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);
+ _vm->r_acc = NULL_REG;
+ return;
}
+ _outbuffer = buffer;
- return string;
-}
+ // 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);
-void DirSeeker::next_file() {
- if (write_filename_to_mem(sci_find_next(&_dir)))
- _vm->r_acc = _outbuffer;
- else
- _vm->r_acc = NULL_REG;
+ // Reset the list iterator and write the first match to the output buffer, if any.
+ _iter = _savefiles.begin();
+ nextFile();
}
-void DirSeeker::first_file(const char *dir, char *mask, reg_t buffer) {
- if (!buffer.segment) {
- sciprintf("Warning: first_file(state,\"%s\",\"%s\", 0) invoked!\n", dir, mask);
+void DirSeeker::nextFile() {
+ if (_iter == _savefiles.end()) {
_vm->r_acc = NULL_REG;
return;
}
- if (strcmp(dir, ".")) {
- sciprintf("%s L%d: Non-local first_file: Not implemented yet\n", __FILE__, __LINE__);
- _vm->r_acc = NULL_REG;
- return;
- }
+ char *mem = kernel_dereference_char_pointer(_vm, _outbuffer, 0);
+ memset(mem, 0, 13);
- // Get rid of the old find structure
- if (_outbuffer.segment)
- sci_finish_find(&_dir);
-
- _outbuffer = buffer;
+ // TODO: Transform the string back into a format usable by the SCI scripts.
+ // I.e., strip any TARGET- prefix.
+ const char *string = _iter->c_str();
+ assert(string);
+ strncpy(mem, string, 12);
- if (write_filename_to_mem(sci_find_first(&_dir, mask)))
- _vm->r_acc = _outbuffer;
- else
- _vm->r_acc = NULL_REG;
+ // Return the result and advance the list iterator :)
+ _vm->r_acc = _outbuffer;
+ ++_iter;
}
+
+
reg_t kFileIO(EngineState *s, int funct_nr, int argc, reg_t *argv) {
int func_nr = UKPV(0);
@@ -952,7 +752,7 @@ reg_t kFileIO(EngineState *s, int funct_nr, int argc, reg_t *argv) {
int mode = UKPV(2);
file_open(s, name, mode);
- debug(3, "K_FILEIO_OPEN(%s,0x%x) -> %d", name, mode, s->r_acc.offset);
+ debug(3, "K_FILEIO_OPEN(%s,0x%x)", name, mode);
break;
}
case K_FILEIO_CLOSE : {
@@ -984,7 +784,11 @@ reg_t kFileIO(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *name = kernel_dereference_char_pointer(s, argv[1], 0);
debug(3, "K_FILEIO_UNLINK(%s)", name);
- unlink(name);
+ Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
+ const Common::String wrappedName = ((Sci::SciEngine*)g_engine)->wrapFilename(name);
+ saveFileMan->removeSavefile(wrappedName.c_str());
+ // TODO/FIXME: Should we return something (like, a bool indicating
+ // whether deleting the save succeeded or failed)?
break;
}
case K_FILEIO_READ_STRING : {
@@ -1002,8 +806,12 @@ reg_t kFileIO(EngineState *s, int funct_nr, int argc, reg_t *argv) {
char *buf = kernel_dereference_char_pointer(s, argv[2], size);
debug(3, "K_FILEIO_WRITE_STRING(%d,%d)", handle, size);
+ // FIXME: What is the difference between K_FILEIO_WRITE_STRING and
+ // K_FILEIO_WRITE_RAW? Normally, I would expect the difference to
+ // be that the former doesn't receive a 'size' parameter. But here
+ // it does. Are we missing something?
if (buf)
- fputs_wrapper(s, handle, size, buf);
+ fwrite_wrapper(s, handle, buf, size);
break;
}
case K_FILEIO_SEEK : {
@@ -1025,23 +833,27 @@ reg_t kFileIO(EngineState *s, int funct_nr, int argc, reg_t *argv) {
if (strcmp(mask, "*.*") == 0)
strcpy(mask, "*"); // For UNIX
#endif
- if (!s->dirseeker)
- s->dirseeker = new DirSeeker(s);
- assert(s->dirseeker);
- s->dirseeker->first_file(".", mask, buf);
+ s->_dirseeker.firstFile(mask, buf);
break;
}
case K_FILEIO_FIND_NEXT : {
- assert(s->dirseeker);
debug(3, "K_FILEIO_FIND_NEXT()");
- s->dirseeker->next_file();
+ s->_dirseeker.nextFile();
break;
}
- case K_FILEIO_STAT : {
+ case K_FILEIO_FILE_EXISTS : {
char *name = kernel_dereference_char_pointer(s, argv[1], 0);
- s->r_acc = make_reg(0, sci_file_size(name) >= 0);
- debug(3, "K_FILEIO_STAT(%s) -> %d", name, s->r_acc.offset);
+ // TODO: Transform the name given by the scripts to us, e.g. by
+ // prepending TARGET-
+ // TODO: We may have to also check for a regular file with the
+ // given name, using File::exists(). Really depends on *how*
+ // scripts use this opcode. Need more test data...
+ Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
+ bool exists = !saveFileMan->listSavefiles(name).empty();
+
+ s->r_acc = make_reg(0, exists);
+ debug(3, "K_FILEIO_FILE_EXISTS(%s) -> %d", name, s->r_acc.offset);
break;
}
default :
diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp
index 5d753255bd..6e89868db2 100644
--- a/engines/sci/engine/scriptdebug.cpp
+++ b/engines/sci/engine/scriptdebug.cpp
@@ -1059,7 +1059,7 @@ int c_save_game(EngineState *s) {
if (!omit_check) {
int result = 0;
for (uint i = 0; i < s->_fileHandles.size(); i++)
- if (s->_fileHandles[i]._file)
+ if (s->_fileHandles[i].isOpen())
result++;
if (result) {
diff --git a/engines/sci/include/engine.h b/engines/sci/include/engine.h
index 085b50b2e4..8035b16025 100644
--- a/engines/sci/include/engine.h
+++ b/engines/sci/include/engine.h
@@ -48,7 +48,22 @@ namespace Sci {
struct menubar_t;
struct kfunct_sig_pair_t; // from kernel.h
-class DirSeeker;
+class DirSeeker {
+protected:
+ EngineState *_vm;
+ reg_t _outbuffer;
+ Common::StringList _savefiles;
+ Common::StringList::const_iterator _iter;
+
+public:
+ DirSeeker(EngineState *s) : _vm(s) {
+ _outbuffer = NULL_REG;
+ _iter = _savefiles.begin();
+ }
+
+ void firstFile(const char *mask, reg_t buffer);
+ void nextFile();
+};
#define FREESCI_CURRENT_SAVEGAME_VERSION 8
#define FREESCI_MINIMUM_SAVEGAME_VERSION 8
@@ -81,15 +96,16 @@ struct SavegameMetadata {
class FileHandle {
public:
- FILE *_file;
+ Common::String _name;
+ Common::SeekableReadStream *_in;
+ Common::WriteStream *_out;
- FileHandle() : _file(0) {
- }
+public:
+ FileHandle();
+ ~FileHandle();
- ~FileHandle() {
- if (_file)
- fclose(_file);
- }
+ void close();
+ bool isOpen() const;
};
struct EngineState {
@@ -187,7 +203,7 @@ struct EngineState {
Common::Array<FileHandle> _fileHandles; /* Array of file handles. Dynamically increased if required. */
- DirSeeker *dirseeker;
+ DirSeeker _dirseeker;
/* VM Information */
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 88829e3d54..38f33916ff 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -339,4 +339,19 @@ Common::String SciEngine::getSavegameName(int nr) const {
return _targetName + extension;
}
+Common::String SciEngine::getSavegamePattern() const {
+ return _targetName + ".???";
+}
+
+Common::String SciEngine::wrapFilename(const Common::String &name) const {
+ return _targetName + "-" + name;
+}
+
+Common::String SciEngine::unwrapFilename(const Common::String &name) const {
+ Common::String prefix = name + "-";
+ if (name.hasPrefix(prefix.c_str()))
+ return Common::String(name.c_str() + prefix.size());
+ return name;
+}
+
} // End of namespace Sci
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 523cb53559..4d4ad53fb6 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -88,7 +88,16 @@ public:
Common::Platform getPlatform() const;
uint32 getFlags() const;
ResourceManager *getResMgr() { return _resmgr; }
+
Common::String getSavegameName(int nr) const;
+ Common::String getSavegamePattern() const;
+
+ /** Prepend 'TARGET-' to the given filename. */
+ Common::String wrapFilename(const Common::String &name) const;
+
+ /** Remove the 'TARGET-' prefix of the given filename, if present. */
+ Common::String unwrapFilename(const Common::String &name) const;
+
private:
const SciGameDescription *_gameDescription;