aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine/kfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/engine/kfile.c')
-rw-r--r--engines/sci/engine/kfile.c1174
1 files changed, 1174 insertions, 0 deletions
diff --git a/engines/sci/engine/kfile.c b/engines/sci/engine/kfile.c
new file mode 100644
index 0000000000..c4f6c63ac2
--- /dev/null
+++ b/engines/sci/engine/kfile.c
@@ -0,0 +1,1174 @@
+/***************************************************************************
+ Kfile.c Copyright (C) 1999 Christoph Reichenbach
+
+
+ This program may be modified and copied freely according to the terms of
+ the GNU general public license (GPL), as long as the above copyright
+ notice and the licensing information contained herein are preserved.
+
+ Please refer to www.gnu.org for licensing details.
+
+ This work is provided AS IS, without warranty of any kind, expressed or
+ implied, including but not limited to the warranties of merchantibility,
+ noninfringement, and fitness for a specific purpose. The author will not
+ be held liable for any damage caused by this work or derivatives of it.
+
+ By using this source code, you agree to the licensing terms as stated
+ above.
+
+
+ Please contact the maintainer for bug reports or inquiries.
+
+ Current Maintainer:
+
+ Christoph Reichenbach (CJR) [jameson@linuxgames.com]
+
+***************************************************************************/
+
+#include <engine.h>
+
+#ifdef _WIN32
+# ifndef PATH_MAX
+# define PATH_MAX 255
+# endif
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# define stat _stat
+#elif defined (_DREAMCAST)
+# include <dc.h>
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+static int _savegame_indices_nr = -1; /* means 'uninitialized' */
+
+static struct _savegame_index_struct {
+ int id;
+ long timestamp;
+} _savegame_indices[MAX_SAVEGAME_NR];
+
+/* This assumes modern stream implementations. It may break on DOS. */
+
+
+/* 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(state_t *s, char *fname)
+{
+ int fd;
+ char *buf = NULL;
+ int fsize;
+
+ chdir(s->resource_dir);
+ fd = sci_open(fname, O_RDONLY | O_BINARY);
+ if (!IS_VALID_FD(fd)) {
+ chdir(s->work_dir);
+ return NULL;
+ }
+
+ fsize = sci_fd_size(fd);
+ if (fsize > 0) {
+ buf = (char*)sci_malloc(fsize);
+ read(fd, buf, fsize);
+ }
+
+ close(fd);
+
+ chdir(s->work_dir);
+
+ /* Visual C++ doesn't allow to specify O_BINARY with creat() */
+#ifdef _MSC_VER
+ fd = _open(fname, O_CREAT | O_BINARY | O_RDWR, S_IREAD | S_IWRITE);
+#else
+ fd = creat(fname, 0600);
+#endif
+
+ if (!IS_VALID_FD(fd) && buf) {
+ free(buf);
+ sciprintf("kfile.c: f_open_mirrored(): Warning: Could not create '%s' in '%s' (%d bytes to copy)\n",
+ fname, s->work_dir, fsize);
+ return NULL;
+ }
+
+ if (fsize) {
+ int ret;
+ ret = write(fd, buf, fsize);
+ if (ret < fsize) {
+ sciprintf("kfile.c: f_open_mirrored(): Warning: Could not write all %ld bytes to '%s' in '%s' (only wrote %ld)\n",
+ (long)fsize, fname, s->work_dir, ret);
+ }
+
+ free(buf);
+ }
+
+ close(fd);
+
+ return sci_fopen(fname, "r" FO_BINARY "+");
+}
+
+
+#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(state_t *s, char *filename, int mode)
+{
+ int retval = 1; /* Ignore file_handles[0] */
+ FILE *file = NULL;
+
+ 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)) {
+ file = sci_fopen(filename, "r" FO_BINARY "+"); /* Attempt to open existing file */
+ SCIkdebug(SCIkFILE, "Opening file %s with mode %d\n", filename, mode);
+ if (!file) {
+ SCIkdebug(SCIkFILE, "Failed. Attempting to copy from resource dir...\n");
+ file = f_open_mirrored(s, filename);
+ if (file)
+ SCIkdebug(SCIkFILE, "Success!\n");
+ else
+ SCIkdebug(SCIkFILE, "Not found.\n");
+ }
+ }
+
+ if ((!file) && ((mode == _K_FILE_MODE_OPEN_OR_CREATE) || (mode == _K_FILE_MODE_CREATE))) {
+ file = sci_fopen(filename, "w" FO_BINARY "+"); /* Attempt to create file */
+ SCIkdebug(SCIkFILE, "Creating file %s with mode %d\n", filename, mode);
+ }
+ if (!file) { /* Failed */
+ SCIkdebug(SCIkFILE, "file_open() failed\n");
+ s->r_acc = make_reg(0, 0xffff);
+ return;
+ }
+
+ while (s->file_handles[retval] && (retval < s->file_handles_nr))
+ retval++;
+
+ if (retval == s->file_handles_nr) /* Hit size limit => Allocate more space */
+ s->file_handles = (FILE**)sci_realloc(s->file_handles, sizeof(FILE *) * ++(s->file_handles_nr));
+
+ s->file_handles[retval] = file;
+
+ s->r_acc = make_reg(0, retval);
+}
+
+reg_t
+kFOpen(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *name = kernel_dereference_char_pointer(s, argv[0], 0);
+ int mode = UKPV(1);
+
+ file_open(s, name, mode);
+ return s->r_acc;
+}
+
+void file_close(state_t *s, int handle)
+{
+ SCIkdebug(SCIkFILE, "Closing file %d\n", handle);
+
+ if (handle == 0) {
+ SCIkwarn(SCIkERROR, "Attempt to close file handle 0\n");
+ return;
+ }
+
+ if ((handle >= s->file_handles_nr) || (s->file_handles[handle] == NULL)) {
+ SCIkwarn(SCIkERROR, "Attempt to close invalid/unused file handle %d\n", handle);
+ return;
+ }
+
+ fclose(s->file_handles[handle]);
+
+ s->file_handles[handle] = NULL;
+}
+
+reg_t
+kFClose(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ file_close(s, UKPV(0));
+ return s->r_acc;
+}
+
+void fputs_wrapper(state_t *s, int handle, int size, char *data)
+{
+ SCIkdebug(SCIkFILE, "FPuts'ing \"%s\" to handle %d\n", data, handle);
+
+ if (handle == 0) {
+ SCIkwarn(SCIkERROR, "Attempt to write to file handle 0\n");
+ return;
+ }
+
+ if ((handle >= s->file_handles_nr) || (s->file_handles[handle] == NULL)) {
+ SCIkwarn(SCIkERROR, "Attempt to write to invalid/unused file handle %d\n", handle);
+ return;
+ }
+
+ fwrite(data, 1, size, s->file_handles[handle]);
+}
+
+void fwrite_wrapper(state_t *s, int handle, char *data, int length)
+{
+ SCIkdebug(SCIkFILE, "fwrite()'ing \"%s\" to handle %d\n", data, handle);
+
+ if (handle == 0) {
+ SCIkwarn(SCIkERROR, "Attempt to write to file handle 0\n");
+ return;
+ }
+
+ if ((handle >= s->file_handles_nr) || (s->file_handles[handle] == NULL)) {
+ SCIkwarn(SCIkERROR, "Attempt to write to invalid/unused file handle %d\n", handle);
+ return;
+ }
+
+ fwrite(data, 1, length, s->file_handles[handle]);
+}
+
+
+reg_t kFPuts(state_t *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);
+ return s->r_acc;
+}
+
+static void
+fgets_wrapper(state_t *s, char *dest, int maxsize, int handle)
+{
+ SCIkdebug(SCIkFILE, "FGets'ing %d bytes from handle %d\n", maxsize, handle);
+
+
+ if (handle == 0) {
+ SCIkwarn(SCIkERROR, "Attempt to read from file handle 0\n");
+ return;
+ }
+
+ if ((handle >= s->file_handles_nr) || (s->file_handles[handle] == NULL)) {
+ SCIkwarn(SCIkERROR, "Attempt to read from invalid/unused file handle %d\n", handle);
+ return;
+ }
+
+ fgets(dest, maxsize, s->file_handles[handle]);
+
+ SCIkdebug(SCIkFILE, "FGets'ed \"%s\"\n", dest);
+}
+
+
+static void
+fread_wrapper(state_t *s, char *dest, int bytes, int handle)
+{
+ SCIkdebug(SCIkFILE, "fread()'ing %d bytes from handle %d\n", bytes, handle);
+
+ if (handle == 0) {
+ SCIkwarn(SCIkERROR, "Attempt to read from file handle 0\n");
+ return;
+ }
+
+ if ((handle >= s->file_handles_nr) || (s->file_handles[handle] == NULL)) {
+ SCIkwarn(SCIkERROR, "Attempt to read from invalid/unused file handle %d\n", handle);
+ return;
+ }
+
+ s->r_acc=make_reg(0,fread(dest, 1, bytes, s->file_handles[handle]));
+}
+
+
+static void
+fseek_wrapper(state_t *s, int handle, int offset, int whence)
+{
+
+ if (handle == 0) {
+ SCIkwarn(SCIkERROR, "Attempt seek on file handle 0\n");
+ return;
+ }
+
+ if ((handle >= s->file_handles_nr) || (s->file_handles[handle] == NULL)) {
+ SCIkwarn(SCIkERROR, "Attempt seek on invalid/unused file handle %d\n", handle);
+ return;
+ }
+
+ s->r_acc=make_reg(0, fseek(s->file_handles[handle], offset, whence));
+}
+
+
+static char *
+_chdir_savedir(state_t *s)
+{
+ char *cwd = sci_getcwd();
+ char *save_dir = kernel_dereference_char_pointer(s,
+ make_reg(s->sys_strings_segment, SYS_STRING_SAVEDIR), 0);
+
+ if (chdir(save_dir) && sci_mkpath(save_dir)) {
+
+ sciprintf(__FILE__": Can't chdir to savegame dir '%s' or "
+ "create it\n", save_dir);
+
+ sci_free(cwd);
+ return NULL;
+ }
+
+ if (!cwd)
+ cwd = strdup(s->work_dir);
+
+ return cwd; /* Potentially try again */
+}
+
+static void
+_chdir_restoredir(char *dir)
+{
+ if (chdir(dir)) {
+ sciprintf(__FILE__": Can't seem to return to previous homedir '%s'\n",
+ dir);
+ }
+ free(dir);
+}
+
+#define TEST_DIR_OR_QUIT(dir) if (!dir) { return NULL_REG; }
+
+reg_t
+kFGets(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *dest = kernel_dereference_char_pointer(s, argv[0], 0);
+ int maxsize = UKPV(1);
+ int handle = UKPV(2);
+
+ fgets_wrapper(s, dest, maxsize, handle);
+ return argv[0];
+}
+
+
+/* kGetCWD(address):
+** Writes the cwd to the supplied address and returns the address in acc.
+*/
+reg_t
+kGetCWD(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *wd = sci_getcwd();
+ char *targetaddr = kernel_dereference_char_pointer(s, argv[0], 0);
+
+ strncpy(targetaddr, wd, MAX_SAVE_DIR_SIZE - 1);
+ targetaddr[MAX_SAVE_DIR_SIZE - 1] = 0; /* Terminate */
+
+ SCIkdebug(SCIkFILE, "Copying cwd='%s'(%d chars) to %p",
+ wd, strlen(wd), targetaddr);
+
+ free(wd);
+ return argv[0];
+}
+
+/* Returns a dynamically allocated pointer to the name of the requested save dir */
+char *
+_k_get_savedir_name(int nr)
+{
+ char suffices[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+ char *savedir_name = (char*)sci_malloc(strlen(FREESCI_SAVEDIR_PREFIX) + 2);
+ assert(nr >= 0);
+ assert(nr < MAX_SAVEGAME_NR);
+ strcpy(savedir_name, FREESCI_SAVEDIR_PREFIX);
+ savedir_name[strlen(FREESCI_SAVEDIR_PREFIX)] = suffices[nr];
+ savedir_name[strlen(FREESCI_SAVEDIR_PREFIX) + 1] = 0;
+
+ return savedir_name;
+}
+
+void
+delete_savegame(state_t *s, int savedir_nr)
+{
+ char *workdir = _chdir_savedir(s);
+ char *savedir = _k_get_savedir_name(savedir_nr);
+ char buffer[256];
+
+ sciprintf("Deleting savegame '%s'\n", savedir);
+
+ sprintf(buffer, "%s/%s.id", savedir, s->game_name);
+ sci_unlink(buffer);
+
+ sprintf(buffer, "%s/state", savedir);
+ sci_unlink(buffer);
+
+ sci_rmdir(savedir);
+
+ free(savedir);
+ _chdir_restoredir(workdir);
+}
+
+#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
+
+#ifdef _WIN32
+
+reg_t
+kDeviceInfo_Win32(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char dir_buffer [MAX_PATH], dir_buffer2 [MAX_PATH];
+ int mode = UKPV(0);
+
+
+ switch(mode) {
+
+ case K_DEVICE_INFO_GET_DEVICE: {
+ char *input_s = (char*)kernel_dereference_bulk_pointer(s, argv[1], 0);
+ char *output_s = (char*)kernel_dereference_bulk_pointer(s, argv[2], 0);
+
+ GetFullPathName (input_s, sizeof (dir_buffer)-1, dir_buffer, NULL);
+ strncpy(output_s, dir_buffer, 2);
+ output_s [2] = 0;
+ }
+ break;
+
+ case K_DEVICE_INFO_GET_CURRENT_DEVICE: {
+ char *output_s = (char*)kernel_dereference_bulk_pointer(s, argv[1], 0);
+
+ _getcwd (dir_buffer, sizeof (dir_buffer)-1);
+ strncpy(output_s, dir_buffer, 2);
+ output_s [2] = 0;
+ }
+ break;
+
+ case K_DEVICE_INFO_PATHS_EQUAL: {
+ char *path1_s = (char*)kernel_dereference_bulk_pointer(s, argv[1], 0);
+ char *path2_s = (char*)kernel_dereference_bulk_pointer(s, argv[2], 0);
+
+ GetFullPathName (path1_s, sizeof (dir_buffer)-1, dir_buffer, NULL);
+ GetFullPathName (path2_s, sizeof (dir_buffer2)-1, dir_buffer2, NULL);
+
+#ifdef _MSC_VER
+ return make_reg(0, !stricmp (path1_s, path2_s));
+#else
+ return make_reg(0, !strcasecmp (path1_s, path2_s));
+#endif
+ }
+ break;
+
+ case K_DEVICE_INFO_IS_FLOPPY: {
+ char *input_s = (char*)kernel_dereference_bulk_pointer(s, argv[1], 0);
+
+ GetFullPathName (input_s, sizeof (dir_buffer)-1, dir_buffer, NULL);
+ dir_buffer [3] = 0; /* leave X:\ */
+
+ return make_reg(0, GetDriveType (dir_buffer) == DRIVE_REMOVABLE);
+ }
+ break;
+
+/* SCI uses these in a less-than-portable way to delete savegames.
+** Read http://www-plan.cs.colorado.edu/creichen/freesci-logs/2005.10/log20051019.html
+** for more information on our workaround for this.
+*/
+ case K_DEVICE_INFO_GET_SAVECAT_NAME: {
+ char *output_buffer = kernel_dereference_char_pointer(s, argv[1], 0);
+ char *game_prefix = kernel_dereference_char_pointer(s, argv[2], 0);
+
+ sprintf(output_buffer, "%s/__throwaway", s->work_dir);
+ }
+
+ break;
+ case K_DEVICE_INFO_GET_SAVEFILE_NAME: {
+ char *output_buffer = kernel_dereference_char_pointer(s, argv[1], 0);
+ char *game_prefix = kernel_dereference_char_pointer(s, argv[2], 0);
+ int savegame_id = UKPV(3);
+ sprintf(output_buffer, "%s/__throwaway", s->work_dir);
+ delete_savegame(s, savegame_id);
+ }
+ break;
+ default: {
+ SCIkwarn(SCIkERROR, "Unknown DeviceInfo() sub-command: %d\n", mode);
+ }
+ }
+ return s->r_acc;
+}
+
+#else /* !_WIN32 */
+
+reg_t
+kDeviceInfo_Unix(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ int mode = UKPV(0);
+
+ switch(mode) {
+
+ case K_DEVICE_INFO_GET_DEVICE: {
+ char *output_s = kernel_dereference_char_pointer(s, argv[2], 0);
+
+ strcpy(output_s, "/");
+ }
+ break;
+
+ case K_DEVICE_INFO_GET_CURRENT_DEVICE: {
+ char *output_s = kernel_dereference_char_pointer(s, argv[1], 0);
+
+ strcpy(output_s, "/");
+ }
+ break;
+
+ case K_DEVICE_INFO_PATHS_EQUAL: {
+ char *path1_s = kernel_dereference_char_pointer(s, argv[1], 0);
+ char *path2_s = kernel_dereference_char_pointer(s, argv[2], 0);
+
+#ifndef HAVE_FNMATCH_H
+#ifndef _DOS
+# warning "File matches will be unprecise!"
+#endif
+ return make_reg(0, !strcmp(path1_s, path2_s));
+#else
+ return make_reg(0, fnmatch(path1_s, path2_s, FNM_PATHNAME) /* POSIX.2 */);
+#endif
+ }
+ break;
+
+ case K_DEVICE_INFO_IS_FLOPPY: {
+
+ return NULL_REG; /* Never */
+ }
+ break;
+
+/* SCI uses these in a less-than-portable way to delete savegames.
+** Read http://www-plan.cs.colorado.edu/creichen/freesci-logs/2005.10/log20051019.html
+** for more information on our workaround for this.
+*/
+ case K_DEVICE_INFO_GET_SAVECAT_NAME: {
+ char *output_buffer = kernel_dereference_char_pointer(s, argv[1], 0);
+/* char *game_prefix = kernel_dereference_char_pointer(s, argv[2], 0);*/
+
+ sprintf(output_buffer, "%s/__throwaway", s->work_dir);
+ }
+
+ break;
+ case K_DEVICE_INFO_GET_SAVEFILE_NAME: {
+ char *output_buffer = kernel_dereference_char_pointer(s, argv[1], 0);
+/* char *game_prefix = kernel_dereference_char_pointer(s, argv[2], 0);*/
+ int savegame_id = UKPV(3);
+ sprintf(output_buffer, "%s/__throwaway", s->work_dir);
+ delete_savegame(s, savegame_id);
+ }
+ break;
+ default: {
+ SCIkwarn(SCIkERROR, "Unknown DeviceInfo() sub-command: %d\n", mode);
+ }
+ }
+
+ return s->r_acc;
+}
+
+#endif /* !_WIN32 */
+
+
+reg_t
+kGetSaveDir(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ return make_reg(s->sys_strings_segment, SYS_STRING_SAVEDIR);
+}
+
+
+reg_t
+kCheckFreeSpace(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *path = kernel_dereference_char_pointer(s, argv[0], 0);
+ char *testpath = (char*)sci_malloc(strlen(path) + 15);
+ char buf[1024];
+ int i;
+ int fd;
+ int failed = 0;
+ int pathlen;
+
+ strcpy(testpath, path);
+ strcat(testpath, "freesci.foo");
+ pathlen = strlen(testpath);
+
+ while (IS_VALID_FD(fd = open(testpath, O_RDONLY))) {
+ close(fd);
+ if (testpath[pathlen - 2] == 'z') { /* Failed. */
+ SCIkwarn(SCIkWARNING, "Failed to find non-existing file for free space test\n");
+ free(testpath);
+ return NULL_REG;
+ }
+
+ /* If this file couldn't be created, try freesci.fop, freesci.foq etc.,
+ ** then freesci.fpa, freesci.fpb. Stop at freesci.fza.
+ ** Yes, this is extremely arbitrary and very strange.
+ */
+ if (testpath[pathlen - 1] == 'z') {
+ testpath[pathlen - 1] = 'a';
+ ++testpath[pathlen - 2];
+ }
+ else
+ ++testpath[pathlen - 1];
+ }
+
+ fd = creat(testpath, 0600);
+
+ if (!IS_VALID_FD(fd)) {
+ SCIkwarn(SCIkWARNING,"Could not test for disk space: %s\n", strerror(errno));
+ SCIkwarn(SCIkWARNING,"Test path was '%s'\n", testpath);
+ free(testpath);
+ return NULL_REG;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ for (i = 0; i < 1024; i++) /* Check for 1 MB */
+ if (write(fd, buf, 1024) < 1024)
+ failed = 1;
+
+ close(fd);
+
+ remove(testpath);
+
+ sci_free(testpath);
+
+ return make_reg(0, !failed);
+}
+
+
+
+int
+_k_check_file(char *filename, int minfilesize)
+ /* Returns 0 if the file exists and is big enough */
+{
+ return (sci_file_size(filename) < minfilesize);
+}
+
+int
+_k_find_savegame_by_name(char *game_id_file, char *name)
+{
+ int savedir_nr = -1;
+ int i;
+ char *buf = NULL;
+
+ for (i = 0; i < MAX_SAVEGAME_NR; i++) {
+ if (!chdir((buf = _k_get_savedir_name(i)))) {
+ char namebuf[32]; /* Save game name buffer */
+ FILE *idfile = sci_fopen(game_id_file, "r");
+
+ if (idfile) {
+ fgets(namebuf, 31, idfile);
+ if (strlen(namebuf) > 0)
+ if (namebuf[strlen(namebuf) - 1] == '\n')
+ namebuf[strlen(namebuf) - 1] = 0; /* Remove trailing newlines */
+
+ if (strcmp(name, namebuf) == 0) {
+ sciprintf("Save game name matched entry %d\n", i);
+ savedir_nr = i;
+ }
+
+ fclose(idfile);
+ }
+
+ chdir("..");
+ }
+ free(buf);
+ }
+ return 0;
+}
+
+#ifdef _DREAMCAST
+static long
+get_file_mtime(int fd)
+{
+ /* FIXME (Dreamcast): Not yet implemented */
+ return 0;
+}
+
+#else
+
+#define get_file_mtime(fd) get_file_mtime_Unix(fd)
+/* Returns the time of the specified file's last modification
+** Parameters: (int) fd: The file descriptor of the file in question
+** Returns : (long) An integer value describing the time of the
+** file's last modification.
+** The only thing that must be ensured is that
+** get_file_mtime(f1) > get_file_mtime(f2)
+** <=>
+** if f1 was modified at a later point in time than the last time
+** f2 was modified.
+*/
+
+static long
+get_file_mtime_Unix(int fd) /* returns the */
+{
+ struct stat fd_stat;
+ fstat(fd, &fd_stat);
+
+ return fd_stat.st_ctime;
+}
+#endif
+
+static int
+_savegame_index_struct_compare(const void *a, const void *b)
+{
+ return ((struct _savegame_index_struct *)b)->timestamp
+ - ((struct _savegame_index_struct *)a)->timestamp;
+}
+
+static void
+update_savegame_indices(char *gfname)
+{
+ int i;
+
+ _savegame_indices_nr = 0;
+
+ for (i = 0; i < MAX_SAVEGAME_NR; i++) {
+ char *dirname = _k_get_savedir_name(i);
+ int fd;
+
+ if (!chdir(dirname)) {
+
+ if (IS_VALID_FD(fd = sci_open(gfname, O_RDONLY))) {
+ _savegame_indices[_savegame_indices_nr].id = i;
+ _savegame_indices[_savegame_indices_nr++].timestamp = get_file_mtime(fd);
+ close(fd);
+ }
+ chdir("..");
+ }
+
+ free(dirname);
+ }
+
+ qsort(_savegame_indices, _savegame_indices_nr, sizeof(struct _savegame_index_struct),
+ _savegame_index_struct_compare);
+
+}
+
+int
+test_savegame(state_t *s, char *savegame_id, char *savegame_name, int savegame_name_length)
+{
+ FILE *f;
+ char buffer[80];
+ int version = -1;
+
+ chdir(savegame_id);
+ f = fopen("state", "r");
+
+ if (!f) return 0;
+ while (!feof(f))
+ {
+ char *seeker;
+ fgets(buffer, sizeof(buffer), f);
+ if ((seeker = strstr(buffer, "savegame_version = ")) != NULL)
+ {
+ seeker += strlen("savegame_version = ");
+ version = strtol(seeker, NULL, 10);
+ break;
+ }
+ }
+
+ fclose(f);
+ return version >= FREESCI_MINIMUM_SAVEGAME_VERSION &&
+ version <= FREESCI_CURRENT_SAVEGAME_VERSION;
+}
+
+reg_t
+kCheckSaveGame(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *game_id = kernel_dereference_char_pointer(s, argv[0], 0);
+ int savedir_nr = UKPV(1);
+ char *buf = NULL;
+ char *workdir = _chdir_savedir(s);
+ TEST_DIR_OR_QUIT(workdir);
+
+ if (_savegame_indices_nr < 0) {
+ char *game_id_file_name = (char*)sci_malloc(strlen(game_id) + strlen(FREESCI_ID_SUFFIX) + 1);
+
+ strcpy(game_id_file_name, game_id);
+ strcat(game_id_file_name, FREESCI_ID_SUFFIX);
+ SCIkwarn(SCIkWARNING, "Savegame index list not initialized!\n");
+ update_savegame_indices(game_id_file_name);
+ }
+
+ savedir_nr = _savegame_indices[savedir_nr].id;
+
+
+ if (savedir_nr > MAX_SAVEGAME_NR-1) {
+ _chdir_restoredir(workdir);
+ return NULL_REG;
+ }
+
+ s->r_acc = make_reg(0, test_savegame(s, (buf = _k_get_savedir_name(savedir_nr)), NULL, 0));
+
+ _chdir_restoredir(workdir);
+ free(buf);
+
+ return s->r_acc;
+}
+
+
+reg_t
+kGetSaveFiles(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *game_id = kernel_dereference_char_pointer(s, argv[0], 0);
+ char *nametarget = kernel_dereference_char_pointer(s, argv[1], 0);
+ reg_t nametarget_base = argv[1];
+ reg_t *nameoffsets = kernel_dereference_reg_pointer(s, argv[2], 0);
+ int gfname_len = strlen(game_id) + strlen(FREESCI_ID_SUFFIX) + 1;
+ char *gfname = (char*)sci_malloc(gfname_len);
+ int i;
+ char *workdir = _chdir_savedir(s);
+ TEST_DIR_OR_QUIT(workdir);
+
+ strcpy(gfname, game_id);
+ strcat(gfname, FREESCI_ID_SUFFIX); /* This file is used to identify in-game savegames */
+
+ update_savegame_indices(gfname);
+
+ s->r_acc = NULL_REG;
+
+ for (i = 0; i < _savegame_indices_nr; i++) {
+ char *savedir_name = _k_get_savedir_name(_savegame_indices[i].id);
+ FILE *idfile;
+
+ if (!chdir(savedir_name)) {
+
+
+ if ((idfile = sci_fopen(gfname, "r"))) { /* Valid game ID file: Assume valid game */
+ char namebuf[SCI_MAX_SAVENAME_LENGTH]; /* Save game name buffer */
+ fgets(namebuf, SCI_MAX_SAVENAME_LENGTH-1, idfile);
+ if (strlen(namebuf) > 0) {
+
+ if (namebuf[strlen(namebuf) - 1] == '\n')
+ namebuf[strlen(namebuf) - 1] = 0; /* Remove trailing newline */
+
+ *nameoffsets = s->r_acc; /* Store savegame ID */
+ ++s->r_acc.offset; /* Increase number of files found */
+
+ nameoffsets++; /* Make sure the next ID string address is written to the next pointer */
+ strncpy(nametarget, namebuf, SCI_MAX_SAVENAME_LENGTH); /* Copy identifier string */
+ *(nametarget + SCI_MAX_SAVENAME_LENGTH - 1) = 0; /* Make sure it's terminated */
+ nametarget += SCI_MAX_SAVENAME_LENGTH; /* Increase name offset pointer accordingly */
+ nametarget_base.offset += SCI_MAX_SAVENAME_LENGTH;
+
+ fclose(idfile);
+ }
+ }
+ chdir("..");
+ }
+ free(savedir_name);
+ }
+
+ free(gfname);
+ *nametarget = 0; /* Terminate list */
+
+ _chdir_restoredir(workdir);
+ return s->r_acc;
+}
+
+reg_t
+kSaveGame(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *game_id = (char*)kernel_dereference_bulk_pointer(s, argv[0], 0);
+ char *savegame_dir;
+ int savedir_nr = UKPV(1);
+ int savedir_id; /* Savegame ID, derived from savedir_nr and the savegame ID list */
+ char *game_id_file_name = (char*)sci_malloc(strlen(game_id) + strlen(FREESCI_ID_SUFFIX) + 1);
+ char *game_description = (char*)kernel_dereference_bulk_pointer(s, argv[2], 0);
+ char *workdir = _chdir_savedir(s);
+ char *version = argc > 3 ? strdup((char*)kernel_dereference_bulk_pointer(s, argv[3], 0)) : NULL;
+ TEST_DIR_OR_QUIT(workdir);
+
+ s->game_version = version;
+
+ strcpy(game_id_file_name, game_id);
+ strcat(game_id_file_name, FREESCI_ID_SUFFIX);
+
+ update_savegame_indices(game_id_file_name);
+
+ if (savedir_nr >= 0 && savedir_nr < _savegame_indices_nr)
+ /* Overwrite */
+ savedir_id = _savegame_indices[savedir_nr].id;
+ else if (savedir_nr >= 0 && savedir_nr < MAX_SAVEGAME_NR) {
+ int i = 0;
+
+ savedir_id = 0;
+
+ /* First, look for holes */
+ while (i < _savegame_indices_nr)
+ if (_savegame_indices[i].id == savedir_id) {
+ ++savedir_id;
+ i = 0;
+ } else ++i;
+
+ if (savedir_id >= MAX_SAVEGAME_NR) {
+ sciprintf("Internal error: Free savegame ID is %d, shouldn't happen!\n",
+ savedir_id);
+ return NULL_REG;
+ }
+
+ /* This loop terminates when savedir_id is not in [x | ex. n. _savegame_indices[n].id = x] */
+ } else {
+ sciprintf("Savegame ID %d is not allowed!\n", savedir_nr);
+ return NULL_REG;
+ }
+
+ savegame_dir = _k_get_savedir_name(savedir_id);
+
+ if (gamestate_save(s, savegame_dir)) {
+ sciprintf("Saving the game failed.\n");
+ s->r_acc = NULL_REG;
+ } else {
+ FILE *idfile;
+
+ chdir(savegame_dir);
+
+ if ((idfile = sci_fopen(game_id_file_name, "w"))) {
+
+ fprintf(idfile, game_description);
+ fclose(idfile);
+
+ } else {
+ sciprintf("Creating the game ID file failed.\n");
+ sciprintf("You can still restore from inside the debugger with \"restore_game %s\"\n", savegame_dir);
+ s->r_acc = NULL_REG;
+ }
+
+ chdir ("..");
+ s->r_acc = make_reg(0, 1);
+ }
+ free(game_id_file_name);
+ _chdir_restoredir(workdir);
+
+ free(s->game_version);
+ s->game_version = NULL;
+ return s->r_acc;
+}
+
+
+reg_t
+kRestoreGame(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *game_id = (char*)kernel_dereference_bulk_pointer(s, argv[0], 0);
+ int savedir_nr = UKPV(1);
+ char *workdir = _chdir_savedir(s);
+ TEST_DIR_OR_QUIT(workdir);
+
+ if (_savegame_indices_nr < 0) {
+ char *game_id_file_name = (char*)sci_malloc(strlen(game_id) + strlen(FREESCI_ID_SUFFIX) + 1);
+
+ strcpy(game_id_file_name, game_id);
+ strcat(game_id_file_name, FREESCI_ID_SUFFIX);
+ SCIkwarn(SCIkWARNING, "Savegame index list not initialized!\n");
+ update_savegame_indices(game_id_file_name);
+ }
+
+ savedir_nr = _savegame_indices[savedir_nr].id;
+
+
+ if (savedir_nr > -1) {
+ char *savedir_name = _k_get_savedir_name(savedir_nr);
+ state_t *newstate = gamestate_restore(s, savedir_name);
+
+ free(savedir_name);
+
+ if (newstate) {
+
+ s->successor = newstate;
+ script_abort_flag = SCRIPT_ABORT_WITH_REPLAY; /* Abort current game */
+ s->execution_stack_pos = s->execution_stack_base;
+
+ } else {
+ s->r_acc = make_reg(0, 1);
+ sciprintf("Restoring failed (game_id = '%s').\n", game_id);
+ }
+
+ } else {
+ s->r_acc = make_reg(0, 1);
+ sciprintf("Savegame #%d not found!\n", savedir_nr);
+ }
+
+ _chdir_restoredir(workdir);
+ return s->r_acc;
+}
+
+
+reg_t
+kValidPath(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ char *pathname = kernel_dereference_char_pointer(s, argv[0], 0);
+ char cpath[PATH_MAX + 1];
+ getcwd(cpath, PATH_MAX + 1);
+
+ s->r_acc = make_reg(0, !chdir(pathname)); /* Try to go there. If it works, return 1, 0 otherwise. */
+
+ chdir(cpath);
+
+ 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
+
+
+char *
+write_filename_to_mem(state_t *s, reg_t address, char *string)
+{
+ char *mem = kernel_dereference_char_pointer(s, address, 0);
+
+ if (string) {
+ memset(mem, 0, 13);
+ strncpy(mem, string, 12);
+ }
+
+ return string;
+}
+
+void
+next_file(state_t *s)
+{
+ if (write_filename_to_mem(s, s->dirseeker_outbuffer,
+ sci_find_next(&(s->dirseeker))))
+ s->r_acc = s->dirseeker_outbuffer;
+ else
+ s->r_acc = NULL_REG;
+}
+
+void
+first_file(state_t *s, const char *dir, char *mask, reg_t buffer)
+{
+ if (!buffer.segment) {
+ sciprintf("Warning: first_file(state,\"%s\",\"%s\", 0) invoked!\n",
+ dir, mask);
+ s->r_acc = NULL_REG;
+ return;
+ }
+
+ if (strcmp(dir, ".")) {
+ sciprintf("%s L%d: Non-local first_file: Not implemented yet\n",
+ __FILE__, __LINE__);
+ s->r_acc = NULL_REG;
+ return;
+ }
+
+ /* Get rid of the old find structure */
+ if (s->dirseeker_outbuffer.segment)
+ sci_finish_find(&(s->dirseeker));
+
+ s->dirseeker_outbuffer = buffer;
+
+ if (write_filename_to_mem(s, s->dirseeker_outbuffer,
+ sci_find_first(&(s->dirseeker), mask)))
+ s->r_acc = s->dirseeker_outbuffer;
+ else
+ s->r_acc = NULL_REG;
+}
+
+reg_t
+kFileIO(state_t *s, int funct_nr, int argc, reg_t *argv)
+{
+ int func_nr = UKPV(0);
+
+ switch (func_nr) {
+
+ case K_FILEIO_OPEN :
+ {
+ char *name = kernel_dereference_char_pointer(s, argv[1], 0);
+ int mode = UKPV(2);
+
+ file_open(s, name, mode);
+ break;
+ }
+ case K_FILEIO_CLOSE :
+ {
+ int handle = UKPV(1);
+
+ file_close(s, handle);
+ break;
+ }
+ case K_FILEIO_READ_RAW :
+ {
+ int handle = UKPV(1);
+ char *dest = kernel_dereference_char_pointer(s, argv[2], 0);
+ int size = UKPV(3);
+
+ fread_wrapper(s, dest, size, handle);
+ break;
+ }
+ case K_FILEIO_WRITE_RAW :
+ {
+ int handle = UKPV(1);
+ char *buf = kernel_dereference_char_pointer(s, argv[2], 0);
+ int size = UKPV(3);
+
+ fwrite_wrapper(s, handle, buf, size);
+ break;
+ }
+ case K_FILEIO_UNLINK :
+ {
+ char *name = kernel_dereference_char_pointer(s, argv[1], 0);
+
+ unlink(name);
+ break;
+ }
+ case K_FILEIO_READ_STRING :
+ {
+ char *dest = kernel_dereference_char_pointer(s, argv[1], 0);
+ int size = UKPV(2);
+ int handle = UKPV(3);
+
+ fgets_wrapper(s, dest, size, handle);
+ return argv[1];
+ }
+ case K_FILEIO_WRITE_STRING :
+ {
+ int handle = UKPV(1);
+ int size = UKPV(3);
+ char *buf = kernel_dereference_char_pointer(s, argv[2], size);
+
+ if (buf)
+ fputs_wrapper(s, handle, size, buf);
+ break;
+ }
+ case K_FILEIO_SEEK :
+ {
+ int handle = UKPV(1);
+ int offset = UKPV(2);
+ int whence = UKPV(3);
+
+ fseek_wrapper(s, handle, offset, whence);
+ break;
+ }
+ case K_FILEIO_FIND_FIRST :
+ {
+ char *mask = kernel_dereference_char_pointer(s, argv[1], 0);
+ reg_t buf = argv[2];
+ /* int attr = UKPV(3); */ /* We won't use this, Win32 might, though... */
+
+#ifndef _WIN32
+ if (strcmp(mask, "*.*")==0) strcpy(mask, "*"); /* For UNIX */
+#endif
+ first_file(s, ".", mask, buf);
+
+ break;
+ }
+ case K_FILEIO_FIND_NEXT :
+ {
+ next_file(s);
+ break;
+ }
+ case K_FILEIO_STAT :
+ {
+ char *name = kernel_dereference_char_pointer(s, argv[1], 0);
+ s->r_acc=make_reg(0, 1-_k_check_file(name, 0));
+ break;
+ }
+ default :
+ SCIkwarn(SCIkERROR, "Unknown FileIO() sub-command: %d\n", func_nr);
+ }
+
+ return s->r_acc;
+}