aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/sci/detection.cpp31
-rw-r--r--engines/sci/include/versions.h6
-rw-r--r--engines/sci/module.mk3
-rw-r--r--engines/sci/scicore/exe.cpp80
-rw-r--r--engines/sci/scicore/exe.h58
-rw-r--r--engines/sci/scicore/exe_dec.h64
-rw-r--r--engines/sci/scicore/exe_lzexe.cpp328
-rw-r--r--engines/sci/scicore/exe_raw.cpp66
-rw-r--r--engines/sci/scicore/versions.cpp194
9 files changed, 824 insertions, 6 deletions
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index a5bd425cce..861826ffc0 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -1044,7 +1044,7 @@ static const struct SciGameDescription SciGameDescriptions[] = {
{},
SCI_VERSION(0, 000, 000) // FIXME: add version here
},
-
+#if 0
// Space Quest 1 VGA Remake - English Amiga (from www.back2roots.org)
{{"sq1sci", "VGA Remake", {
{"resource.map", 0, "106484b372af1d4cbf866472cc2813dc", 6396},
@@ -1085,7 +1085,7 @@ static const struct SciGameDescription SciGameDescriptions[] = {
{},
SCI_VERSION(1, 000, 510)
},
-
+#endif
// Space Quest 3 - English Amiga (from www.back2roots.org)
{{"sq3", "", {
{"resource.map", 0, "bad41385acde6d677a8d55a7b20437e3", 5868},
@@ -1357,11 +1357,30 @@ public:
const ADGameDescription *SciMetaEngine::fallbackDetect(const Common::FSList &fslist) const {
- printf("If this is *NOT* a fan-modified version (in particular, not a fan-made\n");
- printf("translation), please, report the data above, including the following\n");
- printf("version number, from the game's executable: ");
+ int exeVersion = 0;
+
+ // First grab all filenames
+ for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+ if (file->isDirectory()) continue;
+ Common::String filename = file->getName();
+ filename.toLowercase();
+
+ // FIXME: This is all quite hackish
+ if (filename.contains("scidhuv")) {
+ exeVersion = version_detect_from_executable((char *)file->getPath().c_str());
+ break;
+ }
+ }
+
+ printf("If this is *NOT* a fan-modified version (in particular, not a fan-made\n");
+ printf("translation), please, report the data above, including the following\n");
+ printf("version number, from the game's executable:\n");
- // TODO
+ printf("Interpreter version: %d.%03d.%03d (by executable scan)\n",
+ SCI_VERSION_MAJOR(exeVersion),
+ SCI_VERSION_MINOR(exeVersion),
+ SCI_VERSION_PATCHLEVEL(exeVersion));
+
return 0;
}
diff --git a/engines/sci/include/versions.h b/engines/sci/include/versions.h
index c18840598a..15c7e332e9 100644
--- a/engines/sci/include/versions.h
+++ b/engines/sci/include/versions.h
@@ -147,4 +147,10 @@ version_parse(const char *vn, sci_version_t *result);
** (sci_version_t) *result: The resulting version number on success
*/
+int
+version_detect_from_executable(char *filename);
+/* Try to detect version from Sierra executable in cwd
+** Returns : (int) The version number detected, or 0 if we weren't successful
+*/
+
#endif /* !_SCI_VERSIONS_H_ */
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index fbb4508e7c..9cb5241b3d 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -55,6 +55,9 @@ MODULE_OBJS = \
scicore/decompress01.o \
scicore/decompress1.o \
scicore/decompress11.o \
+ scicore/exe.o \
+ scicore/exe_lzexe.o \
+ scicore/exe_raw.o \
scicore/resource.o \
scicore/resource_map.o \
scicore/resource_patch.o \
diff --git a/engines/sci/scicore/exe.cpp b/engines/sci/scicore/exe.cpp
new file mode 100644
index 0000000000..518818ff33
--- /dev/null
+++ b/engines/sci/scicore/exe.cpp
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/include/sci_memory.h"
+
+#include "sci/scicore/exe.h"
+#include "sci/scicore/exe_dec.h"
+
+extern exe_decompressor_t exe_decompressor_lzexe;
+extern exe_decompressor_t exe_decompressor_raw;
+
+exe_decompressor_t *exe_decompressors[] = {
+ &exe_decompressor_lzexe,
+ &exe_decompressor_raw,
+ NULL
+};
+
+struct _exe_file {
+ struct _exe_decompressor *decompressor;
+ struct _exe_handle *handle;
+};
+
+exe_file_t *
+exe_open(const char *filename) {
+ int i = 0;
+ exe_decompressor_t *dec;
+
+ while ((dec = exe_decompressors[i])) {
+ exe_handle_t *handle = dec->open(filename);
+
+ if (handle) {
+ exe_file_t *file = (exe_file_t*)sci_malloc(sizeof(exe_file_t));
+
+ sciprintf("Scanning '%s' with decompressor '%s'\n",
+ filename, dec->name);
+
+ file->handle = handle;
+ file->decompressor = dec;
+ return file;
+ }
+
+ i++;
+ }
+
+ return NULL;
+}
+
+int
+exe_read(exe_file_t *file, void *buf, int count) {
+ return file->decompressor->read(file->handle, buf, count);
+}
+
+void
+exe_close(exe_file_t *file) {
+ file->decompressor->close(file->handle);
+
+ free(file);
+}
diff --git a/engines/sci/scicore/exe.h b/engines/sci/scicore/exe.h
new file mode 100644
index 0000000000..db40d3689c
--- /dev/null
+++ b/engines/sci/scicore/exe.h
@@ -0,0 +1,58 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef _SCI_EXE_H_
+#define _SCI_EXE_H_
+
+typedef struct _exe_file exe_file_t;
+
+exe_file_t *
+exe_open(const char *filename);
+/* Opens an executable file
+** Parameters: (const char *) filename: Filename of executable to open
+** Returns : (exe_file_t *) File handle, or NULL on error
+** This function will try to find a decompressor that can handle this type
+** of executable
+*/
+
+int
+exe_read(exe_file_t *file, void *buf, int count);
+/* Reads from an executable file
+** Parameters: (exe_file_t *) file: File handle
+** (void *) buf: Buffer to store decompressed data
+** (int) count: Size of decompressed data requested, in bytes
+** Returns : (int) Number of bytes of decompressed data that was stored in
+** buf. If this value is less than count an error has
+** occured, or end-of-file was reached.
+*/
+
+void
+exe_close(exe_file_t *handle);
+/* Closes an executable file
+** Parameters: (exe_file_t *) file: File handle
+** Returns : (void)
+*/
+
+#endif /* !_SCI_EXE_H_ */
diff --git a/engines/sci/scicore/exe_dec.h b/engines/sci/scicore/exe_dec.h
new file mode 100644
index 0000000000..45c1f58382
--- /dev/null
+++ b/engines/sci/scicore/exe_dec.h
@@ -0,0 +1,64 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef _SCI_EXE_DEC_H_
+#define _SCI_EXE_DEC_H_
+
+typedef struct _exe_handle exe_handle_t;
+
+typedef struct _exe_decompressor {
+ const char *name; /* Decompressor name. Unique identifier, should consist
+ ** of lower-case (where applicable) alphanumerics
+ */
+
+ exe_handle_t * (*open)(const char *filename);
+ /* Opens an executable file
+ ** Parameters: (const char *) filename: Filename of executable to open.
+ ** Returns : (exe_handle_t *) Decompressor file handle, or NULL on
+ ** error.
+ ** This function will verify that the file can be handled by the
+ ** decompressor. If this is not the case the function will fail.
+ */
+
+ int (*read)(exe_handle_t *handle, void *buf, int count);
+ /* Reads from executable file
+ ** Parameters: (exe_handle_t *) handle: Decompressor file handle.
+ ** (void *) buf: Buffer to store decompressed data.
+ ** (int) count: Size of decompressed data requested, in
+ ** bytes.
+ ** Returns : (int) Number of bytes of decompressed data that was
+ ** stored in buf. If this value is less than count
+ ** an error has occured, or end-of-file was
+ ** reached.
+ */
+
+ void (*close)(exe_handle_t *handle);
+ /* Closes a decompressor file handle.
+ ** Parameters: (exe_handle_t *) handle: Decompressor file handle.
+ ** Returns : (void)
+ */
+} exe_decompressor_t;
+
+#endif /* !_SCI_EXE_DEC_H_ */
diff --git a/engines/sci/scicore/exe_lzexe.cpp b/engines/sci/scicore/exe_lzexe.cpp
new file mode 100644
index 0000000000..a8876b3bc5
--- /dev/null
+++ b/engines/sci/scicore/exe_lzexe.cpp
@@ -0,0 +1,328 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/* Based on public domain code by Mitugu Kurizono. */
+
+#include "sci/include/sci_memory.h"
+#include "sci/scicore/exe_dec.h"
+
+/* Macro to interpret two sequential bytes as an unsigned integer. */
+#define UINT16(A) ((*((A) + 1) << 8) + *(A))
+
+/* The amount of most recent data (in bytes) that we need to keep in the
+** buffer. lzexe compression is based on copying chunks of previous data to
+** form new data.
+*/
+#define LZEXE_WINDOW 8192
+
+/* Buffer size. */
+#define LZEXE_BUFFER_SIZE (LZEXE_WINDOW + 4096)
+
+/* Maximum amount of data (in bytes) that can be in the buffer at the start
+** of the decompression loop. The maximum amount of data that can be added
+** to the buffer during a single step of the loop is 256 bytes.
+*/
+#define LZEXE_BUFFER_MAX (LZEXE_BUFFER_SIZE - 256)
+
+struct _exe_handle {
+ FILE *f;
+
+ /* Output buffer. */
+ guint8 buffer[LZEXE_BUFFER_SIZE];
+ guint8 *bufptr;
+
+ /* Bit buffer. Bits [0..count) still contain unprocessed data. */
+ int buf;
+ int count;
+
+ /* End of data flag. */
+ int eod;
+};
+
+static int
+lzexe_read_uint16(FILE *f, int *value) {
+ int data;
+
+ if ((*value = fgetc(f)) == EOF)
+ return 0;
+
+ if ((data = fgetc(f)) == EOF)
+ return 0;
+
+ *value |= data << 8;
+ return 1;
+}
+
+static int
+lzexe_read_uint8(FILE *f, int *value) {
+ if ((*value = fgetc(f)) == EOF)
+ return 0;
+
+ return 1;
+}
+
+static int
+lzexe_init(exe_handle_t *handle, FILE *f) {
+ handle->f = f;
+ handle->bufptr = handle->buffer;
+ handle->eod = 0;
+
+ if (!lzexe_read_uint16(handle->f, &handle->buf))
+ return 0;
+
+ handle->count = 16;
+ return 1;
+}
+
+static int
+lzexe_get_bit(exe_handle_t *handle, int *bit) {
+ *bit = handle->buf & 1;
+
+ if (--handle->count == 0) {
+ if (!lzexe_read_uint16(handle->f, &handle->buf))
+ return 0;
+ handle->count = 16;
+ } else
+ handle->buf >>= 1;
+
+ return 1;
+}
+
+static int
+lzexe_decompress(exe_handle_t *handle) {
+ while (!handle->eod
+ && handle->bufptr - handle->buffer <= LZEXE_BUFFER_MAX) {
+ int bit;
+ int len, span;
+
+ if (!lzexe_get_bit(handle, &bit))
+ return 0;
+
+ if (bit) {
+ /* 1: copy byte verbatim. */
+
+ int data;
+
+ if (!lzexe_read_uint8(handle->f, &data))
+ return 0;
+
+ *handle->bufptr++ = data;
+
+ continue;
+ }
+
+ if (!lzexe_get_bit(handle, &bit))
+ return 0;
+
+ if (!bit) {
+ /* 00: copy small block. */
+
+ /* Next two bits indicate block length - 2. */
+ if (!lzexe_get_bit(handle, &bit))
+ return 0;
+
+ len = bit << 1;
+
+ if (!lzexe_get_bit(handle, &bit))
+ return 0;
+
+ len |= bit;
+ len += 2;
+
+ /* Read span byte. This forms the low byte of a
+ ** negative two's compliment value.
+ */
+ if (!lzexe_read_uint8(handle->f, &span))
+ return 0;
+
+ /* Convert to negative integer. */
+ span -= 256;
+ } else {
+ /* 01: copy large block. */
+ int data;
+
+ /* Read low byte of span value. */
+ if (!lzexe_read_uint8(handle->f, &span))
+ return 0;
+
+ /* Read next byte. Bits [7..3] contain bits [12..8]
+ ** of span value. Bits [2..0] contain block length -
+ ** 2.
+ */
+ if (!lzexe_read_uint8(handle->f, &data))
+ return 0;
+ span |= (data & 0xf8) << 5;
+ /* Convert to negative integer. */
+ span -= 8192;
+
+ len = (data & 7) + 2;
+
+ if (len == 2) {
+ /* Next byte is block length value - 1. */
+ if (!lzexe_read_uint8(handle->f, &len))
+ return 0;
+
+ if (len == 0) {
+ /* End of data reached. */
+ handle->eod = 1;
+ break;
+ }
+
+ if (len == 1)
+ /* Segment change marker. */
+ continue;
+
+ len++;
+ }
+ }
+
+ assert(handle->bufptr + span >= handle->buffer);
+
+ /* Copy block. */
+ while (len-- > 0) {
+ *handle->bufptr = *(handle->bufptr + span);
+ handle->bufptr++;
+ }
+ }
+
+ return 1;
+}
+
+static exe_handle_t *
+lzexe_open(const char *filename) {
+ exe_handle_t *handle;
+ guint8 head[0x20];
+ guint8 size[2];
+ off_t fpos;
+
+ FILE *f = sci_fopen(filename, "rb");
+
+ if (!f)
+ return NULL;
+
+ /* Read exe header plus possible lzexe signature. */
+ if (fread(head, 1, 0x20, f) != 0x20)
+ return NULL;
+
+ /* Verify "MZ" signature, header size == 2 paragraphs and number of
+ ** overlays == 0.
+ */
+ if (UINT16(head) != 0x5a4d || UINT16(head + 8) != 2
+ || UINT16(head + 0x1a) != 0)
+ return NULL;
+
+ /* Verify that first relocation item offset is 0x1c. */
+ if (UINT16(head + 0x18) != 0x1c)
+ return NULL;
+
+ /* Look for lzexe signature. */
+ if (memcmp(head + 0x1c, "LZ09", 4)
+ && memcmp(head + 0x1c, "LZ91", 4)) {
+ return NULL;
+ }
+
+ /* Calculate code segment offset in exe file. */
+ fpos = (UINT16(head + 0x16) + UINT16(head + 8)) << 4;
+ /* Seek to offset 8 of info table at start of code segment. */
+ if (fseek(f, fpos + 8, SEEK_SET) == -1)
+ return NULL;
+
+ /* Read size of compressed data in paragraphs. */
+ if (fread(size, 1, 2, f) != 2)
+ return NULL;
+
+ /* Move file pointer to start of compressed data. */
+ fpos -= UINT16(size) << 4;
+ if (fseek(f, fpos, SEEK_SET) == -1)
+ return NULL;
+
+ handle = (exe_handle_t*)sci_malloc(sizeof(exe_handle_t));
+
+ if (!lzexe_init(handle, f)) {
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static int
+lzexe_read(exe_handle_t *handle, void *buf, int count) {
+ int done = 0;
+
+ while (done != count) {
+ int size, copy, i;
+ int left = count - done;
+
+ if (!lzexe_decompress(handle))
+ return done;
+
+ /* Total amount of bytes in buffer. */
+ size = handle->bufptr - handle->buffer;
+
+ /* If we're not at end of data we need to maintain the
+ ** window.
+ */
+ if (!handle->eod)
+ copy = size - LZEXE_WINDOW;
+ else {
+ if (size == 0)
+ /* No data left. */
+ return done;
+
+ copy = size;
+ }
+
+ /* Do not copy more than requested. */
+ if (copy > left)
+ copy = left;
+
+ memcpy((char *) buf + done, handle->buffer, copy);
+
+ /* Move remaining data to start of buffer. */
+ for (i = copy; i < size; i++)
+ handle->buffer[i - copy] = handle->buffer[i];
+
+ handle->bufptr -= copy;
+ done += copy;
+ }
+
+ return done;
+}
+
+static void
+lzexe_close(exe_handle_t *handle) {
+ fclose(handle->f);
+
+ free(handle);
+}
+
+exe_decompressor_t
+exe_decompressor_lzexe = {
+ "lzexe",
+ lzexe_open,
+ lzexe_read,
+ lzexe_close
+};
diff --git a/engines/sci/scicore/exe_raw.cpp b/engines/sci/scicore/exe_raw.cpp
new file mode 100644
index 0000000000..17d46f524f
--- /dev/null
+++ b/engines/sci/scicore/exe_raw.cpp
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "sci/include/sci_memory.h"
+
+struct _exe_handle {
+ FILE *f;
+};
+
+#include "sci/scicore/exe_dec.h"
+
+static exe_handle_t *
+raw_open(const char *filename) {
+ FILE *f = sci_fopen(filename, "rb");
+ exe_handle_t *handle;
+
+ if (!f)
+ return NULL;
+
+ handle = (exe_handle_t*)sci_malloc(sizeof(exe_handle_t));
+ handle->f = f;
+
+ return handle;
+}
+
+static int
+raw_read(exe_handle_t *handle, void *buf, int count) {
+ return fread(buf, 1, count, handle->f);
+}
+
+static void
+raw_close(exe_handle_t *handle) {
+ fclose(handle->f);
+
+ free(handle);
+}
+
+exe_decompressor_t
+exe_decompressor_raw = {
+ "raw",
+ raw_open,
+ raw_read,
+ raw_close
+};
diff --git a/engines/sci/scicore/versions.cpp b/engines/sci/scicore/versions.cpp
index 37952ed217..83ad3ddaa5 100644
--- a/engines/sci/scicore/versions.cpp
+++ b/engines/sci/scicore/versions.cpp
@@ -24,9 +24,14 @@
*/
#define NEED_SCI_VERSIONS
+
+#include "common/system.h"
+#include "common/config-manager.h"
+
#include "sci/include/versions.h"
#include "sci/include/engine.h"
#include "sci/include/resource.h"
+#include "sci/scicore/exe.h" // for reading version from the executable
void
version_require_earlier_than(state_t *s, sci_version_t version) {
@@ -83,4 +88,193 @@ version_parse(const char *vn, sci_version_t *result) {
return 0;
}
+// Exe scanning functions
+
+/* Maxmimum number of bytes to hash from start of file */
+#define VERSION_DETECT_HASH_SIZE 1000000
+
+#define VERSION_DETECT_BUF_SIZE 4096
+
+static int
+scan_file(char *filename, sci_version_t *version) {
+ char buf[VERSION_DETECT_BUF_SIZE];
+ char result_string[10]; /* string-encoded result, copied from buf */
+ int characters_left;
+ int state = 0;
+ /* 'state' encodes how far we have matched the version pattern
+ ** "n.nnn.nnn"
+ **
+ ** n.nnn.nnn
+ ** 0123456789
+ **
+ ** Since we cannot be certain that the pattern does not begin with an
+ ** alphanumeric character, some states are ambiguous.
+ ** The pattern is expected to be terminated with a non-alphanumeric
+ ** character.
+ */
+
+ exe_file_t *f = exe_open(filename);
+
+ if (!f)
+ return 1;
+
+ do {
+ int i;
+ int accept;
+
+ characters_left = exe_read(f, buf, VERSION_DETECT_BUF_SIZE);
+
+ for (i = 0; i < characters_left; i++) {
+ const char ch = buf[i];
+ accept = 0; /* By default, we don't like this character */
+
+ if (isalnum((unsigned char) ch)) {
+ accept = (state != 1
+ && state != 5
+ && state != 9);
+ } else if (ch == '.') {
+ accept = (state == 1
+ || state == 5);
+ } else if (state == 9) {
+ result_string[9] = 0; /* terminate string */
+
+ if (!version_parse(result_string, version)) {
+ exe_close(f);
+ return 0; /* success! */
+ }
+
+ /* Continue searching. */
+ }
+
+ if (accept)
+ result_string[state++] = ch;
+ else
+ state = 0;
+
+ }
+
+ } while (characters_left == VERSION_DETECT_BUF_SIZE);
+
+ exe_close(f);
+ return 1; /* failure */
+}
+
+static guint32
+read_uint32(byte *data) {
+ return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+}
+
+static guint16
+read_uint16(byte *data) {
+ return (data[0] << 8) | data[1];
+}
+
+static int
+is_mac_exe(char *filename) {
+ FILE *file;
+ byte buf[4];
+ guint32 val;
+ unsigned int i;
+
+ /* Mac executables have no extension */
+ if (strchr(filename, '.'))
+ return 0;
+
+ file = fopen(filename, "rb");
+ if (!file)
+ return 0;
+
+ if (fseek(file, 4, SEEK_SET) == -1) {
+ fclose(file);
+ return 0;
+ }
+
+ /* Read resource map offset */
+ if (fread(buf, 1, 4, file) < 4) {
+ fclose(file);
+ return 0;
+ }
+
+ val = read_uint32(buf);
+
+ if (fseek(file, val + 28, SEEK_SET) == -1) {
+ fclose(file);
+ return 0;
+ }
+
+ /* Read number of types in map */
+ if (fread(buf, 1, 2, file) < 2) {
+ fclose(file);
+ return 0;
+ }
+
+ val = read_uint16(buf) + 1;
+
+ for (i = 0; i < val; i++) {
+ if (fread(buf, 1, 4, file) < 4) {
+ fclose(file);
+ return 0;
+ }
+
+ /* Look for executable code */
+ if (!memcmp(buf, "CODE", 4)) {
+ fclose(file);
+ return 1;
+ }
+
+ /* Skip to next list entry */
+ if (fseek(file, 4, SEEK_CUR) == -1) {
+ fclose(file);
+ return 0;
+ }
+ }
+
+ fclose(file);
+ return 0;
+}
+
+static int
+is_exe(char *filename) {
+ FILE *file;
+ char buf[4];
+ unsigned char header[] = {0x00, 0x00, 0x03, 0xf3};
+
+ /* PC and Atari ST executable extensions */
+ if (strstr(filename, ".exe") || strstr(filename, ".EXE")
+ || strstr(filename, ".prg") || strstr(filename, ".PRG"))
+ return 1;
+
+ /* Check for Amiga executable */
+ if (strchr(filename, '.'))
+ return 0;
+
+ file = fopen(filename, "rb");
+ if (!file)
+ return 0;
+
+ if (fread(buf, 1, 4, file) < 4) {
+ fclose(file);
+ return 0;
+ }
+
+ fclose(file);
+
+ /* Check header bytes */
+ return memcmp(buf, header, 4) == 0;
+}
+
+int
+version_detect_from_executable(char *filename) {
+ int mac = 0;
+ int result;
+
+ if (mac ? is_mac_exe(filename) : is_exe(filename)) {
+ if (scan_file(filename, &result)) {
+ return result;
+ }
+ }
+
+ return 0;
+}
+
#undef VERSION_DETECT_BUF_SIZE