Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

663
engines/glk/glulx/accel.cpp Normal file
View File

@@ -0,0 +1,663 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
/**
* Git passes along function arguments in reverse order. To make our lives more interesting
*/
#ifdef ARGS_REVERSED
#define ARG(argv, argc, ix) (argv[(argc-1)-ix])
#else
#define ARG(argv, argc, ix) (argv[ix])
#endif
/**
* Any function can be called with any number of arguments. This macro lets us snarf a given argument,
* or zero if it wasn't supplied.
*/
#define ARG_IF_GIVEN(argv, argc, ix) ((argc > ix) ? (ARG(argv, argc, ix)) : 0)
acceleration_func Glulx::accel_find_func(uint index) {
switch (index) {
case 0:
return nullptr; // 0 always means no acceleration
case 1:
return &Glulx::func_1_z__region;
case 2:
return &Glulx::func_2_cp__tab;
case 3:
return &Glulx::func_3_ra__pr;
case 4:
return &Glulx::func_4_rl__pr;
case 5:
return &Glulx::func_5_oc__cl;
case 6:
return &Glulx::func_6_rv__pr;
case 7:
return &Glulx::func_7_op__pr;
case 8:
return &Glulx::func_8_cp__tab;
case 9:
return &Glulx::func_9_ra__pr;
case 10:
return &Glulx::func_10_rl__pr;
case 11:
return &Glulx::func_11_oc__cl;
case 12:
return &Glulx::func_12_rv__pr;
case 13:
return &Glulx::func_13_op__pr;
}
return nullptr;
}
acceleration_func Glulx::accel_get_func(uint addr) {
int bucknum;
accelentry_t *ptr;
if (!accelentries)
return nullptr;
bucknum = (addr % ACCEL_HASH_SIZE);
for (ptr = accelentries[bucknum]; ptr; ptr = ptr->next) {
if (ptr->addr == addr)
return ptr->func;
}
return nullptr;
}
void Glulx::accel_iterate_funcs(void (*func)(uint index, uint addr)) {
int bucknum;
accelentry_t *ptr;
if (!accelentries)
return;
for (bucknum = 0; bucknum < ACCEL_HASH_SIZE; bucknum++) {
for (ptr = accelentries[bucknum]; ptr; ptr = ptr->next) {
if (ptr->func) {
func(ptr->index, ptr->addr);
}
}
}
}
void Glulx::accel_set_func(uint index, uint addr) {
int bucknum;
accelentry_t *ptr;
int functype;
acceleration_func new_func = nullptr;
/* Check the Glulx type identifier byte. */
functype = Mem1(addr);
if (functype != 0xC0 && functype != 0xC1) {
fatal_error_i("Attempt to accelerate non-function.", addr);
}
if (!accelentries) {
accelentries = (accelentry_t **)glulx_malloc(ACCEL_HASH_SIZE
* sizeof(accelentry_t *));
if (!accelentries)
fatal_error("Cannot malloc acceleration table.");
for (bucknum = 0; bucknum < ACCEL_HASH_SIZE; bucknum++)
accelentries[bucknum] = nullptr;
}
new_func = accel_find_func(index);
/* Might be nullptr, if the index is zero or not recognized. */
bucknum = (addr % ACCEL_HASH_SIZE);
for (ptr = accelentries[bucknum]; ptr; ptr = ptr->next) {
if (ptr->addr == addr)
break;
}
if (!ptr) {
if (!new_func) {
return; /* no need for a new entry */
}
ptr = (accelentry_t *)glulx_malloc(sizeof(accelentry_t));
if (!ptr)
fatal_error("Cannot malloc acceleration entry.");
ptr->addr = addr;
ptr->index = 0;
ptr->func = nullptr;
ptr->next = accelentries[bucknum];
accelentries[bucknum] = ptr;
}
ptr->index = index;
ptr->func = new_func;
}
void Glulx::accel_set_param(uint index, uint val) {
switch (index) {
case 0:
classes_table = val;
break;
case 1:
indiv_prop_start = val;
break;
case 2:
class_metaclass = val;
break;
case 3:
object_metaclass = val;
break;
case 4:
routine_metaclass = val;
break;
case 5:
string_metaclass = val;
break;
case 6:
self = val;
break;
case 7:
num_attr_bytes = val;
break;
case 8:
cpv__start = val;
break;
}
}
uint Glulx::accel_get_param_count() const {
return 9;
}
uint Glulx::accel_get_param(uint index) const {
switch (index) {
case 0:
return classes_table;
case 1:
return indiv_prop_start;
case 2:
return class_metaclass;
case 3:
return object_metaclass;
case 4:
return routine_metaclass;
case 5:
return string_metaclass;
case 6:
return self;
case 7:
return num_attr_bytes;
case 8:
return cpv__start;
default:
return 0;
}
}
void Glulx::accel_error(const char *msg) {
glk_put_char('\n');
glk_put_string(msg);
glk_put_char('\n');
}
int Glulx::obj_in_class(uint obj) {
// This checks whether obj is contained in Class, not whether it is a member of Class
return (Mem4(obj + 13 + num_attr_bytes) == class_metaclass);
}
uint Glulx::get_prop(uint obj, uint id) {
uint cla = 0;
uint prop;
uint call_argv[2];
if (id & 0xFFFF0000) {
cla = Mem4(classes_table + ((id & 0xFFFF) * 4));
ARG(call_argv, 2, 0) = obj;
ARG(call_argv, 2, 1) = cla;
if (func_5_oc__cl(2, call_argv) == 0)
return 0;
id >>= 16;
obj = cla;
}
ARG(call_argv, 2, 0) = obj;
ARG(call_argv, 2, 1) = id;
prop = func_2_cp__tab(2, call_argv);
if (prop == 0)
return 0;
if (obj_in_class(obj) && (cla == 0)) {
if ((id < indiv_prop_start) || (id >= indiv_prop_start + 8))
return 0;
}
if (Mem4(self) != obj) {
if (Mem1(prop + 9) & 1)
return 0;
}
return prop;
}
uint Glulx::get_prop_new(uint obj, uint id) {
uint cla = 0;
uint prop;
uint call_argv[2];
if (id & 0xFFFF0000) {
cla = Mem4(classes_table + ((id & 0xFFFF) * 4));
ARG(call_argv, 2, 0) = obj;
ARG(call_argv, 2, 1) = cla;
if (func_11_oc__cl(2, call_argv) == 0)
return 0;
id >>= 16;
obj = cla;
}
ARG(call_argv, 2, 0) = obj;
ARG(call_argv, 2, 1) = id;
prop = func_8_cp__tab(2, call_argv);
if (prop == 0)
return 0;
if (obj_in_class(obj) && (cla == 0)) {
if ((id < indiv_prop_start) || (id >= indiv_prop_start + 8))
return 0;
}
if (Mem4(self) != obj) {
if (Mem1(prop + 9) & 1)
return 0;
}
return prop;
}
uint Glulx::func_1_z__region(uint argc, uint *argv) {
uint addr;
uint tb;
if (argc < 1)
return 0;
addr = ARG(argv, argc, 0);
if (addr < 36)
return 0;
if (addr >= endmem)
return 0;
tb = Mem1(addr);
if (tb >= 0xE0) {
return 3;
}
if (tb >= 0xC0) {
return 2;
}
if (tb >= 0x70 && tb <= 0x7F && addr >= ramstart) {
return 1;
}
return 0;
}
uint Glulx::func_2_cp__tab(uint argc, uint *argv) {
uint obj;
uint id;
uint otab, max;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
if (func_1_z__region(1, &obj) != 1) {
accel_error("[** Programming error: tried to find the \".\" of (something) **]");
return 0;
}
otab = Mem4(obj + 16);
if (!otab)
return 0;
max = Mem4(otab);
otab += 4;
/* @binarysearch id 2 otab 10 max 0 0 res; */
return binary_search(id, 2, otab, 10, max, 0, 0);
}
uint Glulx::func_3_ra__pr(uint argc, uint *argv) {
uint obj;
uint id;
uint prop;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
prop = get_prop(obj, id);
if (prop == 0)
return 0;
return Mem4(prop + 4);
}
uint Glulx::func_4_rl__pr(uint argc, uint *argv) {
uint obj;
uint id;
uint prop;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
prop = get_prop(obj, id);
if (prop == 0)
return 0;
return 4 * Mem2(prop + 2);
}
uint Glulx::func_5_oc__cl(uint argc, uint *argv) {
uint obj;
uint cla;
uint zr, prop, inlist, inlistlen, jx;
obj = ARG_IF_GIVEN(argv, argc, 0);
cla = ARG_IF_GIVEN(argv, argc, 1);
zr = func_1_z__region(1, &obj);
if (zr == 3)
return (cla == string_metaclass) ? 1 : 0;
if (zr == 2)
return (cla == routine_metaclass) ? 1 : 0;
if (zr != 1)
return 0;
if (cla == class_metaclass) {
if (obj_in_class(obj))
return 1;
if (obj == class_metaclass)
return 1;
if (obj == string_metaclass)
return 1;
if (obj == routine_metaclass)
return 1;
if (obj == object_metaclass)
return 1;
return 0;
}
if (cla == object_metaclass) {
if (obj_in_class(obj))
return 0;
if (obj == class_metaclass)
return 0;
if (obj == string_metaclass)
return 0;
if (obj == routine_metaclass)
return 0;
if (obj == object_metaclass)
return 0;
return 1;
}
if ((cla == string_metaclass) || (cla == routine_metaclass))
return 0;
if (!obj_in_class(cla)) {
accel_error("[** Programming error: tried to apply 'ofclass' with non-class **]");
return 0;
}
prop = get_prop(obj, 2);
if (prop == 0)
return 0;
inlist = Mem4(prop + 4);
if (inlist == 0)
return 0;
inlistlen = Mem2(prop + 2);
for (jx = 0; jx < inlistlen; jx++) {
if (Mem4(inlist + (4 * jx)) == cla)
return 1;
}
return 0;
}
uint Glulx::func_6_rv__pr(uint argc, uint *argv) {
uint id;
uint addr;
id = ARG_IF_GIVEN(argv, argc, 1);
addr = func_3_ra__pr(argc, argv);
if (addr == 0) {
if ((id > 0) && (id < indiv_prop_start))
return Mem4(cpv__start + (4 * id));
accel_error("[** Programming error: tried to read (something) **]");
return 0;
}
return Mem4(addr);
}
uint Glulx::func_7_op__pr(uint argc, uint *argv) {
uint obj;
uint id;
uint zr;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
zr = func_1_z__region(1, &obj);
if (zr == 3) {
/* print is INDIV_PROP_START+6 */
if (id == indiv_prop_start + 6)
return 1;
/* print_to_array is INDIV_PROP_START+7 */
if (id == indiv_prop_start + 7)
return 1;
return 0;
}
if (zr == 2) {
/* call is INDIV_PROP_START+5 */
return ((id == indiv_prop_start + 5) ? 1 : 0);
}
if (zr != 1)
return 0;
if ((id >= indiv_prop_start) && (id < indiv_prop_start + 8)) {
if (obj_in_class(obj))
return 1;
}
return ((func_3_ra__pr(argc, argv)) ? 1 : 0);
}
uint Glulx::func_8_cp__tab(uint argc, uint *argv) {
uint obj;
uint id;
uint otab, max;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
if (func_1_z__region(1, &obj) != 1) {
accel_error("[** Programming error: tried to find the \".\" of (something) **]");
return 0;
}
otab = Mem4(obj + 4 * (3 + (int)(num_attr_bytes / 4)));
if (!otab)
return 0;
max = Mem4(otab);
otab += 4;
/* @binarysearch id 2 otab 10 max 0 0 res; */
return binary_search(id, 2, otab, 10, max, 0, 0);
}
uint Glulx::func_9_ra__pr(uint argc, uint *argv) {
uint obj;
uint id;
uint prop;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
prop = get_prop_new(obj, id);
if (prop == 0)
return 0;
return Mem4(prop + 4);
}
uint Glulx::func_10_rl__pr(uint argc, uint *argv) {
uint obj;
uint id;
uint prop;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
prop = get_prop_new(obj, id);
if (prop == 0)
return 0;
return 4 * Mem2(prop + 2);
}
uint Glulx::func_11_oc__cl(uint argc, uint *argv) {
uint obj;
uint cla;
uint zr, prop, inlist, inlistlen, jx;
obj = ARG_IF_GIVEN(argv, argc, 0);
cla = ARG_IF_GIVEN(argv, argc, 1);
zr = func_1_z__region(1, &obj);
if (zr == 3)
return (cla == string_metaclass) ? 1 : 0;
if (zr == 2)
return (cla == routine_metaclass) ? 1 : 0;
if (zr != 1)
return 0;
if (cla == class_metaclass) {
if (obj_in_class(obj))
return 1;
if (obj == class_metaclass)
return 1;
if (obj == string_metaclass)
return 1;
if (obj == routine_metaclass)
return 1;
if (obj == object_metaclass)
return 1;
return 0;
}
if (cla == object_metaclass) {
if (obj_in_class(obj))
return 0;
if (obj == class_metaclass)
return 0;
if (obj == string_metaclass)
return 0;
if (obj == routine_metaclass)
return 0;
if (obj == object_metaclass)
return 0;
return 1;
}
if ((cla == string_metaclass) || (cla == routine_metaclass))
return 0;
if (!obj_in_class(cla)) {
accel_error("[** Programming error: tried to apply 'ofclass' with non-class **]");
return 0;
}
prop = get_prop_new(obj, 2);
if (prop == 0)
return 0;
inlist = Mem4(prop + 4);
if (inlist == 0)
return 0;
inlistlen = Mem2(prop + 2);
for (jx = 0; jx < inlistlen; jx++) {
if (Mem4(inlist + (4 * jx)) == cla)
return 1;
}
return 0;
}
uint Glulx::func_12_rv__pr(uint argc, uint *argv) {
uint id;
uint addr;
id = ARG_IF_GIVEN(argv, argc, 1);
addr = func_9_ra__pr(argc, argv);
if (addr == 0) {
if ((id > 0) && (id < indiv_prop_start))
return Mem4(cpv__start + (4 * id));
accel_error("[** Programming error: tried to read (something) **]");
return 0;
}
return Mem4(addr);
}
uint Glulx::func_13_op__pr(uint argc, uint *argv) {
uint obj;
uint id;
uint zr;
obj = ARG_IF_GIVEN(argv, argc, 0);
id = ARG_IF_GIVEN(argv, argc, 1);
zr = func_1_z__region(1, &obj);
if (zr == 3) {
/* print is INDIV_PROP_START+6 */
if (id == indiv_prop_start + 6)
return 1;
/* print_to_array is INDIV_PROP_START+7 */
if (id == indiv_prop_start + 7)
return 1;
return 0;
}
if (zr == 2) {
/* call is INDIV_PROP_START+5 */
return ((id == indiv_prop_start + 5) ? 1 : 0);
}
if (zr != 1)
return 0;
if ((id >= indiv_prop_start) && (id < indiv_prop_start + 8)) {
if (obj_in_class(obj))
return 1;
}
return ((func_9_ra__pr(argc, argv)) ? 1 : 0);
}
} // End of namespace Glulx
} // End of namespace Glk

