Initial commit
This commit is contained in:
663
engines/glk/glulx/accel.cpp
Normal file
663
engines/glk/glulx/accel.cpp
Normal 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
|
||||
122
engines/glk/glulx/detection.cpp
Normal file
122
engines/glk/glulx/detection.cpp
Normal 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
|
||||
67
engines/glk/glulx/detection.h
Normal file
67
engines/glk/glulx/detection.h
Normal 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
|
||||
3161
engines/glk/glulx/detection_tables.h
Normal file
3161
engines/glk/glulx/detection_tables.h
Normal file
File diff suppressed because it is too large
Load Diff
1055
engines/glk/glulx/exec.cpp
Normal file
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
122
engines/glk/glulx/float.cpp
Normal 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
35
engines/glk/glulx/float.h
Normal 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
298
engines/glk/glulx/funcs.cpp
Normal 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
|
||||
100
engines/glk/glulx/gestalt.cpp
Normal file
100
engines/glk/glulx/gestalt.cpp
Normal 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
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
147
engines/glk/glulx/glulx.cpp
Normal 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
1008
engines/glk/glulx/glulx.h
Normal file
File diff suppressed because it is too large
Load Diff
423
engines/glk/glulx/glulx_types.h
Normal file
423
engines/glk/glulx/glulx_types.h
Normal 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
316
engines/glk/glulx/heap.cpp
Normal 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
|
||||
611
engines/glk/glulx/operand.cpp
Normal file
611
engines/glk/glulx/operand.cpp
Normal 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
|
||||
210
engines/glk/glulx/search.cpp
Normal file
210
engines/glk/glulx/search.cpp
Normal 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
|
||||
988
engines/glk/glulx/serial.cpp
Normal file
988
engines/glk/glulx/serial.cpp
Normal 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
|
||||
807
engines/glk/glulx/string.cpp
Normal file
807
engines/glk/glulx/string.cpp
Normal 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
316
engines/glk/glulx/vm.cpp
Normal 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
|
||||
Reference in New Issue
Block a user