Ticket #8964: sci-file.patch

File sci-file.patch, 29.1 KB (added by fingolfin, 12 years ago)
  • include/engine.h

     
    4848struct menubar_t;
    4949struct kfunct_sig_pair_t;       // from kernel.h
    5050
    51 class DirSeeker;
     51class DirSeeker {
     52protected:
     53        EngineState *_vm;
     54        reg_t _outbuffer;
     55        Common::StringList _savefiles;
     56        Common::StringList::const_iterator _iter;
    5257
     58public:
     59        DirSeeker(EngineState *s) : _vm(s) {
     60                _outbuffer = NULL_REG;
     61                _iter = _savefiles.begin();
     62        }
     63       
     64        void firstFile(const char *mask, reg_t buffer);
     65        void nextFile();
     66};
     67
    5368#define FREESCI_CURRENT_SAVEGAME_VERSION 8
    5469#define FREESCI_MINIMUM_SAVEGAME_VERSION 8
    5570
     
    8196
    8297class FileHandle {
    8398public:
    84         FILE *_file;
     99        Common::String _name;
     100        Common::SeekableReadStream *_in;
     101        Common::WriteStream *_out;
    85102       
    86         FileHandle() : _file(0) {
    87         }
     103public:
     104        FileHandle();
     105        ~FileHandle();
    88106       
    89         ~FileHandle() {
    90                 if (_file)
    91                         fclose(_file);
    92         }
     107        void close();
     108        bool isOpen() const;
    93109};
    94110
    95111struct EngineState {
     
    186202
    187203        Common::Array<FileHandle> _fileHandles; /* Array of file handles. Dynamically increased if required. */
    188204
    189         DirSeeker *dirseeker;
     205        DirSeeker _dirseeker;
    190206
    191207        /* VM Information */
    192208
  • sci.cpp

     
    339339        return _targetName + extension;
    340340}
    341341
     342Common::String SciEngine::getSavegamePattern() const {
     343        return _targetName + ".???";
     344}
     345
     346Common::String SciEngine::wrapFilename(const Common::String &name) const {
     347        return _targetName + "-" + name;
     348}
     349
     350Common::String SciEngine::unwrapFilename(const Common::String &name) const {
     351        Common::String prefix = name + "-";
     352        if (name.hasPrefix(prefix.c_str()))
     353                return Common::String(name.c_str() + prefix.size());
     354        return name;
     355}
     356
    342357} // End of namespace Sci
  • sci.h

     
    8888        Common::Platform getPlatform() const;
    8989        uint32 getFlags() const;
    9090        ResourceManager *getResMgr() { return _resmgr; }
     91
    9192        Common::String getSavegameName(int nr) const;
     93        Common::String getSavegamePattern() const;
    9294
     95        /** Prepend 'TARGET-' to the given filename. */
     96        Common::String wrapFilename(const Common::String &name) const;
     97
     98        /** Remove the 'TARGET-' prefix of the given filename, if present. */
     99        Common::String unwrapFilename(const Common::String &name) const;
     100
     101
    93102private:
    94103        const SciGameDescription *_gameDescription;
    95104        Console *_console;
  • engine/game.cpp

     
    440440        return 0;
    441441}
    442442
    443 EngineState::EngineState() {
     443EngineState::EngineState() : _dirseeker(this) {
    444444        savegame_version = 0;
    445445
    446446        widget_serial_counter = 0;
     
    530530
    531531        _fileHandles.resize(5);
    532532
    533         dirseeker = 0;
    534 
    535533        execution_stack = 0;
    536534        execution_stack_size = 0;
    537535        execution_stack_pos = 0;
     
    662660        s->bp_list = NULL; // No breakpoints defined
    663661        s->have_bp = 0;
    664662
    665         s->dirseeker = 0; // Used by FileIO for FIND_FIRST, FIND_NEXT
    666 
    667663        if (s->version >= SCI_VERSION_FTU_LOFS_ABSOLUTE &&
    668664                s->version < SCI_VERSION(1, 001, 000))
    669665                s->seg_manager->setExportWidth(1);
  • engine/scriptdebug.cpp

     
    10591059        if (!omit_check) {
    10601060                int result = 0;
    10611061                for (uint i = 0; i < s->_fileHandles.size(); i++)
    1062                         if (s->_fileHandles[i]._file)
     1062                        if (s->_fileHandles[i].isOpen())
    10631063                                result++;
    10641064
    10651065                if (result) {
  • engine/kfile.cpp

     
    3838#include "sci/include/engine.h"
    3939#include "sci/engine/kernel.h"
    4040
    41 #include <errno.h>
    42 #include <sys/stat.h>           // for S_IREAD/S_IWRITE
    43 
    44 #define SCI_INVALID_FD -1
    45 #define IS_VALID_FD(a) ((a) != SCI_INVALID_FD) /* Tests validity of a file descriptor */
    46 
    47 // FIXME: rework sci_dir_t to use common/fs.h and remove these includes
    48 #include <sys/types.h>
    49 #ifndef _MSC_VER
    50 #include <dirent.h>
    51 #else
    52 #include <io.h>
    53 #endif
    54 
    55 // FIXME: For chdir() etc.
    56 #ifndef _MSC_VER
    57 #include <unistd.h>
    58 #endif
    59 
    60 #ifdef WIN32
    61 #  define FO_BINARY "b"
    62 #else
    63 #  define FO_BINARY ""
    64 #endif
    65 
    66 #ifdef UNIX
    67 #include <fnmatch.h>
    68 #include <sys/stat.h>
    69 #endif
    70 
    71 
    7241namespace Sci {
    7342
    7443
    75 struct sci_dir_t {
    76 #ifdef WIN32
    77         long search;
    78         struct _finddata_t fileinfo;
    79 #else
    80         DIR *dir;
    81         char *mask_copy;
    82 #endif
    83 }; /* used by sci_find_first and friends */
     44/*
     45 * Note on how file I/O is implemented: In ScummVM, one can not create/write
     46 * arbitrary data files, simply because many of our target platforms do not
     47 * support this. The only files on can create are savestates. But SCI has an
     48 * opcode to create and write to seemingly 'arbitrary' files.
     49 * To implement that opcode, we combine the SaveFileManager with regular file
     50 * code, similarly to how the SCUMM HE engine does it.
     51 *
     52 * To handle opening a file called "foobar", what we do is this: First, we
     53 * create an 'augmented file name', by prepending the game target and a dash,
     54 * so if we running game target sq1vga, the name becomes "sq1vga-foobar".
     55 * Next, we check if such a file is known to the SaveFileManager. If so, we
     56 * we use that for reading/writing, delete it, whatever.
     57 *
     58 * If no such file is present but we were only asked to *read* the file,
     59 * we fallback to looking for a regular file called "foobar", and open that
     60 * for reading only.
     61 *
     62 * There are some caveats to this: First off, SCI apparently has no way
     63 * to signal that a file is supposed to be opened for reading only. For now,
     64 * we hackishly just assume that this is what _K_FILE_MODE_OPEN_OR_FAIL is for.
     65 *
     66 * Secondly, at least in theory, a file could be opened for both reading and
     67 * writing. We currently do not support this. If it turns out that we *have*
     68 * to support it, we could do it as follows: Initially open the file for
     69 * reading. If a write is attempted, store the file offset, close the file,
     70 * if necessary create a mirror clone (i.e., clone it into a suitably named
     71 * savefile), then open the file (resp. its clone for writing) and seek to the
     72 * correct position. If later a read is attempted, we again close and re-open.
     73 *
     74 * However, before putting any effort into implementing such an error-prone
     75 * scheme, we are well advised to first determine whether any game needs this
     76 * at all, and for what. Based on that, we can maybe come up with a better waybill
     77 * to provide this functionality.
     78 */
     79 
     80 
    8481
    85 void sci_init_dir(sci_dir_t *dirent);
    86 /* Initializes an sci directory search structure
    87 ** Parameters: (sci_dir_t *) dirent: The entity to initialize
    88 ** Returns   : (void)
    89 ** The entity is initialized to "empty" values, meaning that it can be
    90 ** used in subsequent sci_find_first/sci_find_next constructs. In no
    91 ** event should this function be used upon a structure which has been
    92 ** subjected to any of the other dirent calls.
    93 */
    94 
    95 char *sci_find_first(sci_dir_t *dirent, const char *mask);
    96 /* Finds the first file matching the specified file mask
    97 ** Parameters: (sci_dir_t *) dirent: Pointer to an unused dirent structure
    98 **             (const char *) mask: File mask to apply
    99 ** Returns   : (char *) Name of the first matching file found, or NULL
    100 */
    101 
    102 char *sci_find_next(sci_dir_t *dirent);
    103 /* Finds the next file specified by an sci_dir initialized by sci_find_first()
    104 ** Parameters: (sci_dir_t *) dirent: Pointer to SCI dir entity
    105 ** Returns   : (char *) Name of the next matching file, or NULL
    106 */
    107 
    108 void sci_finish_find(sci_dir_t *dirent);
    109 /* Completes an 'sci_find_first/next' procedure
    110 ** Parameters: (sci_dir_t *) dirent: Pointer to the dirent used
    111 ** Returns   : (void)
    112 ** In the operation sequences
    113 **   sci_init_dir(x); sci_finish_find(x);
    114 ** and
    115 **   sci_finish_find(x); sci_finish_find(x);
    116 ** the second operation is guaranteed to be a no-op.
    117 */
    118 
    119 FILE *sci_fopen(const char *fname, const char *mode);
    120 /* Opens a FILE* case-insensitively
    121 ** Parameters: (const char *) fname: Name of the file to open
    122 **             (const char *) mode: Mode to open it with
    123 ** Returns   : (FILE *) A valid file handle, or NULL on failure
    124 ** Always refers to the cwd, cannot address subdirectories
    125 */
    126 
    127 int sci_file_size(const char *fname);
    128 /* Returns the filesize of a file
    129 ** Parameters: (const char *) fname: Name of file to get filesize of
    130 ** Returns   : (int) filesize of the file, -1 on error
    131 */
    132 
    133 
    134 #if defined(WIN32)
    135 void sci_init_dir(sci_dir_t *dir) {
    136         dir->search = -1;
     82FileHandle::FileHandle() : _in(0), _out(0) {
    13783}
    13884
    139 char *sci_find_first(sci_dir_t *dir, const char *mask) {
    140         dir->search = _findfirst(mask, &(dir->fileinfo));
    141 
    142         if (dir->search != -1) {
    143                 if (dir->fileinfo.name == NULL) {
    144                         return NULL;
    145                 }
    146 
    147                 if (strcmp(dir->fileinfo.name, ".") == 0 ||
    148                         strcmp(dir->fileinfo.name, "..") == 0) {
    149                         if (sci_find_next(dir) == NULL) {
    150                                 return NULL;
    151                         }
    152                 }
    153 
    154                 return dir->fileinfo.name;
    155         } else {
    156                 switch (errno) {
    157                 case ENOENT: {
    158 #ifdef _DEBUG
    159                         printf("_findfirst errno = ENOENT: no match\n");
    160 
    161                         if (mask)
    162                                 printf(" in: %s\n", mask);
    163                         else
    164                                 printf(" - searching in undefined directory\n");
    165 #endif
    166                         break;
    167                 }
    168                 case EINVAL: {
    169                         printf("_findfirst errno = EINVAL: invalid filename\n");
    170                         break;
    171                 }
    172                 default:
    173                         printf("_findfirst errno = unknown (%d)", errno);
    174                 }
    175         }
    176 
    177         return NULL;
     85FileHandle::~FileHandle() {
     86        close();
    17887}
    17988
    180 char *sci_find_next(sci_dir_t *dir) {
    181         if (dir->search == -1)
    182                 return NULL;
    183 
    184         if (_findnext(dir->search, &(dir->fileinfo)) < 0) {
    185                 _findclose(dir->search);
    186                 dir->search = -1;
    187                 return NULL;
    188         }
    189 
    190         if (strcmp(dir->fileinfo.name, ".") == 0 ||
    191                 strcmp(dir->fileinfo.name, "..") == 0) {
    192                 if (sci_find_next(dir) == NULL) {
    193                         return NULL;
    194                 }
    195         }
    196 
    197         return dir->fileinfo.name;
     89void FileHandle::close() {
     90        delete _in;
     91        delete _out;
     92        _in = 0;
     93        _out = 0;
     94        _name.clear();
    19895}
    19996
    200 void sci_finish_find(sci_dir_t *dir) {
    201         if (dir->search != -1) {
    202                 _findclose(dir->search);
    203                 dir->search = -1;
    204         }
     97bool FileHandle::isOpen() const {
     98        return _in || _out;
    20599}
    206100
    207 #else
    208101
    209 void sci_init_dir(sci_dir_t *dir) {
    210         dir->dir = NULL;
    211         dir->mask_copy = NULL;
    212 }
    213102
    214 char *sci_find_first(sci_dir_t *dir, const char *mask) {
    215         if (dir->dir)
    216                 closedir(dir->dir);
    217 
    218         if (!(dir->dir = opendir("."))) {
    219                 sciprintf("%s, L%d: opendir(\".\") failed!\n", __FILE__, __LINE__);
    220                 return NULL;
    221         }
    222 
    223         dir->mask_copy = sci_strdup(mask);
    224 
    225         return sci_find_next(dir);
    226 }
    227 
    228 #ifndef FNM_CASEFOLD
    229 #define FNM_CASEFOLD 0
    230 #warning "File searches will not be case-insensitive!"
    231 #endif
    232 
    233 char *sci_find_next(sci_dir_t *dir) {
    234         struct dirent *match;
    235 
    236         while ((match = readdir(dir->dir))) {
    237                 if (match->d_name[0] == '.')
    238                         continue;
    239 
    240                 if (!fnmatch(dir->mask_copy, match->d_name, FNM_CASEFOLD))
    241                         return match->d_name;
    242         }
    243 
    244         sci_finish_find(dir);
    245 
    246         return NULL;
    247 }
    248 
    249 void sci_finish_find(sci_dir_t *dir) {
    250         if (dir->dir) {
    251                 closedir(dir->dir);
    252                 dir->dir = NULL;
    253                 free(dir->mask_copy);
    254                 dir->mask_copy = NULL;
    255         }
    256 }
    257 
    258 #endif
    259 
    260 
    261 /* Returns the case-sensitive filename of a file.
    262 ** Expects *dir to be uninitialized and the caller to free it afterwards.
    263 ** Parameters: (const char *) fname: Name of file to get case-sensitive.
    264 **             (sci_dir_t *) dir: Directory to find file within.
    265 ** Returns   : (char *) Case-sensitive filename of the file.
    266 */
    267 Common::String _fcaseseek(const char *fname) {
    268         // Expects *dir to be uninitialized and the caller to
    269         // free it afterwards  */
    270 
    271         // Look up the file, ignoring case
    272         Common::ArchiveMemberList files;
    273         SearchMan.listMatchingMembers(files, fname);
    274 
    275         for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
    276                 const Common::String name = (*x)->getName();
    277                 if (name.equalsIgnoreCase(fname))
    278                         return name;
    279         }
    280 
    281         return Common::String();
    282 }
    283 
    284 FILE *sci_fopen(const char *fname, const char *mode) {
    285         Common::String name = _fcaseseek(fname);
    286         FILE *file = NULL;
    287 
    288         if (!name.empty())
    289                 file = fopen(name.c_str(), mode);
    290         else if (strchr(mode, 'w'))
    291                 file = fopen(fname, mode);
    292 
    293         return file;
    294 }
    295 
    296 int sci_file_size(const char *fname) {
    297         struct stat fn_stat;
    298 
    299         if (stat(fname, &fn_stat))
    300                 return -1;
    301 
    302         return fn_stat.st_size;
    303 }
    304 
    305 
    306 
    307103static int _savegame_indices_nr = -1; // means 'uninitialized'
    308104
    309 static struct _savegame_index_struct {
     105struct SavegameDesc {
    310106        int id;
    311107        int date;
    312108        int time;
    313 } _savegame_indices[MAX_SAVEGAME_NR];
     109};
    314110
    315 // This assumes modern stream implementations. It may break on DOS.
     111static SavegameDesc _savegame_indices[MAX_SAVEGAME_NR];
    316112
    317113
    318 /* Attempts to mirror a file by copying it from the resource firectory
    319 ** to the working directory. Returns NULL if the file didn't exist.
    320 ** Otherwise, the new file is then opened for reading or writing.
    321 */
    322 static FILE *f_open_mirrored(EngineState *s, char *fname) {
    323         debug(3, "f_open_mirrored(%s)", fname);
     114enum {
     115        _K_FILE_MODE_OPEN_OR_CREATE = 0,
     116        _K_FILE_MODE_OPEN_OR_FAIL = 1,
     117        _K_FILE_MODE_CREATE = 2
     118};
    324119
    325 #if 0
    326         Common::File file;
    327         if (!file.open(fname))
    328                 return NULL;
    329120
    330         int fsize = file.size();
    331         if (fsize > 0) {
    332                 buf = (char *)sci_malloc(fsize);
    333                 file.read(buf, fsize);
    334         }
    335121
    336         file.close();
     122void file_open(EngineState *s, const char *filename, int mode) {
     123        const Common::String wrappedName = ((Sci::SciEngine*)g_engine)->wrapFilename(filename);
     124        Common::SeekableReadStream *inFile = 0;
     125        Common::WriteStream *outFile = 0;
     126        Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
    337127
    338         copy the file to a savegame -> only makes sense to perform this change
    339         if we at the same time change the code for loading files to look among the
    340         savestates, and also change *all* file writing code to write to savestates,
    341         as it should
     128        if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
     129                // Try to open file, abort if not possible
     130                inFile = saveFileMan->openForLoading(wrappedName.c_str());
     131                // If no matching savestate exists: fall back to reading from a regular file
     132                if (!inFile)
     133                        inFile = SearchMan.createReadStreamForMember(filename);
     134                if (!inFile)
     135                        warning("file_open(_K_FILE_MODE_OPEN_OR_FAIL) failed to open file '%s'", filename);
     136        } else if (mode == _K_FILE_MODE_CREATE) {
     137                // Create the file, destroying any content it might have had
     138                outFile = saveFileMan->openForSaving(wrappedName.c_str());
     139                if (!outFile)
     140                        warning("file_open(_K_FILE_MODE_CREATE) failed to create file '%s'", filename);
     141        } else if (mode == _K_FILE_MODE_OPEN_OR_CREATE) {
     142                // Try to open file, create it if it doesn't exist
    342143
    343         Also, we may have to change the filename when creating a matchin savegame,
    344         e.g. prefix it with the target name
    345 #endif
     144                // FIXME: I am disabling this for now, as it's not quite clear what
     145                // should happen if the given file already exists... open it for appending?
     146                // Or (more likely), open it for reading *and* writing? We may have to
     147                // clone the file for that, etc., see also the long comment at the start
     148                // of this file.
     149                // We really need some examples on how this is used.
     150                warning("file_open(_K_FILE_MODE_OPEN_OR_CREATE) File creation currently not supported");
     151        } else {
     152                error("file_open: unsupported mode %d", mode);
     153        }
    346154
    347         // FIXME: for now we just pretend this has failed
    348         return 0;
    349 }
     155        if (!inFile && !outFile) { // Failed
     156                debug(3, "file_open() failed");
     157                s->r_acc = make_reg(0, 0xffff);
     158                return;
     159        }
    350160
    351 #define _K_FILE_MODE_OPEN_OR_CREATE 0
    352 #define _K_FILE_MODE_OPEN_OR_FAIL 1
    353 #define _K_FILE_MODE_CREATE 2
    354161
    355 void file_open(EngineState *s, char *filename, int mode) {
    356         FILE *file = NULL;
     162#if 0
     163        // FIXME: The old FreeSCI code for opening a file. Left as a reference, as apparently
     164        // the implementation below used to work well enough.
    357165
    358166        SCIkdebug(SCIkFILE, "Opening file %s with mode %d\n", filename, mode);
    359167        if ((mode == _K_FILE_MODE_OPEN_OR_FAIL) || (mode == _K_FILE_MODE_OPEN_OR_CREATE)) {
     
    378186                s->r_acc = make_reg(0, 0xffff);
    379187                return;
    380188        }
     189#endif
    381190
    382         uint retval = 1; // Ignore _fileHandles[0]
    383         while ((retval < s->_fileHandles.size()) && s->_fileHandles[retval]._file)
    384                 retval++;
     191        // Find a free file handle
     192        uint handle = 1; // Ignore _fileHandles[0]
     193        while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
     194                handle++;
    385195
    386         if (retval == s->_fileHandles.size()) { // Hit size limit => Allocate more space
     196        if (handle == s->_fileHandles.size()) { // Hit size limit => Allocate more space
    387197                s->_fileHandles.resize(s->_fileHandles.size() + 1);
    388198        }
    389199
    390         s->_fileHandles[retval]._file = file;
     200        s->_fileHandles[handle]._in = inFile;
     201        s->_fileHandles[handle]._out = outFile;
     202        s->_fileHandles[handle]._name = filename;
    391203
    392         s->r_acc = make_reg(0, retval);
     204        s->r_acc = make_reg(0, handle);
     205       
     206        debug(3, " -> opened file '%s' with handle %d", filename, handle);
    393207}
    394208
    395209reg_t kFOpen(EngineState *s, int funct_nr, int argc, reg_t *argv) {
    396210        char *name = kernel_dereference_char_pointer(s, argv[0], 0);
    397211        int mode = UKPV(1);
    398212
     213        debug(3, "kFOpen(%s,0x%x)", name, mode);
    399214        file_open(s, name, mode);
    400         debug(3, "kFOpen(%s,0x%x) -> %d", name, mode, s->r_acc.offset);
    401215        return s->r_acc;
    402216}
    403217
    404 static FILE *getFileFromHandle(EngineState *s, uint handle) {
     218static FileHandle *getFileFromHandle(EngineState *s, uint handle) {
    405219        if (handle == 0) {
    406220                SCIkwarn(SCIkERROR, "Attempt to use file handle 0\n");
    407221                return 0;
    408222        }
    409223
    410         if ((handle >= s->_fileHandles.size()) || (s->_fileHandles[handle]._file == NULL)) {
     224        if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) {
    411225                SCIkwarn(SCIkERROR, "Attempt to use invalid/unused file handle %d\n", handle);
    412226                return 0;
    413227        }
    414228
    415         return s->_fileHandles[handle]._file;
     229        return &s->_fileHandles[handle];
    416230}
    417231
    418232void file_close(EngineState *s, int handle) {
    419233        SCIkdebug(SCIkFILE, "Closing file %d\n", handle);
    420234
    421         FILE *f = getFileFromHandle(s, handle);
    422         if (!f)
    423                 return;
    424 
    425         fclose(f);
    426 
    427         s->_fileHandles[handle]._file = NULL;
     235        FileHandle *f = getFileFromHandle(s, handle);
     236        if (f)
     237                f->close();
    428238}
    429239
    430240reg_t kFClose(EngineState *s, int funct_nr, int argc, reg_t *argv) {
     
    433243        return s->r_acc;
    434244}
    435245
    436 void fputs_wrapper(EngineState *s, int handle, int size, char *data) {
    437         SCIkdebug(SCIkFILE, "FPuts'ing \"%s\" to handle %d\n", data, handle);
    438 
    439         FILE *f = getFileFromHandle(s, handle);
    440         if (f)
    441                 fwrite(data, 1, size, f);
    442 }
    443 
    444246void fwrite_wrapper(EngineState *s, int handle, char *data, int length) {
    445247        SCIkdebug(SCIkFILE, "fwrite()'ing \"%s\" to handle %d\n", data, handle);
    446248
    447         FILE *f = getFileFromHandle(s, handle);
    448         if (f)
    449                 fwrite(data, 1, length, f);
     249        FileHandle *f = getFileFromHandle(s, handle);
     250        if (!f)
     251                return;
     252
     253        if (!f->_out) {
     254                warning("fgets_wrapper: Trying to write to file '%s' opened for reading", f->_name.c_str());
     255                return;
     256        }
     257
     258        f->_out->write(data, length);
    450259}
    451260
    452261reg_t kFPuts(EngineState *s, int funct_nr, int argc, reg_t *argv) {
    453262        int handle = UKPV(0);
    454263        char *data = kernel_dereference_char_pointer(s, argv[1], 0);
    455264
    456         fputs_wrapper(s, handle, strlen(data), data);
     265        fwrite_wrapper(s, handle, data, strlen(data));
    457266        return s->r_acc;
    458267}
    459268
    460269static void fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
    461270        SCIkdebug(SCIkFILE, "FGets'ing %d bytes from handle %d\n", maxsize, handle);
    462271
    463         FILE *f = getFileFromHandle(s, handle);
     272        FileHandle *f = getFileFromHandle(s, handle);
    464273        if (!f)
    465274                return;
    466275
    467         fgets(dest, maxsize, f);
     276        if (!f->_in) {
     277                warning("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
     278                return;
     279        }
     280        f->_in->readLine_NEW(dest, maxsize);
    468281
    469282        SCIkdebug(SCIkFILE, "FGets'ed \"%s\"\n", dest);
    470283}
     
    472285static void fread_wrapper(EngineState *s, char *dest, int bytes, int handle) {
    473286        SCIkdebug(SCIkFILE, "fread()'ing %d bytes from handle %d\n", bytes, handle);
    474287
    475         FILE *f = getFileFromHandle(s, handle);
     288        FileHandle *f = getFileFromHandle(s, handle);
    476289        if (!f)
    477290                return;
    478291
    479         s->r_acc = make_reg(0, fread(dest, 1, bytes, f));
     292        if (!f->_in) {
     293                warning("fread_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
     294                return;
     295        }
     296
     297        s->r_acc = make_reg(0, f->_in->read(dest, bytes));
    480298}
    481299
    482300static void fseek_wrapper(EngineState *s, int handle, int offset, int whence) {
    483         FILE *f = getFileFromHandle(s, handle);
     301        FileHandle *f = getFileFromHandle(s, handle);
    484302        if (!f)
    485303                return;
    486304
    487         s->r_acc = make_reg(0, fseek(f, offset, whence));
     305        if (!f->_in) {
     306                warning("fseek_wrapper: Trying to seek in file '%s' opened for writing", f->_name.c_str());
     307                return;
     308        }
     309
     310        s->r_acc = make_reg(0, f->_in->seek(offset, whence));
    488311}
    489312
    490 #define TEST_DIR_OR_QUIT(dir) if (!dir) { return NULL_REG; }
    491 
    492313reg_t kFGets(EngineState *s, int funct_nr, int argc, reg_t *argv) {
    493314        char *dest = kernel_dereference_char_pointer(s, argv[0], 0);
    494315        int maxsize = UKPV(1);
     
    499320        return argv[0];
    500321}
    501322
    502 /* kGetCWD(address):
    503 ** Writes the cwd to the supplied address and returns the address in acc.
    504 */
     323/**
     324 * Writes the cwd to the supplied address and returns the address in acc.
     325 */
    505326reg_t kGetCWD(EngineState *s, int funct_nr, int argc, reg_t *argv) {
    506327        char *targetaddr = kernel_dereference_char_pointer(s, argv[0], 0);
    507328
    508         getcwd(targetaddr, MAX_SAVE_DIR_SIZE - 1);
     329        // We do not let the scripts see the file system, instead pretending
     330        // we are always in the same directory.
     331        // TODO/FIXME: Is "/" a good value? Maybe "" or "." or "C:\" are better?
     332        strcpy(targetaddr, "/");
    509333
    510         targetaddr[MAX_SAVE_DIR_SIZE - 1] = 0; // Terminate
    511334        debug(3, "kGetCWD() -> %s", targetaddr);
    512335
    513336        return argv[0];
     
    522345        saveFileMan->removeSavefile(filename.c_str());
    523346}
    524347
    525 #define K_DEVICE_INFO_GET_DEVICE 0
    526 #define K_DEVICE_INFO_GET_CURRENT_DEVICE 1
    527 #define K_DEVICE_INFO_PATHS_EQUAL 2
    528 #define K_DEVICE_INFO_IS_FLOPPY 3
    529 #define K_DEVICE_INFO_GET_SAVECAT_NAME 7
    530 #define K_DEVICE_INFO_GET_SAVEFILE_NAME 8
     348enum {
     349        K_DEVICE_INFO_GET_DEVICE = 0,
     350        K_DEVICE_INFO_GET_CURRENT_DEVICE = 1,
     351        K_DEVICE_INFO_PATHS_EQUAL = 2,
     352        K_DEVICE_INFO_IS_FLOPPY = 3,
     353        K_DEVICE_INFO_GET_SAVECAT_NAME = 7,
     354        K_DEVICE_INFO_GET_SAVEFILE_NAME = 8
     355};
    531356
    532357reg_t kDeviceInfo(EngineState *s, int funct_nr, int argc, reg_t *argv) {
    533358        int mode = UKPV(0);
     
    610435}
    611436
    612437static int _savegame_index_struct_compare(const void *a, const void *b) {
    613         struct _savegame_index_struct *A = (struct _savegame_index_struct *)a;
    614         struct _savegame_index_struct *B = (struct _savegame_index_struct *)b;
     438        SavegameDesc *A = (SavegameDesc *)a;
     439        SavegameDesc *B = (SavegameDesc *)b;
    615440
    616441        if (B->date != A->date)
    617442                return B->date - A->date;
     
    645470                }
    646471        }
    647472
    648         qsort(_savegame_indices, _savegame_indices_nr, sizeof(struct _savegame_index_struct), _savegame_index_struct_compare);
     473        qsort(_savegame_indices, _savegame_indices_nr, sizeof(SavegameDesc), _savegame_index_struct_compare);
    649474}
    650475
    651476reg_t kCheckSaveGame(EngineState *s, int funct_nr, int argc, reg_t *argv) {
     
    851676}
    852677
    853678reg_t kValidPath(EngineState *s, int funct_nr, int argc, reg_t *argv) {
    854         char *pathname = kernel_dereference_char_pointer(s, argv[0], 0);
    855         char cpath[MAXPATHLEN + 1];
    856         getcwd(cpath, MAXPATHLEN + 1);
     679        const char *path = kernel_dereference_char_pointer(s, argv[0], 0);
    857680
     681        // FIXME: For now, we only accept the (fake) root dir "/" as a valid path.
     682        s->r_acc = make_reg(0, 0 == strcmp(path, "/"));
    858683
    859         s->r_acc = make_reg(0, !chdir(pathname)); // Try to go there. If it works, return 1, 0 otherwise.
    860         chdir(cpath);
     684        debug(3, "kValidPath(%s) -> %d", path, s->r_acc.offset);
    861685
    862         debug(3, "kValidPath(%s) -> %d", pathname, s->r_acc.offset);
    863 
    864686        return s->r_acc;
    865687}
    866688
    867 #define K_FILEIO_OPEN           0
    868 #define K_FILEIO_CLOSE          1
    869 #define K_FILEIO_READ_RAW       2
    870 #define K_FILEIO_WRITE_RAW      3
    871 #define K_FILEIO_UNLINK         4
    872 #define K_FILEIO_READ_STRING    5
    873 #define K_FILEIO_WRITE_STRING   6
    874 #define K_FILEIO_SEEK           7
    875 #define K_FILEIO_FIND_FIRST     8
    876 #define K_FILEIO_FIND_NEXT      9
    877 #define K_FILEIO_STAT           10
    878 
    879 
    880 class DirSeeker {
    881 protected:
    882         EngineState *_vm;
    883         reg_t _outbuffer;
    884         sci_dir_t _dir;
    885 
    886         const char *write_filename_to_mem(const char *string);
    887 
    888 public:
    889         DirSeeker(EngineState *s) : _vm(s) {
    890                 _outbuffer = NULL_REG;
    891                 sci_init_dir(&_dir);
    892         }
    893        
    894         void first_file(const char *dir, char *mask, reg_t buffer);
    895         void next_file();
     689enum {
     690        K_FILEIO_OPEN                   = 0,
     691        K_FILEIO_CLOSE                  = 1,
     692        K_FILEIO_READ_RAW               = 2,
     693        K_FILEIO_WRITE_RAW              = 3,
     694        K_FILEIO_UNLINK                 = 4,
     695        K_FILEIO_READ_STRING    = 5,
     696        K_FILEIO_WRITE_STRING   = 6,
     697        K_FILEIO_SEEK                   = 7,
     698        K_FILEIO_FIND_FIRST             = 8,
     699        K_FILEIO_FIND_NEXT              = 9,
     700        K_FILEIO_FILE_EXISTS    = 10
    896701};
    897702
    898703
    899 const char *DirSeeker::write_filename_to_mem(const char *string) {
    900         char *mem = kernel_dereference_char_pointer(_vm, _outbuffer, 0);
    901 
    902         if (string) {
    903                 memset(mem, 0, 13);
    904                 strncpy(mem, string, 12);
     704void DirSeeker::firstFile(const char *mask, reg_t buffer) {
     705       
     706        // Verify that we are given a valid buffer
     707        if (!buffer.segment) {
     708                warning("DirSeeker::firstFile('%s') invoked with invalid buffer", mask);
     709                _vm->r_acc = NULL_REG;
     710                return;
    905711        }
     712        _outbuffer = buffer;
    906713
    907         return string;
    908 }
     714        // Obtain a list of all savefiles matching the given mask
     715        // TODO: Modify the mask, e.g. by prefixing "TARGET-".
     716        Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
     717        _savefiles = saveFileMan->listSavefiles(mask);
    909718
    910 void DirSeeker::next_file() {
    911         if (write_filename_to_mem(sci_find_next(&_dir)))
    912                 _vm->r_acc = _outbuffer;
    913         else
    914                 _vm->r_acc = NULL_REG;
     719        // Reset the list iterator and write the first match to the output buffer, if any.
     720        _iter = _savefiles.begin();
     721        nextFile();
    915722}
    916723
    917 void DirSeeker::first_file(const char *dir, char *mask, reg_t buffer) {
    918         if (!buffer.segment) {
    919                 sciprintf("Warning: first_file(state,\"%s\",\"%s\", 0) invoked!\n", dir, mask);
     724void DirSeeker::nextFile() {
     725        if (_iter == _savefiles.end()) {
    920726                _vm->r_acc = NULL_REG;
    921727                return;
    922728        }
    923729
    924         if (strcmp(dir, ".")) {
    925                 sciprintf("%s L%d: Non-local first_file: Not implemented yet\n", __FILE__, __LINE__);
    926                 _vm->r_acc = NULL_REG;
    927                 return;
    928         }
     730        char *mem = kernel_dereference_char_pointer(_vm, _outbuffer, 0);
     731        memset(mem, 0, 13);
    929732
    930         // Get rid of the old find structure
    931         if (_outbuffer.segment)
    932                 sci_finish_find(&_dir);
    933        
    934         _outbuffer = buffer;
     733        // TODO: Transform the string back into a format usable by the SCI scripts.
     734        // I.e., strip any TARGET- prefix.
     735        const char *string = _iter->c_str();
     736        assert(string);
     737        strncpy(mem, string, 12);
    935738
    936         if (write_filename_to_mem(sci_find_first(&_dir, mask)))
    937                 _vm->r_acc = _outbuffer;
    938         else
    939                 _vm->r_acc = NULL_REG;
     739        // Return the result and advance the list iterator :)
     740        _vm->r_acc = _outbuffer;
     741        ++_iter;
    940742}
    941743
     744
     745
    942746reg_t kFileIO(EngineState *s, int funct_nr, int argc, reg_t *argv) {
    943747        int func_nr = UKPV(0);
    944748
     
    948752                int mode = UKPV(2);
    949753
    950754                file_open(s, name, mode);
    951                 debug(3, "K_FILEIO_OPEN(%s,0x%x) -> %d", name, mode, s->r_acc.offset);
     755                debug(3, "K_FILEIO_OPEN(%s,0x%x)", name, mode);
    952756                break;
    953757        }
    954758        case K_FILEIO_CLOSE : {
     
    980784                char *name = kernel_dereference_char_pointer(s, argv[1], 0);
    981785                debug(3, "K_FILEIO_UNLINK(%s)", name);
    982786
    983                 unlink(name);
     787                Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
     788                const Common::String wrappedName = ((Sci::SciEngine*)g_engine)->wrapFilename(name);
     789                saveFileMan->removeSavefile(wrappedName.c_str());
     790                // TODO/FIXME: Should we return something (like, a bool indicating
     791                // whether deleting the save succeeded or failed)?
    984792                break;
    985793        }
    986794        case K_FILEIO_READ_STRING : {
     
    998806                char *buf = kernel_dereference_char_pointer(s, argv[2], size);
    999807                debug(3, "K_FILEIO_WRITE_STRING(%d,%d)", handle, size);
    1000808
     809                // FIXME: What is the difference between K_FILEIO_WRITE_STRING and
     810                // K_FILEIO_WRITE_RAW? Normally, I would expect the difference to
     811                // be that the former doesn't receive a 'size' parameter. But here
     812                // it does. Are we missing something?
    1001813                if (buf)
    1002                         fputs_wrapper(s, handle, size, buf);
     814                        fwrite_wrapper(s, handle, buf, size);
    1003815                break;
    1004816        }
    1005817        case K_FILEIO_SEEK : {
     
    1021833                if (strcmp(mask, "*.*") == 0)
    1022834                        strcpy(mask, "*"); // For UNIX
    1023835#endif
    1024                 if (!s->dirseeker)
    1025                         s->dirseeker = new DirSeeker(s);
    1026                 assert(s->dirseeker);
    1027                 s->dirseeker->first_file(".", mask, buf);
     836                s->_dirseeker.firstFile(mask, buf);
    1028837
    1029838                break;
    1030839        }
    1031840        case K_FILEIO_FIND_NEXT : {
    1032                 assert(s->dirseeker);
    1033841                debug(3, "K_FILEIO_FIND_NEXT()");
    1034                 s->dirseeker->next_file();
     842                s->_dirseeker.nextFile();
    1035843                break;
    1036844        }
    1037         case K_FILEIO_STAT : {
     845        case K_FILEIO_FILE_EXISTS : {
    1038846                char *name = kernel_dereference_char_pointer(s, argv[1], 0);
    1039                 s->r_acc = make_reg(0, sci_file_size(name) >= 0);
    1040                 debug(3, "K_FILEIO_STAT(%s) -> %d", name, s->r_acc.offset);
     847                // TODO: Transform the name given by the scripts to us, e.g. by
     848                // prepending TARGET-
     849                // TODO: We may have to also check for a regular file with the
     850                // given name, using File::exists(). Really depends on *how*
     851                // scripts use this opcode. Need more test data...
     852                Common::SaveFileManager *saveFileMan = g_engine->getSaveFileManager();
     853                bool exists = !saveFileMan->listSavefiles(name).empty();
     854
     855                s->r_acc = make_reg(0, exists);
     856                debug(3, "K_FILEIO_FILE_EXISTS(%s) -> %d", name, s->r_acc.offset);
    1041857                break;
    1042858        }
    1043859        default :