View File

@@ -0,0 +1,122 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/detection.h"
#include "glk/glulx/detection_tables.h"
#include "glk/blorb.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/md5.h"
#include "engines/game.h"
namespace Glk {
namespace Glulx {
void GlulxMetaEngine::getSupportedGames(PlainGameList &games) {
for (const PlainGameDescriptor *pd = GLULXE_GAME_LIST; pd->gameId; ++pd) {
games.push_back(*pd);
}
}
const GlkDetectionEntry* GlulxMetaEngine::getDetectionEntries() {
return GLULXE_GAMES;
}
GameDescriptor GlulxMetaEngine::findGame(const char *gameId) {
for (const PlainGameDescriptor *pd = GLULXE_GAME_LIST; pd->gameId; ++pd) {
if (!strcmp(gameId, pd->gameId)) {
GameDescriptor gd = *pd;
gd._supportLevel = kTestingGame;
/*
* Tested against ScummVM 2.8.0git, following entries are confirmed not to be playable
*/
if (!strcmp(gameId, "glkebook") ||
!strcmp(gameId, "if01_aafn") ||
!strcmp(gameId, "if01_sittm") ||
!strcmp(gameId, "if14_transparent"))
gd._supportLevel = kUnstableGame;
return gd;
}
}
return GameDescriptor::empty();
}
bool GlulxMetaEngine::detectGames(const Common::FSList &fslist, DetectedGames &gameList) {
const char *const EXTENSIONS[] = { ".ulx", nullptr };
// Loop through the files of the folder
for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
// Check for a recognised filename
if (file->isDirectory())
continue;
Common::String filename = file->getName();
bool hasExt = Blorb::hasBlorbExt(filename), isBlorb = false;
for (const char *const *ext = &EXTENSIONS[0]; *ext && !hasExt; ++ext)
hasExt = filename.hasSuffixIgnoreCase(*ext);
if (!hasExt)
continue;
// Open up the file and calculate the md5
Common::File gameFile;
if (!gameFile.open(*file))
continue;
Common::String md5 = Common::computeStreamMD5AsString(gameFile, 5000);
size_t filesize = gameFile.size();
gameFile.seek(0);
isBlorb = Blorb::isBlorb(gameFile, ID_GLUL);
gameFile.close();
if (!isBlorb && Blorb::hasBlorbExt(filename))
continue;
// Check for known games
const GlkDetectionEntry *p = GLULXE_GAMES;
while (p->_gameId && (md5 != p->_md5 || filesize != p->_filesize))
++p;
if (!p->_gameId) {
const PlainGameDescriptor &desc = GLULXE_GAME_LIST[0];
gameList.push_back(GlkDetectedGame(desc.gameId, desc.description, filename, md5, filesize));
} else {
PlainGameDescriptor gameDesc = findGame(p->_gameId);
DetectedGame gd = DetectedGame("glk", p->_gameId, gameDesc.description, p->_language, Common::kPlatformUnknown, p->_extra);
gd.addExtraEntry("filename", filename);
gameList.push_back(gd);
}
}
return !gameList.empty();
}
void GlulxMetaEngine::detectClashes(Common::StringMap &map) {
for (const PlainGameDescriptor *pd = GLULXE_GAME_LIST; pd->gameId; ++pd) {
if (map.contains(pd->gameId))
error("Duplicate game Id found - %s", pd->gameId);
map[pd->gameId] = "";
}
}
} // End of namespace Glulx
} // End of namespace Glk

View File

@@ -0,0 +1,67 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_GLULXE_DETECTION
#define GLK_GLULXE_DETECTION
#include "common/fs.h"
#include "common/hash-str.h"
#include "engines/game.h"
#include "glk/detection.h"
namespace Glk {
namespace Glulx {
/**
* Meta engine for Glulx interpreter
*/
class GlulxMetaEngine {
public:
/**
* Get a list of supported games
*/
static void getSupportedGames(PlainGameList &games);
/**
* Get the detection entries
*/
static const GlkDetectionEntry* getDetectionEntries();
/**
* Returns a game description for the given game Id, if it's supported
*/
static GameDescriptor findGame(const char *gameId);
/**
* Detect supported games
*/
static bool detectGames(const Common::FSList &fslist, DetectedGames &gameList);
/**
* Check for game Id clashes with other sub-engines
*/
static void detectClashes(Common::StringMap &map);
};
} // End of namespace Glulx
} // End of namespace Glk
#endif

File diff suppressed because it is too large Load Diff

1055
engines/glk/glulx/exec.cpp Normal file

File diff suppressed because it is too large Load Diff

122
engines/glk/glulx/float.cpp Normal file
View File

@@ -0,0 +1,122 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
uint Glulx::encode_float(gfloat32 val) {
gfloat32 absval;
uint sign;
int expo;
gfloat32 mant;
uint fbits;
if (signbit(val)) {
sign = 0x80000000;
absval = -val;
} else {
sign = 0x0;
absval = val;
}
if (isinf(val)) {
return sign | 0x7f800000; /* infinity */
}
if (isnan(val)) {
return sign | 0x7fc00000;
}
mant = frexpf(absval, &expo);
/* Normalize mantissa to be in the range [1.0, 2.0) */
if (0.5 <= mant && mant < 1.0) {
mant *= 2.0;
expo--;
} else if (mant == 0.0) {
expo = 0;
} else {
return sign | 0x7f800000; /* infinity */
}
if (expo >= 128) {
return sign | 0x7f800000; /* infinity */
} else if (expo < -126) {
/* Denormalized (very small) number */
mant = ldexpf(mant, 126 + expo);
expo = 0;
} else if (!(expo == 0 && mant == 0.0)) {
expo += 127;
mant -= 1.0; /* Get rid of leading 1 */
}
mant *= 8388608.0; /* 2^23 */
fbits = (uint)(mant + 0.5); /* round mant to nearest int */
if (fbits >> 23) {
/* The carry propagated out of a string of 23 1 bits. */
fbits = 0;
expo++;
if (expo >= 255) {
return sign | 0x7f800000; /* infinity */
}
}
return (sign) | ((uint)(expo << 23)) | (fbits);
}
gfloat32 Glulx::decode_float(uint val) {
int sign;
int expo;
uint mant;
gfloat32 res;
/* First byte */
sign = ((val & 0x80000000) != 0);
expo = (val >> 23) & 0xFF;
mant = val & 0x7FFFFF;
if (expo == 255) {
if (mant == 0) {
/* Infinity */
return (sign ? (-INFINITY) : (INFINITY));
} else {
/* Not a number */
return (sign ? (-NAN) : (NAN));
}
}
res = (gfloat32)mant / 8388608.0;
if (expo == 0) {
expo = -126;
} else {
res += 1.0;
expo -= 127;
}
res = ldexpf(res, expo);
return (sign ? (-res) : (res));
}
} // End of namespace Glulx
} // End of namespace Glk

35
engines/glk/glulx/float.h Normal file
View File

@@ -0,0 +1,35 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_GLULXE_FLOAT
#define GLK_GLULXE_FLOAT
#include "common/scummsys.h"
#include "glk/glk_api.h"
namespace Glk {
namespace Glulx {
} // End of namespace Glulx
} // End of namespace Glk
#endif

298
engines/glk/glulx/funcs.cpp Normal file
View File

@@ -0,0 +1,298 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
void Glulx::enter_function(uint funcaddr, uint argc, uint *argv) {
uint ix, jx;
acceleration_func accelFunc;
int locallen;
int functype;
uint modeaddr, opaddr, val;
int loctype, locnum;
uint addr = funcaddr;
accelFunc = accel_get_func(addr);
if (accelFunc) {
profile_in(addr, stackptr, true);
val = (this->*accelFunc)(argc, argv);
profile_out(stackptr);
pop_callstub(val);
return;
}
profile_in(addr, stackptr, false);
/* Check the Glulx type identifier byte. */
functype = Mem1(addr);
if (functype != 0xC0 && functype != 0xC1) {
if (functype >= 0xC0 && functype <= 0xDF)
fatal_error_i("Call to unknown type of function.", addr);
else
fatal_error_i("Call to non-function.", addr);
}
addr++;
/* Bump the frameptr to the top. */
frameptr = stackptr;
/* Go through the function's locals-format list, copying it to the
call frame. At the same time, we work out how much space the locals
will actually take up. (Including padding.) */
ix = 0;
locallen = 0;
while (1) {
/* Grab two bytes from the locals-format list. These are
unsigned (0..255 range). */
loctype = Mem1(addr);
addr++;
locnum = Mem1(addr);
addr++;
/* Copy them into the call frame. */
StkW1(frameptr + 8 + 2 * ix, loctype);
StkW1(frameptr + 8 + 2 * ix + 1, locnum);
ix++;
/* If the type is zero, we're done, except possibly for two more
zero bytes in the call frame (to ensure 4-byte alignment.) */
if (loctype == 0) {
/* Make sure ix is even. */
if (ix & 1) {
StkW1(frameptr + 8 + 2 * ix, 0);
StkW1(frameptr + 8 + 2 * ix + 1, 0);
ix++;
}
break;
}
/* Pad to 4-byte or 2-byte alignment if these locals are 4 or 2
bytes long. */
if (loctype == 4) {
while (locallen & 3)
locallen++;
} else if (loctype == 2) {
while (locallen & 1)
locallen++;
} else if (loctype == 1) {
/* no padding */
} else {
fatal_error("Illegal local type in locals-format list.");
}
/* Add the length of the locals themselves. */
locallen += (loctype * locnum);
}
/* Pad the locals to 4-byte alignment. */
while (locallen & 3)
locallen++;
/* We now know how long the locals-frame and locals segments are. */
localsbase = frameptr + 8 + 2 * ix;
valstackbase = localsbase + locallen;
/* Test for stack overflow. */
/* This really isn't good enough; if the format list overflowed the
stack, we've already written outside the stack array. */
if (valstackbase >= stacksize)
fatal_error("Stack overflow in function call.");
/* Fill in the beginning of the stack frame. */
StkW4(frameptr + 4, 8 + 2 * ix);
StkW4(frameptr, 8 + 2 * ix + locallen);
/* Set the stackptr and PC. */
stackptr = valstackbase;
pc = addr;
/* Zero out all the locals. */
for (jx = 0; jx < (uint)locallen; jx++)
StkW1(localsbase + jx, 0);
if (functype == 0xC0) {
/* Push the function arguments on the stack. The locals have already
been zeroed. */
if (stackptr + 4 * (argc + 1) >= stacksize)
fatal_error("Stack overflow in function arguments.");
for (ix = 0; ix < argc; ix++) {
val = argv[(argc - 1) - ix];
StkW4(stackptr, val);
stackptr += 4;
}
StkW4(stackptr, argc);
stackptr += 4;
} else {
/* Copy in function arguments. This is a bit gross, since we have to
follow the locals format. If there are fewer arguments than locals,
that's fine -- we've already zeroed out this space. If there are
more arguments than locals, the extras are silently dropped. */
modeaddr = frameptr + 8;
opaddr = localsbase;
ix = 0;
while (ix < argc) {
loctype = Stk1(modeaddr);
modeaddr++;
locnum = Stk1(modeaddr);
modeaddr++;
if (loctype == 0)
break;
if (loctype == 4) {
while (opaddr & 3)
opaddr++;
while (ix < argc && locnum) {
val = argv[ix];
StkW4(opaddr, val);
opaddr += 4;
ix++;
locnum--;
}
} else if (loctype == 2) {
while (opaddr & 1)
opaddr++;
while (ix < argc && locnum) {
val = argv[ix] & 0xFFFF;
StkW2(opaddr, val);
opaddr += 2;
ix++;
locnum--;
}
} else if (loctype == 1) {
while (ix < argc && locnum) {
val = argv[ix] & 0xFF;
StkW1(opaddr, val);
opaddr += 1;
ix++;
locnum--;
}
}
}
}
/* If the debugger is compiled in, check for a breakpoint on this
function. (Checking the function address, not the starting PC.) */
debugger_check_func_breakpoint(funcaddr);
}
void Glulx::leave_function() {
profile_out(stackptr);
stackptr = frameptr;
}
void Glulx::push_callstub(uint desttype, uint destaddr) {
if (stackptr + 16 > stacksize)
fatal_error("Stack overflow in callstub.");
StkW4(stackptr + 0, desttype);
StkW4(stackptr + 4, destaddr);
StkW4(stackptr + 8, pc);
StkW4(stackptr + 12, frameptr);
stackptr += 16;
}
void Glulx::pop_callstub(uint returnvalue) {
uint desttype, destaddr;
uint newpc, newframeptr;
if (stackptr < 16)
fatal_error("Stack underflow in callstub.");
stackptr -= 16;
newframeptr = Stk4(stackptr + 12);
newpc = Stk4(stackptr + 8);
destaddr = Stk4(stackptr + 4);
desttype = Stk4(stackptr + 0);
pc = newpc;
frameptr = newframeptr;
/* Recompute valstackbase and localsbase */
valstackbase = frameptr + Stk4(frameptr);
localsbase = frameptr + Stk4(frameptr + 4);
switch (desttype) {
case 0x11:
fatal_error("String-terminator call stub at end of function call.");
break;
case 0x10:
/* This call stub was pushed during a string-decoding operation!
We have to restart it. (Note that the return value is discarded.) */
stream_string(pc, 0xE1, destaddr);
break;
case 0x12:
/* This call stub was pushed during a number-printing operation.
Restart that. (Return value discarded.) */
stream_num(pc, true, destaddr);
break;
case 0x13:
/* This call stub was pushed during a C-string printing operation.
We have to restart it. (Note that the return value is discarded.) */
stream_string(pc, 0xE0, destaddr);
break;
case 0x14:
/* This call stub was pushed during a Unicode printing operation.
We have to restart it. (Note that the return value is discarded.) */
stream_string(pc, 0xE2, destaddr);
break;
default:
/* We're back in the original frame, so we can store the returnvalue.
(If we tried to do this before resetting frameptr, a result
destination on the stack would go astray.) */
store_operand(desttype, destaddr, returnvalue);
break;
}
}
uint Glulx::pop_callstub_string(int *bitnum) {
uint desttype, destaddr, newpc;
if (stackptr < 16)
fatal_error("Stack underflow in callstub.");
stackptr -= 16;
newpc = Stk4(stackptr + 8);
destaddr = Stk4(stackptr + 4);
desttype = Stk4(stackptr + 0);
pc = newpc;
if (desttype == 0x11) {
return 0;
}
if (desttype == 0x10) {
*bitnum = destaddr;
return pc;
}
fatal_error("Function-terminator call stub at end of string.");
return 0;
}
} // End of namespace Glulx
} // End of namespace Glk

View File

