SCI: Commited file handling revamp, work in progress

svn-id: r38919
This commit is contained in:
Max Horn 2009-02-27 01:17:24 +00:00
parent 5a5c51bb48
commit bf0860fc5e
6 changed files with 298 additions and 447 deletions

View File

@ -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);

View File

@ -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 */
/*
* 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.
*/
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;
FileHandle::FileHandle() : _in(0), _out(0) {
}
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;
FileHandle::~FileHandle() {
close();
}
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 FileHandle::close() {
delete _in;
delete _out;
_in = 0;
_out = 0;
_name.clear();
}
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);
return NULL;
}
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;
}
}
#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();
}
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);
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();
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);
}
file.close();
if (!inFile && !outFile) { // Failed
debug(3, "file_open() failed");
s->r_acc = make_reg(0, 0xffff);
return;
}
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
Also, we may have to change the filename when creating a matchin savegame,
e.g. prefix it with the target name
#endif
// FIXME: for now we just pretend this has failed
return 0;
}
#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++;
// Find a free file handle
uint handle = 1; // Ignore _fileHandles[0]
while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
handle++;
// Ignore _fileHandles[0]
if (retval < 1)
retval = 1;
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);
}
return string;
}
void DirSeeker::next_file() {
if (write_filename_to_mem(sci_find_next(&_dir)))
_vm->r_acc = _outbuffer;
else
_vm->r_acc = NULL_REG;
}
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);
_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;
}
// Get rid of the old find structure
if (_outbuffer.segment)
sci_finish_find(&_dir);
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;
if (write_filename_to_mem(sci_find_first(&_dir, mask)))
_vm->r_acc = _outbuffer;
else
_vm->r_acc = NULL_REG;
// 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()) {
_vm->r_acc = NULL_REG;
return;
}
char *mem = kernel_dereference_char_pointer(_vm, _outbuffer, 0);
memset(mem, 0, 13);
// 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);
// 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 :

View File

@ -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) {

View File

@ -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 */

View File

@ -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

View File

@ -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;