mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-17 07:07:10 +00:00
5778350053
svn-id: r38528
281 lines
6.5 KiB
C++
281 lines
6.5 KiB
C++
/* 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$
|
|
*
|
|
*/
|
|
|
|
#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) {
|
|
if (s->version_lock_flag)
|
|
return;
|
|
|
|
if (version <= s->min_version) {
|
|
sciprintf("Version autodetect conflict: Less than %d.%03d.%03d was requested, but "
|
|
"%d.%03d.%03d is the current minimum\n",
|
|
SCI_VERSION_MAJOR(version), SCI_VERSION_MINOR(version), SCI_VERSION_PATCHLEVEL(version),
|
|
SCI_VERSION_MAJOR(s->min_version), SCI_VERSION_MINOR(s->min_version),
|
|
SCI_VERSION_PATCHLEVEL(s->min_version));
|
|
return;
|
|
} else if (version < s->max_version) {
|
|
s->max_version = version - 1;
|
|
if (s->max_version < s->version)
|
|
s->version = s->max_version;
|
|
}
|
|
}
|
|
|
|
void
|
|
version_require_later_than(state_t *s, sci_version_t version) {
|
|
if (s->version_lock_flag)
|
|
return;
|
|
|
|
if (version > s->max_version) {
|
|
sciprintf("Version autodetect conflict: More than %d.%03d.%03d was requested, but less than"
|
|
"%d.%03d.%03d is required ATM\n",
|
|
SCI_VERSION_MAJOR(version), SCI_VERSION_MINOR(version), SCI_VERSION_PATCHLEVEL(version),
|
|
SCI_VERSION_MAJOR(s->max_version), SCI_VERSION_MINOR(s->max_version),
|
|
SCI_VERSION_PATCHLEVEL(s->max_version));
|
|
return;
|
|
} else if (version > s->min_version) {
|
|
s->min_version = version;
|
|
if (s->min_version > s->version)
|
|
s->version = s->min_version;
|
|
}
|
|
}
|
|
|
|
int
|
|
version_parse(const char *vn, sci_version_t *result) {
|
|
char *endptr[3];
|
|
int major = strtol(vn, &endptr[0], 10);
|
|
int minor = strtol(vn + 2, &endptr[1], 10);
|
|
int patchlevel = strtol(vn + 6, &endptr[2], 10);
|
|
|
|
if (endptr[0] != vn + 1 || endptr[1] != vn + 5
|
|
|| *endptr[2] != '\0') {
|
|
sciprintf("Warning: Failed to parse version string '%s'\n", vn);
|
|
return 1;
|
|
}
|
|
|
|
*result = SCI_VERSION(major, minor, patchlevel);
|
|
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) == 0) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#undef VERSION_DETECT_BUF_SIZE
|