@@ -0,0 +1,100 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
uint Glulx::do_gestalt(uint val, uint val2) {
switch (val) {
case gestulx_GlulxVersion:
return 0x00030102; /* Glulx spec version 3.1.2 */
case gestulx_TerpVersion:
return 0x00000504; /* Glulx version 0.5.4 */
case gestulx_ResizeMem:
#ifdef FIXED_MEMSIZE
return 0; /* The setmemsize opcodes are compiled out. */
#else /* FIXED_MEMSIZE */
return 1; /* We can handle setmemsize. */
#endif /* FIXED_MEMSIZE */
case gestulx_Undo:
return 1; /* We can handle saveundo and restoreundo. */
case gestulx_IOSystem:
switch (val2) {
case 0:
return 1; /* The "null" system always works. */
case 1:
return 1; /* The "filter" system always works. */
case 2:
return 1; /* A Glk library is hooked up. */
default:
return 0;
}
case gestulx_Unicode:
return 1; /* We can handle Unicode. */
case gestulx_MemCopy:
return 1; /* We can do mcopy/mzero. */
case gestulx_MAlloc:
#ifdef FIXED_MEMSIZE
return 0; /* The malloc opcodes are compiled out. */
#else /* FIXED_MEMSIZE */
return 1; /* We can handle malloc/mfree. */
#endif /* FIXED_MEMSIZE */
case gestulx_MAllocHeap:
return heap_get_start();
case gestulx_Acceleration:
return 1; /* We can do accelfunc/accelparam. */
case gestulx_AccelFunc:
if (accel_find_func(val2))
return 1; /* We know this accelerated function. */
return 0;
case gestulx_Float:
#ifdef FLOAT_SUPPORT
return 1; /* We can do floating-point operations. */
#else /* FLOAT_SUPPORT */
return 0; /* The floating-point opcodes are not compiled in. */
#endif /* FLOAT_SUPPORT */
#ifdef GLULX_EXTEND_GESTALT
GLULX_EXTEND_GESTALT
#endif /* GLULX_EXTEND_GESTALT */
default:
return 0;
}
}
} // End of namespace Glulx
} // End of namespace Glk

1385
engines/glk/glulx/glkop.cpp Normal file

File diff suppressed because it is too large Load Diff

147
engines/glk/glulx/glulx.cpp Normal file
View File

@@ -0,0 +1,147 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
#include "common/config-manager.h"
#include "common/translation.h"
namespace Glk {
namespace Glulx {
Glulx *g_vm;
Glulx::Glulx(OSystem *syst, const GlkGameDescription &gameDesc) : GlkAPI(syst, gameDesc),
vm_exited_cleanly(false), gamefile_start(0), gamefile_len(0), memmap(nullptr), stack(nullptr),
ramstart(0), endgamefile(0), origendmem(0), stacksize(0), startfuncaddr(0), checksum(0),
stackptr(0), frameptr(0), pc(0), prevpc(0), origstringtable(0), stringtable(0), valstackbase(0),
localsbase(0), endmem(0), protectstart(0), protectend(0),
stream_char_handler(nullptr), stream_unichar_handler(nullptr),
// main
library_autorestore_hook(nullptr),
// accel
classes_table(0), indiv_prop_start(0), class_metaclass(0), object_metaclass(0),
routine_metaclass(0), string_metaclass(0), self(0), num_attr_bytes(0), cpv__start(0),
accelentries(nullptr),
// heap
heap_start(0), alloc_count(0), heap_head(nullptr), heap_tail(nullptr),
// serial
max_undo_level(8), undo_chain_size(0), undo_chain_num(0), undo_chain(nullptr), ramcache(nullptr),
// string
iosys_mode(0), iosys_rock(0), tablecache_valid(false), glkio_unichar_han_ptr(nullptr) {
g_vm = this;
glkopInit();
}
void Glulx::runGame() {
if (!is_gamefile_valid())
return;
gamefile_start = 0;
gamefile_len = _gameFile.size();
setup_vm();
if (!init_dispatch())
return;
if (library_autorestore_hook)
library_autorestore_hook();
execute_loop();
finalize_vm();
gamefile_start = 0;
gamefile_len = 0;
init_err = nullptr;
vm_exited_cleanly = true;
profile_quit();
}
bool Glulx::is_gamefile_valid() {
if (_gameFile.size() < 8) {
GUIErrorMessage(_("This is too short to be a valid Glulx file."));
return false;
}
if (_gameFile.readUint32BE() != MKTAG('G', 'l', 'u', 'l')) {
GUIErrorMessage(_("This is not a valid Glulx file."));
return false;
}
// We support version 2.0 through 3.1.*
uint version = _gameFile.readUint32BE();
if (version < 0x20000) {
GUIErrorMessage(_("This Glulx file is too old a version to execute."));
return false;
}
if (version >= 0x30200) {
GUIErrorMessage(_("This Glulx file is too new a version to execute."));
return false;
}
return true;
}
void Glulx::fatal_error_handler(const char *str, const char *arg, bool useVal, int val) {
Common::String msg = Common::String::format("Glulx fatal error: %s", str);
if (arg || useVal) {
msg += " (";
if (arg)
msg += Common::String::format("%s", arg);
if (arg && useVal)
msg += " ";
if (useVal)
msg += Common::String::format("%x", val);
msg += ")";
}
error("%s", msg.c_str());
}
void Glulx::nonfatal_warning_handler(const char *str, const char *arg, bool useVal, int val) {
Common::String msg = Common::String::format("Glulx warning: %s", str);
if (arg || useVal) {
msg += " (";
if (arg)
msg += Common::String::format("%s", arg);
if (arg && useVal)
msg += " ";
if (useVal)
msg += Common::String::format("%x", val);
msg += ")";
}
warning("%s", msg.c_str());
}
void Glulx::glulx_sort(void *addr, int count, int size, int(*comparefunc)(const void *p1, const void *p2)) {
qsort(addr, count, size, comparefunc);
}
} // End of namespace Glulx
} // End of namespace Glk

1008
engines/glk/glulx/glulx.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,423 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GLK_GLULXE_TYPES
#define GLK_GLULXE_TYPES
#include "common/scummsys.h"
namespace Glk {
namespace Glulx {
class Glulx;
/**
* Comment this definition to turn off memory-address checking. With verification on,
* all reads and writes to main memory will be checked to ensure they're in range.
* This is slower, but prevents malformed game files from crashing the interpreter.
*/
#define VERIFY_MEMORY_ACCESS (1)
/**
* Uncomment this definition to permit an exception for memory-address checking for @glk and @copy
* opcodes that try to write to memory address 0. This was a bug in old Superglus-built game files.
*/
/* #define TOLERATE_SUPERGLUS_BUG (1) */
/**
* Uncomment this definition to turn on Glulx VM profiling. In this mode, all function calls are timed,
* and the timing information is written to a data file called "profile-raw".
* (Build note: on Linux, glibc may require you to also define _BSD_SOURCE or _DEFAULT_SOURCE or both
* for the timeradd() macro.)
*/
/* #define VM_PROFILING (1) */
/**
* Uncomment this definition to turn on the Glulx debugger. You should only do this when debugging
* facilities are desired; it slows down the interpreter. If you do, you will need to build with libxml2;
* see the Makefile.
*/
/* #define VM_DEBUGGER (1) */
/**
* Comment this definition to turn off floating-point support. You might need to do this if you are building
* on a very limited platform with no math library.
*/
#define FLOAT_SUPPORT (1)
/**
* Comment this definition to not cache the original state of RAM in (real) memory. This saves some memory,
* but slows down save/restore/undo operations, which will have to read the original state off disk
* every time.
*/
#define SERIALIZE_CACHE_RAM (1)
/**
* Some macros to read and write integers to memory, always in big-endian format.
*/
#define Read4(ptr) READ_BE_UINT32(ptr)
#define Read2(ptr) READ_BE_UINT16(ptr)
#define Read1(ptr) ((byte)(((byte *)(ptr))[0]))
#define Write4(ptr, vl) WRITE_BE_UINT32(ptr, vl)
#define Write2(ptr, vl) WRITE_BE_UINT16(ptr, vl)
#define Write1(ptr, vl) (((byte *)(ptr))[0] = (vl))
#if VERIFY_MEMORY_ACCESS
#define Verify(adr, ln) verify_address(adr, ln)
#define VerifyW(adr, ln) verify_address_write(adr, ln)
#else
#define Verify(adr, ln) (0)
#define VerifyW(adr, ln) (0)
#endif /* VERIFY_MEMORY_ACCESS */
#define Mem1(adr) (Read1(memmap+(adr)))
#define Mem2(adr) (Read2(memmap+(adr)))
#define Mem4(adr) (Read4(memmap+(adr)))
#define MemW1(adr, vl) (VerifyW(adr, 1), Write1(memmap+(adr), (vl)))
#define MemW2(adr, vl) (VerifyW(adr, 2), Write2(memmap+(adr), (vl)))
#define MemW4(adr, vl) (VerifyW(adr, 4), Write4(memmap+(adr), (vl)))
#ifndef _HUGE_ENUF
#define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow
#endif
#ifndef INFINITY
#define INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF))
#endif
#ifndef NAN
#define NAN ((float)(INFINITY * 0.0F))
#endif
/**
* Macros to access values on the stack. These *must* be used with proper alignment!
* (That is, Stk4 and StkW4 must take addresses which are multiples of four, etc.)
* If the alignment rules are not followed, the program will see performance
* degradation or even crashes, depending on the machine CPU.
*/
#define Stk1(adr) \
(*((unsigned char *)(stack+(adr))))
#define Stk2(adr) \
(*((uint16 *)(stack+(adr))))
#define Stk4(adr) \
(*((uint32 *)(stack+(adr))))
#define StkW1(adr, vl) \
(*((byte *)(stack+(adr))) = (byte)(vl))
#define StkW2(adr, vl) \
(*((uint16 *)(stack+(adr))) = (uint16)(vl))
#define StkW4(adr, vl) \
(*((uint32 *)(stack+(adr))) = (uint32)(vl))
enum Opcode {
op_nop = 0x00,
op_add = 0x10,
op_sub = 0x11,
op_mul = 0x12,
op_div = 0x13,
op_mod = 0x14,
op_neg = 0x15,
op_bitand = 0x18,
op_bitor = 0x19,
op_bitxor = 0x1A,
op_bitnot = 0x1B,
op_shiftl = 0x1C,
op_sshiftr = 0x1D,
op_ushiftr = 0x1E,
op_jump = 0x20,
op_jz = 0x22,
op_jnz = 0x23,
op_jeq = 0x24,
op_jne = 0x25,
op_jlt = 0x26,
op_jge = 0x27,
op_jgt = 0x28,
op_jle = 0x29,
op_jltu = 0x2A,
op_jgeu = 0x2B,
op_jgtu = 0x2C,
op_jleu = 0x2D,
op_call = 0x30,
op_return = 0x31,
op_catch = 0x32,
op_throw = 0x33,
op_tailcall = 0x34,
op_copy = 0x40,
op_copys = 0x41,
op_copyb = 0x42,
op_sexs = 0x44,
op_sexb = 0x45,
op_aload = 0x48,
op_aloads = 0x49,
op_aloadb = 0x4A,
op_aloadbit = 0x4B,
op_astore = 0x4C,
op_astores = 0x4D,
op_astoreb = 0x4E,
op_astorebit = 0x4F,
op_stkcount = 0x50,
op_stkpeek = 0x51,
op_stkswap = 0x52,
op_stkroll = 0x53,
op_stkcopy = 0x54,
op_streamchar = 0x70,
op_streamnum = 0x71,
op_streamstr = 0x72,
op_streamunichar = 0x73,
op_gestalt = 0x100,
op_debugtrap = 0x101,
op_getmemsize = 0x102,
op_setmemsize = 0x103,
op_jumpabs = 0x104,
op_random = 0x110,
op_setrandom = 0x111,
op_quit = 0x120,
op_verify = 0x121,
op_restart = 0x122,
op_save = 0x123,
op_restore = 0x124,
op_saveundo = 0x125,
op_restoreundo = 0x126,
op_protect = 0x127,
op_glk = 0x130,
op_getstringtbl = 0x140,
op_setstringtbl = 0x141,
op_getiosys = 0x148,
op_setiosys = 0x149,
op_linearsearch = 0x150,
op_binarysearch = 0x151,
op_linkedsearch = 0x152,
op_callf = 0x160,
op_callfi = 0x161,
op_callfii = 0x162,
op_callfiii = 0x163,
op_mzero = 0x170,
op_mcopy = 0x171,
op_malloc = 0x178,
op_mfree = 0x179,
op_accelfunc = 0x180,
op_accelparam = 0x181,
op_numtof = 0x190,
op_ftonumz = 0x191,
op_ftonumn = 0x192,
op_ceil = 0x198,
op_floor = 0x199,
op_fadd = 0x1A0,
op_fsub = 0x1A1,
op_fmul = 0x1A2,
op_fdiv = 0x1A3,
op_fmod = 0x1A4,
op_sqrt = 0x1A8,
op_exp = 0x1A9,
op_log = 0x1AA,
op_pow = 0x1AB,
op_sin = 0x1B0,
op_cos = 0x1B1,
op_tan = 0x1B2,
op_asin = 0x1B3,
op_acos = 0x1B4,
op_atan = 0x1B5,
op_atan2 = 0x1B6,
op_jfeq = 0x1C0,
op_jfne = 0x1C1,
op_jflt = 0x1C2,
op_jfle = 0x1C3,
op_jfgt = 0x1C4,
op_jfge = 0x1C5,
op_jisnan = 0x1C8,
op_jisinf = 0x1C9
};
enum gestulx {
gestulx_GlulxVersion = 0,
gestulx_TerpVersion = 1,
gestulx_ResizeMem = 2,
gestulx_Undo = 3,
gestulx_IOSystem = 4,
gestulx_Unicode = 5,
gestulx_MemCopy = 6,
gestulx_MAlloc = 7,
gestulx_MAllocHeap = 8,
gestulx_Acceleration = 9,
gestulx_AccelFunc = 10,
gestulx_Float = 11
};
/**
* You may have to edit the definition of gfloat32 to make sure it's really a 32-bit floating-point type.
*/
typedef float gfloat32;
struct dispatch_splot_struct {
int numwanted;
int maxargs;
gluniversal_t *garglist;
uint *varglist;
int numvargs;
uint *retval;
};
typedef dispatch_splot_struct dispatch_splot_t;
/**
* We maintain a linked list of arrays being used for Glk calls. It is only used for integer
* (uint) arrays -- char arrays are handled in place. It's not worth bothering with a hash table,
* since most arrays appear here only momentarily.
*/
struct arrayref_struct {
void *array;
uint addr;
uint elemsize;
uint len; /* elements */
int retained;
arrayref_struct *next;
};
typedef arrayref_struct arrayref_t;
/**
* We maintain a hash table for each opaque Glk class. classref_t are the nodes of the table,
* and classtable_t are the tables themselves.
*/
struct classref_struct {
void *obj;
uint id;
int bucknum;
classref_struct *next;
};
typedef classref_struct classref_t;
#define CLASSHASH_SIZE (31)
struct classtable_struct {
uint lastid;
classref_t *bucket[CLASSHASH_SIZE];
};
typedef classtable_struct classtable_t;
/**
* Represents the operand structure of an opcode.
*/
struct operandlist_struct {
int num_ops; ///< Number of operands for this opcode
int arg_size; ///< Usually 4, but can be 1 or 2
const int *formlist; ///< Array of values, either modeform_Load or modeform_Store
};
typedef operandlist_struct operandlist_t;
enum modeform {
modeform_Load = 1,
modeform_Store = 2
};
/**
* Represents one operand value to an instruction being executed. The
* code in exec.c assumes that no instruction has more than MAX_OPERANDS of these.
*/
struct oparg_struct {
uint desttype;
uint value;
};
typedef oparg_struct oparg_t;
#define MAX_OPERANDS (8)
typedef uint(Glulx::*acceleration_func)(uint argc, uint *argv);
struct accelentry_struct {
uint addr;
uint index;
acceleration_func func;
accelentry_struct *next;
};
typedef accelentry_struct accelentry_t;
#define ACCEL_HASH_SIZE (511)
struct heapblock_struct {
uint addr;
uint len;
int isfree;
struct heapblock_struct *next;
struct heapblock_struct *prev;
};
typedef heapblock_struct heapblock_t;
/**
* This structure allows us to write either to a Glk stream or to a dynamically-allocated memory chunk.
*/
struct dest_struct {
bool _isMem;
/* If it's a raw stream */
Common::SeekableReadStream *_src;
Common::WriteStream *_dest;
/* If it's a block of memory: */
byte *_ptr;
uint _pos;
uint _size;
dest_struct() : _isMem(false), _src(nullptr), _dest(nullptr),
_ptr(nullptr), _pos(0), _size(0) {}
};
typedef dest_struct dest_t;
/**
* These constants are defined in the Glulx spec.
*/
enum iosys {
iosys_None = 0,
iosys_Filter = 1,
iosys_Glk = 2
};
#define CACHEBITS (4)
#define CACHESIZE (1 << CACHEBITS)
#define CACHEMASK (15)
struct cacheblock_struct {
int depth; /* 1 to 4 */
int type;
union {
struct cacheblock_struct *branches;
unsigned char ch;
uint uch;
uint addr;
} u;
};
typedef cacheblock_struct cacheblock_t;
} // End of namespace Glulx
} // End of namespace Glk
#endif

