aboutsummaryrefslogtreecommitdiff
path: root/engines/glk/glulxe
diff options
context:
space:
mode:
authorPaul Gilbert2019-04-16 19:30:44 -0700
committerPaul Gilbert2019-04-17 20:46:06 -0700
commitee8362cc073a02c91038add400f67dc2e6dba683 (patch)
treec35a9648b0890f08cc8898d516f5559e76d4ccda /engines/glk/glulxe
parent5965b02bcac9dbad6eb2c9b30a59edba0a3b1b5d (diff)
downloadscummvm-rg350-ee8362cc073a02c91038add400f67dc2e6dba683.tar.gz
scummvm-rg350-ee8362cc073a02c91038add400f67dc2e6dba683.tar.bz2
scummvm-rg350-ee8362cc073a02c91038add400f67dc2e6dba683.zip
GLK: GLULXE: Added vm methods
Diffstat (limited to 'engines/glk/glulxe')
-rw-r--r--engines/glk/glulxe/glulxe.h84
-rw-r--r--engines/glk/glulxe/serial.cpp4
-rw-r--r--engines/glk/glulxe/vm.cpp325
3 files changed, 390 insertions, 23 deletions
diff --git a/engines/glk/glulxe/glulxe.h b/engines/glk/glulxe/glulxe.h
index 4044074e37..f7684f1830 100644
--- a/engines/glk/glulxe/glulxe.h
+++ b/engines/glk/glulxe/glulxe.h
@@ -38,7 +38,11 @@ typedef void (Glulxe::*UnicharHandler)(uint);
* Glulxe game interpreter
*/
class Glulxe : public GlkAPI {
-public:
+private:
+ /**
+ * \defgroup vm fields
+ * @{
+ */
CharHandler stream_char_handler;
UnicharHandler stream_unichar_handler, glkio_unichar_han_ptr;
@@ -67,6 +71,8 @@ public:
uint protectstart, protectend;
uint prevpc;
+ /**@}*/
+
/**
* \defgroup accel fields
* @{
@@ -342,7 +348,7 @@ protected:
* @{
*/
- void stream_setup_unichar(void);
+ void stream_setup_unichar();
void nopio_char_han(unsigned char ch);
void filio_char_han(unsigned char ch);
@@ -385,8 +391,6 @@ public:
* \defgroup Main access methods
* @{
*/
- void set_library_start_hook(void(*)(void));
- void set_library_autorestore_hook(void(*)(void));
/**
* Display an error in the error window, and then exit.
@@ -414,8 +418,6 @@ public:
*/
bool is_gamefile_valid();
- int locate_gamefile(int isblorb);
-
/**@}*/
/**
@@ -423,13 +425,53 @@ public:
* @{
*/
- void setup_vm(void);
- void finalize_vm(void);
- void vm_restart(void);
- uint change_memsize(uint newlen, int internal);
+ /**
+ * Read in the game file and build the machine, allocating all the memory necessary.
+ */
+ void setup_vm();
+
+ /**
+ * Deallocate all the memory and shut down the machine.
+ */
+ void finalize_vm();
+
+ /**
+ * Put the VM into a state where it's ready to begin executing the game. This is called
+ * both at startup time, and when the machine performs a "restart" opcode.
+ */
+ void vm_restart();
+
+ /**
+ * Change the size of the memory map. This may not be available at all; #define FIXED_MEMSIZE
+ * if you want the interpreter to unconditionally refuse. The internal flag should be true only
+ * when the heap-allocation system is calling. Returns 0 for success; otherwise, the operation failed.
+ */
+ uint change_memsize(uint newlen, bool internal);
+
+ /**
+ * If addr is 0, pop N arguments off the stack, and put them in an array. If non-0, take N arguments
+ * from that main memory address instead. This has to dynamically allocate if there are more than
+ * 32 arguments, but that shouldn't be a problem.
+ */
uint *pop_arguments(uint count, uint addr);
+
+ /**
+ * Make sure that count bytes beginning with addr all fall within the current memory map.
+ * This is called at every memory (read) access if VERIFY_MEMORY_ACCESS is defined in the header file.
+ */
void verify_address(uint addr, uint count);
+
+ /**
+ * Make sure that count bytes beginning with addr all fall within RAM. This is called at every memory
+ * write if VERIFY_MEMORY_ACCESS is defined in the header file.
+ */
void verify_address_write(uint addr, uint count);
+
+ /**
+ * Make sure that an array of count elements (size bytes each), starting at addr, does not fall
+ * outside the memory map. This goes to some trouble that verify_address() does not, because we need
+ * to be wary of lengths near -- or beyond -- 0x7FFFFFFF.
+ */
void verify_array_addresses(uint addr, uint count, uint size);
/**@}*/
@@ -527,7 +569,7 @@ public:
* Set the heap state to inactive, and free the block lists. This is called when the game
* starts or restarts.
*/
- void heap_clear(void);
+ void heap_clear();
/**
* Returns whether the heap is active.
@@ -637,7 +679,7 @@ public:
void *glulx_realloc(void *ptr, uint len);
void glulx_free(void *ptr);
void glulx_setrandom(uint seed);
- uint glulx_random(void);
+ uint glulx_random();
void glulx_sort(void *addr, int count, int size,
int(*comparefunc)(void *p1, void *p2));
@@ -723,16 +765,16 @@ public:
*/
void setup_profile(strid_t stream, char *filename);
- int init_profile(void);
+ int init_profile();
void profile_set_call_counts(int flag);
#if VM_PROFILING
uint profile_opcount;
#define profile_tick() (profile_opcount++)
- int profile_profiling_active(void);
+ int profile_profiling_active();
void profile_in(uint addr, uint stackuse, int accel);
void profile_out(uint stackuse);
void profile_fail(char *reason);
- void profile_quit(void);
+ void profile_quit();
#else /* VM_PROFILING */
#define profile_tick() (0)
#define profile_profiling_active() (0)
@@ -751,15 +793,15 @@ public:
void debugger_set_start_trap(int flag);
void debugger_set_quit_trap(int flag);
void debugger_set_crash_trap(int flag);
- void debugger_check_story_file(void);
- void debugger_setup_start_state(void);
- int debugger_ever_invoked(void);
+ void debugger_check_story_file();
+ void debugger_setup_start_state();
+ int debugger_ever_invoked();
int debugger_cmd_handler(char *cmd);
void debugger_cycle_handler(int cycle);
void debugger_check_func_breakpoint(uint addr);
void debugger_block_and_debug(char *msg);
void debugger_handle_crash(char *msg);
- void debugger_handle_quit(void);
+ void debugger_handle_quit();
#else /* VM_DEBUGGER */
#define debugger_tick() (0)
#define debugger_check_story_file() (0)
@@ -805,7 +847,7 @@ public:
/* #define FLOAT_NOT_NATIVE (1) */
/* float.c */
- int init_float(void);
+ int init_float();
uint encode_float(gfloat32 val);
gfloat32 decode_float(uint val);
@@ -884,7 +926,7 @@ public:
/**
* Get the current table address.
*/
- uint stream_get_table(void);
+ uint stream_get_table();
/**
* Set the current table address, and rebuild decoding cache.
diff --git a/engines/glk/glulxe/serial.cpp b/engines/glk/glulxe/serial.cpp
index 66464e80f9..6d662166c4 100644
--- a/engines/glk/glulxe/serial.cpp
+++ b/engines/glk/glulxe/serial.cpp
@@ -517,7 +517,7 @@ int Glulxe::write_buffer(dest_t *dest, const byte *ptr, uint len) {
return 0;
}
-int Glulxe::read_buffer(dest_t *dest, unsigned char *ptr, uint len) {
+int Glulxe::read_buffer(dest_t *dest, byte *ptr, uint len) {
uint newlen;
if (dest->ismem) {
@@ -546,7 +546,7 @@ int Glulxe::write_short(dest_t *dest, uint16 val) {
return write_buffer(dest, buf, 2);
}
-int Glulxe::write_byte(dest_t *dest, unsigned char val) {
+int Glulxe::write_byte(dest_t *dest, byte val) {
return write_buffer(dest, &val, 1);
}
diff --git a/engines/glk/glulxe/vm.cpp b/engines/glk/glulxe/vm.cpp
new file mode 100644
index 0000000000..f332c92248
--- /dev/null
+++ b/engines/glk/glulxe/vm.cpp
@@ -0,0 +1,325 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/glk/glulxe/glulxe.h"
+
+namespace Glk {
+namespace Glulxe {
+
+void Glulxe::setup_vm() {
+ unsigned char buf[4 * 7];
+ int res;
+
+ 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 = NULL;
+ stream_unichar_handler = NULL;
+
+ glk_stream_set_position(gamefile, gamefile_start+8, seekmode_Start);
+ res = glk_get_buffer_stream(gamefile, (char *)buf, 4 * 7);
+ if (res != 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 = (unsigned char *)glulx_malloc(origendmem);
+ if (!memmap) {
+ fatal_error("Unable to allocate Glulx memory space.");
+ }
+ stack = (unsigned char *)glulx_malloc(stacksize);
+ if (!stack) {
+ glulx_free(memmap);
+ memmap = NULL;
+ 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 Glulxe::finalize_vm() {
+ stream_set_table(0);
+
+ if (memmap) {
+ glulx_free(memmap);
+ memmap = NULL;
+ }
+ if (stack) {
+ glulx_free(stack);
+ stack = NULL;
+ }
+
+ final_serial();
+}
+
+void Glulxe::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? */
+ glk_stream_set_position(gamefile, gamefile_start, seekmode_Start);
+ bufpos = 0x100;
+
+ for (lx=0; lx<endgamefile; lx++) {
+ if (bufpos >= 0x100) {
+ int count = glk_get_buffer_stream(gamefile, 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, NULL);
+
+ /* We're now ready to execute. */
+}
+
+uint Glulxe::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 *Glulxe::pop_arguments(uint count, uint addr) {
+ int ix;
+ uint argptr;
+ uint *array;
+
+ #define MAXARGS (32)
+ static uint statarray[MAXARGS];
+ static uint *dynarray = NULL;
+ static uint dynarray_size = 0;
+
+ if (count == 0)
+ return NULL;
+
+ 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 Glulxe::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 Glulxe::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 Glulxe::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 Glulxe
+} // End of namespace Glk