316
engines/glk/glulx/heap.cpp Normal file
View File

@@ -0,0 +1,316 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
void Glulx::heap_clear() {
while (heap_head) {
heapblock_t *blo = heap_head;
heap_head = blo->next;
blo->next = nullptr;
blo->prev = nullptr;
glulx_free(blo);
}
heap_tail = nullptr;
if (heap_start) {
uint res = change_memsize(heap_start, true);
if (res)
fatal_error_i("Unable to revert memory size when deactivating heap.",
heap_start);
}
heap_start = 0;
alloc_count = 0;
/* heap_sanity_check(); */
}
int Glulx::heap_is_active() const {
return (heap_start != 0);
}
uint Glulx::heap_get_start() const {
return heap_start;
}
uint Glulx::heap_alloc(uint len) {
heapblock_t *blo, *newblo;
#ifdef FIXED_MEMSIZE
return 0;
#else /* FIXED_MEMSIZE */
if (len <= 0)
fatal_error("Heap allocation length must be positive.");
blo = heap_head;
while (blo) {
if (blo->isfree && blo->len >= len)
break;
if (!blo->isfree) {
blo = blo->next;
continue;
}
if (!blo->next || !blo->next->isfree) {
blo = blo->next;
continue;
}
/* This is a free block, but the next block in the list is also
free, so we "advance" by merging rather than by going to
blo->next. */
newblo = blo->next;
blo->len += newblo->len;
if (newblo->next) {
blo->next = newblo->next;
newblo->next->prev = blo;
} else {
blo->next = nullptr;
heap_tail = blo;
}
newblo->next = nullptr;
newblo->prev = nullptr;
glulx_free(newblo);
newblo = nullptr;
continue;
}
if (!blo) {
/* No free area is visible on the list. Try extending memory. How
much? Double the heap size, or by 256 bytes, or by the memory
length requested -- whichever is greatest. */
uint res;
uint extension;
uint oldendmem = endmem;
extension = 0;
if (heap_start)
extension = endmem - heap_start;
if (extension < len)
extension = len;
if (extension < 256)
extension = 256;
/* And it must be rounded up to a multiple of 256. */
extension = (extension + 0xFF) & (~(uint)0xFF);
res = change_memsize(endmem + extension, true);
if (res)
return 0;
/* If we just started the heap, note that. */
if (heap_start == 0)
heap_start = oldendmem;
if (heap_tail && heap_tail->isfree) {
/* Append the new space to the last block. */
blo = heap_tail;
blo->len += extension;
} else {
/* Append the new space to the block list, as a new block. */
newblo = (heapblock_t *)glulx_malloc(sizeof(heapblock_t));
if (!newblo)
fatal_error("Unable to allocate record for heap block.");
newblo->addr = oldendmem;
newblo->len = extension;
newblo->isfree = true;
newblo->next = nullptr;
newblo->prev = nullptr;
if (!heap_tail) {
heap_head = newblo;
heap_tail = newblo;
} else {
blo = heap_tail;
heap_tail = newblo;
blo->next = newblo;
newblo->prev = blo;
}
blo = newblo;
newblo = nullptr;
}
/* and continue forwards, using this new block (blo). */
}
/* Something strange happened. */
if (!blo || !blo->isfree || blo->len < len)
return 0;
/* We now have a free block of size len or longer. */
if (blo->len == len) {
blo->isfree = false;
} else {
newblo = (heapblock_t *)glulx_malloc(sizeof(heapblock_t));
if (!newblo)
fatal_error("Unable to allocate record for heap block.");
newblo->isfree = true;
newblo->addr = blo->addr + len;
newblo->len = blo->len - len;
blo->len = len;
blo->isfree = false;
newblo->next = blo->next;
if (newblo->next)
newblo->next->prev = newblo;
newblo->prev = blo;
blo->next = newblo;
if (heap_tail == blo)
heap_tail = newblo;
}
alloc_count++;
/* heap_sanity_check(); */
return blo->addr;
#endif /* FIXED_MEMSIZE */
}
void Glulx::heap_free(uint addr) {
heapblock_t *blo;
for (blo = heap_head; blo; blo = blo->next) {
if (blo->addr == addr)
break;
};
if (!blo || blo->isfree)
fatal_error_i("Attempt to free unallocated address from heap.", addr);
blo->isfree = true;
alloc_count--;
if (alloc_count <= 0) {
heap_clear();
}
/* heap_sanity_check(); */
}
int Glulx::heap_get_summary(uint *valcount, uint **summary) {
uint *arr, len, pos;
heapblock_t *blo;
*valcount = 0;
*summary = nullptr;
if (heap_start == 0)
return 0;
len = 2 + 2 * alloc_count;
arr = (uint *)glulx_malloc(len * sizeof(uint));
if (!arr)
return 1;
pos = 0;
arr[pos++] = heap_start;
arr[pos++] = alloc_count;
for (blo = heap_head; blo; blo = blo->next) {
if (blo->isfree)
continue;
arr[pos++] = blo->addr;
arr[pos++] = blo->len;
}
if (pos != len)
fatal_error("Wrong number of active blocks in heap");
*valcount = len;
*summary = arr;
return 0;
}
int Glulx::heap_apply_summary(uint valcount, uint *summary) {
uint lx, jx, lastend;
if (heap_start)
fatal_error("Heap active when heap_apply_summary called");
if (valcount == 0 || summary == nullptr)
return 0;
if (valcount == 2 && summary[0] == 0 && summary[1] == 0)
return 0;
#ifdef FIXED_MEMSIZE
return 1;
#else /* FIXED_MEMSIZE */
lx = 0;
heap_start = summary[lx++];
alloc_count = summary[lx++];
for (jx = lx; jx + 2 < valcount; jx += 2) {
if (summary[jx] >= summary[jx + 2])
fatal_error("Heap block summary is out of order.");
}
lastend = heap_start;
while (lx < valcount || lastend < endmem) {
heapblock_t *blo;
blo = (heapblock_t *)glulx_malloc(sizeof(heapblock_t));
if (!blo)
fatal_error("Unable to allocate record for heap block.");
if (lx >= valcount) {
blo->addr = lastend;
blo->len = endmem - lastend;
blo->isfree = true;
} else {
if (lastend < summary[lx]) {
blo->addr = lastend;
blo->len = summary[lx] - lastend;
blo->isfree = true;
} else {
blo->addr = summary[lx++];
blo->len = summary[lx++];
blo->isfree = false;
}
}
blo->prev = nullptr;
blo->next = nullptr;
if (!heap_head) {
heap_head = blo;
heap_tail = blo;
} else {
heap_tail->next = blo;
blo->prev = heap_tail;
heap_tail = blo;
}
lastend = blo->addr + blo->len;
}
/* heap_sanity_check(); */
return 0;
#endif /* FIXED_MEMSIZE */
}
} // End of namespace Glulx
} // End of namespace Glk

View File

@@ -0,0 +1,611 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
/**
* The actual immutable structures which lookup_operandlist() returns.
*/
static const operandlist_t list_none = { 0, 4, nullptr };
static const int array_S[1] = { modeform_Store };
static const operandlist_t list_S = { 1, 4, &array_S[0] };
static const int array_LS[2] = { modeform_Load, modeform_Store };
static const operandlist_t list_LS = { 2, 4, &array_LS[0] };
static const int array_LLS[3] = { modeform_Load, modeform_Load, modeform_Store };
static const operandlist_t list_LLS = { 3, 4, &array_LLS[0] };
static const int array_LLLS[4] = { modeform_Load, modeform_Load, modeform_Load, modeform_Store };
static const operandlist_t list_LLLS = { 4, 4, &array_LLLS[0] };
static const int array_LLLLS[5] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
static const operandlist_t list_LLLLS = { 5, 4, &array_LLLLS[0] };
/* static const int array_LLLLLS[6] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
static const operandlist_t list_LLLLLS = { 6, 4, &array_LLLLLS }; */ /* not currently used */
static const int array_LLLLLLS[7] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
static const operandlist_t list_LLLLLLS = { 7, 4, &array_LLLLLLS[0] };
static const int array_LLLLLLLS[8] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Load, modeform_Store };
static const operandlist_t list_LLLLLLLS = { 8, 4, &array_LLLLLLLS[0] };
static const int array_L[1] = { modeform_Load };
static const operandlist_t list_L = { 1, 4, &array_L[0] };
static const int array_LL[2] = { modeform_Load, modeform_Load };
static const operandlist_t list_LL = { 2, 4, &array_LL[0] };
static const int array_LLL[3] = { modeform_Load, modeform_Load, modeform_Load };
static const operandlist_t list_LLL = { 3, 4, &array_LLL[0] };
static const operandlist_t list_2LS = { 2, 2, &array_LS[0] };
static const operandlist_t list_1LS = { 2, 1, &array_LS[0] };
static const int array_LLLL[4] = { modeform_Load, modeform_Load, modeform_Load, modeform_Load };
static const operandlist_t list_LLLL = { 4, 4, &array_LLLL[0] };
static const int array_SL[2] = { modeform_Store, modeform_Load };
static const operandlist_t list_SL = { 2, 4, &array_SL[0] };
static const int array_SS[2] = { modeform_Store, modeform_Store };
static const operandlist_t list_SS = { 2, 4, &array_SS[0] };
static const int array_LLSS[4] = { modeform_Load, modeform_Load, modeform_Store, modeform_Store };
static const operandlist_t list_LLSS = { 4, 4, &array_LLSS[0] };
void Glulx::init_operands() {
for (int ix = 0; ix < 0x80; ix++)
fast_operandlist[ix] = lookup_operandlist(ix);
}
const operandlist_t *Glulx::lookup_operandlist(uint opcode) {
switch (opcode) {
case op_nop:
return &list_none;
case op_add:
case op_sub:
case op_mul:
case op_div:
case op_mod:
case op_bitand:
case op_bitor:
case op_bitxor:
case op_shiftl:
case op_sshiftr:
case op_ushiftr:
return &list_LLS;
case op_neg:
case op_bitnot:
return &list_LS;
case op_jump:
case op_jumpabs:
return &list_L;
case op_jz:
case op_jnz:
return &list_LL;
case op_jeq:
case op_jne:
case op_jlt:
case op_jge:
case op_jgt:
case op_jle:
case op_jltu:
case op_jgeu:
case op_jgtu:
case op_jleu:
return &list_LLL;
case op_call:
return &list_LLS;
case op_return:
return &list_L;
case op_catch:
return &list_SL;
case op_throw:
return &list_LL;
case op_tailcall:
return &list_LL;
case op_sexb:
case op_sexs:
return &list_LS;
case op_copy:
return &list_LS;
case op_copys:
return &list_2LS;
case op_copyb:
return &list_1LS;
case op_aload:
case op_aloads:
case op_aloadb:
case op_aloadbit:
return &list_LLS;
case op_astore:
case op_astores:
case op_astoreb:
case op_astorebit:
return &list_LLL;
case op_stkcount:
return &list_S;
case op_stkpeek:
return &list_LS;
case op_stkswap:
return &list_none;
case op_stkroll:
return &list_LL;
case op_stkcopy:
return &list_L;
case op_streamchar:
case op_streamunichar:
case op_streamnum:
case op_streamstr:
return &list_L;
case op_getstringtbl:
return &list_S;
case op_setstringtbl:
return &list_L;
case op_getiosys:
return &list_SS;
case op_setiosys:
return &list_LL;
case op_random:
return &list_LS;
case op_setrandom:
return &list_L;
case op_verify:
return &list_S;
case op_restart:
return &list_none;
case op_save:
case op_restore:
return &list_LS;
case op_saveundo:
case op_restoreundo:
return &list_S;
case op_protect:
return &list_LL;
case op_quit:
return &list_none;
case op_gestalt:
return &list_LLS;
case op_debugtrap:
return &list_L;
case op_getmemsize:
return &list_S;
case op_setmemsize:
return &list_LS;
case op_linearsearch:
return &list_LLLLLLLS;
case op_binarysearch:
return &list_LLLLLLLS;
case op_linkedsearch:
return &list_LLLLLLS;
case op_glk:
return &list_LLS;
case op_callf:
return &list_LS;
case op_callfi:
return &list_LLS;
case op_callfii:
return &list_LLLS;
case op_callfiii:
return &list_LLLLS;
case op_mzero:
return &list_LL;
case op_mcopy:
return &list_LLL;
case op_malloc:
return &list_LS;
case op_mfree:
return &list_L;
case op_accelfunc:
case op_accelparam:
return &list_LL;
#ifdef FLOAT_SUPPORT
case op_numtof:
case op_ftonumz:
case op_ftonumn:
case op_ceil:
case op_floor:
case op_sqrt:
case op_exp:
case op_log:
return &list_LS;
case op_fadd:
case op_fsub:
case op_fmul:
case op_fdiv:
case op_pow:
case op_atan2:
return &list_LLS;
case op_fmod:
return &list_LLSS;
case op_sin:
case op_cos:
case op_tan:
case op_asin:
case op_acos:
case op_atan:
return &list_LS;
case op_jfeq:
case op_jfne:
return &list_LLLL;
case op_jflt:
case op_jfle:
case op_jfgt:
case op_jfge:
return &list_LLL;
case op_jisnan:
case op_jisinf:
return &list_LL;
#endif /* FLOAT_SUPPORT */
#ifdef GLULX_EXTEND_OPERANDS
GLULX_EXTEND_OPERANDS
#endif /* GLULX_EXTEND_OPERANDS */
default:
return nullptr;
}
}
void Glulx::parse_operands(oparg_t *args, const operandlist_t *oplist) {
int ix;
oparg_t *curarg;
int numops = oplist->num_ops;
int argsize = oplist->arg_size;
uint modeaddr = pc;
int modeval = 0;
pc += (numops + 1) / 2;
for (ix = 0, curarg = args; ix < numops; ix++, curarg++) {
int mode;
uint value;
uint addr;
curarg->desttype = 0;
if ((ix & 1) == 0) {
modeval = Mem1(modeaddr);
mode = (modeval & 0x0F);
} else {
mode = ((modeval >> 4) & 0x0F);
modeaddr++;
}
if (oplist->formlist[ix] == modeform_Load) {
switch (mode) {
case 8: /* pop off stack */
if (stackptr < valstackbase + 4) {
fatal_error("Stack underflow in operand.");
}
stackptr -= 4;
value = Stk4(stackptr);
break;
case 0: /* constant zero */
value = 0;
break;
case 1: /* one-byte constant */
/* Sign-extend from 8 bits to 32 */
value = (int)(signed char)(Mem1(pc));
pc++;
break;
case 2: /* two-byte constant */
/* Sign-extend the first byte from 8 bits to 32; the subsequent
byte must not be sign-extended. */
value = (int)(signed char)(Mem1(pc));
pc++;
value = (value << 8) | (uint)(Mem1(pc));
pc++;
break;
case 3: /* four-byte constant */
/* Bytes must not be sign-extended. */
value = Mem4(pc);
pc += 4;
break;
case 15: /* main memory RAM, four-byte address */
addr = Mem4(pc);
addr += ramstart;
pc += 4;
goto MainMemAddr;
case 14: /* main memory RAM, two-byte address */
addr = (uint)Mem2(pc);
addr += ramstart;
pc += 2;
goto MainMemAddr;
case 13: /* main memory RAM, one-byte address */
addr = (uint)(Mem1(pc));
addr += ramstart;
pc++;
goto MainMemAddr;
case 7: /* main memory, four-byte address */
addr = Mem4(pc);
pc += 4;
goto MainMemAddr;
case 6: /* main memory, two-byte address */
addr = (uint)Mem2(pc);
pc += 2;
goto MainMemAddr;
case 5: /* main memory, one-byte address */
addr = (uint)(Mem1(pc));
pc++;
/* fall through */
MainMemAddr:
/* cases 5, 6, 7, 13, 14, 15 all wind up here. */
if (argsize == 4) {
value = Mem4(addr);
} else if (argsize == 2) {
value = Mem2(addr);
} else {
value = Mem1(addr);
}
break;
case 11: /* locals, four-byte address */
addr = Mem4(pc);
pc += 4;
goto LocalsAddr;
case 10: /* locals, two-byte address */
addr = (uint)Mem2(pc);
pc += 2;
goto LocalsAddr;
case 9: /* locals, one-byte address */
addr = (uint)(Mem1(pc));
pc++;
/* fall through */
LocalsAddr:
/* cases 9, 10, 11 all wind up here. It's illegal for addr to not
be four-byte aligned, but we don't check this explicitly.
A "strict mode" interpreter probably should. It's also illegal
for addr to be less than zero or greater than the size of
the locals segment. */
addr += localsbase;
if (argsize == 4) {
value = Stk4(addr);
} else if (argsize == 2) {
value = Stk2(addr);
} else {
value = Stk1(addr);
}
break;
default:
value = 0;
fatal_error("Unknown addressing mode in load operand.");
}
curarg->value = value;
} else { /* modeform_Store */
switch (mode) {
case 0: /* discard value */
curarg->desttype = 0;
curarg->value = 0;
break;
case 8: /* push on stack */
curarg->desttype = 3;
curarg->value = 0;
break;
case 15: /* main memory RAM, four-byte address */
addr = Mem4(pc);
addr += ramstart;
pc += 4;
goto WrMainMemAddr;
case 14: /* main memory RAM, two-byte address */
addr = (uint)Mem2(pc);
addr += ramstart;
pc += 2;
goto WrMainMemAddr;
case 13: /* main memory RAM, one-byte address */
addr = (uint)(Mem1(pc));
addr += ramstart;
pc++;
goto WrMainMemAddr;
case 7: /* main memory, four-byte address */
addr = Mem4(pc);
pc += 4;
goto WrMainMemAddr;
case 6: /* main memory, two-byte address */
addr = (uint)Mem2(pc);
pc += 2;
goto WrMainMemAddr;
case 5: /* main memory, one-byte address */
addr = (uint)(Mem1(pc));
pc++;
/* fall through */
WrMainMemAddr:
/* cases 5, 6, 7 all wind up here. */
curarg->desttype = 1;
curarg->value = addr;
break;
case 11: /* locals, four-byte address */
addr = Mem4(pc);
pc += 4;
goto WrLocalsAddr;
case 10: /* locals, two-byte address */
addr = (uint)Mem2(pc);
pc += 2;
goto WrLocalsAddr;
case 9: /* locals, one-byte address */
addr = (uint)(Mem1(pc));
pc++;
/* fall through */
WrLocalsAddr:
/* cases 9, 10, 11 all wind up here. It's illegal for addr to not
be four-byte aligned, but we don't check this explicitly.
A "strict mode" interpreter probably should. It's also illegal
for addr to be less than zero or greater than the size of
the locals segment. */
curarg->desttype = 2;
/* We don't add localsbase here; the store address for desttype 2
is relative to the current locals segment, not an absolute
stack position. */
curarg->value = addr;
break;
case 1:
case 2:
case 3:
fatal_error("Constant addressing mode in store operand.");
break;
default:
fatal_error("Unknown addressing mode in store operand.");
}
}
}
}
void Glulx::store_operand(uint desttype, uint destaddr, uint storeval) {
switch (desttype) {
case 0: /* do nothing; discard the value. */
return;
case 1: /* main memory. */
MemW4(destaddr, storeval);
return;
case 2: /* locals. */
destaddr += localsbase;
StkW4(destaddr, storeval);
return;
case 3: /* push on stack. */
if (stackptr + 4 > stacksize) {
fatal_error("Stack overflow in store operand.");
}
StkW4(stackptr, storeval);
stackptr += 4;
return;
default:
fatal_error("Unknown destination type in store operand.");
}
}
void Glulx::store_operand_s(uint desttype, uint destaddr, uint storeval) {
storeval &= 0xFFFF;
switch (desttype) {
case 0: /* do nothing; discard the value. */
return;
case 1: /* main memory. */
MemW2(destaddr, storeval);
return;
case 2: /* locals. */
destaddr += localsbase;
StkW2(destaddr, storeval);
return;
case 3: /* push on stack. A four-byte value is actually pushed. */
if (stackptr + 4 > stacksize) {
fatal_error("Stack overflow in store operand.");
}
StkW4(stackptr, storeval);
stackptr += 4;
return;
default:
fatal_error("Unknown destination type in store operand.");
}
}
void Glulx::store_operand_b(uint desttype, uint destaddr, uint storeval) {
storeval &= 0xFF;
switch (desttype) {
case 0: /* do nothing; discard the value. */
return;
case 1: /* main memory. */
MemW1(destaddr, storeval);
return;
case 2: /* locals. */
destaddr += localsbase;
StkW1(destaddr, storeval);
return;
case 3: /* push on stack. A four-byte value is actually pushed. */
if (stackptr + 4 > stacksize) {
fatal_error("Stack overflow in store operand.");
}
StkW4(stackptr, storeval);
stackptr += 4;
return;
default:
fatal_error("Unknown destination type in store operand.");
}
}
} // End of namespace Glulx
} // End of namespace Glk

View File

@@ -0,0 +1,210 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
enum serop {
serop_KeyIndirect = 0x01,
serop_ZeroKeyTerminates = 0x02,
serop_ReturnIndex = 0x04
};
uint Glulx::linear_search(uint key, uint keysize, uint start, uint structsize, uint numstructs,
uint keyoffset, uint options) {
unsigned char keybuf[4];
uint count;
uint ix;
int retindex = ((options & serop_ReturnIndex) != 0);
int zeroterm = ((options & serop_ZeroKeyTerminates) != 0);
fetchkey(keybuf, key, keysize, options);
for (count = 0; count < numstructs; count++, start += structsize) {
int match = true;
if (keysize <= 4) {
for (ix = 0; match && ix < keysize; ix++) {
if (Mem1(start + keyoffset + ix) != keybuf[ix])
match = false;
}
} else {
for (ix = 0; match && ix < keysize; ix++) {
if (Mem1(start + keyoffset + ix) != Mem1(key + ix))
match = false;
}
}
if (match) {
if (retindex)
return count;
else
return start;
}
if (zeroterm) {
match = true;
for (ix = 0; match && ix < keysize; ix++) {
if (Mem1(start + keyoffset + ix) != 0)
match = false;
}
if (match) {
break;
}
}
}
if (retindex)
return (uint) - 1;
else
return 0;
}
uint Glulx::binary_search(uint key, uint keysize, uint start, uint structsize, uint numstructs,
uint keyoffset, uint options) {
byte keybuf[4];
byte byte1, byte2;
uint top, bot, val, addr;
uint ix;
int retindex = ((options & serop_ReturnIndex) != 0);
fetchkey(keybuf, key, keysize, options);
bot = 0;
top = numstructs;
while (bot < top) {
int cmp = 0;
val = (top + bot) / 2;
addr = start + val * structsize;
if (keysize <= 4) {
for (ix = 0; (!cmp) && ix < keysize; ix++) {
byte1 = Mem1(addr + keyoffset + ix);
byte2 = keybuf[ix];
if (byte1 < byte2)
cmp = -1;
else if (byte1 > byte2)
cmp = 1;
}
} else {
for (ix = 0; (!cmp) && ix < keysize; ix++) {
byte1 = Mem1(addr + keyoffset + ix);
byte2 = Mem1(key + ix);
if (byte1 < byte2)
cmp = -1;
else if (byte1 > byte2)
cmp = 1;
}
}
if (!cmp) {
if (retindex)
return val;
else
return addr;
}
if (cmp < 0) {
bot = val + 1;
} else {
top = val;
}
}
if (retindex)
return (uint) - 1;
else
return 0;
}
uint Glulx::linked_search(uint key, uint keysize, uint start, uint keyoffset, uint nextoffset, uint options) {
unsigned char keybuf[4];
uint ix;
uint val;
int zeroterm = ((options & serop_ZeroKeyTerminates) != 0);
fetchkey(keybuf, key, keysize, options);
while (start != 0) {
int match = true;
if (keysize <= 4) {
for (ix = 0; match && ix < keysize; ix++) {
if (Mem1(start + keyoffset + ix) != keybuf[ix])
match = false;
}
} else {
for (ix = 0; match && ix < keysize; ix++) {
if (Mem1(start + keyoffset + ix) != Mem1(key + ix))
match = false;
}
}
if (match) {
return start;
}
if (zeroterm) {
match = true;
for (ix = 0; match && ix < keysize; ix++) {
if (Mem1(start + keyoffset + ix) != 0)
match = false;
}
if (match) {
break;
}
}
val = start + nextoffset;
start = Mem4(val);
}
return 0;
}
void Glulx::fetchkey(unsigned char *keybuf, uint key, uint keysize, uint options) {
uint ix;
if (options & serop_KeyIndirect) {
if (keysize <= 4) {
for (ix = 0; ix < keysize; ix++)
keybuf[ix] = Mem1(key + ix);
}
} else {
switch (keysize) {
case 4:
Write4(keybuf, key);
break;
case 2:
Write2(keybuf, key);
break;
case 1:
Write1(keybuf, key);
break;
default:
fatal_error("Direct search key must hold one, two, or four bytes.");
}
}
}
} // End of namespace Glulx
} // End of namespace Glk

View File

@@ -0,0 +1,988 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
#define IFFID(c1, c2, c3, c4) MKTAG(c1, c2, c3, c4)
bool Glulx::init_serial() {
undo_chain_num = 0;
undo_chain_size = max_undo_level;
undo_chain = (unsigned char **)glulx_malloc(sizeof(unsigned char *) * undo_chain_size);
if (!undo_chain)
return false;
#ifdef SERIALIZE_CACHE_RAM
{
uint len = (endmem - ramstart);
uint res;
ramcache = (unsigned char *)glulx_malloc(sizeof(unsigned char *) * len);
if (!ramcache)
return false;
_gameFile.seek(gamefile_start + ramstart);
res = _gameFile.read(ramcache, len);
if (res != len)
return false;
}
#endif /* SERIALIZE_CACHE_RAM */
return true;
}
void Glulx::final_serial() {
if (undo_chain) {
int ix;
for (ix = 0; ix < undo_chain_num; ix++) {
glulx_free(undo_chain[ix]);
}
glulx_free(undo_chain);
}
undo_chain = nullptr;
undo_chain_size = 0;
undo_chain_num = 0;
#ifdef SERIALIZE_CACHE_RAM
if (ramcache) {
glulx_free(ramcache);
ramcache = nullptr;
}
#endif /* SERIALIZE_CACHE_RAM */
}
uint Glulx::perform_saveundo() {
dest_t dest;
uint res;
uint memstart = 0, memlen = 0, heapstart = 0, heaplen = 0;
uint stackstart = 0, stacklen = 0;
/* The format for undo-saves is simpler than for saves on disk. We
just have a memory chunk, a heap chunk, and a stack chunk, in
that order. We skip the IFF chunk headers (although the size
fields are still there.) We also don't bother with IFF's 16-bit
alignment. */
if (undo_chain_size == 0)
return 1;
dest._isMem = true;
res = 0;
if (res == 0) {
res = write_long(&dest, 0); /* space for chunk length */
}
if (res == 0) {
memstart = dest._pos;
res = write_memstate(&dest);
memlen = dest._pos - memstart;
}
if (res == 0) {
res = write_long(&dest, 0); /* space for chunk length */
}
if (res == 0) {
heapstart = dest._pos;
res = write_heapstate(&dest, false);
heaplen = dest._pos - heapstart;
}
if (res == 0) {
res = write_long(&dest, 0); /* space for chunk length */
}
if (res == 0) {
stackstart = dest._pos;
res = write_stackstate(&dest, false);
stacklen = dest._pos - stackstart;
}
if (res == 0) {
/* Trim it down to the perfect size. */
dest._ptr = (byte *)glulx_realloc(dest._ptr, dest._pos);
if (!dest._ptr)
res = 1;
}
if (res == 0) {
res = reposition_write(&dest, memstart - 4);
}
if (res == 0) {
res = write_long(&dest, memlen);
}
if (res == 0) {
res = reposition_write(&dest, heapstart - 4);
}
if (res == 0) {
res = write_long(&dest, heaplen);
}
if (res == 0) {
res = reposition_write(&dest, stackstart - 4);
}
if (res == 0) {
res = write_long(&dest, stacklen);
}
if (res == 0) {
/* It worked. */
if (undo_chain_num >= undo_chain_size) {
glulx_free(undo_chain[undo_chain_num - 1]);
undo_chain[undo_chain_num - 1] = nullptr;
}
if (undo_chain_size > 1)
memmove(undo_chain + 1, undo_chain,
(undo_chain_size - 1) * sizeof(unsigned char *));
undo_chain[0] = dest._ptr;
if (undo_chain_num < undo_chain_size)
undo_chain_num += 1;
dest._ptr = nullptr;
} else {
/* It didn't work. */
if (dest._ptr) {
glulx_free(dest._ptr);
dest._ptr = nullptr;
}
}
return res;
}
uint Glulx::perform_restoreundo() {
dest_t dest;
uint res, val = 0;
uint heapsumlen = 0;
uint *heapsumarr = nullptr;
/* If profiling is enabled and active then fail. */
#ifdef VM_PROFILING
if (profile_profiling_active())
return 1;
#endif /* VM_PROFILING */
if (undo_chain_size == 0 || undo_chain_num == 0)
return 1;
dest._isMem = true;
dest._ptr = undo_chain[0];
res = 0;
if (res == 0) {
res = read_long(&dest, &val);
}
if (res == 0) {
res = read_memstate(&dest, val);
}
if (res == 0) {
res = read_long(&dest, &val);
}
if (res == 0) {
res = read_heapstate(&dest, val, false, &heapsumlen, &heapsumarr);
}
if (res == 0) {
res = read_long(&dest, &val);
}
if (res == 0) {
res = read_stackstate(&dest, val, false);
}
/* ### really, many of the failure modes of those calls ought to
cause fatal errors. The stack or main memory may be damaged now. */
if (res == 0) {
if (heapsumarr)
res = heap_apply_summary(heapsumlen, heapsumarr);
}
if (res == 0) {
/* It worked. */
if (undo_chain_size > 1)
memmove(undo_chain, undo_chain + 1,
(undo_chain_size - 1) * sizeof(unsigned char *));
undo_chain_num -= 1;
glulx_free(dest._ptr);
dest._ptr = nullptr;
} else {
/* It didn't work. */
dest._ptr = nullptr;
}
return res;
}
Common::Error Glulx::readSaveData(Common::SeekableReadStream *rs) {
Common::ErrorCode errCode = Common::kNoError;
QuetzalReader r;
if (r.open(rs))
// Load in the savegame chunks
errCode = loadGameChunks(r).getCode();
return errCode;
}
Common::Error Glulx::writeGameData(Common::WriteStream *ws) {
QuetzalWriter w;
Common::ErrorCode errCode = saveGameChunks(w).getCode();
if (errCode == Common::kNoError) {
w.save(ws, _savegameDescription);
}
return errCode;
}
Common::Error Glulx::loadGameChunks(QuetzalReader &quetzal) {
uint res = 0;
uint heapsumlen = 0;
uint *heapsumarr = nullptr;
for (QuetzalReader::Iterator it = quetzal.begin();
it != quetzal.end() && !res; ++it) {
Common::SeekableReadStream *rs = it.getStream();
dest_t dest;
dest._src = rs;
switch ((*it)._id) {
case ID_IFhd:
for (int ix = 0; ix < 128 && !res; ix++) {
byte v = rs->readByte();
if (Mem1(ix) != v)
// ### non-matching header
res = 1;
}
break;
case ID_CMem:
res = read_memstate(&dest, rs->size());
break;
case MKTAG('M', 'A', 'l', 'l'):
res = read_heapstate(&dest, rs->size(), true, &heapsumlen, &heapsumarr);
break;
case ID_Stks:
res = read_stackstate(&dest, rs->size(), true);
break;
default:
break;
}
delete rs;
}
if (!res) {
if (heapsumarr) {
/* The summary might have come from any interpreter, so it could
be out of order. We'll sort it. */
glulx_sort(heapsumarr + 2, (heapsumlen - 2) / 2, 2 * sizeof(uint), &sort_heap_summary);
res = heap_apply_summary(heapsumlen, heapsumarr);
}
}
return res ? Common::kReadingFailed : Common::kNoError;
}
Common::Error Glulx::saveGameChunks(QuetzalWriter &quetzal) {
uint res = 0;
// IFHd
if (!res) {
Common::WriteStream &ws = quetzal.add(ID_IFhd);
for (int ix = 0; res == 0 && ix < 128; ix++)
ws.writeByte(Mem1(ix));
}
// CMem
if (!res) {
Common::WriteStream &ws = quetzal.add(ID_CMem);
dest_t dest;
dest._dest = &ws;
res = write_memstate(&dest);
}
// MAll
if (!res) {
Common::WriteStream &ws = quetzal.add(MKTAG('M', 'A', 'l', 'l'));
dest_t dest;
dest._dest = &ws;
res = write_heapstate(&dest, true);
}
// Stks
if (!res) {
Common::WriteStream &ws = quetzal.add(ID_Stks);
dest_t dest;
dest._dest = &ws;
res = write_stackstate(&dest, true);
}
// All done
return res ? Common::kUnknownError : Common::kNoError;
}
int Glulx::reposition_write(dest_t *dest, uint pos) {
if (dest->_isMem) {
dest->_pos = pos;
} else {
error("Seeking a WriteStream isn't allowed");
}
return 0;
}
int Glulx::write_buffer(dest_t *dest, const byte *ptr, uint len) {
if (dest->_isMem) {
if (dest->_pos + len > dest->_size) {
dest->_size = dest->_pos + len + 1024;
if (!dest->_ptr) {
dest->_ptr = (byte *)glulx_malloc(dest->_size);
} else {
dest->_ptr = (byte *)glulx_realloc(dest->_ptr, dest->_size);
}
if (!dest->_ptr)
return 1;
}
memcpy(dest->_ptr + dest->_pos, ptr, len);
} else {
dest->_dest->write(ptr, len);
}
dest->_pos += len;
return 0;
}
int Glulx::read_buffer(dest_t *dest, byte *ptr, uint len) {
uint newlen;
if (dest->_isMem) {
memcpy(ptr, dest->_ptr + dest->_pos, len);
} else {
newlen = dest->_src->read(ptr, len);
if (newlen != len)
return 1;
}
dest->_pos += len;
return 0;
}
int Glulx::write_long(dest_t *dest, uint val) {
unsigned char buf[4];
Write4(buf, val);
return write_buffer(dest, buf, 4);
}
int Glulx::write_short(dest_t *dest, uint16 val) {
unsigned char buf[2];
Write2(buf, val);
return write_buffer(dest, buf, 2);
}
int Glulx::write_byte(dest_t *dest, byte val) {
return write_buffer(dest, &val, 1);
}
int Glulx::read_long(dest_t *dest, uint *val) {
unsigned char buf[4];
int res = read_buffer(dest, buf, 4);
if (res)
return res;
*val = Read4(buf);
return 0;
}
int Glulx::read_short(dest_t *dest, uint16 *val) {
unsigned char buf[2];
int res = read_buffer(dest, buf, 2);
if (res)
return res;
*val = Read2(buf);
return 0;
}
int Glulx::read_byte(dest_t *dest, byte *val) {
return read_buffer(dest, val, 1);
}
uint Glulx::write_memstate(dest_t *dest) {
uint res, pos;
int val;
int runlen;
unsigned char ch;
#ifdef SERIALIZE_CACHE_RAM
uint cachepos;
#endif /* SERIALIZE_CACHE_RAM */
res = write_long(dest, endmem);
if (res)
return res;
runlen = 0;
#ifdef SERIALIZE_CACHE_RAM
cachepos = 0;
#else /* SERIALIZE_CACHE_RAM */
_gameFile.seek(gamefile_start + ramstart);
#endif /* SERIALIZE_CACHE_RAM */
for (pos = ramstart; pos < endmem; pos++) {
ch = Mem1(pos);
if (pos < endgamefile) {
#ifdef SERIALIZE_CACHE_RAM
val = ramcache[cachepos];
cachepos++;
#else /* SERIALIZE_CACHE_RAM */
val = glk_get_char_stream(gamefile);
if (val == -1) {
fatal_error("The game file ended unexpectedly while saving.");
}
#endif /* SERIALIZE_CACHE_RAM */
ch ^= (unsigned char)val;
}
if (ch == 0) {
runlen++;
} else {
/* Write any run we've got. */
while (runlen) {
if (runlen >= 0x100)
val = 0x100;
else
val = runlen;
res = write_byte(dest, 0);
if (res)
return res;
res = write_byte(dest, (val - 1));
if (res)
return res;
runlen -= val;
}
/* Write the byte we got. */
res = write_byte(dest, ch);
if (res)
return res;
}
}
/* It's possible we've got a run left over, but we don't write it. */
return 0;
}
uint Glulx::read_memstate(dest_t *dest, uint chunklen) {
uint chunkend = dest->_pos + chunklen;
uint newlen;
uint res, pos;
int val;
int runlen;
unsigned char ch, ch2;
#ifdef SERIALIZE_CACHE_RAM
uint cachepos;
#endif /* SERIALIZE_CACHE_RAM */
heap_clear();
res = read_long(dest, &newlen);
if (res)
return res;
res = change_memsize(newlen, false);
if (res)
return res;
runlen = 0;
#ifdef SERIALIZE_CACHE_RAM
cachepos = 0;
#else /* SERIALIZE_CACHE_RAM */
_gameFile.seek(gamefile_start + ramstart);
#endif /* SERIALIZE_CACHE_RAM */
for (pos = ramstart; pos < endmem; pos++) {
if (pos < endgamefile) {
#ifdef SERIALIZE_CACHE_RAM
val = ramcache[cachepos];
cachepos++;
#else /* SERIALIZE_CACHE_RAM */
if (_gameFile.pos() >= _gameFile.size()) {
fatal_error("The game file ended unexpectedly while restoring.");
val = _gameFile.readByte();
}
#endif /* SERIALIZE_CACHE_RAM */
ch = (unsigned char)val;
} else {
ch = 0;
}
if (dest->_pos >= chunkend) {
/* we're into the final, unstored run. */
} else if (runlen) {
runlen--;
} else {
res = read_byte(dest, &ch2);
if (res)
return res;
if (ch2 == 0) {
res = read_byte(dest, &ch2);
if (res)
return res;
runlen = (uint)ch2;
} else {
ch ^= ch2;
}
}
if (pos >= protectstart && pos < protectend)
continue;
MemW1(pos, ch);
}
return 0;
}
uint Glulx::write_heapstate(dest_t *dest, int portable) {
uint res;
uint sumlen;
uint *sumarray;
res = heap_get_summary(&sumlen, &sumarray);
if (res)
return res;
if (!sumarray)
return 0; /* no heap */
res = write_heapstate_sub(sumlen, sumarray, dest, portable);
glulx_free(sumarray);
return res;
}
uint Glulx::write_heapstate_sub(uint sumlen, uint *sumarray, dest_t *dest, int portable) {
uint res, lx;
/* If we're storing for the purpose of undo, we don't need to do any
byte-swapping, because the result will only be used by this session. */
if (!portable) {
res = write_buffer(dest, (const byte *)sumarray, sumlen * sizeof(uint));
if (res)
return res;
return 0;
}
for (lx = 0; lx < sumlen; lx++) {
res = write_long(dest, sumarray[lx]);
if (res)
return res;
}
return 0;
}
int Glulx::sort_heap_summary(const void *p1, const void *p2) {
uint v1 = *(const uint *)p1;
uint v2 = *(const uint *)p2;
if (v1 < v2)
return -1;
if (v1 > v2)
return 1;
return 0;
}
uint Glulx::read_heapstate(dest_t *dest, uint chunklen, int portable, uint *sumlen, uint **summary) {
uint res, count, lx;
uint *arr;
*sumlen = 0;
*summary = nullptr;
if (chunklen == 0)
return 0; /* no heap */
if (!portable) {
count = chunklen / sizeof(uint);
arr = (uint *)glulx_malloc(chunklen);
if (!arr)
return 1;
res = read_buffer(dest, (byte *)arr, chunklen);
if (res)
return res;
*sumlen = count;
*summary = arr;
return 0;
}
count = chunklen / 4;
arr = (uint *)glulx_malloc(count * sizeof(uint));
if (!arr)
return 1;
for (lx = 0; lx < count; lx++) {
res = read_long(dest, arr + lx);
if (res)
return res;
}
*sumlen = count;
*summary = arr;
return 0;
}
uint Glulx::write_stackstate(dest_t *dest, int portable) {
uint res;
uint lx;
uint lastframe;
/* If we're storing for the purpose of undo, we don't need to do any
byte-swapping, because the result will only be used by this session. */
if (!portable) {
res = write_buffer(dest, stack, stackptr);
if (res)
return res;
return 0;
}
/* Write a portable stack image. To do this, we have to write stack
frames in order, bottom to top. Remember that the last word of
every stack frame is a pointer to the beginning of that stack frame.
(This includes the last frame, because the save opcode pushes on
a call stub before it calls perform_save().) */
lastframe = (uint)(-1);
while (1) {
uint frameend, frm, frm2, frm3;
unsigned char loctype, loccount;
uint numlocals, frlen, locpos;
/* Find the next stack frame (after the one in lastframe). Sadly,
this requires searching the stack from the top down. We have to
do this for *every* frame, which takes N^2 time overall. But
save routines usually aren't nested very deep.
If it becomes a practical problem, we can build a stack-frame
array, which requires dynamic allocation. */
for (frm = stackptr, frameend = stackptr;
frm != 0 && (frm2 = Stk4(frm - 4)) != lastframe;
frameend = frm, frm = frm2) { };
/* Write out the frame. */
frm2 = frm;
frlen = Stk4(frm2);
frm2 += 4;
res = write_long(dest, frlen);
if (res)
return res;
locpos = Stk4(frm2);
frm2 += 4;
res = write_long(dest, locpos);
if (res)
return res;
frm3 = frm2;
numlocals = 0;
while (1) {
loctype = Stk1(frm2);
frm2 += 1;
loccount = Stk1(frm2);
frm2 += 1;
res = write_byte(dest, loctype);
if (res)
return res;
res = write_byte(dest, loccount);
if (res)
return res;
if (loctype == 0 && loccount == 0)
break;
numlocals++;
}
if ((numlocals & 1) == 0) {
res = write_byte(dest, 0);
if (res)
return res;
res = write_byte(dest, 0);
if (res)
return res;
frm2 += 2;
}
if (frm2 != frm + locpos)
fatal_error("Inconsistent stack frame during save.");
/* Write out the locals. */
for (lx = 0; lx < numlocals; lx++) {
loctype = Stk1(frm3);
frm3 += 1;
loccount = Stk1(frm3);
frm3 += 1;
if (loctype == 0 && loccount == 0)
break;
/* Put in up to 0, 1, or 3 bytes of padding, depending on loctype. */
while (frm2 & (loctype - 1)) {
res = write_byte(dest, 0);
if (res)
return res;
frm2 += 1;
}
/* Put in this set of locals. */
switch (loctype) {
case 1:
do {
res = write_byte(dest, Stk1(frm2));
if (res)
return res;
frm2 += 1;
loccount--;
} while (loccount);
break;
case 2:
do {
res = write_short(dest, Stk2(frm2));
if (res)
return res;
frm2 += 2;
loccount--;
} while (loccount);
break;
case 4:
do {
res = write_long(dest, Stk4(frm2));
if (res)
return res;
frm2 += 4;
loccount--;
} while (loccount);
break;
}
}
if (frm2 != frm + frlen)
fatal_error("Inconsistent stack frame during save.");
while (frm2 < frameend) {
res = write_long(dest, Stk4(frm2));
if (res)
return res;
frm2 += 4;
}
/* Go on to the next frame. */
if (frameend == stackptr)
break; /* All done. */
lastframe = frm;
}
return 0;
}
uint Glulx::read_stackstate(dest_t *dest, uint chunklen, int portable) {
uint res;
uint frameend, frm, frm2, frm3, locpos, frlen, numlocals;
if (chunklen > stacksize)
return 1;
stackptr = chunklen;
frameptr = 0;
valstackbase = 0;
localsbase = 0;
if (!portable) {
res = read_buffer(dest, stack, stackptr);
if (res)
return res;
return 0;
}
/* This isn't going to be pleasant; we're going to read the data in
as a block, and then convert it in-place. */
res = read_buffer(dest, stack, stackptr);
if (res)
return res;
frameend = stackptr;
while (frameend != 0) {
/* Read the beginning-of-frame pointer. Remember, right now, the
whole frame is stored big-endian. So we have to read with the
Read*() macros, and then write with the StkW*() macros. */
frm = Read4(stack + (frameend - 4));
frm2 = frm;
frlen = Read4(stack + frm2);
StkW4(frm2, frlen);
frm2 += 4;
locpos = Read4(stack + frm2);
StkW4(frm2, locpos);
frm2 += 4;
/* The locals-format list is in bytes, so we don't have to convert it. */
frm3 = frm2;
frm2 = frm + locpos;
numlocals = 0;
while (1) {
unsigned char loctype, loccount;
loctype = Read1(stack + frm3);
frm3 += 1;
loccount = Read1(stack + frm3);
frm3 += 1;
if (loctype == 0 && loccount == 0)
break;
/* Skip up to 0, 1, or 3 bytes of padding, depending on loctype. */
while (frm2 & (loctype - 1)) {
StkW1(frm2, 0);
frm2++;
}
/* Convert this set of locals. */
switch (loctype) {
case 1:
do {
/* Don't need to convert bytes. */
frm2 += 1;
loccount--;
} while (loccount);
break;
case 2:
do {
uint16 loc = Read2(stack + frm2);
StkW2(frm2, loc);
frm2 += 2;
loccount--;
} while (loccount);
break;
case 4:
do {
uint loc = Read4(stack + frm2);
StkW4(frm2, loc);
frm2 += 4;
loccount--;
} while (loccount);
break;
}
numlocals++;
}
if ((numlocals & 1) == 0) {
StkW1(frm3, 0);
frm3++;
StkW1(frm3, 0);
frm3++;
}
if (frm3 != frm + locpos) {
return 1;
}
while (frm2 & 3) {
StkW1(frm2, 0);
frm2++;
}
if (frm2 != frm + frlen) {
return 1;
}
/* Now, the values pushed on the stack after the call frame itself.
This includes the stub. */
while (frm2 < frameend) {
uint loc = Read4(stack + frm2);
StkW4(frm2, loc);
frm2 += 4;
}
frameend = frm;
}
return 0;
}
uint Glulx::perform_verify() {
uint len, chksum = 0, newlen;
unsigned char buf[4];
uint val, newsum, ix;
len = gamefile_len;
if (len < 256 || (len & 0xFF) != 0)
return 1;
_gameFile.seek(gamefile_start);
newsum = 0;
/* Read the header */
for (ix = 0; ix < 9; ix++) {
newlen = _gameFile.read(buf, 4);
if (newlen != 4)
return 1;
val = Read4(buf);
if (ix == 3) {
if (len != val)
return 1;
}
if (ix == 8)
chksum = val;
else
newsum += val;
}
/* Read everything else */
for (; ix < len / 4; ix++) {
newlen = _gameFile.read(buf, 4);
if (newlen != 4)
return 1;
val = Read4(buf);
newsum += val;
}
if (newsum != chksum)
return 1;
return 0;
}
} // End of namespace Glulx
} // End of namespace Glk

View File

@@ -0,0 +1,807 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
void Glulx::stream_get_iosys(uint *mode, uint *rock) {
*mode = iosys_mode;
*rock = iosys_rock;
}
void Glulx::stream_setup_unichar() {
#ifdef GLK_MODULE_UNICODE
if (glk_gestalt(gestalt_Unicode, 0))
glkio_unichar_han_ptr = &Glulx::glk_put_char_uni;
else
glkio_unichar_han_ptr = &Glulx::glkio_unichar_nouni_han;
#else /* GLK_MODULE_UNICODE */
glkio_unichar_han_ptr = glkio_unichar_nouni_han;
#endif /* GLK_MODULE_UNICODE */
}
void Glulx::stream_set_iosys(uint mode, uint rock) {
switch (mode) {
default:
mode = 0;
// fall through
case iosys_None:
rock = 0;
stream_char_handler = &Glulx::nopio_char_han;
stream_unichar_handler = &Glulx::nopio_unichar_han;
break;
case iosys_Filter:
stream_char_handler = &Glulx::filio_char_han;
stream_unichar_handler = &Glulx::filio_unichar_han;
break;
case iosys_Glk:
if (!glkio_unichar_han_ptr)
stream_setup_unichar();
rock = 0;
stream_char_handler = &Glulx::glk_put_char;
stream_unichar_handler = glkio_unichar_han_ptr;
break;
}
iosys_mode = mode;
iosys_rock = rock;
}
void Glulx::nopio_char_han(unsigned char ch) {
}
void Glulx::nopio_unichar_han(uint32 ch) {
}
void Glulx::filio_char_han(unsigned char ch) {
uint val = ch;
push_callstub(0, 0);
enter_function(iosys_rock, 1, &val);
}
void Glulx::filio_unichar_han(uint32 val) {
uint v = val;
push_callstub(0, 0);
enter_function(iosys_rock, 1, &v);
}
void Glulx::glkio_unichar_nouni_han(uint32 val) {
/* Only used if the Glk library has no Unicode functions */
if (val > 0xFF)
val = '?';
glk_put_char(val);
}
void Glulx::stream_num(int val, int inmiddle, int charnum) {
int ix = 0;
int res, jx;
char buf[16];
uint ival;
if (val == 0) {
buf[ix] = '0';
ix++;
} else {
if (val < 0)
ival = -val;
else
ival = val;
while (ival != 0) {
buf[ix] = (ival % 10) + '0';
ix++;
ival /= 10;
}
if (val < 0) {
buf[ix] = '-';
ix++;
}
}
switch (iosys_mode) {
case iosys_Glk:
ix -= charnum;
while (ix > 0) {
ix--;
glk_put_char(buf[ix]);
}
break;
case iosys_Filter:
if (!inmiddle) {
push_callstub(0x11, 0);
inmiddle = true;
}
if (charnum < ix) {
ival = buf[(ix - 1) - charnum] & 0xFF;
pc = val;
push_callstub(0x12, charnum + 1);
enter_function(iosys_rock, 1, &ival);
return;
}
break;
default:
break;
}
if (inmiddle) {
res = pop_callstub_string(&jx);
if (res)
fatal_error("String-on-string call stub while printing number.");
}
}
void Glulx::stream_string(uint addr, int inmiddle, int bitnum) {
int ch;
int type;
int alldone = false;
int substring = (inmiddle != 0);
uint ival;
if (!addr)
fatal_error("Called stream_string with null address.");
while (!alldone) {
if (inmiddle == 0) {
type = Mem1(addr);
if (type == 0xE2)
addr += 4;
else
addr++;
bitnum = 0;
} else {
type = inmiddle;
}
if (type == 0xE1) {
if (tablecache_valid) {
int bits, numbits;
int readahead;
uint tmpaddr;
cacheblock_t *cablist;
int done = 0;
/* bitnum is already set right */
bits = Mem1(addr);
if (bitnum)
bits >>= bitnum;
numbits = (8 - bitnum);
readahead = false;
if (tablecache.type != 0) {
/* This is a bit of a cheat. If the top-level block is not
a branch, then it must be a string-terminator -- otherwise
the string would be an infinite repetition of that block.
We check for this case and bail immediately. */
done = 1;
}
cablist = tablecache.u.branches;
while (!done) {
cacheblock_t *cab;
if (numbits < CACHEBITS) {
/* readahead is certainly false */
int newbyte = Mem1(addr + 1);
bits |= (newbyte << numbits);
numbits += 8;
readahead = true;
}
cab = &(cablist[bits & CACHEMASK]);
numbits -= cab->depth;
bits >>= cab->depth;
bitnum += cab->depth;
if (bitnum >= 8) {
addr += 1;
bitnum -= 8;
if (readahead) {
readahead = false;
} else {
int newbyte = Mem1(addr);
bits |= (newbyte << numbits);
numbits += 8;
}
}
switch (cab->type) {
case 0x00: /* non-leaf node */
cablist = cab->u.branches;
break;
case 0x01: /* string terminator */
done = 1;
break;
case 0x02: /* single character */
switch (iosys_mode) {
case iosys_Glk:
glk_put_char(cab->u.ch);
break;
case iosys_Filter:
ival = cab->u.ch & 0xFF;
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
enter_function(iosys_rock, 1, &ival);
return;
}
cablist = tablecache.u.branches;
break;
case 0x04: /* single Unicode character */
switch (iosys_mode) {
case iosys_Glk:
(this->*glkio_unichar_han_ptr)(cab->u.uch);
break;
case iosys_Filter:
ival = cab->u.uch;
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
enter_function(iosys_rock, 1, &ival);
return;
}
cablist = tablecache.u.branches;
break;
case 0x03: /* C string */
switch (iosys_mode) {
case iosys_Glk:
for (tmpaddr = cab->u.addr; (ch = Mem1(tmpaddr)) != '\0'; tmpaddr++)
glk_put_char(ch);
cablist = tablecache.u.branches;
break;
case iosys_Filter:
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
inmiddle = 0xE0;
addr = cab->u.addr;
done = 2;
break;
default:
cablist = tablecache.u.branches;
break;
}
break;
case 0x05: /* C Unicode string */
switch (iosys_mode) {
case iosys_Glk:
for (tmpaddr = cab->u.addr; (ival = Mem4(tmpaddr)) != 0; tmpaddr += 4)
(this->*glkio_unichar_han_ptr)(ival);
cablist = tablecache.u.branches;
break;
case iosys_Filter:
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
inmiddle = 0xE2;
addr = cab->u.addr;
done = 2;
break;
default:
cablist = tablecache.u.branches;
break;
}
break;
case 0x08:
case 0x09:
case 0x0A:
case 0x0B: {
uint oaddr;
int otype;
oaddr = cab->u.addr;
if (cab->type >= 0x09)
oaddr = Mem4(oaddr);
if (cab->type == 0x0B)
oaddr = Mem4(oaddr);
otype = Mem1(oaddr);
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
if (otype >= 0xE0 && otype <= 0xFF) {
pc = addr;
push_callstub(0x10, bitnum);
inmiddle = 0;
addr = oaddr;
done = 2;
} else if (otype >= 0xC0 && otype <= 0xDF) {
uint argc;
uint *argv;
if (cab->type == 0x0A || cab->type == 0x0B) {
argc = Mem4(cab->u.addr + 4);
argv = pop_arguments(argc, cab->u.addr + 8);
} else {
argc = 0;
argv = nullptr;
}
pc = addr;
push_callstub(0x10, bitnum);
enter_function(oaddr, argc, argv);
return;
} else {
fatal_error("Unknown object while decoding string indirect reference.");
}
}
break;
default:
fatal_error("Unknown entity in string decoding (cached).");
break;
}
}
if (done > 1) {
continue; /* restart the top-level loop */
}
} else { /* tablecache not valid */
uint node;
int byte1;
int nodetype;
int done = 0;
if (!stringtable)
fatal_error("Attempted to print a compressed string with no table set.");
/* bitnum is already set right */
byte1 = Mem1(addr);
if (bitnum)
byte1 >>= bitnum;
node = Mem4(stringtable + 8);
while (!done) {
nodetype = Mem1(node);
node++;
switch (nodetype) {
case 0x00: /* non-leaf node */
if (byte1 & 1)
node = Mem4(node + 4);
else
node = Mem4(node + 0);
if (bitnum == 7) {
bitnum = 0;
addr++;
byte1 = Mem1(addr);
} else {
bitnum++;
byte1 >>= 1;
}
break;
case 0x01: /* string terminator */
done = 1;
break;
case 0x02: /* single character */
ch = Mem1(node);
switch (iosys_mode) {
case iosys_Glk:
glk_put_char(ch);
break;
case iosys_Filter:
ival = ch & 0xFF;
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
enter_function(iosys_rock, 1, &ival);
return;
}
node = Mem4(stringtable + 8);
break;
case 0x04: /* single Unicode character */
ival = Mem4(node);
switch (iosys_mode) {
case iosys_Glk:
(this->*glkio_unichar_han_ptr)(ival);
break;
case iosys_Filter:
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
enter_function(iosys_rock, 1, &ival);
return;
}
node = Mem4(stringtable + 8);
break;
case 0x03: /* C string */
switch (iosys_mode) {
case iosys_Glk:
for (; (ch = Mem1(node)) != '\0'; node++)
glk_put_char(ch);
node = Mem4(stringtable + 8);
break;
case iosys_Filter:
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
inmiddle = 0xE0;
addr = node;
done = 2;
break;
default:
node = Mem4(stringtable + 8);
break;
}
break;
case 0x05: /* C Unicode string */
switch (iosys_mode) {
case iosys_Glk:
for (; (ival = Mem4(node)) != 0; node += 4)
(this->*glkio_unichar_han_ptr)(ival);
node = Mem4(stringtable + 8);
break;
case iosys_Filter:
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
pc = addr;
push_callstub(0x10, bitnum);
inmiddle = 0xE2;
addr = node;
done = 2;
break;
default:
node = Mem4(stringtable + 8);
break;
}
break;
case 0x08:
case 0x09:
case 0x0A:
case 0x0B: {
uint oaddr;
int otype;
oaddr = Mem4(node);
if (nodetype == 0x09 || nodetype == 0x0B)
oaddr = Mem4(oaddr);
otype = Mem1(oaddr);
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
if (otype >= 0xE0 && otype <= 0xFF) {
pc = addr;
push_callstub(0x10, bitnum);
inmiddle = 0;
addr = oaddr;
done = 2;
} else if (otype >= 0xC0 && otype <= 0xDF) {
uint argc;
uint *argv;
if (nodetype == 0x0A || nodetype == 0x0B) {
argc = Mem4(node + 4);
argv = pop_arguments(argc, node + 8);
} else {
argc = 0;
argv = nullptr;
}
pc = addr;
push_callstub(0x10, bitnum);
enter_function(oaddr, argc, argv);
return;
} else {
fatal_error("Unknown object while decoding string indirect reference.");
}
}
break;
default:
fatal_error("Unknown entity in string decoding.");
break;
}
}
if (done > 1) {
continue; /* restart the top-level loop */
}
}
} else if (type == 0xE0) {
switch (iosys_mode) {
case iosys_Glk:
while (1) {
ch = Mem1(addr);
addr++;
if (ch == '\0')
break;
glk_put_char(ch);
}
break;
case iosys_Filter:
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
ch = Mem1(addr);
addr++;
if (ch != '\0') {
ival = ch & 0xFF;
pc = addr;
push_callstub(0x13, 0);
enter_function(iosys_rock, 1, &ival);
return;
}
break;
}
} else if (type == 0xE2) {
switch (iosys_mode) {
case iosys_Glk:
while (1) {
ival = Mem4(addr);
addr += 4;
if (ival == 0)
break;
(this->*glkio_unichar_han_ptr)(ival);
}
break;
case iosys_Filter:
if (!substring) {
push_callstub(0x11, 0);
substring = true;
}
ival = Mem4(addr);
addr += 4;
if (ival != 0) {
pc = addr;
push_callstub(0x14, 0);
enter_function(iosys_rock, 1, &ival);
return;
}
break;
}
} else if (type >= 0xE0 && type <= 0xFF) {
fatal_error("Attempt to print unknown type of string.");
} else {
fatal_error("Attempt to print non-string.");
}
if (!substring) {
/* Just get straight out. */
alldone = true;
} else {
/* Pop a stub and see what's to be done. */
addr = pop_callstub_string(&bitnum);
if (addr == 0) {
alldone = true;
} else {
inmiddle = 0xE1;
}
}
}
}
uint Glulx::stream_get_table() {
return stringtable;
}
void Glulx::stream_set_table(uint addr) {
if (stringtable == addr)
return;
/* Drop cache. */
if (tablecache_valid) {
if (tablecache.type == 0)
dropcache(tablecache.u.branches);
tablecache.u.branches = nullptr;
tablecache_valid = false;
}
stringtable = addr;
if (stringtable) {
/* Build cache. We can only do this if the table is entirely in ROM. */
uint tablelen = Mem4(stringtable);
uint rootaddr = Mem4(stringtable + 8);
int cache_stringtable = (stringtable + tablelen <= ramstart);
/* cache_stringtable = true; ...for testing only */
/* cache_stringtable = false; ...for testing only */
if (cache_stringtable) {
buildcache(&tablecache, rootaddr, CACHEBITS, 0);
/* dumpcache(&tablecache, 1, 0); */
tablecache_valid = true;
}
}
}
void Glulx::buildcache(cacheblock_t *cablist, uint nodeaddr, int depth, int mask) {
int ix, type;
type = Mem1(nodeaddr);
if (type == 0 && depth == CACHEBITS) {
cacheblock_t *list, *cab;
list = (cacheblock_t *)glulx_malloc(sizeof(cacheblock_t) * CACHESIZE);
buildcache(list, nodeaddr, 0, 0);
cab = &(cablist[mask]);
cab->type = 0;
cab->depth = CACHEBITS;
cab->u.branches = list;
return;
}
if (type == 0) {
uint leftaddr = Mem4(nodeaddr + 1);
uint rightaddr = Mem4(nodeaddr + 5);
buildcache(cablist, leftaddr, depth + 1, mask);
buildcache(cablist, rightaddr, depth + 1, (mask | (1 << depth)));
return;
}
/* Leaf node. */
nodeaddr++;
for (ix = mask; ix < CACHESIZE; ix += (1 << depth)) {
cacheblock_t *cab = &(cablist[ix]);
cab->type = type;
cab->depth = depth;
switch (type) {
case 0x02:
cab->u.ch = Mem1(nodeaddr);
break;
case 0x04:
cab->u.uch = Mem4(nodeaddr);
break;
case 0x03:
case 0x05:
case 0x0A:
case 0x0B:
cab->u.addr = nodeaddr;
break;
case 0x08:
case 0x09:
cab->u.addr = Mem4(nodeaddr);
break;
}
}
}
#if 0
#include <stdio.h>
void Glulx::dumpcache(cacheblock_t *cablist, int count, int indent) {
int ix, jx;
for (ix = 0; ix < count; ix++) {
cacheblock_t *cab = &(cablist[ix]);
for (jx = 0; jx < indent; jx++)
printf(" ");
printf("%X: ", ix);
switch (cab->type) {
case 0:
printf("...\n");
dumpcache(cab->u.branches, CACHESIZE, indent + 1);
break;
case 1:
printf("<EOS>\n");
break;
case 2:
printf("0x%02X", cab->u.ch);
if (cab->u.ch < 32)
printf(" ''\n");
else
printf(" '%c'\n", cab->u.ch);
break;
default:
printf("type %02X, address %06lX\n", cab->type, cab->u.addr);
break;
}
}
}
#endif /* 0 */
void Glulx::dropcache(cacheblock_t *cablist) {
int ix;
for (ix = 0; ix < CACHESIZE; ix++) {
cacheblock_t *cab = &(cablist[ix]);
if (cab->type == 0) {
dropcache(cab->u.branches);
cab->u.branches = nullptr;
}
}
glulx_free(cablist);
}
char *Glulx::make_temp_string(uint addr) {
int ix, len;
uint addr2;
char *res;
if (Mem1(addr) != 0xE0)
fatal_error("String argument to a Glk call must be unencoded.");
addr++;
for (addr2 = addr; Mem1(addr2); addr2++) { };
len = (addr2 - addr);
if (len < STATIC_TEMP_BUFSIZE) {
res = temp_buf;
} else {
res = (char *)glulx_malloc(len + 1);
if (!res)
fatal_error("Unable to allocate space for string argument to Glk call.");
}
for (ix = 0, addr2 = addr; ix < len; ix++, addr2++) {
res[ix] = Mem1(addr2);
}
res[len] = '\0';
return res;
}
uint32 *Glulx::make_temp_ustring(uint addr) {
int ix, len;
uint addr2;
uint32 *res;
if (Mem1(addr) != 0xE2)
fatal_error("Ustring argument to a Glk call must be unencoded.");
addr += 4;
for (addr2 = addr; Mem4(addr2); addr2 += 4) { };
len = (addr2 - addr) / 4;
if ((len + 1) * 4 < STATIC_TEMP_BUFSIZE) {
res = (uint32 *)temp_buf;
} else {
res = (uint32 *)glulx_malloc((len + 1) * 4);
if (!res)
fatal_error("Unable to allocate space for ustring argument to Glk call.");
}
for (ix = 0, addr2 = addr; ix < len; ix++, addr2 += 4) {
res[ix] = Mem4(addr2);
}
res[len] = 0;
return res;
}
void Glulx::free_temp_string(char *str) {
if (str && str != temp_buf)
glulx_free(str);
}
void Glulx::free_temp_ustring(uint32 *str) {
if (str && str != (uint32 *)temp_buf)
glulx_free(str);
}
} // End of namespace Glulx
} // End of namespace Glk

316
engines/glk/glulx/vm.cpp Normal file
View File

@@ -0,0 +1,316 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "glk/glulx/glulx.h"
namespace Glk {
namespace Glulx {
void Glulx::setup_vm() {
byte buf[4 * 7];
pc = 0; // Clear this, so that error messages are cleaner.
prevpc = 0;
// Read in all the size constants from the game file header
stream_char_handler = nullptr;
stream_unichar_handler = nullptr;
_gameFile.seek(gamefile_start + 8);
if (_gameFile.read(buf, 4 * 7) != (4 * 7))
fatal_error("The game file header is too short.");
ramstart = Read4(buf + 0);
endgamefile = Read4(buf + 4);
origendmem = Read4(buf + 8);
stacksize = Read4(buf + 12);
startfuncaddr = Read4(buf + 16);
origstringtable = Read4(buf + 20);
checksum = Read4(buf + 24);
// Set the protection range to (0, 0), meaning "off".
protectstart = 0;
protectend = 0;
// Do a few sanity checks.
if ((ramstart & 0xFF)
|| (endgamefile & 0xFF)
|| (origendmem & 0xFF)
|| (stacksize & 0xFF)) {
nonfatal_warning("One of the segment boundaries in the header is not "
"256-byte aligned.");
}
if (endgamefile != gamefile_len) {
nonfatal_warning("The gamefile length does not match the header "
"endgamefile length.");
}
if (ramstart < 0x100 || endgamefile < ramstart || origendmem < endgamefile) {
fatal_error("The segment boundaries in the header are in an impossible "
"order.");
}
if (stacksize < 0x100) {
fatal_error("The stack size in the header is too small.");
}
/* Allocate main memory and the stack. This is where memory allocation
errors are most likely to occur. */
endmem = origendmem;
memmap = (byte *)glulx_malloc(origendmem);
if (!memmap) {
fatal_error("Unable to allocate Glulx memory space.");
}
stack = (byte *)glulx_malloc(stacksize);
if (!stack) {
glulx_free(memmap);
memmap = nullptr;
fatal_error("Unable to allocate Glulx stack space.");
}
stringtable = 0;
// Initialize various other things in the terp.
init_operands();
init_serial();
// Set up the initial machine state.
vm_restart();
/* If the debugger is compiled in, check that the debug data matches
the game. (This only prints warnings for mismatch.) */
debugger_check_story_file();
/* Also, set up any start-time debugger state. This may do a block-
and-debug, if the user has requested that. */
debugger_setup_start_state();
}
void Glulx::finalize_vm() {
stream_set_table(0);
if (memmap) {
glulx_free(memmap);
memmap = nullptr;
}
if (stack) {
glulx_free(stack);
stack = nullptr;
}
final_serial();
}
void Glulx::vm_restart() {
uint lx;
int res;
int bufpos;
char buf[0x100];
/* Deactivate the heap (if it was active). */
heap_clear();
/* Reset memory to the original size. */
lx = change_memsize(origendmem, false);
if (lx)
fatal_error("Memory could not be reset to its original size.");
/* Load in all of main memory. We do this in 256-byte chunks, because
why rely on OS stream buffering? */
_gameFile.seek(gamefile_start);
bufpos = 0x100;
for (lx = 0; lx < endgamefile; lx++) {
if (bufpos >= 0x100) {
int count = _gameFile.read(buf, 0x100);
if (count != 0x100) {
fatal_error("The game file ended unexpectedly.");
}
bufpos = 0;
}
res = buf[bufpos++];
if (lx >= protectstart && lx < protectend)
continue;
memmap[lx] = res;
}
for (lx = endgamefile; lx < origendmem; lx++) {
memmap[lx] = 0;
}
/* Reset all the registers */
stackptr = 0;
frameptr = 0;
pc = 0;
prevpc = 0;
stream_set_iosys(0, 0);
stream_set_table(origstringtable);
valstackbase = 0;
localsbase = 0;
/* Note that we do not reset the protection range. */
/* Push the first function call. (No arguments.) */
enter_function(startfuncaddr, 0, nullptr);
/* We're now ready to execute. */
}
uint Glulx::change_memsize(uint newlen, bool internal) {
uint lx;
unsigned char *newmemmap;
if (newlen == endmem)
return 0;
#ifdef FIXED_MEMSIZE
return 1;
#else /* FIXED_MEMSIZE */
if ((!internal) && heap_is_active())
fatal_error("Cannot resize Glulx memory space while heap is active.");
if (newlen < origendmem)
fatal_error("Cannot resize Glulx memory space smaller than it started.");
if (newlen & 0xFF)
fatal_error("Can only resize Glulx memory space to a 256-byte boundary.");
newmemmap = (unsigned char *)glulx_realloc(memmap, newlen);
if (!newmemmap) {
/* The old block is still in place, unchanged. */
return 1;
}
memmap = newmemmap;
if (newlen > endmem) {
for (lx = endmem; lx < newlen; lx++) {
memmap[lx] = 0;
}
}
endmem = newlen;
return 0;
#endif /* FIXED_MEMSIZE */
}
uint *Glulx::pop_arguments(uint count, uint addr) {
uint ix;
uint argptr;
uint *array;
#define MAXARGS (32)
static uint statarray[MAXARGS];
static uint *dynarray = nullptr;
static uint dynarray_size = 0;
if (count == 0)
return nullptr;
if (count <= MAXARGS) {
/* Store in the static array. */
array = statarray;
} else {
if (!dynarray) {
dynarray_size = count + 8;
dynarray = (uint *)glulx_malloc(sizeof(uint) * dynarray_size);
if (!dynarray)
fatal_error("Unable to allocate function arguments.");
array = dynarray;
} else {
if (dynarray_size >= count) {
/* It fits. */
array = dynarray;
} else {
dynarray_size = count + 8;
dynarray = (uint *)glulx_realloc(dynarray, sizeof(uint) * dynarray_size);
if (!dynarray)
fatal_error("Unable to reallocate function arguments.");
array = dynarray;
}
}
}
if (!addr) {
if (stackptr < valstackbase + 4 * count)
fatal_error("Stack underflow in arguments.");
stackptr -= 4 * count;
for (ix = 0; ix < count; ix++) {
argptr = stackptr + 4 * ((count - 1) - ix);
array[ix] = Stk4(argptr);
}
} else {
for (ix = 0; ix < count; ix++) {
array[ix] = Mem4(addr);
addr += 4;
}
}
return array;
}
void Glulx::verify_address(uint addr, uint count) {
if (addr >= endmem)
fatal_error_i("Memory access out of range", addr);
if (count > 1) {
addr += (count - 1);
if (addr >= endmem)
fatal_error_i("Memory access out of range", addr);
}
}
void Glulx::verify_address_write(uint addr, uint count) {
if (addr < ramstart)
fatal_error_i("Memory write to read-only address", addr);
if (addr >= endmem)
fatal_error_i("Memory access out of range", addr);
if (count > 1) {
addr += (count - 1);
if (addr >= endmem)
fatal_error_i("Memory access out of range", addr);
}
}
void Glulx::verify_array_addresses(uint addr, uint count, uint size) {
uint bytecount;
if (addr >= endmem)
fatal_error_i("Memory access out of range", addr);
if (count == 0)
return;
bytecount = count * size;
/* If just multiplying by the element size overflows, we have trouble. */
if (bytecount < count)
fatal_error_i("Memory access way too long", addr);
/* If the byte length by itself is too long, or if its end overflows,
we have trouble. */
if (bytecount > endmem || addr + bytecount < addr)
fatal_error_i("Memory access much too long", addr);
/* The simple length test. */
if (addr + bytecount > endmem)
fatal_error_i("Memory access too long", addr);
}
} // End of namespace Glulx
} // End of namespace Glk